From 31ad6e39caecf52045189c5fef772587d9926410 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Tue, 1 Sep 2020 19:13:11 +0100 Subject: [PATCH] ui: Improved filtering and sorting (#8591) --- .../consul-intention-search-bar/index.hbs | 74 +++++++ .../consul-intention-search-bar/index.js | 5 + .../consul-node-search-bar/index.hbs | 65 ++++++ .../consul-node-search-bar/index.js | 5 + .../consul-nspace-search-bar/index.hbs | 37 ++++ .../consul-nspace-search-bar/index.js | 5 + .../consul-policy-search-bar/index.hbs | 77 +++++++ .../consul-policy-search-bar/index.js | 3 + .../consul-role-search-bar/index.hbs | 43 ++++ .../consul-role-search-bar/index.js | 3 + .../index.hbs | 86 ++++++++ .../index.js | 5 + .../consul-service-search-bar/index.hbs | 111 ++++++++++ .../consul-service-search-bar/index.js | 5 + .../consul-token-search-bar/index.hbs | 57 +++++ .../consul-token-search-bar/index.js | 3 + .../consul-upstream-search-bar/index.hbs | 62 ++++++ .../consul-upstream-search-bar/index.js | 5 + ui-v2/app/components/popover-select/index.js | 2 +- .../popover-select/option/index.hbs | 1 + .../app/controllers/dc/acls/policies/index.js | 2 + ui-v2/app/controllers/dc/intentions/index.js | 2 + ui-v2/app/controllers/dc/nodes/index.js | 1 + ui-v2/app/controllers/dc/services/index.js | 10 + .../controllers/dc/services/show/instances.js | 10 + .../controllers/dc/services/show/services.js | 12 ++ .../controllers/dc/services/show/upstreams.js | 12 ++ ui-v2/app/filter/predicates/intention.js | 9 + ui-v2/app/filter/predicates/node.js | 8 + ui-v2/app/filter/predicates/policy.js | 28 +++ .../app/filter/predicates/service-instance.js | 19 ++ ui-v2/app/filter/predicates/service.js | 67 ++++++ ui-v2/app/filter/predicates/token.js | 21 ++ ui-v2/app/helpers/filter-predicate.js | 9 + ui-v2/app/helpers/policy/group.js | 3 +- ui-v2/app/initializers/sort.js | 2 + ui-v2/app/models/policy.js | 6 + ui-v2/app/models/service-instance.js | 46 +++- ui-v2/app/models/token.js | 5 + ui-v2/app/services/filter.js | 23 ++ ui-v2/app/services/repository/intention.js | 2 +- .../app/sort/comparators/service-instance.js | 23 ++ .../styles/base/components/buttons/index.scss | 3 - .../base/components/popover-menu/skin.scss | 1 - .../styles/base/icons/icon-placeholders.scss | 28 +++ ui-v2/app/styles/components/app-view.scss | 4 +- ui-v2/app/styles/components/filter-bar.scss | 15 +- .../styles/components/filter-bar/layout.scss | 36 +++- .../styles/components/filter-bar/skin.scss | 3 - .../main-nav-horizontal/layout.scss | 3 + .../app/styles/components/popover-select.scss | 58 ++++++ .../app/templates/dc/acls/policies/index.hbs | 195 ++++++++--------- ui-v2/app/templates/dc/acls/roles/index.hbs | 45 +--- ui-v2/app/templates/dc/acls/tokens/index.hbs | 196 ++++++++---------- ui-v2/app/templates/dc/intentions/index.hbs | 90 +++----- ui-v2/app/templates/dc/kv/index.hbs | 82 ++++---- ui-v2/app/templates/dc/nodes/index.hbs | 66 ++---- ui-v2/app/templates/dc/nspaces/index.hbs | 42 +--- ui-v2/app/templates/dc/services/index.hbs | 73 +++---- .../templates/dc/services/show/instances.hbs | 37 +++- .../dc/services/show/intentions/index.hbs | 88 +++----- .../templates/dc/services/show/services.hbs | 64 ++++-- .../templates/dc/services/show/upstreams.hbs | 53 ++++- .../dc/acls/policies/sorting.feature | 72 +++---- .../tests/acceptance/dc/nodes/sorting.feature | 146 ++++++------- .../dc/services/show/upstreams.feature | 1 + .../acceptance/dc/services/sorting.feature | 32 +-- ui-v2/tests/pages/dc/acls/policies/index.js | 2 +- ui-v2/tests/pages/dc/acls/roles/index.js | 2 +- ui-v2/tests/pages/dc/acls/tokens/index.js | 2 +- ui-v2/tests/pages/dc/intentions/index.js | 2 +- ui-v2/tests/pages/dc/nodes/index.js | 2 +- ui-v2/tests/pages/dc/nspaces/index.js | 2 +- ui-v2/tests/pages/dc/services/index.js | 2 +- ui-v2/tests/pages/dc/services/show.js | 2 +- .../unit/filter/predicates/intention-test.js | 43 ++++ .../unit/filter/predicates/service-test.js | 171 +++++++++++++++ 77 files changed, 1905 insertions(+), 732 deletions(-) create mode 100644 ui-v2/app/components/consul-intention-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-intention-search-bar/index.js create mode 100644 ui-v2/app/components/consul-node-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-node-search-bar/index.js create mode 100644 ui-v2/app/components/consul-nspace-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-nspace-search-bar/index.js create mode 100644 ui-v2/app/components/consul-policy-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-policy-search-bar/index.js create mode 100644 ui-v2/app/components/consul-role-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-role-search-bar/index.js create mode 100644 ui-v2/app/components/consul-service-instance-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-service-instance-search-bar/index.js create mode 100644 ui-v2/app/components/consul-service-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-service-search-bar/index.js create mode 100644 ui-v2/app/components/consul-token-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-token-search-bar/index.js create mode 100644 ui-v2/app/components/consul-upstream-search-bar/index.hbs create mode 100644 ui-v2/app/components/consul-upstream-search-bar/index.js create mode 100644 ui-v2/app/controllers/dc/services/show/services.js create mode 100644 ui-v2/app/controllers/dc/services/show/upstreams.js create mode 100644 ui-v2/app/filter/predicates/intention.js create mode 100644 ui-v2/app/filter/predicates/node.js create mode 100644 ui-v2/app/filter/predicates/policy.js create mode 100644 ui-v2/app/filter/predicates/service-instance.js create mode 100644 ui-v2/app/filter/predicates/service.js create mode 100644 ui-v2/app/filter/predicates/token.js create mode 100644 ui-v2/app/helpers/filter-predicate.js create mode 100644 ui-v2/app/services/filter.js create mode 100644 ui-v2/app/sort/comparators/service-instance.js create mode 100644 ui-v2/tests/unit/filter/predicates/intention-test.js create mode 100644 ui-v2/tests/unit/filter/predicates/service-test.js diff --git a/ui-v2/app/components/consul-intention-search-bar/index.hbs b/ui-v2/app/components/consul-intention-search-bar/index.hbs new file mode 100644 index 0000000000..c848f9a51f --- /dev/null +++ b/ui-v2/app/components/consul-intention-search-bar/index.hbs @@ -0,0 +1,74 @@ +
+ +
+ + + + Permissions + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + +{{/let}} + + +
+
+ + + + {{#let (from-entries (array + (array "Action:asc" "Allow to Deny") + (array "Action:desc" "Deny to Allow") + (array "SourceName:asc" "Source: A to Z") + (array "SourceName:desc" "Source: Z to A") + (array "DestinationName:asc" "Destination: A to Z") + (array "DestinationName:desc" "Destination: Z to A") + (array "Precedence:asc" "Precedence: Ascending") + (array "Precedence:desc" "Precedence: Descending") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + + + + + + + + + +{{/let}} + + +
+ \ No newline at end of file diff --git a/ui-v2/app/components/consul-intention-search-bar/index.js b/ui-v2/app/components/consul-intention-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-intention-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/consul-node-search-bar/index.hbs b/ui-v2/app/components/consul-node-search-bar/index.hbs new file mode 100644 index 0000000000..82814f07aa --- /dev/null +++ b/ui-v2/app/components/consul-node-search-bar/index.hbs @@ -0,0 +1,65 @@ +
+ +
+ + + + Health Status + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + +
+
+ + + + {{#let (from-entries (array + (array "Node:asc" "A to Z") + (array "Node:desc" "Z to A") + (array "Status:asc" "Unhealthy to Healthy") + (array "Status:desc" "Healthy to Unhealthy") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + +{{/let}} + + +
+ \ No newline at end of file diff --git a/ui-v2/app/components/consul-node-search-bar/index.js b/ui-v2/app/components/consul-node-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-node-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/consul-nspace-search-bar/index.hbs b/ui-v2/app/components/consul-nspace-search-bar/index.hbs new file mode 100644 index 0000000000..f7a5beca93 --- /dev/null +++ b/ui-v2/app/components/consul-nspace-search-bar/index.hbs @@ -0,0 +1,37 @@ +
+ +
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + +
+ \ No newline at end of file diff --git a/ui-v2/app/components/consul-nspace-search-bar/index.js b/ui-v2/app/components/consul-nspace-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-nspace-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/consul-policy-search-bar/index.hbs b/ui-v2/app/components/consul-policy-search-bar/index.hbs new file mode 100644 index 0000000000..88e3482005 --- /dev/null +++ b/ui-v2/app/components/consul-policy-search-bar/index.hbs @@ -0,0 +1,77 @@ +
+ +
+ + + + Datacenters + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + {{#each dcs as |dc|}} + + {{/each}} +{{/let}} + + + + + + + Type + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + +{{/let}} + + +
+
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-policy-search-bar/index.js b/ui-v2/app/components/consul-policy-search-bar/index.js new file mode 100644 index 0000000000..5570647734 --- /dev/null +++ b/ui-v2/app/components/consul-policy-search-bar/index.js @@ -0,0 +1,3 @@ +import Component from '@ember/component'; + +export default Component.extend({}); diff --git a/ui-v2/app/components/consul-role-search-bar/index.hbs b/ui-v2/app/components/consul-role-search-bar/index.hbs new file mode 100644 index 0000000000..359f672c44 --- /dev/null +++ b/ui-v2/app/components/consul-role-search-bar/index.hbs @@ -0,0 +1,43 @@ +
+ +
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + (array "CreateIndex:desc" "Newest to oldest") + (array "CreateIndex:asc" "Oldest to newest") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-role-search-bar/index.js b/ui-v2/app/components/consul-role-search-bar/index.js new file mode 100644 index 0000000000..5570647734 --- /dev/null +++ b/ui-v2/app/components/consul-role-search-bar/index.js @@ -0,0 +1,3 @@ +import Component from '@ember/component'; + +export default Component.extend({}); diff --git a/ui-v2/app/components/consul-service-instance-search-bar/index.hbs b/ui-v2/app/components/consul-service-instance-search-bar/index.hbs new file mode 100644 index 0000000000..95df8a2925 --- /dev/null +++ b/ui-v2/app/components/consul-service-instance-search-bar/index.hbs @@ -0,0 +1,86 @@ +
+ +
+ + + + Health Status + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + +{{#if (gt sources.length 0)}} + + + + Source + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} +{{#each sources as |source|}} + +{{/each}} +{{/let}} + + +{{/if}} +
+
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + (array "Status:asc" "Unhealthy to Healthy") + (array "Status:desc" "Healthy to Unhealthy") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-service-instance-search-bar/index.js b/ui-v2/app/components/consul-service-instance-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-service-instance-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/consul-service-search-bar/index.hbs b/ui-v2/app/components/consul-service-search-bar/index.hbs new file mode 100644 index 0000000000..9013c715f0 --- /dev/null +++ b/ui-v2/app/components/consul-service-search-bar/index.hbs @@ -0,0 +1,111 @@ +
+ +
+ + + + Health Status + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + + + + + Service Type + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + + + +{{/let}} + + +{{#if (gt sources.length 0)}} + + + + Source + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} +{{#each sources as |source|}} + +{{/each}} +{{/let}} + + +{{/if}} +
+
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + (array "Status:asc" "Unhealthy to Healthy") + (array "Status:desc" "Healthy to Unhealthy") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-service-search-bar/index.js b/ui-v2/app/components/consul-service-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-service-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/consul-token-search-bar/index.hbs b/ui-v2/app/components/consul-token-search-bar/index.hbs new file mode 100644 index 0000000000..87fdfe9201 --- /dev/null +++ b/ui-v2/app/components/consul-token-search-bar/index.hbs @@ -0,0 +1,57 @@ +
+ +
+ + + + Type + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + +{{/let}} + + +
+
+ + + + {{#let (from-entries (array + (array "CreateTime:desc" "Newest to oldest") + (array "CreateTime:asc" "Oldest to newest") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-token-search-bar/index.js b/ui-v2/app/components/consul-token-search-bar/index.js new file mode 100644 index 0000000000..5570647734 --- /dev/null +++ b/ui-v2/app/components/consul-token-search-bar/index.js @@ -0,0 +1,3 @@ +import Component from '@ember/component'; + +export default Component.extend({}); diff --git a/ui-v2/app/components/consul-upstream-search-bar/index.hbs b/ui-v2/app/components/consul-upstream-search-bar/index.hbs new file mode 100644 index 0000000000..d8a4af87b9 --- /dev/null +++ b/ui-v2/app/components/consul-upstream-search-bar/index.hbs @@ -0,0 +1,62 @@ +
+ +
+ + + + Type + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + +{{/let}} + + +
+
+ + + + {{#let (from-entries (array + (array "Name:asc" "A to Z") + (array "Name:desc" "Z to A") + (array "Status:asc" "Unhealthy to Healthy") + (array "Status:desc" "Healthy to Unhealthy") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + +{{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + +{{/let}} + + +
+ diff --git a/ui-v2/app/components/consul-upstream-search-bar/index.js b/ui-v2/app/components/consul-upstream-search-bar/index.js new file mode 100644 index 0000000000..4798652642 --- /dev/null +++ b/ui-v2/app/components/consul-upstream-search-bar/index.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/popover-select/index.js b/ui-v2/app/components/popover-select/index.js index 4324249454..5c63497fb2 100644 --- a/ui-v2/app/components/popover-select/index.js +++ b/ui-v2/app/components/popover-select/index.js @@ -6,7 +6,7 @@ export default Component.extend(Slotted, { tagName: '', dom: service('dom'), multiple: false, - subtractive: true, + subtractive: false, onchange: function() {}, addOption: function(option) { if (typeof this._options === 'undefined') { diff --git a/ui-v2/app/components/popover-select/option/index.hbs b/ui-v2/app/components/popover-select/option/index.hbs index 8b2cca90ee..86119b1e92 100644 --- a/ui-v2/app/components/popover-select/option/index.hbs +++ b/ui-v2/app/components/popover-select/option/index.hbs @@ -1,6 +1,7 @@ {{#let components.MenuItem as |MenuItem|}} diff --git a/ui-v2/app/controllers/dc/acls/policies/index.js b/ui-v2/app/controllers/dc/acls/policies/index.js index f99cde37f3..54f6cac2d1 100644 --- a/ui-v2/app/controllers/dc/acls/policies/index.js +++ b/ui-v2/app/controllers/dc/acls/policies/index.js @@ -2,6 +2,8 @@ import Controller from '@ember/controller'; export default Controller.extend({ queryParams: { sortBy: 'sort', + dc: 'dc', + type: 'type', search: { as: 'filter', replace: true, diff --git a/ui-v2/app/controllers/dc/intentions/index.js b/ui-v2/app/controllers/dc/intentions/index.js index f99cde37f3..c22bc54cef 100644 --- a/ui-v2/app/controllers/dc/intentions/index.js +++ b/ui-v2/app/controllers/dc/intentions/index.js @@ -1,7 +1,9 @@ import Controller from '@ember/controller'; + export default Controller.extend({ queryParams: { sortBy: 'sort', + access: 'access', search: { as: 'filter', replace: true, diff --git a/ui-v2/app/controllers/dc/nodes/index.js b/ui-v2/app/controllers/dc/nodes/index.js index a33e295d30..b3f36efaee 100644 --- a/ui-v2/app/controllers/dc/nodes/index.js +++ b/ui-v2/app/controllers/dc/nodes/index.js @@ -3,6 +3,7 @@ import Controller from '@ember/controller'; export default Controller.extend({ queryParams: { sortBy: 'sort', + status: 'status', search: { as: 'filter', replace: true, diff --git a/ui-v2/app/controllers/dc/services/index.js b/ui-v2/app/controllers/dc/services/index.js index deebfa8e02..ad77eb5934 100644 --- a/ui-v2/app/controllers/dc/services/index.js +++ b/ui-v2/app/controllers/dc/services/index.js @@ -4,6 +4,9 @@ import { computed } from '@ember/object'; export default Controller.extend({ queryParams: { sortBy: 'sort', + status: 'status', + source: 'source', + type: 'type', search: { as: 'filter', }, @@ -13,4 +16,11 @@ export default Controller.extend({ return item.Kind !== 'connect-proxy'; }); }), + externalSources: computed('services', function() { + const sources = this.services.reduce(function(prev, item) { + return prev.concat(item.ExternalSources || []); + }, []); + // unique, non-empty values, alpha sort + return [...new Set(sources)].filter(Boolean).sort(); + }), }); diff --git a/ui-v2/app/controllers/dc/services/show/instances.js b/ui-v2/app/controllers/dc/services/show/instances.js index a33e295d30..d8a4846dca 100644 --- a/ui-v2/app/controllers/dc/services/show/instances.js +++ b/ui-v2/app/controllers/dc/services/show/instances.js @@ -1,11 +1,21 @@ import Controller from '@ember/controller'; +import { computed } from '@ember/object'; export default Controller.extend({ queryParams: { sortBy: 'sort', + status: 'status', + source: 'source', search: { as: 'filter', replace: true, }, }, + externalSources: computed('items', function() { + const sources = this.items.reduce(function(prev, item) { + return prev.concat(item.ExternalSources || []); + }, []); + // unique, non-empty values, alpha sort + return [...new Set(sources)].filter(Boolean).sort(); + }), }); diff --git a/ui-v2/app/controllers/dc/services/show/services.js b/ui-v2/app/controllers/dc/services/show/services.js new file mode 100644 index 0000000000..b71de3054d --- /dev/null +++ b/ui-v2/app/controllers/dc/services/show/services.js @@ -0,0 +1,12 @@ +import Controller from '@ember/controller'; + +export default Controller.extend({ + queryParams: { + sortBy: 'sort', + instance: 'instance', + search: { + as: 'filter', + replace: true, + }, + }, +}); diff --git a/ui-v2/app/controllers/dc/services/show/upstreams.js b/ui-v2/app/controllers/dc/services/show/upstreams.js new file mode 100644 index 0000000000..b71de3054d --- /dev/null +++ b/ui-v2/app/controllers/dc/services/show/upstreams.js @@ -0,0 +1,12 @@ +import Controller from '@ember/controller'; + +export default Controller.extend({ + queryParams: { + sortBy: 'sort', + instance: 'instance', + search: { + as: 'filter', + replace: true, + }, + }, +}); diff --git a/ui-v2/app/filter/predicates/intention.js b/ui-v2/app/filter/predicates/intention.js new file mode 100644 index 0000000000..fd7e7ffce0 --- /dev/null +++ b/ui-v2/app/filter/predicates/intention.js @@ -0,0 +1,9 @@ +export default () => ({ accesses = [] }) => item => { + if (accesses.length > 0) { + if (accesses.includes(item.Action)) { + return true; + } + return false; + } + return true; +}; diff --git a/ui-v2/app/filter/predicates/node.js b/ui-v2/app/filter/predicates/node.js new file mode 100644 index 0000000000..7effc140a1 --- /dev/null +++ b/ui-v2/app/filter/predicates/node.js @@ -0,0 +1,8 @@ +export default () => ({ statuses = [] }) => { + return item => { + if (statuses.length > 0 && !statuses.includes(item.Status)) { + return false; + } + return true; + }; +}; diff --git a/ui-v2/app/filter/predicates/policy.js b/ui-v2/app/filter/predicates/policy.js new file mode 100644 index 0000000000..f7bd68fd5e --- /dev/null +++ b/ui-v2/app/filter/predicates/policy.js @@ -0,0 +1,28 @@ +import setHelpers from 'mnemonist/set'; +export default () => ({ dcs = [], types = [] }) => { + const typeIncludes = ['global-management', 'standard'].reduce((prev, item) => { + prev[item] = types.includes(item); + return prev; + }, {}); + const selectedDcs = new Set(dcs); + return item => { + let type = true; + let dc = true; + if (types.length > 0) { + type = false; + if (typeIncludes['global-management'] && item.isGlobalManagement) { + type = true; + } + if (typeIncludes['standard'] && !item.isGlobalManagement) { + type = true; + } + } + if (dcs.length > 0) { + // if datacenters is undefined it means the policy is applicable to all datacenters + dc = + typeof item.Datacenters === 'undefined' || + setHelpers.intersectionSize(selectedDcs, new Set(item.Datacenters)) > 0; + } + return type && dc; + }; +}; diff --git a/ui-v2/app/filter/predicates/service-instance.js b/ui-v2/app/filter/predicates/service-instance.js new file mode 100644 index 0000000000..09d05c130b --- /dev/null +++ b/ui-v2/app/filter/predicates/service-instance.js @@ -0,0 +1,19 @@ +import setHelpers from 'mnemonist/set'; +export default () => ({ sources = [], statuses = [] }) => { + const uniqueSources = new Set(sources); + return item => { + if (statuses.length > 0) { + if (statuses.includes(item.Status)) { + return true; + } + return false; + } + if (sources.length > 0) { + if (setHelpers.intersectionSize(uniqueSources, new Set(item.ExternalSources || [])) !== 0) { + return true; + } + return false; + } + return true; + }; +}; diff --git a/ui-v2/app/filter/predicates/service.js b/ui-v2/app/filter/predicates/service.js new file mode 100644 index 0000000000..9aa5d4f09e --- /dev/null +++ b/ui-v2/app/filter/predicates/service.js @@ -0,0 +1,67 @@ +import setHelpers from 'mnemonist/set'; +export default () => ({ instances = [], sources = [], statuses = [], types = [] }) => { + const uniqueSources = new Set(sources); + const typeIncludes = [ + 'ingress-gateway', + 'terminating-gateway', + 'mesh-gateway', + 'service', + 'mesh-enabled', + 'mesh-disabled', + ].reduce((prev, item) => { + prev[item] = types.includes(item); + return prev; + }, {}); + const instanceIncludes = ['registered', 'not-registered'].reduce((prev, item) => { + prev[item] = instances.includes(item); + return prev; + }, {}); + return item => { + if (statuses.length > 0) { + if (statuses.includes(item.MeshStatus)) { + return true; + } + return false; + } + if (instances.length > 0) { + if (item.InstanceCount > 0) { + if (instanceIncludes['registered']) { + return true; + } + } else { + if (instanceIncludes['not-registered']) { + return true; + } + } + return false; + } + if (types.length > 0) { + if (typeIncludes['ingress-gateway'] && item.Kind === 'ingress-gateway') { + return true; + } + if (typeIncludes['terminating-gateway'] && item.Kind === 'terminating-gateway') { + return true; + } + if (typeIncludes['mesh-gateway'] && item.Kind === 'mesh-gateway') { + return true; + } + if (typeIncludes['service'] && typeof item.Kind === 'undefined') { + return true; + } + if (typeIncludes['mesh-enabled'] && typeof item.Proxy !== 'undefined') { + return true; + } + if (typeIncludes['mesh-disabled'] && typeof item.Proxy === 'undefined') { + return true; + } + return false; + } + if (sources.length > 0) { + if (setHelpers.intersectionSize(uniqueSources, new Set(item.ExternalSources || [])) !== 0) { + return true; + } + return false; + } + return true; + }; +}; diff --git a/ui-v2/app/filter/predicates/token.js b/ui-v2/app/filter/predicates/token.js new file mode 100644 index 0000000000..e50b1f2919 --- /dev/null +++ b/ui-v2/app/filter/predicates/token.js @@ -0,0 +1,21 @@ +export default () => ({ types = [] }) => { + const typeIncludes = ['global-management', 'global', 'local'].reduce((prev, item) => { + prev[item] = types.includes(item); + return prev; + }, {}); + return item => { + if (types.length > 0) { + if (typeIncludes['global-management'] && item.isGlobalManagement) { + return true; + } + if (typeIncludes['global'] && !item.Local) { + return true; + } + if (typeIncludes['local'] && item.Local) { + return true; + } + return false; + } + return true; + }; +}; diff --git a/ui-v2/app/helpers/filter-predicate.js b/ui-v2/app/helpers/filter-predicate.js new file mode 100644 index 0000000000..d90b370075 --- /dev/null +++ b/ui-v2/app/helpers/filter-predicate.js @@ -0,0 +1,9 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; + +export default Helper.extend({ + filter: service('filter'), + compute([type, filters], hash) { + return this.filter.predicate(type)(filters); + }, +}); diff --git a/ui-v2/app/helpers/policy/group.js b/ui-v2/app/helpers/policy/group.js index b58ad46404..1a2c829d0a 100644 --- a/ui-v2/app/helpers/policy/group.js +++ b/ui-v2/app/helpers/policy/group.js @@ -1,7 +1,6 @@ import { helper } from '@ember/component/helper'; import { get } from '@ember/object'; - -const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001'; +import { MANAGEMENT_ID } from 'consul-ui/models/policy'; export default helper(function policyGroup([items] /*, hash*/) { return items.reduce( diff --git a/ui-v2/app/initializers/sort.js b/ui-v2/app/initializers/sort.js index 6b173481b2..1f30f55b0b 100644 --- a/ui-v2/app/initializers/sort.js +++ b/ui-v2/app/initializers/sort.js @@ -1,4 +1,5 @@ import service from 'consul-ui/sort/comparators/service'; +import serviceInstance from 'consul-ui/sort/comparators/service-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 @@ export function initialize(container) { const Sort = container.resolveRegistration('service:sort'); const comparators = { service: service(), + serviceInstance: serviceInstance(), kv: kv(), check: check(), intention: intention(), diff --git a/ui-v2/app/models/policy.js b/ui-v2/app/models/policy.js index 7db67378ed..b4c2661b68 100644 --- a/ui-v2/app/models/policy.js +++ b/ui-v2/app/models/policy.js @@ -1,9 +1,12 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; +import { computed } from '@ember/object'; export const PRIMARY_KEY = 'uid'; export const SLUG_KEY = 'ID'; +export const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001'; + export default Model.extend({ [PRIMARY_KEY]: attr('string'), [SLUG_KEY]: attr('string'), @@ -19,6 +22,9 @@ export default Model.extend({ // frontend only for ordering where CreateIndex can't be used CreateTime: attr('date', { defaultValue: 0 }), // + isGlobalManagement: computed('ID', function() { + return this.ID === MANAGEMENT_ID; + }), Datacenter: attr('string'), Namespace: attr('string'), SyncTime: attr('number'), diff --git a/ui-v2/app/models/service-instance.js b/ui-v2/app/models/service-instance.js index 4b8946098b..9df5bd6d03 100644 --- a/ui-v2/app/models/service-instance.js +++ b/ui-v2/app/models/service-instance.js @@ -1,7 +1,8 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { belongsTo } from 'ember-data/relationships'; -import { filter, alias } from '@ember/object/computed'; +import { computed } from '@ember/object'; +import { or, filter, alias } from '@ember/object/computed'; export const PRIMARY_KEY = 'uid'; export const SLUG_KEY = 'Node.Node,Service.ID'; @@ -19,13 +20,52 @@ export default Model.extend({ Checks: attr(), SyncTime: attr('number'), meta: attr(), + Name: or('Service.ID', 'Service.Service'), Tags: alias('Service.Tags'), Meta: alias('Service.Meta'), Namespace: alias('Service.Namespace'), - ServiceChecks: filter('Checks', function(item, i, arr) { + ExternalSources: computed('Service.Meta', function() { + const sources = Object.entries(this.Service.Meta || {}) + .filter(([key, value]) => key === 'external-source') + .map(([key, value]) => { + return value; + }); + return [...new Set(sources)]; + }), + ServiceChecks: filter('Checks.[]', function(item, i, arr) { return item.ServiceID !== ''; }), - NodeChecks: filter('Checks', function(item, i, arr) { + NodeChecks: filter('Checks.[]', function(item, i, arr) { return item.ServiceID === ''; }), + Status: computed('ChecksPassing', 'ChecksWarning', 'ChecksCritical', function() { + switch (true) { + case this.ChecksCritical.length !== 0: + return 'critical'; + case this.ChecksWarning.length !== 0: + return 'warning'; + case this.ChecksPassing.length !== 0: + return 'passing'; + default: + return 'empty'; + } + }), + ChecksPassing: computed('Checks.[]', function() { + return this.Checks.filter(item => item.Status === 'passing'); + }), + ChecksWarning: computed('Checks.[]', function() { + return this.Checks.filter(item => item.Status === 'warning'); + }), + ChecksCritical: computed('Checks.[]', function() { + return this.Checks.filter(item => item.Status === 'critical'); + }), + PercentageChecksPassing: computed('Checks.[]', 'ChecksPassing', function() { + return (this.ChecksPassing.length / this.Checks.length) * 100; + }), + PercentageChecksWarning: computed('Checks.[]', 'ChecksWarning', function() { + return (this.ChecksWarning.length / this.Checks.length) * 100; + }), + PercentageChecksCritical: computed('Checks.[]', 'ChecksCritical', function() { + return (this.ChecksCritical.length / this.Checks.length) * 100; + }), }); diff --git a/ui-v2/app/models/token.js b/ui-v2/app/models/token.js index 5b57072871..9015f73628 100644 --- a/ui-v2/app/models/token.js +++ b/ui-v2/app/models/token.js @@ -1,5 +1,7 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; +import { computed } from '@ember/object'; +import { MANAGEMENT_ID } from 'consul-ui/models/policy'; export const PRIMARY_KEY = 'uid'; export const SLUG_KEY = 'AccessorID'; @@ -24,6 +26,9 @@ export default Model.extend({ Datacenter: attr('string'), Namespace: attr('string'), Local: attr('boolean'), + isGlobalManagement: computed('Policies.[]', function() { + return (this.Policies || []).find(item => item.ID === MANAGEMENT_ID); + }), Policies: attr({ defaultValue: function() { return []; diff --git a/ui-v2/app/services/filter.js b/ui-v2/app/services/filter.js new file mode 100644 index 0000000000..5123a617cb --- /dev/null +++ b/ui-v2/app/services/filter.js @@ -0,0 +1,23 @@ +import Service from '@ember/service'; + +import service from 'consul-ui/filter/predicates/service'; +import serviceInstance from 'consul-ui/filter/predicates/service-instance'; +import node from 'consul-ui/filter/predicates/node'; +import intention from 'consul-ui/filter/predicates/intention'; +import token from 'consul-ui/filter/predicates/token'; +import policy from 'consul-ui/filter/predicates/policy'; + +const predicates = { + service: service(), + serviceInstance: serviceInstance(), + node: node(), + intention: intention(), + token: token(), + policy: policy(), +}; + +export default Service.extend({ + predicate: function(type) { + return predicates[type]; + }, +}); diff --git a/ui-v2/app/services/repository/intention.js b/ui-v2/app/services/repository/intention.js index 72b8a62275..e2ad0e6970 100644 --- a/ui-v2/app/services/repository/intention.js +++ b/ui-v2/app/services/repository/intention.js @@ -16,7 +16,7 @@ export default RepositoryService.extend({ const query = { dc: dc, nspace: nspace, - filter: `SourceName == "${slug}" or DestinationName == "${slug}"`, + filter: `SourceName == "${slug}" or DestinationName == "${slug}" or SourceName == "*" or DestinationName == "*"`, }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; diff --git a/ui-v2/app/sort/comparators/service-instance.js b/ui-v2/app/sort/comparators/service-instance.js new file mode 100644 index 0000000000..66f40eb163 --- /dev/null +++ b/ui-v2/app/sort/comparators/service-instance.js @@ -0,0 +1,23 @@ +export default () => key => { + if (key.startsWith('Status:')) { + const [, dir] = key.split(':'); + const props = [ + 'PercentageChecksPassing', + 'PercentageChecksWarning', + 'PercentageChecksCritical', + ]; + if (dir === 'asc') { + props.reverse(); + } + return function(a, b) { + for (let i in props) { + let prop = props[i]; + if (a[prop] === b[prop]) { + continue; + } + return a[prop] > b[prop] ? -1 : 1; + } + }; + } + return key; +}; diff --git a/ui-v2/app/styles/base/components/buttons/index.scss b/ui-v2/app/styles/base/components/buttons/index.scss index 8f5aa10ddf..bc18252196 100644 --- a/ui-v2/app/styles/base/components/buttons/index.scss +++ b/ui-v2/app/styles/base/components/buttons/index.scss @@ -1,5 +1,2 @@ @import './skin'; @import './layout'; -%sort-button { - @extend %split-button; -} diff --git a/ui-v2/app/styles/base/components/popover-menu/skin.scss b/ui-v2/app/styles/base/components/popover-menu/skin.scss index 917bc35604..8dc9f6d15c 100644 --- a/ui-v2/app/styles/base/components/popover-menu/skin.scss +++ b/ui-v2/app/styles/base/components/popover-menu/skin.scss @@ -6,7 +6,6 @@ width: 16px; height: 16px; position: relative; - top: 2px; } %popover-menu-toggle:checked + label > *::after { @extend %with-chevron-up-mask; diff --git a/ui-v2/app/styles/base/icons/icon-placeholders.scss b/ui-v2/app/styles/base/icons/icon-placeholders.scss index 907ab91d63..6f7988dd3c 100644 --- a/ui-v2/app/styles/base/icons/icon-placeholders.scss +++ b/ui-v2/app/styles/base/icons/icon-placeholders.scss @@ -948,6 +948,16 @@ mask-image: $logo-bitbucket-monochrome-svg; } +%with-logo-consul-color-icon { + @extend %with-icon; + background-image: $consul-logo-color-svg; +} +%with-logo-consul-color-mask { + @extend %with-mask; + -webkit-mask-image: $consul-logo-color-svg; + mask-image: $consul-logo-color-svg; +} + %with-logo-gcp-color-icon { @extend %with-icon; background-image: $logo-gcp-color-svg; @@ -1047,6 +1057,15 @@ -webkit-mask-image: $logo-microsoft-color-svg; mask-image: $logo-microsoft-color-svg; } +%with-logo-nomad-color-icon { + @extend %with-icon; + background-image: $nomad-logo-color-svg; +} +%with-logo-nomad-color-mask { + @extend %with-mask; + -webkit-mask-image: $nomad-logo-color-svg; + mask-image: $nomad-logo-color-svg; +} %with-logo-okta-color-icon { @extend %with-icon; @@ -1098,6 +1117,15 @@ mask-image: $logo-slack-monochrome-svg; } +%with-logo-terraform-color-icon { + @extend %with-icon; + background-image: $terraform-logo-color-svg; +} +%with-logo-terraform-color-mask { + @extend %with-mask; + -webkit-mask-image: $terraform-logo-color-svg; + mask-image: $terraform-logo-color-svg; +} %with-logo-vmware-color-icon { @extend %with-icon; background-image: $logo-vmware-color-svg; diff --git a/ui-v2/app/styles/components/app-view.scss b/ui-v2/app/styles/components/app-view.scss index 650b6a87ce..4f605fb175 100644 --- a/ui-v2/app/styles/components/app-view.scss +++ b/ui-v2/app/styles/components/app-view.scss @@ -72,10 +72,10 @@ main { display: none; } #toolbar-toggle:checked + * { - display: block; + display: flex; } html.template-service.template-show #toolbar-toggle + * { - display: block; + display: flex; padding: 4px; } html.template-service.template-show .actions { diff --git a/ui-v2/app/styles/components/filter-bar.scss b/ui-v2/app/styles/components/filter-bar.scss index c27212a633..935cfe6a6c 100644 --- a/ui-v2/app/styles/components/filter-bar.scss +++ b/ui-v2/app/styles/components/filter-bar.scss @@ -3,14 +3,18 @@ .filter-bar { @extend %filter-bar; } -%filter-bar { +%filter-bar .popover-select { + height: 35px; position: relative; z-index: 3; } -%filter-bar:not(.with-sort) { +%filter-bar [role='menuitem'] { + justify-content: normal !important; +} +html.template-acl.template-list .filter-bar { @extend %filter-bar-reversed; } -%filter-bar [role='radiogroup'] { +html.template-acl.template-list .filter-bar [role='radiogroup'] { @extend %expanded-single-select; } %filter-bar span::before { @@ -19,6 +23,11 @@ margin-left: -2px; } +%filter-bar .popover-menu > [type='checkbox']:checked + label button { + color: $blue-500; + background-color: $gray-100; +} + %filter-bar .value-passing span::before { @extend %with-check-circle-fill-icon, %as-pseudo; } diff --git a/ui-v2/app/styles/components/filter-bar/layout.scss b/ui-v2/app/styles/components/filter-bar/layout.scss index 563b0d0679..5a08de6e19 100644 --- a/ui-v2/app/styles/components/filter-bar/layout.scss +++ b/ui-v2/app/styles/components/filter-bar/layout.scss @@ -5,34 +5,50 @@ margin-top: 0 !important; margin-bottom: -12px; } +%filter-bar .filters { + display: flex; + margin-right: 12px; +} +%filter-bar .filters > *:not(:last-child) { + margin-right: 6px; +} %filter-bar + :not(.notice) { margin-top: 1.8em; } +%filter-bar fieldset { + flex: 0 1 auto; + width: auto; +} %filter-bar-reversed { flex-direction: row-reverse; padding: 4px; margin-bottom: 8px !important; } -%filter-bar fieldset { - flex: 0 1 auto; +%filter-bar-reversed fieldset { + min-width: 210px; width: auto; } -%filter-bar fieldset:first-child:not(:last-child) { - flex: 1 1 auto; - margin-right: 12px; -} %filter-bar-reversed fieldset:first-child:not(:last-child) { flex: 0 1 auto; margin-left: auto; } -%filter-bar-reversed fieldset { - min-width: 210px; - width: auto; -} %filter-bar-reversed > *:first-child { margin-left: 12px; } +@media #{$--horizontal-filters} { + %filter-bar fieldset:first-child:not(:last-child) { + flex: 1 1 auto; + margin-right: 12px; + } +} @media #{$--lt-horizontal-filters} { + %filter-bar { + flex-wrap: wrap; + } + %filter-bar fieldset { + flex: 0 1 100%; + margin-bottom: 8px; + } %filter-bar-reversed > *:first-child { margin-left: 0; } diff --git a/ui-v2/app/styles/components/filter-bar/skin.scss b/ui-v2/app/styles/components/filter-bar/skin.scss index f6090f4b71..f0998780aa 100644 --- a/ui-v2/app/styles/components/filter-bar/skin.scss +++ b/ui-v2/app/styles/components/filter-bar/skin.scss @@ -13,9 +13,6 @@ } } @media #{$--lt-horizontal-selects} { - %filter-bar label:not(:last-child) { - border-bottom: $decor-border-100; - } } %filter-bar [role='radiogroup'] label { cursor: pointer; diff --git a/ui-v2/app/styles/components/main-nav-horizontal/layout.scss b/ui-v2/app/styles/components/main-nav-horizontal/layout.scss index 04a3690900..534ad871ed 100644 --- a/ui-v2/app/styles/components/main-nav-horizontal/layout.scss +++ b/ui-v2/app/styles/components/main-nav-horizontal/layout.scss @@ -14,6 +14,9 @@ right: auto; top: 28px !important; } +%main-nav-horizontal .popover-menu > label > button::after { + top: 2px; +} @media #{$--horizontal-nav} { %main-nav-horizontal > ul, %main-nav-horizontal-panel { diff --git a/ui-v2/app/styles/components/popover-select.scss b/ui-v2/app/styles/components/popover-select.scss index 928eaf81ae..7cc3675e46 100644 --- a/ui-v2/app/styles/components/popover-select.scss +++ b/ui-v2/app/styles/components/popover-select.scss @@ -1,6 +1,64 @@ .popover-select { @extend %popover-select; } +%popover-select label { + height: 100%; +} %popover-select label > * { + @extend %button; + padding: 0 8px !important; + height: 100% !important; + justify-content: space-between !important; + min-width: auto !important; +} +%popover-select label > *::after { + margin-left: 6px; +} +%popover-select.type-sort label > * { @extend %sort-button; } + +%popover-select.type-access button::before, +%popover-select.type-source button::before, +%popover-select.type-status button::before { + margin-right: 10px; +} +%popover-select .value-allow button::before, +%popover-select .value-passing button::before { + @extend %with-check-circle-fill-mask, %as-pseudo; + color: $green-500; +} +%popover-select .value-warning button::before { + @extend %with-alert-triangle-mask, %as-pseudo; + color: $orange-500; +} +%popover-select .value-deny button::before, +%popover-select .value-critical button::before { + @extend %with-cancel-square-fill-mask, %as-pseudo; + color: $red-500; +} +%popover-select .value-empty button::before { + @extend %with-minus-square-fill-mask, %as-pseudo; + color: $gray-400; +} +%popover-select.type-source li button { + text-transform: capitalize; +} +%popover-select.type-source li.aws button { + text-transform: uppercase; +} +%popover-select .aws button::before { + @extend %with-logo-aws-color-icon, %as-pseudo; +} +%popover-select .kubernetes button::before { + @extend %with-logo-kubernetes-color-icon, %as-pseudo; +} +%popover-select .consul button::before { + @extend %with-logo-consul-color-icon, %as-pseudo; +} +%popover-select .nomad button::before { + @extend %with-logo-nomad-color-icon, %as-pseudo; +} +%popover-select .terraform button::before { + @extend %with-logo-terraform-color-icon, %as-pseudo; +} diff --git a/ui-v2/app/templates/dc/acls/policies/index.hbs b/ui-v2/app/templates/dc/acls/policies/index.hbs index 7909a6f96a..b7c7094b62 100644 --- a/ui-v2/app/templates/dc/acls/policies/index.hbs +++ b/ui-v2/app/templates/dc/acls/policies/index.hbs @@ -3,115 +3,100 @@ {{else}} {{title 'Access Controls'}} {{/if}} +{{#let (hash + types=(if type (split type ',') undefined) + dcs=(if dc (split dc ',') undefined) +) as |filters|}} + {{#let (or sortBy "Name:asc") as |sort|}} + + + {{partial 'dc/acls/policies/notifications'}} + + +

+ Access Controls +

+
+ + {{#if isAuthorized }} + {{partial 'dc/acls/nav'}} + {{/if}} + + + {{partial 'dc/acls/disabled'}} + + + {{partial 'dc/acls/authorization'}} + + + Create + + + {{#if (gt items.length 0) }} + - - {{partial 'dc/acls/policies/notifications'}} - - -

- Access Controls -

-
- -{{#if isAuthorized }} - {{partial 'dc/acls/nav'}} -{{/if}} - - - {{partial 'dc/acls/disabled'}} - - - {{partial 'dc/acls/authorization'}} - - - Create - - - {{#if (gt items.length 0) }} - - - - - - {{#let (from-entries (array - (array "Name:asc" "A to Z") - (array "Name:desc" "Z to A") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - -{{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - -{{/let}} - - - - - {{/if}} - - - {{#let (sort-by (comparator 'policy' sort) items) as |sorted|}} - - - + {{/if}} - - - -

- {{#if (gt items.length 0)}} - No policies found - {{else}} - Welcome to Policies - {{/if}} -

+ +{{#let (filter (filter-predicate 'policy' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'policy' sort) filtered) as |sorted|}} + + + - -

- {{#if (gt items.length 0)}} - No policies where found matching that search, or you may not have access to view the policies you are searching for. - {{else}} - There don't seem to be any policies, or you may not have access to view policies yet. - {{/if}} -

+ + + +

+ {{#if (gt items.length 0)}} + No policies found + {{else}} + Welcome to Policies + {{/if}} +

+
+ +

+ {{#if (gt items.length 0)}} + No policies where found matching that search, or you may not have access to view the policies you are searching for. + {{else}} + There don't seem to be any policies, or you may not have access to view policies yet. + {{/if}} +

+
+ + + + +
- - - - -
-
-
+ + {{/let}} +{{/let}} +
+
{{/let}} - - {{/let}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/acls/roles/index.hbs b/ui-v2/app/templates/dc/acls/roles/index.hbs index 003abf01e3..b373225367 100644 --- a/ui-v2/app/templates/dc/acls/roles/index.hbs +++ b/ui-v2/app/templates/dc/acls/roles/index.hbs @@ -35,46 +35,13 @@ {{#if (gt items.length 0) }} - - - - - - {{#let (from-entries (array - (array "Name:asc" "A to Z") - (array "Name:desc" "Z to A") - (array "CreateIndex:desc" "Newest to oldest") - (array "CreateIndex:asc" "Oldest to newest") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - -{{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - -{{/let}} - - - - + + @sort={{sort}} + @onsort={{action (mut sortBy) value="target.selected"}} + /> {{/if}} diff --git a/ui-v2/app/templates/dc/acls/tokens/index.hbs b/ui-v2/app/templates/dc/acls/tokens/index.hbs index cf226665cc..d1fad80e13 100644 --- a/ui-v2/app/templates/dc/acls/tokens/index.hbs +++ b/ui-v2/app/templates/dc/acls/tokens/index.hbs @@ -4,113 +4,97 @@ {{title 'Access Controls'}} {{/if}} -{{#let (or sortBy "CreateTime:desc") as |sort|}} - - - {{partial 'dc/acls/tokens/notifications'}} - - -

- Access Controls -

-
- -{{#if isAuthorized }} - {{partial 'dc/acls/nav'}} -{{/if}} - - - {{partial 'dc/acls/disabled'}} - - - {{partial 'dc/acls/authorization'}} - - - Create - - - {{#if (gt items.length 0)}} - - - - - - {{#let (from-entries (array - (array "CreateTime:desc" "Newest to oldest") - (array "CreateTime:asc" "Oldest to newest") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - +{{#let (hash + types=(if type (split type ',') undefined) +) as |filters|}} + {{#let (or sortBy "CreateTime:desc") as |sort|}} + + + {{partial 'dc/acls/tokens/notifications'}} + + +

+ Access Controls +

+
+ + {{#if isAuthorized }} + {{partial 'dc/acls/nav'}} + {{/if}} + + + {{partial 'dc/acls/disabled'}} + + + {{partial 'dc/acls/authorization'}} + + + Create + + + {{#if (gt items.length 0)}} + + {{/if}} + + + {{#if (token/is-legacy items)}} +

Update. We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our documentation.

+ {{/if}} + {{#let (filter (filter-predicate 'token' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'token' sort) filtered) as |sorted|}} + + + + + + + +

+ {{#if (gt items.length 0)}} + No tokens found + {{else}} + Welcome to ACL Tokens + {{/if}} +

+
+ +

+ {{#if (gt items.length 0)}} + No tokens where found matching that search, or you may not have access to view the tokens you are searching for. + {{else}} + There don't seem to be any tokens, or you may not have access to view tokens yet. + {{/if}} +

+
+
+
+
+ {{/let}} {{/let}} -
-
-
-
- {{/if}} -
- -{{#if (token/is-legacy items)}} -

Update. We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our documentation.

-{{/if}} - {{#let (sort-by (comparator 'token' sort) items) as |sorted|}} - - - - - - -

- {{#if (gt items.length 0)}} - No tokens found - {{else}} - Welcome to ACL Tokens - {{/if}} -

-
- -

- {{#if (gt items.length 0)}} - No tokens where found matching that search, or you may not have access to view the tokens you are searching for. - {{else}} - There don't seem to be any tokens, or you may not have access to view tokens yet. - {{/if}} -

-
-
-
-
+
{{/let}} -
- {{/let}} diff --git a/ui-v2/app/templates/dc/intentions/index.hbs b/ui-v2/app/templates/dc/intentions/index.hbs index 57a19a5834..80047c2ddc 100644 --- a/ui-v2/app/templates/dc/intentions/index.hbs +++ b/ui-v2/app/templates/dc/intentions/index.hbs @@ -6,11 +6,15 @@ +{{#let api.data as |items|}} + {{#let (hash + accesses=(if access (split access ',') undefined) + ) as |filters|}} {{#let (or sortBy "Action:asc") as |sort|}}

- Intentions {{format-number api.data.length}} total + Intentions {{format-number items.length}} total

@@ -18,73 +22,34 @@ Create
- {{#if (gt api.data.length 0) }} - - - - - - {{#let (from-entries (array - (array "Action:asc" "Allow to Deny") - (array "Action:desc" "Deny to Allow") - (array "SourceName:asc" "Source: A to Z") - (array "SourceName:desc" "Source: Z to A") - (array "DestinationName:asc" "Destination: A to Z") - (array "DestinationName:desc" "Destination: Z to A") - (array "Precedence:asc" "Precedence: Ascending") - (array "Precedence:desc" "Precedence: Descending") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - - - - - - - - - {{/let}} - - - - - {{/if}} + + @sort={{sort}} + @onsort={{action (mut sortBy) value="target.selected"}} + + @filter={{filters}} + @onfilter={{hash + access=(action (mut access) value="target.selectedItems") + }} + /> + {{/if}} - {{#let (sort-by (comparator 'intention' sort) api.data) as |sorted|}} + {{#let (filter (filter-predicate 'intention' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'intention' sort) filtered) as |sorted|}} - +

- {{#if (gt api.data.length 0)}} + {{#if (gt items.length 0)}} No intentions found {{else}} Welcome to Intentions @@ -93,7 +58,7 @@

- {{#if (gt api.data.length 0)}} + {{#if (gt items.length 0)}} No intentions where found matching that search, or you may not have access to view the intentions you are searching for. {{else}} There don't seem to be any intentions, or you may not have access to view intentions yet. @@ -112,9 +77,12 @@ - {{/let}} + {{/let}} + {{/let}} - {{/let}} + {{/let}} + {{/let}} +{{/let}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/kv/index.hbs b/ui-v2/app/templates/dc/kv/index.hbs index 013abf9bc5..b21cdafd33 100644 --- a/ui-v2/app/templates/dc/kv/index.hbs +++ b/ui-v2/app/templates/dc/kv/index.hbs @@ -23,46 +23,48 @@ {{#if (gt items.length 0) }} - - - - - - {{#let (from-entries (array - (array "Key:asc" "A to Z") - (array "Key:desc" "Z to A") - (array "isFolder:desc" "Folders to Keys") - (array "isFolder:asc" "Keys to Folders") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - {{/let}} - - - - +

+ +
+ + + + {{#let (from-entries (array + (array "Key:asc" "A to Z") + (array "Key:desc" "Z to A") + (array "isFolder:desc" "Folders to Keys") + (array "isFolder:asc" "Keys to Folders") + )) + as |selectable| + }} + {{get selectable sort}} + {{/let}} + + + + {{#let components.Optgroup components.Option as |Optgroup Option|}} + + + + + + + + + {{/let}} + + +
+ {{/if}}
diff --git a/ui-v2/app/templates/dc/nodes/index.hbs b/ui-v2/app/templates/dc/nodes/index.hbs index c5b38372b6..558b182054 100644 --- a/ui-v2/app/templates/dc/nodes/index.hbs +++ b/ui-v2/app/templates/dc/nodes/index.hbs @@ -1,5 +1,8 @@ {{title 'Nodes'}} +{{#let (hash + statuses=(if status (split status ',') undefined) +) as |filters|}} {{#let (or sortBy "Node:asc") as |sort|}} @@ -10,53 +13,26 @@ {{#if (gt items.length 0) }} - - - - - - {{#let (from-entries (array - (array "Node:asc" "A to Z") - (array "Node:desc" "Z to A") - (array "Status:asc" "Unhealthy to Healthy") - (array "Status:desc" "Healthy to Unhealthy") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - {{/let}} - - - - + {{/if}} - {{#let (sort-by (comparator 'node' sort) items) as |sorted|}} +{{#let (filter (filter-predicate 'node' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'node' sort) filtered) as |sorted|}} - - + + @@ -68,7 +44,9 @@ - {{/let}} + {{/let}} +{{/let}} + {{/let}} {{/let}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/nspaces/index.hbs b/ui-v2/app/templates/dc/nspaces/index.hbs index a062ba1bb7..034d71331d 100644 --- a/ui-v2/app/templates/dc/nspaces/index.hbs +++ b/ui-v2/app/templates/dc/nspaces/index.hbs @@ -15,40 +15,14 @@ {{#if (gt items.length 0)}} - - - - - - {{#let (from-entries (array - (array "Name:asc" "A to Z") - (array "Name:desc" "Z to A") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - -{{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - -{{/let}} - - - - + {{/if}} diff --git a/ui-v2/app/templates/dc/services/index.hbs b/ui-v2/app/templates/dc/services/index.hbs index b9395b31d8..3eacce3fc9 100644 --- a/ui-v2/app/templates/dc/services/index.hbs +++ b/ui-v2/app/templates/dc/services/index.hbs @@ -1,65 +1,46 @@ {{title 'Services'}} -{{#let (or sortBy "Name:asc") as |sort|}} +{{#let (hash + statuses=(if status (split status ',') undefined) + types=(if type (split type ',') undefined) + sources=(if source (split source ',') undefined) +) as |filters|}} + {{#let (or sortBy "Name:asc") as |sort|}} {{partial 'dc/services/notifications'}}

- Services {{format-number services.length}} total + Services {{format-number services.length}} total

{{#if (gt services.length 0) }} - - - - - - {{#let (from-entries (array - (array "Name:asc" "A to Z") - (array "Name:desc" "Z to A") - (array "Status:asc" "Unhealthy to Healthy") - (array "Status:desc" "Healthy to Unhealthy") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - {{/let}} - - - - + + @sort={{sort}} + @onsort={{action (mut sortBy) value="target.selected"}} + + @filter={{filters}} + @onfilter={{hash + status=(action (mut status) value="target.selectedItems") + type=(action (mut type) value="target.selectedItems") + source=(action (mut source) value="target.selectedItems") + }} + /> {{/if}} -{{#let (sort-by (comparator 'service' sort) services) as |sorted|}} +{{#let (filter (filter-predicate 'service' filters) services) as |filtered|}} + {{#let (sort-by (comparator 'service' sort) filtered) as |sorted|}} - - + + @@ -92,7 +73,9 @@ - {{/let}} + {{/let}} +{{/let}}
+ {{/let}} {{/let}} diff --git a/ui-v2/app/templates/dc/services/show/instances.hbs b/ui-v2/app/templates/dc/services/show/instances.hbs index b919a1cd82..369420aa38 100644 --- a/ui-v2/app/templates/dc/services/show/instances.hbs +++ b/ui-v2/app/templates/dc/services/show/instances.hbs @@ -1,15 +1,32 @@
- {{#if (gt items.length 0) }} +{{#let (hash + statuses=(if status (split status ',') undefined) + sources=(if source (split source ',') undefined) +) as |filters|}} + {{#let (or sortBy "Name:asc") as |sort|}} + {{#if (gt items.length 0) }} - - {{/if}} - - - + + @sort={{sort}} + @onsort={{action (mut sortBy) value="target.selected"}} + + @filter={{filters}} + @onfilter={{hash + status=(action (mut status) value="target.selectedItems") + source=(action (mut source) value="target.selectedItems") + }} + /> + {{/if}} +{{#let (filter (filter-predicate 'serviceInstance' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'serviceInstance' sort) filtered) as |sorted|}} + + + @@ -21,5 +38,9 @@ + {{/let}} +{{/let}} + {{/let}} +{{/let}}
diff --git a/ui-v2/app/templates/dc/services/show/intentions/index.hbs b/ui-v2/app/templates/dc/services/show/intentions/index.hbs index 0849ca5b12..40aa59945b 100644 --- a/ui-v2/app/templates/dc/services/show/intentions/index.hbs +++ b/ui-v2/app/templates/dc/services/show/intentions/index.hbs @@ -3,87 +3,55 @@
+{{#let api.data as |items|}} + {{#let (hash + accesses=(if access (split access ',') undefined) + ) as |filters|}} {{#let (or sortBy "Action:asc") as |sort|}}
Create - {{#if (gt api.data.length 0) }} - - - - - - {{#let (from-entries (array - (array "Action:asc" "Allow to Deny") - (array "Action:desc" "Deny to Allow") - (array "SourceName:asc" "Source: A to Z") - (array "SourceName:desc" "Source: Z to A") - (array "DestinationName:asc" "Destination: A to Z") - (array "DestinationName:desc" "Destination: Z to A") - (array "Precedence:asc" "Precedence: Ascending") - (array "Precedence:desc" "Precedence: Descending") - )) - as |selectable| - }} - {{get selectable sort}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - - - - - - - - - {{/let}} - - - - - {{/if}} - {{#let (sort-by (comparator 'intention' sort) api.data) as |sorted|}} +{{#if (gt items.length 0) }} + +{{/if}} +{{#let (filter (filter-predicate 'intention' filters) items) as |filtered|}} + {{#let (sort-by (comparator 'intention' sort) filtered) as |sorted|}} - +

- There are no intentions {{if (gt intentions.length 0) 'found '}} for this service. + There are no intentions {{if (gt items.length 0) 'found '}} for this service.

- {{/let}} + {{/let}} +{{/let}}
- {{/let}} + {{/let}} + {{/let}} +{{/let}}
diff --git a/ui-v2/app/templates/dc/services/show/services.hbs b/ui-v2/app/templates/dc/services/show/services.hbs index 4b973dc2b5..857f21efd2 100644 --- a/ui-v2/app/templates/dc/services/show/services.hbs +++ b/ui-v2/app/templates/dc/services/show/services.hbs @@ -1,22 +1,50 @@
- {{#if (gt gatewayServices.length 0)}} -

- The following services may receive traffic from external services through this gateway. Learn more about configuring gateways in our - step-by-step guide. -

- - {{else}} - - -

- There are no linked services. -

-
-
- {{/if}} +{{#let (hash + instances=(if instance (split instance ',') undefined) +) as |filters|}} + {{#let (or sortBy "Name:asc") as |sort|}} +{{#if (gt gatewayServices.length 0)}} + + +{{/if}} +

+ The following services may receive traffic from external services through this gateway. Learn more about configuring gateways in our + step-by-step guide. +

+{{#let (filter (filter-predicate 'service' filters) gatewayServices) as |filtered|}} + {{#let (sort-by (comparator 'service' sort) filtered) as |sorted|}} + + + + + + + +

+ There are no linked services. +

+
+
+
+
+ {{/let}} +{{/let}} + {{/let}} +{{/let}}
diff --git a/ui-v2/app/templates/dc/services/show/upstreams.hbs b/ui-v2/app/templates/dc/services/show/upstreams.hbs index d300097615..0a376080c1 100644 --- a/ui-v2/app/templates/dc/services/show/upstreams.hbs +++ b/ui-v2/app/templates/dc/services/show/upstreams.hbs @@ -1,18 +1,49 @@
- {{#if (gt gatewayServices.length 0)}} +{{#let (hash + instances=(if instance (split instance ',') undefined) +) as |filters|}} + {{#let (or sortBy "Name:asc") as |sort|}} +{{#if (gt gatewayServices.length 0)}} + + +{{/if}}

Upstreams are services that may receive traffic from this gateway. Learn more about configuring gateways in our documentation.

- - {{else}} - - -

- There are no upstreams. -

-
-
- {{/if}} +{{#let (filter (filter-predicate 'service' filters) gatewayServices) as |filtered|}} + {{#let (sort-by (comparator 'service' sort) filtered) as |sorted|}} + + + + + + + +

+ There are no upstreams. +

+
+
+
+
+ {{/let}} +{{/let}} + {{/let}} +{{/let}}
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/sorting.feature b/ui-v2/tests/acceptance/dc/acls/policies/sorting.feature index a2b3692686..7ab40d6497 100644 --- a/ui-v2/tests/acceptance/dc/acls/policies/sorting.feature +++ b/ui-v2/tests/acceptance/dc/acls/policies/sorting.feature @@ -1,36 +1,36 @@ -@setupApplicationTest -Feature: dc / acls / policies / sorting - Scenario: Sorting Policies - Given 1 datacenter model with the value "dc-1" - And 4 policy models from yaml - --- - - Name: "system-A" - - Name: "system-D" - - Name: "system-C" - - Name: "system-B" - --- - When I visit the policies page for yaml - --- - dc: dc-1 - --- - Then the url should be /dc-1/acls/policies - Then I see 4 policy models - When I click selected on the sort - When I click options.1.button on the sort - Then I see name on the policies vertically like yaml - --- - - "system-D" - - "system-C" - - "system-B" - - "system-A" - --- - When I click selected on the sort - When I click options.0.button on the sort - Then I see name on the policies vertically like yaml - --- - - "system-A" - - "system-B" - - "system-C" - - "system-D" - --- - +@setupApplicationTest +Feature: dc / acls / policies / sorting + Scenario: Sorting Policies + Given 1 datacenter model with the value "dc-1" + And 4 policy models from yaml + --- + - Name: "system-A" + - Name: "system-D" + - Name: "system-C" + - Name: "system-B" + --- + When I visit the policies page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/acls/policies + Then I see 4 policy models + When I click selected on the sort + When I click options.1.button on the sort + Then I see name on the policies vertically like yaml + --- + - "system-D" + - "system-C" + - "system-B" + - "system-A" + --- + When I click selected on the sort + When I click options.0.button on the sort + Then I see name on the policies vertically like yaml + --- + - "system-A" + - "system-B" + - "system-C" + - "system-D" + --- + diff --git a/ui-v2/tests/acceptance/dc/nodes/sorting.feature b/ui-v2/tests/acceptance/dc/nodes/sorting.feature index d23795d924..dc09fec40c 100644 --- a/ui-v2/tests/acceptance/dc/nodes/sorting.feature +++ b/ui-v2/tests/acceptance/dc/nodes/sorting.feature @@ -1,73 +1,73 @@ -@setupApplicationTest -Feature: dc / nodes / sorting - Scenario: - Given 1 datacenter model with the value "dc-1" - And 6 node models from yaml - --- - - Node: Node-A - Checks: - - Status: critical - - Node: Node-B - Checks: - - Status: passing - - Node: Node-C - Checks: - - Status: warning - - Node: Node-D - Checks: - - Status: critical - - Node: Node-E - Checks: - - Status: critical - - Node: Node-F - Checks: - - Status: warning - --- - When I visit the nodes page for yaml - --- - dc: dc-1 - --- - When I click selected on the sort - When I click options.3.button on the sort - Then I see name on the nodes vertically like yaml - --- - - Node-B - - Node-C - - Node-F - - Node-A - - Node-D - - Node-E - --- - When I click selected on the sort - When I click options.2.button on the sort - Then I see name on the nodes vertically like yaml - --- - - Node-A - - Node-D - - Node-E - - Node-C - - Node-F - - Node-B - --- - When I click selected on the sort - When I click options.0.button on the sort - Then I see name on the nodes vertically like yaml - --- - - Node-A - - Node-B - - Node-C - - Node-D - - Node-E - - Node-F - --- - When I click selected on the sort - When I click options.1.button on the sort - Then I see name on the nodes vertically like yaml - --- - - Node-F - - Node-E - - Node-D - - Node-C - - Node-B - - Node-A - --- +@setupApplicationTest +Feature: dc / nodes / sorting + Scenario: + Given 1 datacenter model with the value "dc-1" + And 6 node models from yaml + --- + - Node: Node-A + Checks: + - Status: critical + - Node: Node-B + Checks: + - Status: passing + - Node: Node-C + Checks: + - Status: warning + - Node: Node-D + Checks: + - Status: critical + - Node: Node-E + Checks: + - Status: critical + - Node: Node-F + Checks: + - Status: warning + --- + When I visit the nodes page for yaml + --- + dc: dc-1 + --- + When I click selected on the sort + When I click options.0.button on the sort + Then I see name on the nodes vertically like yaml + --- + - Node-A + - Node-D + - Node-E + - Node-C + - Node-F + - Node-B + --- + When I click selected on the sort + When I click options.1.button on the sort + Then I see name on the nodes vertically like yaml + --- + - Node-B + - Node-C + - Node-F + - Node-A + - Node-D + - Node-E + --- + When I click selected on the sort + When I click options.2.button on the sort + Then I see name on the nodes vertically like yaml + --- + - Node-A + - Node-B + - Node-C + - Node-D + - Node-E + - Node-F + --- + When I click selected on the sort + When I click options.3.button on the sort + Then I see name on the nodes vertically like yaml + --- + - Node-F + - Node-E + - Node-D + - Node-C + - Node-B + - Node-A + --- diff --git a/ui-v2/tests/acceptance/dc/services/show/upstreams.feature b/ui-v2/tests/acceptance/dc/services/show/upstreams.feature index cc703e4a43..00dc65fc0f 100644 --- a/ui-v2/tests/acceptance/dc/services/show/upstreams.feature +++ b/ui-v2/tests/acceptance/dc/services/show/upstreams.feature @@ -28,6 +28,7 @@ Feature: dc / services / show / upstreams --- And the title should be "ingress-gateway-1 - Consul" When I click upstreams on the tabs + And I see upstreamsIsSelected on the tabs Then I see 3 service models on the tabs.upstreamsTab component Scenario: Don't see the Upstreams tab Given 1 datacenter model with the value "dc1" diff --git a/ui-v2/tests/acceptance/dc/services/sorting.feature b/ui-v2/tests/acceptance/dc/services/sorting.feature index 99af3b233b..b3826f9fc4 100644 --- a/ui-v2/tests/acceptance/dc/services/sorting.feature +++ b/ui-v2/tests/acceptance/dc/services/sorting.feature @@ -40,15 +40,28 @@ Feature: dc / services / sorting dc: dc-1 --- When I click selected on the sort - When I click options.3.button on the sort + # unhealthy / healthy + When I click options.0.button on the sort Then I see name on the services vertically like yaml --- + - Service-B + - Service-C + - Service-A + - Service-D - Service-F - Service-E + --- + When I click selected on the sort + # healthy / unhealthy + When I click options.1.button on the sort + Then I see name on the services vertically like yaml + --- + - Service-E + - Service-F - Service-D + - Service-A - Service-C - Service-B - - Service-A --- When I click selected on the sort When I click options.2.button on the sort @@ -62,24 +75,13 @@ Feature: dc / services / sorting - Service-F --- When I click selected on the sort - When I click options.0.button on the sort + When I click options.3.button on the sort Then I see name on the services vertically like yaml --- - - Service-B - - Service-C - - Service-A - - Service-D - Service-F - Service-E - --- - When I click selected on the sort - When I click options.1.button on the sort - Then I see name on the services vertically like yaml - --- - - Service-E - - Service-F - Service-D - - Service-A - Service-C - Service-B + - Service-A --- diff --git a/ui-v2/tests/pages/dc/acls/policies/index.js b/ui-v2/tests/pages/dc/acls/policies/index.js index 40303efffa..a17926d733 100644 --- a/ui-v2/tests/pages/dc/acls/policies/index.js +++ b/ui-v2/tests/pages/dc/acls/policies/index.js @@ -2,6 +2,6 @@ export default function(visitable, creatable, policies, popoverSelect) { return creatable({ visit: visitable('/:dc/acls/policies'), policies: policies(), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), }); } diff --git a/ui-v2/tests/pages/dc/acls/roles/index.js b/ui-v2/tests/pages/dc/acls/roles/index.js index 851fcbe19b..67279ffc68 100644 --- a/ui-v2/tests/pages/dc/acls/roles/index.js +++ b/ui-v2/tests/pages/dc/acls/roles/index.js @@ -2,7 +2,7 @@ export default function(visitable, creatable, roles, popoverSelect) { return { visit: visitable('/:dc/acls/roles'), roles: roles(), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), ...creatable(), }; } diff --git a/ui-v2/tests/pages/dc/acls/tokens/index.js b/ui-v2/tests/pages/dc/acls/tokens/index.js index b08510794c..0af210d6f0 100644 --- a/ui-v2/tests/pages/dc/acls/tokens/index.js +++ b/ui-v2/tests/pages/dc/acls/tokens/index.js @@ -3,7 +3,7 @@ export default function(visitable, creatable, text, tokens, popoverSelect) { visit: visitable('/:dc/acls/tokens'), update: text('[data-test-notification-update]'), tokens: tokens(), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), ...creatable(), }; } diff --git a/ui-v2/tests/pages/dc/intentions/index.js b/ui-v2/tests/pages/dc/intentions/index.js index 2c292bab1c..f554d19640 100644 --- a/ui-v2/tests/pages/dc/intentions/index.js +++ b/ui-v2/tests/pages/dc/intentions/index.js @@ -2,7 +2,7 @@ export default function(visitable, creatable, clickable, intentions, popoverSele return creatable({ visit: visitable('/:dc/intentions'), intentions: intentions(), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), create: clickable('[data-test-create]'), }); } diff --git a/ui-v2/tests/pages/dc/nodes/index.js b/ui-v2/tests/pages/dc/nodes/index.js index c26bbd4153..81aabe3ff2 100644 --- a/ui-v2/tests/pages/dc/nodes/index.js +++ b/ui-v2/tests/pages/dc/nodes/index.js @@ -8,6 +8,6 @@ export default function(visitable, text, clickable, attribute, collection, popov visit: visitable('/:dc/nodes'), nodes: collection('.consul-node-list [data-test-list-row]', node), home: clickable('[data-test-home]'), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), }; } diff --git a/ui-v2/tests/pages/dc/nspaces/index.js b/ui-v2/tests/pages/dc/nspaces/index.js index 96b1a6b7cf..4a3e5d0fb0 100644 --- a/ui-v2/tests/pages/dc/nspaces/index.js +++ b/ui-v2/tests/pages/dc/nspaces/index.js @@ -2,6 +2,6 @@ export default function(visitable, creatable, nspaces, popoverSelect) { return creatable({ visit: visitable('/:dc/namespaces'), nspaces: nspaces(), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), }); } diff --git a/ui-v2/tests/pages/dc/services/index.js b/ui-v2/tests/pages/dc/services/index.js index deac821ada..5ca6123a3c 100644 --- a/ui-v2/tests/pages/dc/services/index.js +++ b/ui-v2/tests/pages/dc/services/index.js @@ -10,6 +10,6 @@ export default function(visitable, clickable, text, attribute, present, collecti visit: visitable('/:dc/services'), services: collection('.consul-service-list > ul > li:not(:first-child)', service), home: clickable('[data-test-home]'), - sort: popoverSelect(), + sort: popoverSelect('[data-test-sort-control]'), }; } diff --git a/ui-v2/tests/pages/dc/services/show.js b/ui-v2/tests/pages/dc/services/show.js index 0630edbe97..68cf5ea655 100644 --- a/ui-v2/tests/pages/dc/services/show.js +++ b/ui-v2/tests/pages/dc/services/show.js @@ -23,7 +23,7 @@ export default function(visitable, attribute, collection, text, intentions, filt intentions: intentions(), }; page.tabs.upstreamsTab = { - services: collection('.consul-upstream-list > ul > li:not(:first-child)', { + services: collection('.consul-service-list > ul > li:not(:first-child)', { name: text('[data-test-service-name]'), }), }; diff --git a/ui-v2/tests/unit/filter/predicates/intention-test.js b/ui-v2/tests/unit/filter/predicates/intention-test.js new file mode 100644 index 0000000000..9fde7ea33c --- /dev/null +++ b/ui-v2/tests/unit/filter/predicates/intention-test.js @@ -0,0 +1,43 @@ +import factory from 'consul-ui/filter/predicates/intention'; +import { module, test } from 'qunit'; + +module('Unit | Filter | Predicates | intention', function() { + const predicate = factory(); + + test('it returns items depending on Action', function(assert) { + const items = [ + { + Action: 'allow', + }, + { + Action: 'deny', + }, + ]; + + let expected, actual; + + expected = [items[0]]; + actual = items.filter( + predicate({ + accesses: ['allow'], + }) + ); + assert.deepEqual(actual, expected); + + expected = [items[1]]; + actual = items.filter( + predicate({ + accesses: ['deny'], + }) + ); + assert.deepEqual(actual, expected); + + expected = items; + actual = items.filter( + predicate({ + accesses: ['allow', 'deny'], + }) + ); + assert.deepEqual(actual, expected); + }); +}); diff --git a/ui-v2/tests/unit/filter/predicates/service-test.js b/ui-v2/tests/unit/filter/predicates/service-test.js new file mode 100644 index 0000000000..55d243610f --- /dev/null +++ b/ui-v2/tests/unit/filter/predicates/service-test.js @@ -0,0 +1,171 @@ +import factory from 'consul-ui/filter/predicates/service'; +import { module, test } from 'qunit'; + +module('Unit | Filter | Predicates | service', function() { + const predicate = factory(); + + test('it returns registered/unregistered items depending on instance count', function(assert) { + const items = [ + { + InstanceCount: 1, + }, + { + InstanceCount: 0, + }, + ]; + + let expected, actual; + + expected = [items[0]]; + actual = items.filter( + predicate({ + instances: ['registered'], + }) + ); + assert.deepEqual(actual, expected); + + expected = [items[1]]; + actual = items.filter( + predicate({ + instances: ['not-registered'], + }) + ); + assert.deepEqual(actual, expected); + + expected = items; + actual = items.filter( + predicate({ + instances: ['registered', 'not-registered'], + }) + ); + assert.deepEqual(actual, expected); + }); + + test('it returns items depending on status', function(assert) { + const items = [ + { + MeshStatus: 'passing', + }, + { + MeshStatus: 'warning', + }, + { + MeshStatus: 'critical', + }, + ]; + + let expected, actual; + + expected = [items[0]]; + actual = items.filter( + predicate({ + statuses: ['passing'], + }) + ); + assert.deepEqual(actual, expected); + + expected = [items[1]]; + actual = items.filter( + predicate({ + statuses: ['warning'], + }) + ); + assert.deepEqual(actual, expected); + + expected = items; + actual = items.filter( + predicate({ + statuses: ['passing', 'warning', 'critical'], + }) + ); + assert.deepEqual(actual, expected); + }); + + test('it returns items depending on service type', function(assert) { + const items = [ + { + Kind: 'ingress-gateway', + }, + { + Kind: 'mesh-gateway', + }, + {}, + ]; + + let expected, actual; + + expected = [items[0]]; + actual = items.filter( + predicate({ + types: ['ingress-gateway'], + }) + ); + assert.deepEqual(actual, expected); + + expected = [items[1]]; + actual = items.filter( + predicate({ + types: ['mesh-gateway'], + }) + ); + assert.deepEqual(actual, expected); + + expected = items; + actual = items.filter( + predicate({ + types: ['ingress-gateway', 'mesh-gateway', 'service'], + }) + ); + assert.deepEqual(actual, expected); + }); + test('it returns items depending on a mixture of properties', function(assert) { + const items = [ + { + Kind: 'ingress-gateway', + MeshStatus: 'passing', + InstanceCount: 1, + }, + { + Kind: 'mesh-gateway', + MeshStatus: 'warning', + InstanceCount: 1, + }, + { + MeshStatus: 'critical', + InstanceCount: 0, + }, + ]; + + let expected, actual; + + expected = [items[0]]; + actual = items.filter( + predicate({ + types: ['ingress-gateway'], + statuses: ['passing'], + instances: ['registered'], + }) + ); + assert.deepEqual(actual, expected); + + expected = [items[1]]; + actual = items.filter( + predicate({ + types: ['mesh-gateway'], + statuses: ['warning'], + instances: ['registered'], + }) + ); + assert.deepEqual(actual, expected); + + expected = items; + actual = items.filter( + predicate({ + types: ['ingress-gateway', 'mesh-gateway', 'service'], + statuses: ['passing', 'warning', 'critical'], + instances: ['registered', 'not-registered'], + }) + ); + assert.deepEqual(actual, expected); + }); +});