diff --git a/ui/packages/consul-ui/app/components/changeable-set/index.hbs b/ui/packages/consul-ui/app/components/changeable-set/index.hbs deleted file mode 100644 index 020700435..000000000 --- a/ui/packages/consul-ui/app/components/changeable-set/index.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{yield}} - {{yield}} -{{#if (gt items.length 0)}} - {{yield}} -{{else}} - {{yield}} -{{/if}} \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/changeable-set/index.js b/ui/packages/consul-ui/app/components/changeable-set/index.js deleted file mode 100644 index 3cb2c3e83..000000000 --- a/ui/packages/consul-ui/app/components/changeable-set/index.js +++ /dev/null @@ -1,28 +0,0 @@ -import Component from '@ember/component'; -import { get, set } from '@ember/object'; -import { inject as service } from '@ember/service'; -import Slotted from 'block-slots'; - -export default Component.extend(Slotted, { - tagName: '', - dom: service('dom'), - init: function() { - this._super(...arguments); - this._listeners = this.dom.listeners(); - }, - willDestroyElement: function() { - this._listeners.remove(); - this._super(...arguments); - }, - didReceiveAttrs: function() { - this._super(...arguments); - if (this.items !== this.dispatcher.data) { - this._listeners.remove(); - this._listeners.add(this.dispatcher, { - change: e => set(this, 'items', e.target.data), - }); - set(this, 'items', get(this.dispatcher, 'data')); - } - this.dispatcher.search(this.terms); - }, -}); diff --git a/ui/packages/consul-ui/app/components/child-selector/index.hbs b/ui/packages/consul-ui/app/components/child-selector/index.hbs index 801005122..525bca8c6 100644 --- a/ui/packages/consul-ui/app/components/child-selector/index.hbs +++ b/ui/packages/consul-ui/app/components/child-selector/index.hbs @@ -1,4 +1,5 @@
{{yield}} @@ -11,18 +12,28 @@ @onchange={{action (mut allOptions) value="data"}} /> {{/if}} + + @searchEnabled={{true}} + @search={{action collection.search}} + @options={{options}} + @loadingMessage="Loading..." + @searchMessage="No possible options" + @searchPlaceholder={{placeholder}} + @onOpen={{action (mut isOpen) true}} + @onClose={{action (mut isOpen) false}} + @onChange={{action "change" "items[]" items}} + as |item|> {{yield}} + {{#if (gt items.length 0)}} {{yield}} diff --git a/ui/packages/consul-ui/app/components/child-selector/index.js b/ui/packages/consul-ui/app/components/child-selector/index.js index 6291d631c..9760e1f58 100644 --- a/ui/packages/consul-ui/app/components/child-selector/index.js +++ b/ui/packages/consul-ui/app/components/child-selector/index.js @@ -15,8 +15,6 @@ export default Component.extend(Slotted, { type: '', dom: service('dom'), - search: service('search'), - sort: service('sort'), formContainer: service('form'), item: alias('form.data'), @@ -26,7 +24,6 @@ export default Component.extend(Slotted, { init: function() { this._super(...arguments); this._listeners = this.dom.listeners(); - this.searchable = this.search.searchable(this.type); this.form = this.formContainer.form(this.type); this.form.clear({ Datacenter: this.dc, Namespace: this.nspace }); }, @@ -34,14 +31,10 @@ export default Component.extend(Slotted, { this._super(...arguments); this._listeners.remove(); }, - sortedOptions: sort('allOptions.[]', 'comparator'), - comparator: computed(function() { - return this.sort.comparator(this.type)(); - }), - options: computed('selectedOptions.[]', 'sortedOptions.[]', function() { + options: computed('selectedOptions.[]', 'allOptions.[]', function() { // It's not massively important here that we are defaulting `items` and // losing reference as its just to figure out the diff - let options = this.sortedOptions || []; + let options = this.allOptions || []; const items = this.selectedOptions || []; if (get(items, 'length') > 0) { // filter out any items from the available options that have already been @@ -49,7 +42,6 @@ export default Component.extend(Slotted, { // TODO: find a proper ember-data diff options = options.filter(item => !items.findBy('ID', get(item, 'ID'))); } - this.searchable.add(options); return options; }), save: task(function*(item, items, success = function() {}) { @@ -72,19 +64,6 @@ export default Component.extend(Slotted, { } }), actions: { - search: function(term) { - // TODO: make sure we can either search before things are loaded - // or wait until we are loaded, guess power select take care of that - return new Promise(resolve => { - const remove = this._listeners.add(this.searchable, { - change: e => { - remove(); - resolve(e.target.data); - }, - }); - this.searchable.search(term); - }); - }, reset: function() { this.form.clear({ Datacenter: this.dc, Namespace: this.nspace }); }, diff --git a/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.hbs b/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.hbs index cf5a08726..15b8927c4 100644 --- a/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.hbs @@ -2,65 +2,61 @@ class="consul-upstream-instance-list" ...attributes > - {{#if (gt this.items.length 0)}} -
diff --git a/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.js b/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.js deleted file mode 100644 index a02657584..000000000 --- a/ui/packages/consul-ui/app/components/consul/upstream-instance/list/index.js +++ /dev/null @@ -1,24 +0,0 @@ -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); - } -} diff --git a/ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs b/ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs index fee027149..2111718bb 100644 --- a/ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs @@ -2,11 +2,33 @@ class="consul-upstream-instance-search-bar filter-bar" ...attributes > - +
{{#let (or @sort 'DestinationName:asc') as |sort|}} + {{collection.items.length}} + + Has Results + + + Is Empty + + +``` + +### Arguments + +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `items` | `Array` | [] | The collection of data to search/sort/filter | +| `search` | `String` | '' | A search term to use for searching | +| `sort` | `String` | '' | A sort term to use for sorting on ('Name:asc') | +| `filter` | `Object` | | An object whose properties are the properties/values to filter on | + +### Yields + +The DataCollection yields an object containing the following: + +| Property | Type | Description | +| --- | --- | --- | +| `items` | `Array` | The resulting collection of data after searching/sorting/filtering | +| `search` | `Function` | An action used to perform a search - takes a single string argument that should be the search term | +| `Collection` | `Component` | A slot-like component that only renders when the items in the collection is greater than 0 | +| `Empty` | `Component` | A slot-like component that only renders when the items in the collection is 0 | + +### See + +- [Component Source Code](./index.js) +- [Template Source Code](./index.hbs) + +--- diff --git a/ui/packages/consul-ui/app/components/data-collection/index.hbs b/ui/packages/consul-ui/app/components/data-collection/index.hbs index 45e6c381c..021d36bad 100644 --- a/ui/packages/consul-ui/app/components/data-collection/index.hbs +++ b/ui/packages/consul-ui/app/components/data-collection/index.hbs @@ -1,4 +1,6 @@ +{{did-update (action (fn (set this 'term') '') @search)}} {{yield (hash + search=(action this.search) items=this.items Collection=(if (gt this.items.length 0) (component 'anonymous') '') Empty=(if (eq this.items.length 0) (component 'anonymous') '') diff --git a/ui/packages/consul-ui/app/components/data-collection/index.js b/ui/packages/consul-ui/app/components/data-collection/index.js index 62e0d8220..77f3aa4b8 100644 --- a/ui/packages/consul-ui/app/components/data-collection/index.js +++ b/ui/packages/consul-ui/app/components/data-collection/index.js @@ -1,18 +1,32 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; -import { computed } from '@ember/object'; +import { computed, get, action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; import { sort } from '@ember/object/computed'; import { defineProperty } from '@ember/object'; export default class DataCollectionComponent extends Component { @service('filter') filter; @service('sort') sort; - @service('search') search; + @service('search') searchService; + + @tracked term = ''; get type() { return this.args.type; } + @computed('term', 'args.search') + get searchTerm() { + return this.term || this.args.search || ''; + } + + @action + search(term) { + this.term = term; + return this.items; + } + @computed('args.items', 'args.items.content') get content() { // TODO: Temporary little hack to ensure we detect DataSource proxy @@ -37,17 +51,17 @@ export default class DataCollectionComponent extends Component { return this.sorted; } - @computed('type', 'filtered', 'args.filters.searchproperties', 'args.search') + @computed('type', 'filtered', 'args.filters.searchproperties', 'searchTerm') get searched() { - if (typeof this.args.search === 'undefined') { + if (typeof this.searchTerm === '') { return this.filtered; } - const predicate = this.search.predicate(this.type); + const predicate = this.searchService.predicate(this.type); const options = {}; - if (typeof this.args.filters.searchproperties !== 'undefined') { + if (typeof get(this, 'args.filters.searchproperties') !== 'undefined') { options.properties = this.args.filters.searchproperties; } - return this.filtered.filter(predicate(this.args.search, options)); + return this.filtered.filter(predicate(this.searchTerm, options)); } @computed('type', 'content', 'args.filters') diff --git a/ui/packages/consul-ui/app/helpers/comparator.js b/ui/packages/consul-ui/app/helpers/comparator.js deleted file mode 100644 index 3d502276e..000000000 --- a/ui/packages/consul-ui/app/helpers/comparator.js +++ /dev/null @@ -1,9 +0,0 @@ -import Helper from '@ember/component/helper'; -import { inject as service } from '@ember/service'; - -export default Helper.extend({ - sort: service('sort'), - compute([type, key], hash) { - return this.sort.comparator(type)(key); - }, -}); diff --git a/ui/packages/consul-ui/app/helpers/searchable.js b/ui/packages/consul-ui/app/helpers/searchable.js deleted file mode 100644 index d7abb0b4f..000000000 --- a/ui/packages/consul-ui/app/helpers/searchable.js +++ /dev/null @@ -1,9 +0,0 @@ -import Helper from '@ember/component/helper'; -import { inject as service } from '@ember/service'; - -export default Helper.extend({ - search: service('search'), - compute([type, items], hash) { - return this.search.searchable(type).add(items); - }, -}); diff --git a/ui/packages/consul-ui/app/helpers/selectable-key-values.js b/ui/packages/consul-ui/app/helpers/selectable-key-values.js deleted file mode 100644 index dbc8396b9..000000000 --- a/ui/packages/consul-ui/app/helpers/selectable-key-values.js +++ /dev/null @@ -1,46 +0,0 @@ -import { helper } from '@ember/component/helper'; -import { slugify } from 'consul-ui/helpers/slugify'; -export const selectableKeyValues = function(params = [], hash = {}) { - let selected; - - const items = params.map(function(item, i) { - let key, value; - switch (typeof item) { - case 'string': - key = slugify([item]); - value = item; - break; - default: - if (item.length > 1) { - key = item[0]; - value = item[1]; - } else { - key = slugify([item[0]]); - value = item[0]; - } - break; - } - const kv = { - key: key, - value: value, - }; - switch (typeof hash.selected) { - case 'string': - if (hash.selected === item[0]) { - selected = kv; - } - break; - case 'number': - if (hash.selected === i) { - selected = kv; - } - break; - } - return kv; - }); - return { - items: items, - selected: typeof selected === 'undefined' ? items[0] : selected, - }; -}; -export default helper(selectableKeyValues); diff --git a/ui/packages/consul-ui/app/routes/dc/services/instance/upstreams.js b/ui/packages/consul-ui/app/routes/dc/services/instance/upstreams.js index 7dece4286..c37a4f951 100644 --- a/ui/packages/consul-ui/app/routes/dc/services/instance/upstreams.js +++ b/ui/packages/consul-ui/app/routes/dc/services/instance/upstreams.js @@ -7,6 +7,10 @@ export default class UpstreamsRoute extends Route { as: 'filter', replace: true, }, + searchproperty: { + as: 'searchproperty', + empty: [['DestinationName', 'LocalBindAddress', 'LocalBindPort']], + }, }; model() { @@ -14,7 +18,10 @@ export default class UpstreamsRoute extends Route { .split('.') .slice(0, -1) .join('.'); - return this.modelFor(parent); + return { + ...this.modelFor(parent), + searchProperties: this.queryParams.searchproperty.empty[0], + }; } setupController(controller, model) { diff --git a/ui/packages/consul-ui/app/search/filters/node/service.js b/ui/packages/consul-ui/app/search/filters/node/service.js deleted file mode 100644 index 255c238ea..000000000 --- a/ui/packages/consul-ui/app/search/filters/node/service.js +++ /dev/null @@ -1,21 +0,0 @@ -import { get } from '@ember/object'; -export default function(filterable) { - return filterable(function(item, { s = '' }) { - const term = s.toLowerCase(); - return ( - get(item, 'Service') - .toLowerCase() - .indexOf(term) !== -1 || - get(item, 'ID') - .toLowerCase() - .indexOf(term) !== -1 || - (get(item, 'Tags') || []).some(function(item) { - return item.toLowerCase().indexOf(term) !== -1; - }) || - get(item, 'Port') - .toString() - .toLowerCase() - .indexOf(term) !== -1 - ); - }); -} diff --git a/ui/packages/consul-ui/app/search/filters/policy.js b/ui/packages/consul-ui/app/search/filters/policy.js deleted file mode 100644 index 3144ac6e2..000000000 --- a/ui/packages/consul-ui/app/search/filters/policy.js +++ /dev/null @@ -1,14 +0,0 @@ -import { get } from '@ember/object'; -export default function(filterable) { - return filterable(function(item, { s = '' }) { - const sLower = s.toLowerCase(); - return ( - get(item, 'Name') - .toLowerCase() - .indexOf(sLower) !== -1 || - get(item, 'Description') - .toLowerCase() - .indexOf(sLower) !== -1 - ); - }); -} diff --git a/ui/packages/consul-ui/app/search/filters/role.js b/ui/packages/consul-ui/app/search/filters/role.js deleted file mode 100644 index 420b36dc6..000000000 --- a/ui/packages/consul-ui/app/search/filters/role.js +++ /dev/null @@ -1,20 +0,0 @@ -import { get } from '@ember/object'; -export default function(filterable) { - return filterable(function(item, { s = '' }) { - const sLower = s.toLowerCase(); - return ( - get(item, 'Name') - .toLowerCase() - .indexOf(sLower) !== -1 || - get(item, 'Description') - .toLowerCase() - .indexOf(sLower) !== -1 || - (get(item, 'Policies') || []).some(function(item) { - return item.Name.toLowerCase().indexOf(sLower) !== -1; - }) || - (get(item, 'ServiceIdentities') || []).some(function(item) { - return item.ServiceName.toLowerCase().indexOf(sLower) !== -1; - }) - ); - }); -} diff --git a/ui/packages/consul-ui/app/search/filters/service/node.js b/ui/packages/consul-ui/app/search/filters/service/node.js deleted file mode 100644 index b15625465..000000000 --- a/ui/packages/consul-ui/app/search/filters/service/node.js +++ /dev/null @@ -1,15 +0,0 @@ -import { get } from '@ember/object'; -export default function(filterable) { - return filterable(function(item, { s = '' }) { - const term = s.toLowerCase(); - return ( - get(item, 'Node.Node') - .toLowerCase() - .indexOf(term) !== -1 || - get(item, 'Service.ID') - .toLowerCase() - .indexOf(term) !== -1 || - `${get(item, 'Service.Address')}:${get(item, 'Service.Port')}`.indexOf(term) !== -1 - ); - }); -} diff --git a/ui/packages/consul-ui/app/search/predicates/upstream-instance.js b/ui/packages/consul-ui/app/search/predicates/upstream-instance.js index 2d1a993df..26b133d53 100644 --- a/ui/packages/consul-ui/app/search/predicates/upstream-instance.js +++ b/ui/packages/consul-ui/app/search/predicates/upstream-instance.js @@ -1,12 +1,10 @@ -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 - ); +export default { + DestinationName: (item, value) => + item.DestinationName.toLowerCase().indexOf(value.toLowerCase()) !== -1, + LocalBindAddress: (item, value) => + item.LocalBindAddress.toLowerCase().indexOf(value.toLowerCase()) !== -1, + LocalBindPort: (item, value) => + item.LocalBindPort.toString() + .toLowerCase() + .indexOf(value.toLowerCase()) !== -1, }; diff --git a/ui/packages/consul-ui/app/services/search.js b/ui/packages/consul-ui/app/services/search.js index f6970d9c8..87393b841 100644 --- a/ui/packages/consul-ui/app/services/search.js +++ b/ui/packages/consul-ui/app/services/search.js @@ -14,20 +14,6 @@ import role from 'consul-ui/search/predicates/role'; import policy from 'consul-ui/search/predicates/policy'; import nspace from 'consul-ui/search/predicates/nspace'; -import filteredRole from 'consul-ui/search/filters/role'; -import filteredPolicy from 'consul-ui/search/filters/policy'; -// service instance -import nodeService from 'consul-ui/search/filters/node/service'; -import serviceNode from 'consul-ui/search/filters/service/node'; - -import filterableFactory from 'consul-ui/utils/search/filterable'; -const filterable = filterableFactory(); -const searchables = { - serviceInstance: serviceNode(filterable), - nodeservice: nodeService(filterable), - role: filteredRole(filterable), - policy: filteredPolicy(filterable), -}; export const search = spec => { let possible = Object.keys(spec); return (term, options = {}) => { @@ -47,7 +33,7 @@ const predicates = { intention: search(intention), service: search(service), ['service-instance']: search(serviceInstance), - ['upstream-instance']: upstreamInstance(), + ['upstream-instance']: search(upstreamInstance), ['health-check']: search(healthCheck), node: search(node), kv: search(kv), @@ -58,9 +44,6 @@ const predicates = { nspace: search(nspace), }; export default class SearchService extends Service { - searchable(name) { - return searchables[name]; - } predicate(name) { return predicates[name]; } diff --git a/ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs b/ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs index 3708b3f22..27e72fbca 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs @@ -1,37 +1,53 @@
-{{#let (or sortBy "DestinationName:asc") as |sort|}} - {{#if (gt proxy.Service.Proxy.Upstreams.length 0)}} - - + - - <:empty> - - -

- {{#if search.length}} - No upstreams where found matching that search. - {{else}} - This service has no upstreams. - {{/if}} -

-
-
- -
- {{/if}} + @sort={{sort}} + @onsort={{action (mut sortBy) value="target.selected"}} + + @filter={{filters}} + @onfilter={{hash + searchproperty=(action (mut searchproperty) value="target.selectedItems") + }} + /> + + + + + + + +

+ This service has no upstreams{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}} matching that search{{/if}}. +

+
+
+
+
+ {{/if}} + {{/let}} {{/let}}
\ No newline at end of file diff --git a/ui/packages/consul-ui/app/utils/search/filterable.js b/ui/packages/consul-ui/app/utils/search/filterable.js deleted file mode 100644 index 36e01c332..000000000 --- a/ui/packages/consul-ui/app/utils/search/filterable.js +++ /dev/null @@ -1,49 +0,0 @@ -import RSVP from 'rsvp'; -export default function(EventTarget = RSVP.EventTarget, P = Promise) { - // TODO: Class-ify - return function(filter) { - return EventTarget.mixin({ - value: '', - _data: [], - add: function(data) { - this.data = this._data = data; - return this; - }, - find: function(terms = []) { - this.value = terms - .filter(function(item) { - return typeof item === 'string' && item !== ''; - }) - .map(function(term) { - return term.trim(); - }); - return P.resolve( - this.value.reduce(function(prev, term) { - return prev.filter(item => { - return filter(item, { s: term }); - }); - }, this._data) - ); - }, - search: function(terms = []) { - // specifically no return here we return `this` instead - // right now filtering is sync but we introduce an async - // flow now for later on - this.find(Array.isArray(terms) ? terms : [terms]).then(data => { - // TODO: For the moment, lets just fake a target - this.data = data; - this.trigger('change', { - target: { - value: this.value.join('\n'), - // TODO: selectedOptions is what