diff --git a/ui-v2/app/components/phrase-editor.js b/ui-v2/app/components/phrase-editor.js new file mode 100644 index 0000000000..e280b571c1 --- /dev/null +++ b/ui-v2/app/components/phrase-editor.js @@ -0,0 +1,44 @@ +import Component from '@ember/component'; +import { get, set } from '@ember/object'; + +export default Component.extend({ + classNames: ['phrase-editor'], + item: '', + remove: function(index, e) { + this.items.removeAt(index, 1); + this.onchange(e); + }, + add: function(e) { + const value = get(this, 'item').trim(); + if (value !== '') { + set(this, 'item', ''); + const currentItems = get(this, 'items') || []; + const items = new Set(currentItems).add(value); + if (items.size > currentItems.length) { + set(this, 'items', [...items]); + this.onchange(e); + } + } + }, + onkeydown: function(e) { + switch (e.keyCode) { + case 8: + if (e.target.value == '' && this.items.length > 0) { + this.remove(this.items.length - 1); + } + break; + } + }, + oninput: function(e) { + set(this, 'item', e.target.value); + }, + onchange: function(e) { + let searchable = get(this, 'searchable'); + if (!Array.isArray(searchable)) { + searchable = [searchable]; + } + searchable.forEach(item => { + item.search(get(this, 'items')); + }); + }, +}); diff --git a/ui-v2/app/controllers/dc/services/index.js b/ui-v2/app/controllers/dc/services/index.js index 912a5316b3..e8bfff9a45 100644 --- a/ui-v2/app/controllers/dc/services/index.js +++ b/ui-v2/app/controllers/dc/services/index.js @@ -2,7 +2,6 @@ import Controller from '@ember/controller'; import { get, computed } from '@ember/object'; import { htmlSafe } from '@ember/string'; import WithEventSource from 'consul-ui/mixins/with-event-source'; -import WithHealthFiltering from 'consul-ui/mixins/with-health-filtering'; import WithSearching from 'consul-ui/mixins/with-searching'; const max = function(arr, prop) { return arr.reduce(function(prev, item) { @@ -26,21 +25,23 @@ const width = function(num) { const widthDeclaration = function(num) { return htmlSafe(`width: ${num}px`); }; -export default Controller.extend(WithEventSource, WithSearching, WithHealthFiltering, { +export default Controller.extend(WithEventSource, WithSearching, { + queryParams: { + s: { + as: 'filter', + }, + }, init: function() { this.searchParams = { service: 's', }; this._super(...arguments); }, - searchable: computed('filtered', function() { + searchable: computed('items.[]', function() { return get(this, 'searchables.service') - .add(get(this, 'filtered')) - .search(get(this, this.searchParams.service)); + .add(get(this, 'items')) + .search(get(this, 'terms')); }), - filter: function(item, { s = '', status = '' }) { - return item.hasStatus(status); - }, maxWidth: computed('{maxPassing,maxWarning,maxCritical}', function() { const PADDING = 32 * 3 + 13; return ['maxPassing', 'maxWarning', 'maxCritical'].reduce((prev, item) => { @@ -58,14 +59,14 @@ export default Controller.extend(WithEventSource, WithSearching, WithHealthFilte // 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('filtered', function() { - return max(get(this, 'filtered'), 'ChecksPassing'); + maxPassing: computed('items.[]', function() { + return max(get(this, 'items'), 'ChecksPassing'); }), - maxWarning: computed('filtered', function() { - return max(get(this, 'filtered'), 'ChecksWarning'); + maxWarning: computed('items.[]', function() { + return max(get(this, 'items'), 'ChecksWarning'); }), - maxCritical: computed('filtered', function() { - return max(get(this, 'filtered'), 'ChecksCritical'); + maxCritical: computed('items.[]', function() { + return max(get(this, 'items'), 'ChecksCritical'); }), passingWidth: computed('maxPassing', function() { return widthDeclaration(width(get(this, 'maxPassing'))); diff --git a/ui-v2/app/routes/dc/services/index.js b/ui-v2/app/routes/dc/services/index.js index 2b900f502e..7d479a6adc 100644 --- a/ui-v2/app/routes/dc/services/index.js +++ b/ui-v2/app/routes/dc/services/index.js @@ -10,10 +10,29 @@ export default Route.extend({ as: 'filter', replace: true, }, + // temporary support of old style status + status: { + as: 'status', + }, }, model: function(params) { const repo = get(this, 'repo'); + let terms = params.s || ''; + // we check for the old style `status` variable here + // and convert it to the new style filter=status:critical + let status = params.status; + if (status) { + status = `status:${status}`; + if (terms.indexOf(status) === -1) { + terms = terms + .split('\n') + .concat(status) + .join('\n') + .trim(); + } + } return hash({ + terms: terms !== '' ? terms.split('\n') : [], items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name), }); }, diff --git a/ui-v2/app/search/filters/service.js b/ui-v2/app/search/filters/service.js index c1fcc0daaf..a5c6547bc7 100644 --- a/ui-v2/app/search/filters/service.js +++ b/ui-v2/app/search/filters/service.js @@ -1,14 +1,34 @@ import { get } from '@ember/object'; +import ucfirst from 'consul-ui/utils/ucfirst'; +const find = function(obj, term) { + if (Array.isArray(obj)) { + return obj.some(function(item) { + return find(item, term); + }); + } + return obj.toLowerCase().indexOf(term) !== -1; +}; export default function(filterable) { return filterable(function(item, { s = '' }) { const term = s.toLowerCase(); - return ( - get(item, 'Name') - .toLowerCase() - .indexOf(term) !== -1 || - (get(item, 'Tags') || []).some(function(item) { - return item.toLowerCase().indexOf(term) !== -1; - }) - ); + let status; + switch (true) { + case term.startsWith('service:'): + return find(get(item, 'Name'), term.substr(8)); + case term.startsWith('tag:'): + return find(get(item, 'Tags') || [], term.substr(4)); + case term.startsWith('status:'): + status = term.substr(7); + switch (term.substr(7)) { + case 'warning': + case 'critical': + case 'passing': + return get(item, `Checks${ucfirst(status)}`) > 0; + default: + return false; + } + default: + return find(get(item, 'Name'), term) || find(get(item, 'Tags') || [], term); + } }); } diff --git a/ui-v2/app/styles/base/icons/base-placeholders.scss b/ui-v2/app/styles/base/icons/base-placeholders.scss new file mode 100644 index 0000000000..a708206da9 --- /dev/null +++ b/ui-v2/app/styles/base/icons/base-placeholders.scss @@ -0,0 +1,14 @@ +%with-icon { + background-repeat: no-repeat; + background-position: center; +} +%as-pseudo { + display: inline-block; + content: ''; + visibility: visible; + background-size: contain; +} +%with-cancel-plain-icon { + @extend %with-icon; + background-image: $cancel-plain-svg; +} diff --git a/ui-v2/app/styles/base/icons/index.scss b/ui-v2/app/styles/base/icons/index.scss index bb221c7e8f..3fe0d277f0 100644 --- a/ui-v2/app/styles/base/icons/index.scss +++ b/ui-v2/app/styles/base/icons/index.scss @@ -1 +1,2 @@ @import './base-variables'; +@import './base-placeholders'; diff --git a/ui-v2/app/styles/components/index.scss b/ui-v2/app/styles/components/index.scss index edfda9d16e..3493041c17 100644 --- a/ui-v2/app/styles/components/index.scss +++ b/ui-v2/app/styles/components/index.scss @@ -21,6 +21,7 @@ @import './healthcheck-info'; @import './healthchecked-resource'; @import './freetext-filter'; +@import './phrase-editor'; @import './filter-bar'; @import './tomography-graph'; @import './action-group'; diff --git a/ui-v2/app/styles/components/phrase-editor.scss b/ui-v2/app/styles/components/phrase-editor.scss new file mode 100644 index 0000000000..b7852453a1 --- /dev/null +++ b/ui-v2/app/styles/components/phrase-editor.scss @@ -0,0 +1,4 @@ +@import './phrase-editor/index'; +.phrase-editor { + @extend %phrase-editor; +} diff --git a/ui-v2/app/styles/components/phrase-editor/index.scss b/ui-v2/app/styles/components/phrase-editor/index.scss new file mode 100644 index 0000000000..bc18252196 --- /dev/null +++ b/ui-v2/app/styles/components/phrase-editor/index.scss @@ -0,0 +1,2 @@ +@import './skin'; +@import './layout'; diff --git a/ui-v2/app/styles/components/phrase-editor/layout.scss b/ui-v2/app/styles/components/phrase-editor/layout.scss new file mode 100644 index 0000000000..d998a37807 --- /dev/null +++ b/ui-v2/app/styles/components/phrase-editor/layout.scss @@ -0,0 +1,46 @@ +%phrase-editor { + display: flex; + margin-top: 14px; + margin-bottom: 5px; +} +%phrase-editor ul { + overflow: hidden; +} +%phrase-editor li { + @extend %pill; + float: left; + margin-right: 4px; +} +%phrase-editor span { + display: none; +} +%phrase-editor label { + flex-grow: 1; +} +%phrase-editor input { + width: 100%; + height: 33px; + padding: 8px 10px; + box-sizing: border-box; +} +@media #{$--horizontal-selects} { + %phrase-editor { + margin-top: 14px; + } + %phrase-editor ul { + padding-top: 5px; + padding-left: 5px; + } + %phrase-editor input { + padding-left: 3px; + } +} +@media #{$--lt-horizontal-selects} { + %phrase-editor { + margin-top: 9px; + } + %phrase-editor label { + display: block; + margin-top: 5px; + } +} diff --git a/ui-v2/app/styles/components/phrase-editor/skin.scss b/ui-v2/app/styles/components/phrase-editor/skin.scss new file mode 100644 index 0000000000..78046d57bc --- /dev/null +++ b/ui-v2/app/styles/components/phrase-editor/skin.scss @@ -0,0 +1,18 @@ +@media #{$--horizontal-selects} { + %phrase-editor { + border: 1px solid $gray-300; + border-radius: 2px; + } + %phrase-editor input:focus { + outline: 0; + } +} +@media #{$--lt-horizontal-selects} { + %phrase-editor label { + border: 1px solid $gray-300; + border-radius: 2px; + } +} +%phrase-editor input { + -webkit-appearance: none; +} diff --git a/ui-v2/app/styles/components/pill/layout.scss b/ui-v2/app/styles/components/pill/layout.scss index fceb4a5a55..325dc503fb 100644 --- a/ui-v2/app/styles/components/pill/layout.scss +++ b/ui-v2/app/styles/components/pill/layout.scss @@ -2,3 +2,7 @@ display: inline-block; padding: 1px 5px; } +%pill button { + padding: 0; + margin-right: 3px; +} diff --git a/ui-v2/app/styles/components/pill/skin.scss b/ui-v2/app/styles/components/pill/skin.scss index b234ac46df..169bda915f 100644 --- a/ui-v2/app/styles/components/pill/skin.scss +++ b/ui-v2/app/styles/components/pill/skin.scss @@ -2,3 +2,13 @@ @extend %frame-gray-900; border-radius: $radius-small; } +%pill button { + background-color: transparent; + font-size: 0; + cursor: pointer; +} +%pill button::before { + @extend %with-cancel-plain-icon, %as-pseudo; + width: 10px; + height: 10px; +} diff --git a/ui-v2/app/templates/components/phrase-editor.hbs b/ui-v2/app/templates/components/phrase-editor.hbs new file mode 100644 index 0000000000..480849c5f9 --- /dev/null +++ b/ui-v2/app/templates/components/phrase-editor.hbs @@ -0,0 +1,11 @@ +