Browse Source

ui: Search/filtering 'Filtered by:' search status (#9442)

Adds a 'status' for the filtering/searching in the UI, without this its not super clear that you are filtering a recordset due to the menu selections being hidden once closed. You can also use the pills in this status view to delete individual filters.
pull/9638/head
John Cowen 4 years ago committed by GitHub
parent
commit
bb95738321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .changelog/9442.txt
  2. 2
      ui/packages/consul-ui/.template-lintrc.js
  3. 2
      ui/packages/consul-ui/app/components/action/index.hbs
  4. 1
      ui/packages/consul-ui/app/components/confirmation-alert/index.hbs
  5. 180
      ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs
  6. 290
      ui/packages/consul-ui/app/components/consul/health-check/search-bar/index.hbs
  7. 210
      ui/packages/consul-ui/app/components/consul/intention/search-bar/index.hbs
  8. 190
      ui/packages/consul-ui/app/components/consul/kv/search-bar/index.hbs
  9. 197
      ui/packages/consul-ui/app/components/consul/node/search-bar/index.hbs
  10. 137
      ui/packages/consul-ui/app/components/consul/nspace/search-bar/index.hbs
  11. 222
      ui/packages/consul-ui/app/components/consul/policy/search-bar/index.hbs
  12. 148
      ui/packages/consul-ui/app/components/consul/role/search-bar/index.hbs
  13. 233
      ui/packages/consul-ui/app/components/consul/service-instance/search-bar/index.hbs
  14. 301
      ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs
  15. 182
      ui/packages/consul-ui/app/components/consul/token/search-bar/index.hbs
  16. 135
      ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs
  17. 188
      ui/packages/consul-ui/app/components/consul/upstream/search-bar/index.hbs
  18. 19
      ui/packages/consul-ui/app/components/data-collection/index.js
  19. 2
      ui/packages/consul-ui/app/components/popover-select/index.hbs
  20. 51
      ui/packages/consul-ui/app/components/popover-select/index.js
  21. 4
      ui/packages/consul-ui/app/components/popover-select/optgroup/index.hbs
  22. 5
      ui/packages/consul-ui/app/components/popover-select/optgroup/index.js
  23. 12
      ui/packages/consul-ui/app/components/popover-select/option/index.hbs
  24. 34
      ui/packages/consul-ui/app/components/popover-select/option/index.js
  25. 61
      ui/packages/consul-ui/app/components/search-bar/README.mdx
  26. 99
      ui/packages/consul-ui/app/components/search-bar/index.hbs
  27. 38
      ui/packages/consul-ui/app/components/search-bar/index.js
  28. 60
      ui/packages/consul-ui/app/components/search-bar/index.scss
  29. 7
      ui/packages/consul-ui/app/components/search-bar/pageobject.js
  30. 7
      ui/packages/consul-ui/app/components/search-bar/remove-filter/index.hbs
  31. 38
      ui/packages/consul-ui/app/components/search-bar/utils.js
  32. 2
      ui/packages/consul-ui/app/filter/predicates/acl.js
  33. 6
      ui/packages/consul-ui/app/filter/predicates/health-check.js
  34. 2
      ui/packages/consul-ui/app/filter/predicates/intention.js
  35. 2
      ui/packages/consul-ui/app/filter/predicates/kv.js
  36. 2
      ui/packages/consul-ui/app/filter/predicates/node.js
  37. 4
      ui/packages/consul-ui/app/filter/predicates/policy.js
  38. 5
      ui/packages/consul-ui/app/filter/predicates/service-instance.js
  39. 9
      ui/packages/consul-ui/app/filter/predicates/service.js
  40. 2
      ui/packages/consul-ui/app/filter/predicates/token.js
  41. 4
      ui/packages/consul-ui/app/models/role.js
  42. 10
      ui/packages/consul-ui/app/models/service.js
  43. 35
      ui/packages/consul-ui/app/routes/dc/acls/index.js
  44. 5
      ui/packages/consul-ui/app/routes/dc/acls/policies/index.js
  45. 1
      ui/packages/consul-ui/app/routes/dc/acls/roles/index.js
  46. 20
      ui/packages/consul-ui/app/routes/dc/acls/tokens/index.js
  47. 3
      ui/packages/consul-ui/app/routes/dc/intentions/index.js
  48. 14
      ui/packages/consul-ui/app/routes/dc/nodes/index.js
  49. 10
      ui/packages/consul-ui/app/routes/dc/nspaces/index.js
  50. 1
      ui/packages/consul-ui/app/routes/dc/services/index.js
  51. 1
      ui/packages/consul-ui/app/routes/dc/services/instance/healthchecks.js
  52. 3
      ui/packages/consul-ui/app/routes/dc/services/show/intentions/index.js
  53. 7
      ui/packages/consul-ui/app/routes/dc/services/show/services.js
  54. 1
      ui/packages/consul-ui/app/styles/components.scss
  55. 2
      ui/packages/consul-ui/app/styles/layout.scss
  56. 2
      ui/packages/consul-ui/app/styles/typography.scss
  57. 164
      ui/packages/consul-ui/app/templates/dc/acls/index.hbs
  58. 208
      ui/packages/consul-ui/app/templates/dc/acls/policies/index.hbs
  59. 34
      ui/packages/consul-ui/app/templates/dc/acls/roles/index.hbs
  60. 218
      ui/packages/consul-ui/app/templates/dc/acls/tokens/index.hbs
  61. 191
      ui/packages/consul-ui/app/templates/dc/intentions/index.hbs
  62. 28
      ui/packages/consul-ui/app/templates/dc/kv/index.hbs
  63. 39
      ui/packages/consul-ui/app/templates/dc/nodes/index.hbs
  64. 58
      ui/packages/consul-ui/app/templates/dc/nodes/show/healthchecks.hbs
  65. 47
      ui/packages/consul-ui/app/templates/dc/nodes/show/services.hbs
  66. 36
      ui/packages/consul-ui/app/templates/dc/nspaces/index.hbs
  67. 40
      ui/packages/consul-ui/app/templates/dc/services/index.hbs
  68. 54
      ui/packages/consul-ui/app/templates/dc/services/instance/healthchecks.hbs
  69. 39
      ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs
  70. 46
      ui/packages/consul-ui/app/templates/dc/services/show/instances.hbs
  71. 131
      ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs
  72. 47
      ui/packages/consul-ui/app/templates/dc/services/show/services.hbs
  73. 47
      ui/packages/consul-ui/app/templates/dc/services/show/upstreams.hbs
  74. 9
      ui/packages/consul-ui/app/utils/intl/missing-message.js
  75. 24
      ui/packages/consul-ui/tests/integration/components/search-bar-test.js
  76. 23
      ui/packages/consul-ui/tests/pages.js
  77. 3
      ui/packages/consul-ui/tests/pages/dc/acls/index.js
  78. 12
      ui/packages/consul-ui/tests/pages/dc/services/show.js
  79. 154
      ui/packages/consul-ui/tests/unit/components/search-bar/filters-test.js
  80. 6
      ui/packages/consul-ui/tests/unit/filter/predicates/intention-test.js
  81. 36
      ui/packages/consul-ui/tests/unit/filter/predicates/service-test.js
  82. 157
      ui/packages/consul-ui/translations/en-us.yaml

4
.changelog/9442.txt

@ -0,0 +1,4 @@
```release-note:feature
ui: Add additional search/filter status pills for viewing and removing current
filters in listing views
```

2
ui/packages/consul-ui/.template-lintrc.js

@ -31,6 +31,8 @@ module.exports = {
'no-unnecessary-component-helper': false,
'link-href-attributes': false,
// we need to be able to say tabindex={{@tabindex}}
'no-positive-tabindex': false,
'no-bare-strings': false,
},

2
ui/packages/consul-ui/app/components/action/index.hbs

@ -19,9 +19,9 @@
{{~/if~}}
{{~else~}}
<button
tabindex="-1"
type="button"
{{on 'click' (optional @onclick)}}
tabindex={{@tabindex}}
...attributes
>{{yield}}</button>
{{~/if}}

1
ui/packages/consul-ui/app/components/confirmation-alert/index.hbs

@ -14,6 +14,7 @@
<YieldSlot @name="confirm" @params={{
block-params (component 'action'
onclick=(action @onclick)
tabindex="-1"
)
}}
>

180
ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs

@ -1,59 +1,127 @@
<form
class="consul-acl-search-bar filter-bar"
<SearchBar
class="consul-acl-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
/>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="management" @selected={{contains 'management' @filter.kinds}}>Management</Option>
<Option @value="client" @selected={{contains 'service' @filter.kinds}}>Client</Option>
<:status as |search|>
{{#let
(t (concat "components.consul.acl.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.acl.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
{{#if @filter.searchproperty}}
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "components.consul.acl.search-bar.kind.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each (array "management" "client") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
{{t (concat "components.acl.search-bar.kind.options." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.consul.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

290
ui/packages/consul-ui/app/components/consul/health-check/search-bar/index.hbs

@ -1,140 +1,190 @@
<form
class="consul-health-check-search-bar filter-bar"
<SearchBar
class="consul-healthcheck-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.health-check.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.health-check.search-bar." search.status.key ".options." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
{{#if @filter.searchproperty}}
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.status.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "common.consul.status"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @searchproperties as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{prop}}</Option>
{{#each (array "passing" "warning" "critical" "empty") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
{{t (concat "common.consul." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Health Status
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
<PopoverSelect
class="type-kind"
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Kind
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="service" @selected={{contains 'service' @filter.kinds}}>Service Check</Option>
<Option @value="node" @selected={{contains 'node' @filter.kinds}}>Node Check</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
<PopoverSelect
class="type-check"
@position="left"
@onchange={{action @onfilter.check}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="alias" @selected={{contains 'alias' @filter.checks}}>alias</Option>
<Option @value="docker" @selected={{contains 'docker' @filter.checks}}>Docker</Option>
<Option @value="grpc" @selected={{contains 'grpc' @filter.checks}}>gRPC</Option>
<Option @value="http" @selected={{contains 'http' @filter.checks}}>HTTP</Option>
<Option @value="serf" @selected={{contains 'serf' @filter.checks}}>Serf</Option>
<Option @value="tcp" @selected={{contains 'tcp' @filter.checks}}>TCP</Option>
<Option @value="ttl" @selected={{contains 'ttl' @filter.checks}}>TTL</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
(array "Status:asc" "Unhealthy to Healthy")
(array "Status:desc" "Healthy to Unhealthy")
(array "Kind:asc" "Service to Node")
(array "Kind:desc" "Node to Service")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
</search.Select>
{{#if @filter.kind}}
<search.Select
class="type-kind"
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "components.consul.health-check.search-bar.kind.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each (array "service" "node") as |item|}}
<Option @value={{item}} @selected={{contains item @filter.kind.value}}>
{{t (concat "components.consul.health-check.search-bar.kind.options." item)
default=(array
(concat "common.search." item)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
<search.Select
class="type-check"
@position="left"
@onchange={{action @filter.check.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "components.consul.health-check.search-bar.check.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each (array "alias" "docker" "grpc" "http" "script" "serf" "tcp" "ttl") as |item|}}
<Option @value={{item}} @selected={{contains item @filter.check.value}}>
{{t (concat "components.consul.health-check.search-bar.check.options." item)
default=(array
(concat "common.search." item)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
(array "Status:asc" (t "common.sort.status.asc"))
(array "Status:desc" (t "common.sort.status.desc"))
(array "Kind:asc" (t "components.consul.health-check.search-bar.sort.kind.asc"))
(array "Kind:desc" (t "components.consul.health-check.search-bar.sort.kind.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort}}>Healthy to Unhealthy</Option>
<Optgroup @label={{t "common.consul.status"}}>
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</Option>
</Optgroup>
<Optgroup @label="Check Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
<Optgroup @label={{t "components.consul.health-check.search-bar.sort.name.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<Optgroup @label="Check Type">
<Optgroup @label={{t "components.consul.health-check.search-bar.sort.kind.name"}}>
<Option @value="Kind:asc" @selected={{eq "Kind:asc" @sort}}>Service to Node</Option>
<Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort}}>Node to Service</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

210
ui/packages/consul-ui/app/components/consul/intention/search-bar/index.hbs

@ -1,102 +1,140 @@
<form
class="consul-intention-search-bar filter-bar"
<SearchBar
class="consul-intention-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.intention.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.intention.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
{{#if @filter.searchproperty}}
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-access"
@position="left"
@onchange={{action @filter.access.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "components.consul.intention.search-bar.access.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="SourceName" @selected={{contains 'SourceName' @filter.searchproperties}}>Source Name</Option>
<Option @value="DestinationName" @selected={{contains 'DestinationName' @filter.searchproperties}}>Destination Name</Option>
{{#each (array "allow" "deny" "") as |item|}}
<Option class={{concat 'value-' item}} @value={{or item 'app-aware'}} @selected={{contains (or item 'app-aware') @filter.access.value}}>
<span>{{t (concat "components.consul.intention.search-bar.access.options." (or item 'app-aware'))}}</span>
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action @onfilter.access}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Permission
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-allow" @value="allow" @selected={{contains 'allow'
@filter.accesses}}><span>Allow</span></Option>
<Option class="value-deny" @value="deny" @selected={{contains 'deny'
@filter.accesses}}><span>Deny</span></Option>
<Option class="value-" @value="app-aware" @selected={{contains
'app-aware' @filter.accesses}}><span>App aware</span></Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Permission">
<Option @value="Action:asc" @selected={{eq "Action:asc" @sort}}>Allow to Deny</Option>
<Option @value="Action:desc" @selected={{eq "Action:desc" @sort}}>Deny to Allow</Option>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Action:asc" (t "components.consul.intention.search-bar.sort.access.asc"))
(array "Action:desc" (t "components.consul.intention.search-bar.sort.access.desc"))
(array "SourceName:asc" (t "components.consul.intention.search-bar.sort.source-name.asc"))
(array "SourceName:desc" (t "components.consul.intention.search-bar.sort.source-name.desc"))
(array "DestinationName:asc" (t "components.consul.intention.search-bar.sort.destination-name.asc"))
(array "DestinationName:desc" (t "components.consul.intention.search-bar.sort.destination-name.desc"))
(array "Precedence:asc" (t "common.sort.numeric.asc"))
(array "Precedence:desc" (t "common.sort.numeric.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "components.consul.intention.search-bar.sort.access.name"}}>
<Option @value="Action:asc" @selected={{eq "Action:asc" @sort.value}}>{{t "components.consul.intention.search-bar.sort.access.asc"}}</Option>
<Option @value="Action:desc" @selected={{eq "Action:desc" @sort.value}}>{{t "components.consul.intention.search-bar.sort.access.desc"}}</Option>
</Optgroup>
<Optgroup @label="Source">
<Option @value="SourceName:asc" @selected={{eq "SourceName:asc" @sort}}>A to Z</Option>
<Option @value="SourceName:desc" @selected={{eq "SourceName:desc" @sort}}>Z to A</Option>
<Optgroup @label={{t "components.consul.intention.search-bar.sort.source-name.name"}}>
<Option @value="SourceName:asc" @selected={{eq "SourceName:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="SourceName:desc" @selected={{eq "SourceName:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<Optgroup @label="Destination">
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" @sort}}>A to Z</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" @sort}}>Z to A</Option>
<Optgroup @label={{t "components.consul.intention.search-bar.sort.destination-name.name"}}>
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<Optgroup @label="Precedence">
<Option @value="Precedence:asc" @selected={{eq "Precedence:asc" @sort}}>Ascending</Option>
<Option @value="Precedence:desc" @selected={{eq "Precedence:desc" @sort}}>Descending</Option>
<Optgroup @label={{t "components.consul.intention.search-bar.sort.precedence.name"}}>
<Option @value="Precedence:asc" @selected={{eq "Precedence:asc" @sort.value}}>{{t "common.sort.numeric.asc"}}</Option>
<Option @value="Precedence:desc" @selected={{eq "Precedence:desc" @sort.value}}>{{t "common.sort.numeric.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

190
ui/packages/consul-ui/app/components/consul/kv/search-bar/index.hbs

@ -1,68 +1,128 @@
<form
class="consul-kv-search-bar filter-bar"
<SearchBar
class="consul-kv-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
/>
</div>
<div class="filters">
<PopoverSelect
class="type-kind"
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="folder" @selected={{contains 'folder' @filter.kinds}}>Folder</Option>
<Option @value="key" @selected={{contains 'key' @filter.kinds}}>Key</Option>
<:status as |search|>
{{#let
(t (concat "components.consul.kv.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.kv.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Key:asc" "A to Z")
(array "Key:desc" "Z to A")
(array "Kind:asc" "Folders to Keys")
(array "Kind:desc" "Keys to Folders")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Key:asc" @selected={{eq "Key:asc" @sort}}>A to Z</Option>
<Option @value="Key:desc" @selected={{eq "Key:desc" @sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Type">
<Option @value="Kind:asc" @selected={{eq "Kind:asc" @sort}}>Folders to Keys</Option>
<Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort}}>Keys to Folders</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
{{#if @filter.searchproperty}}
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-kind"
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "components.consul.kv.search-bar.kind.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each (array "folder" "key") as |item|}}
<Option class="value-{item}}" @value={{item}} @selected={{contains item @filter.kind.value}}>
{{t (concat "components.consul.kv.search-bar.kind.options." item)}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Key:asc" (t "common.sort.alpha.asc"))
(array "Key:desc" (t "common.sort.alpha.desc"))
(array "Kind:asc" (t "components.consul.kv.search-bar.sort.kind.asc"))
(array "Kind:desc" (t "components.consul.kv.search-bar.sort.kind.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.consul.name"}}>
<Option @value="Key:asc" @selected={{eq "Key:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Key:desc" @selected={{eq "Key:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<Optgroup @label={{t "components.consul.kv.search-bar.kind.name"}}>
<Option @value="Kind:asc" @selected={{eq "Kind:asc" @sort.value}}>{{t "components.consul.kv.search-bar.sort.kind.asc"}}</Option>
<Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort.value}}>{{t "components.consul.kv.search-bar.sort.kind.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

197
ui/packages/consul-ui/app/components/consul/node/search-bar/index.hbs

@ -1,90 +1,131 @@
<form
class="consul-node-search-bar filter-bar"
<SearchBar
class="consul-node-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.node.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.node.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.status.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "common.consul.status"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Node" @selected={{contains 'Node' @filter.searchproperties}}>Node Name</Option>
<Option @value="Address" @selected={{contains 'Address' @filter.searchproperties}}>Address</Option>
<Option @value="Meta" @selected={{contains 'Meta' @filter.searchproperties}}>Node Meta</Option>
{{#each (array "passing" "warning" "critical") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
{{t (concat "common.consul." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Health Status
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort}}>Healthy to Unhealthy</Option>
</Optgroup>
<Optgroup @label="Service Name">
<Option @value="Node:asc" @selected={{eq "Node:asc" @sort}}>A to Z</Option>
<Option @value="Node:desc" @selected={{eq "Node:desc" @sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Node:asc" (t "common.sort.alpha.asc"))
(array "Node:desc" (t "common.sort.alpha.desc"))
(array "Status:asc" (t "common.sort.status.asc"))
(array "Status:desc" (t "common.sort.status.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.consul.status"}}>
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</Option>
</Optgroup>
<Optgroup @label={{t "common.consul.node-name"}}>
<Option @value="Node:asc" @selected={{eq "Node:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Node:desc" @selected={{eq "Node:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

137
ui/packages/consul-ui/app/components/consul/nspace/search-bar/index.hbs

@ -1,63 +1,98 @@
<form
class="consul-nspace-search-bar filter-bar"
<SearchBar
class="consul-nspace-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
<:status as |search|>
{{#let
(t (concat "components.consul.nspace.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.nspace.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
<Option @value="Role" @selected={{contains 'Role' @filter.searchproperties}}>Role</Option>
<Optgroup @label={{t "common.consul.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</search.Select>
</:sort>
</SearchBar>

222
ui/packages/consul-ui/app/components/consul/policy/search-bar/index.hbs

@ -1,101 +1,145 @@
<form
class="consul-policy-search-bar filter-bar"
<SearchBar
class="consul-policy-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.policy.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.policy.search-bar." search.status.key ".options." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{if (eq search.status.key 'datacenter') search.status.value value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-datacenter"
@position="left"
@onchange={{action @filter.datacenter.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.consul.datacenter"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each dcs as |dc|}}
<Option @value={{dc.Name}} @selected={{contains dc.Name @filter.datacenter.value}}>{{dc.Name}}</Option>
{{/each}}
<DataSource @src="/*/*/datacenters" @loading="lazy" @onchange={{action (mut this.dcs) value="data"}} />
{{/let}}
</BlockSlot>
</search.Select>
<search.Select
class="type-kind"
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "components.consul.policy.search-bar.kind.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
{{#each (array "global-management" "standard") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.kind.value}}>
{{t (concat "components.consul.policy.search-bar.kind.options." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="select-dcs"
@position="left"
@onchange={{action @onfilter.dc}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Datacenters
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each dcs as |dc|}}
<Option @value={{@dc.Name}} @selected={{contains dc.Name @filter.dcs}}>{{dc.Name}}</Option>
{{/each}}
{{/let}}
<DataSource @src="/*/*/datacenters" @loading="lazy" @onchange={{action (mut this.dcs) value="data"}} />
</BlockSlot>
</PopoverSelect>
<PopoverSelect
class="select-type"
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="global-management" @selected={{contains 'global-management' @filter.kinds}}>Global Management</Option>
<Option @value="standard" @selected={{contains 'standard' @filter.kinds}}>Standard</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.ui.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

148
ui/packages/consul-ui/app/components/consul/role/search-bar/index.hbs

@ -1,68 +1,104 @@
<form
class="consul-role-search-bar filter-bar"
<SearchBar
class="consul-role-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
<:status as |search|>
{{#let
(t (concat "components.consul.role.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.role.search-bar." search.status.key ".options." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
(array "CreateIndex:desc" (t "common.sort.age.desc"))
(array "CreateIndex:asc" (t "common.sort.age.asc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
<Optgroup @label={{t "common.ui.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<Optgroup @label="Creation">
<Option @value="CreateIndex:desc" @selected={{eq "CreateIndex:desc" @sort}}>Newest to oldest</Option>
<Option @value="CreateIndex:asc" @selected={{eq "CreateIndex:asc" @sort}}>Oldest to newest</Option>
<Optgroup @label={{t "common.ui.creation"}}>
<Option @value="CreateIndex:desc" @selected={{eq "CreateIndex:desc" @sort.value}}>{{t "common.sort.age.desc"}}</Option>
<Option @value="CreateIndex:asc" @selected={{eq "CreateIndex:asc" @sort.value}}>{{t "common.sort.age.asc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

233
ui/packages/consul-ui/app/components/consul/service-instance/search-bar/index.hbs

@ -1,111 +1,156 @@
<form
class="consul-service-instance-search-bar filter-bar"
<SearchBar
class="consul-service-instance-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.service-instance.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.service-instance.search-bar." search.status.key ".options." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
{{#if @filter.searchproperty}}
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.status.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "common.consul.status"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @searchproperties as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{string-replace-all prop '\.' ' '}}</Option>
{{#each (array "passing" "warning" "critical" "empty") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
{{t (concat "common.consul." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Health Status
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
{{#if (gt @sources.length 0)}}
<PopoverSelect
class="type-source"
@position="left"
@onchange={{action @onfilter.source}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Source
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.sources}}>{{source}}</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
{{/if}}
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
</search.Select>
{{#if (gt @sources.length 0)}}
<search.Select
class="type-source"
@position="left"
@onchange={{action @filter.source.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.source"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.source.value}}>
{{t (concat "common.brand." source)}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
(array "Status:asc" (t "common.sort.status.asc"))
(array "Status:desc" (t "common.sort.status.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort}}>Healthy to Unhealthy</Option>
<Optgroup @label={{t "common.consul.status"}}>
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</Option>
</Optgroup>
<Optgroup @label="Service Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
<Optgroup @label={{t "components.consul.service-instance.search-bar.sort.name.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

301
ui/packages/consul-ui/app/components/consul/service/search-bar/index.hbs

@ -1,135 +1,190 @@
<form
class="consul-service-search-bar filter-bar"
<SearchBar
class="consul-service-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.service.search-bar." search.status.key)
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.service.search-bar." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.status.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "common.consul.status"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Tags" @selected={{contains 'Tags' @filter.searchproperties}}>Tags</Option>
{{#each (array "passing" "warning" "critical" "empty") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
{{t (concat "common.consul." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Health Status
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
<PopoverSelect
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Service Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="service" @selected={{contains 'service' @filter.kinds}}>Service</Option>
<Optgroup @label="Gateway">
<Option @value="ingress-gateway" @selected={{contains 'ingress-gateway' @filter.kinds}}>Ingress Gateway</Option>
<Option @value="terminating-gateway" @selected={{contains 'terminating-gateway' @filter.kinds}}>Terminating Gateway</Option>
<Option @value="mesh-gateway" @selected={{contains 'mesh-gateway' @filter.kinds}}>Mesh Gateway</Option>
</Optgroup>
<Optgroup @label="Mesh">
<Option @value="in-mesh" @selected={{contains 'in-mesh' @filter.kinds}}>In service mesh</Option>
<Option @value="not-in-mesh" @selected={{contains 'not-in-mesh' @filter.kinds}}>Not in service mesh</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
{{#if (gt @sources.length 0)}}
<PopoverSelect
class="type-source"
@position="left"
@onchange={{action @onfilter.source}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Source
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.sources}}>{{source}}</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
{{/if}}
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort}}>Healthy to Unhealthy</Option>
</Optgroup>
<Optgroup @label="Service Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</search.Select>
<search.Select
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "components.consul.service.search-bar.kind"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="service" @selected={{contains 'service' @filter.kind.value}}>
{{t "common.consul.service"}}
</Option>
<Optgroup
@label={{t "common.consul.gateway"}}
>
{{#each (array "ingress-gateway" "terminating-gateway" "mesh-gateway") as |kind|}}
<Option @value={{kind}} @selected={{contains kind @filter.kind.value}}>
{{t (concat "common.consul." kind)}}
</Option>
{{/each}}
</Optgroup>
<Optgroup
@label={{t "common.consul.mesh"}}
>
{{#each (array "in-mesh" "not-in-mesh") as |state|}}
<Option @value={{state}} @selected={{contains state @filter.kind.value}}>
{{t (concat "common.search." state)}}
</Option>
{{/each}}
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
{{#if (gt @sources.length 0)}}
<search.Select
class="type-source"
@position="left"
@onchange={{action @filter.source.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.source"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.source.value}}>
{{t (concat "common.brand." source)}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
{{/if}}
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
(array "Status:asc" (t "common.sort.status.asc"))
(array "Status:desc" (t "common.sort.status.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.consul.status"}}>
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</Option>
</Optgroup>
<Optgroup @label={{t "common.consul.service-name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

182
ui/packages/consul-ui/app/components/consul/token/search-bar/index.hbs

@ -1,83 +1,125 @@
<form
class="consul-token-search-bar filter-bar"
<SearchBar
class="consul-token-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.token.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.token.search-bar." search.status.key ".options." search.status.value)
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:filter as |search|>
<search.Select
class="type-status"
@position="left"
@onchange={{action @filter.kind.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "components.consul.token.search-bar.kind.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="AccessorID" @selected={{contains 'AccessorID' @filter.searchproperties}}>AccessorID</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
<Option @value="Role" @selected={{contains 'Role' @filter.searchproperties}}>Role</Option>
{{#each (array "global-management" "global" "local") as |state|}}
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.kind.value}}>
{{t (concat "components.consul.token.search-bar.kind.options." state)
default=(array
(concat "common.search." state)
)
}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="global-management" @selected={{contains 'global-management' @filter.kinds}}>Global Management</Option>
<Option @value="global" @selected={{contains 'global' @filter.kinds}}>Global Scope</Option>
<Option @value="local" @selected={{contains 'local' @filter.kinds}}>Local Scope</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "CreateTime:desc" "Newest to oldest")
(array "CreateTime:asc" "Oldest to newest")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Creation">
<Option @value="CreateTime:desc" @selected={{eq "CreateTime:desc" @sort}}>Newest to oldest</Option>
<Option @value="CreateTime:asc" @selected={{eq "CreateTime:asc" @sort}}>Oldest to newest</Option>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "CreateTime:desc" (t "common.sort.age.desc"))
(array "CreateTime:asc" (t "common.sort.age.asc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.ui.creation"}}>
<Option @value="CreateTime:desc" @selected={{eq "CreateTime:desc" @sort.value}}>{{t "common.sort.age.desc"}}</Option>
<Option @value="CreateTime:asc" @selected={{eq "CreateTime:asc" @sort.value}}>{{t "common.sort.age.asc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

135
ui/packages/consul-ui/app/components/consul/upstream-instance/search-bar/index.hbs

@ -1,63 +1,96 @@
<form
class="consul-upstream-instance-search-bar filter-bar"
<SearchBar
class="consul-upstream-instance-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
<:status as |search|>
{{#let
(t (concat "components.consul.upstream-instance.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.upstream-instance.search-bar." search.status.value ".name")
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{#let (from-entries (array
(array "DestinationName:asc" (t "common.sort.alpha.asc"))
(array "DestinationName:desc" (t "common.sort.alpha.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @searchproperties as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{prop}}</Option>
{{/each}}
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="sort">
{{#let (or @sort 'DestinationName:asc') as |sort|}}
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "DestinationName:asc" "A to Z")
(array "DestinationName:desc" "Z to A")
))
as |selectable|}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Service Name">
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" sort}}>A to Z</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
{{/let}}
</div>
</form>
</search.Select>
</:sort>
</SearchBar>

188
ui/packages/consul-ui/app/components/consul/upstream/search-bar/index.hbs

@ -1,86 +1,126 @@
<form
class="consul-upstream-search-bar filter-bar"
<SearchBar
class="consul-upstream-search-bar"
...attributes
@filter={{@filter}}
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
<:status as |search|>
{{#let
(t (concat "components.consul.upstream.search-bar." search.status.key ".name")
default=(array
(concat "common.search." search.status.key)
(concat "common.consul." search.status.key)
)
)
(t (concat "components.consul.upstream.search-bar." search.status.value ".name")
default=(array
(concat "common.search." search.status.value)
(concat "common.consul." search.status.value)
(concat "common.brand." search.status.value)
)
)
as |key value|}}
<search.RemoveFilter
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
>
<dl>
<dt>{{key}}</dt>
<dd>{{value}}</dd>
</dl>
</search.RemoveFilter>
{{/let}}
</:status>
<:search as |search|>
<search.Search
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder={{t "common.search.search"}}
>
<search.Select
class="type-search-properties"
@position="right"
@onchange={{action @filter.searchproperty.change}}
@multiple={{true}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{t "common.search.searchproperty"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @filter.searchproperty.default as |prop|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
{{t (concat "common.consul." (lowercase prop))}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</search.Select>
</search.Search>
</:search>
<:filter as |search|>
<search.Select
@position="left"
@onchange={{action @filter.instance.change}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
{{t "components.consul.upstream.search-bar.instance.name"}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Tags" @selected={{contains 'Tags' @filter.searchproperties}}>Tags</Option>
{{#each (array "registered" "not-registered") as |item|}}
<Option @value={{item}} @selected={{contains item @filter.instance.value}}>
{{t (concat "common.consul." item)}}
</Option>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action @onfilter.instance}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="registered" @selected={{contains 'registered' @filter.instances}}>Registered</Option>
<Option @value="not-registered" @selected={{contains 'not-registered' @filter.instances}}>Not Registered</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "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}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort}}>Healthy to Unhealthy</Option>
</Optgroup>
<Optgroup @label="Service Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>
</search.Select>
</:filter>
<:sort as |search|>
<search.Select
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @sort.change}}
@multiple={{false}}
@required={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" (t "common.sort.alpha.asc"))
(array "Name:desc" (t "common.sort.alpha.desc"))
(array "Status:asc" (t "common.sort.status.asc"))
(array "Status:desc" (t "common.sort.status.desc"))
))
as |selectable|
}}
{{get selectable @sort.value}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label={{t "common.consul.status"}}>
<Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</Option>
</Optgroup>
<Optgroup @label={{t "common.consul.service-name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</search.Select>
</:sort>
</SearchBar>

19
ui/packages/consul-ui/app/components/data-collection/index.js

@ -1,6 +1,6 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { computed, action } from '@ember/object';
import { computed, get, action } from '@ember/object';
import { alias } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import { sort } from '@ember/object/computed';
@ -32,18 +32,19 @@ export default class DataCollectionComponent extends Component {
return this.term || this.args.search || '';
}
@computed('type', 'searchMethod', 'filtered', 'searchProperties')
@computed('type', 'searchMethod', 'filtered', 'args.filters')
get searchable() {
const searchproperties =
get(this, 'args.filters.searchproperty.value') || get(this, 'args.filters.searchproperty');
const Searchable =
typeof this.searchMethod === 'string'
? this.searchableMap[this.searchMethod]
: this.args.searchable;
return new Searchable(this.filtered, {
finders: Object.fromEntries(
Object.entries(this.searchService.predicate(this.type)).filter(([key, value]) => {
return typeof this.searchProperties === 'undefined'
? true
: this.searchProperties.includes(key);
return typeof searchproperties === 'undefined' ? true : searchproperties.includes(key);
})
),
});
@ -89,7 +90,13 @@ export default class DataCollectionComponent extends Component {
if (typeof predicate === 'undefined') {
return this.content.slice();
}
return this.content.filter(predicate(this.args.filters));
const filters = Object.entries(this.args.filters)
.filter(([key, value]) => Boolean(value))
.map(([key, value]) => {
const val = typeof value !== 'string' ? value.value : value;
return [key, val];
});
return this.content.filter(predicate(Object.fromEntries(filters)));
}
@computed('args.{items.[],items.content.[]}')

2
ui/packages/consul-ui/app/components/popover-select/index.hbs

@ -9,7 +9,7 @@
(component 'popover-select/optgroup' components=components)
(component 'popover-select/option'
select=this components=components
onclick=(queue
onclick=(pipe
(action "click")
(if multiple (noop) menu.toggle)
)

51
ui/packages/consul-ui/app/components/popover-select/index.js

@ -6,47 +6,50 @@ export default Component.extend(Slotted, {
tagName: '',
dom: service('dom'),
multiple: false,
subtractive: false,
required: false,
onchange: function() {},
addOption: function(option) {
if (typeof this._options === 'undefined') {
this._options = new Set();
}
if (this.subtractive) {
if (!option.selected) {
this._options.add(option.value);
}
} else {
if (option.selected) {
this._options.add(option.value);
}
}
this._options.add(option);
},
removeOption: function(option) {
this._options.delete(option.value);
this._options.delete(option);
},
actions: {
click: function(e, value) {
let options = [value];
if (this.multiple) {
if (this._options.has(value)) {
this._options.delete(value);
} else {
this._options.add(value);
click: function(option, e) {
// required={{true}} ?
if (!this.multiple) {
if (option.selected && this.required) {
return e;
}
[...this._options]
.filter(item => item !== option)
.forEach(item => {
item.selected = false;
});
} else {
if (option.selected && this.required) {
const other = [...this._options].find(item => item !== option && item.selected);
if (!other) {
return e;
}
}
options = this._options;
}
option.selected = !option.selected;
this.onchange(
this.dom.setEventTargetProperties(e, {
selected: target => value,
selected: target => option.args.value,
selectedItems: target => {
return [...options].join(',');
return [...this._options]
.filter(item => item.selected)
.map(item => item.args.value)
.join(',');
},
})
);
},
change: function(option, e) {
this.onchange(this.dom.setEventTargetProperty(e, 'selected', selected => option));
return e;
},
},
});

4
ui/packages/consul-ui/app/components/popover-select/optgroup/index.hbs

@ -1,7 +1,7 @@
{{#let components.MenuSeparator as |MenuSeparator|}}
{{#let @components.MenuSeparator as |MenuSeparator|}}
<MenuSeparator>
<BlockSlot @name="label">
{{label}}
{{@label}}
</BlockSlot>
</MenuSeparator>
{{yield}}

5
ui/packages/consul-ui/app/components/popover-select/optgroup/index.js

@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

12
ui/packages/consul-ui/app/components/popover-select/option/index.hbs

@ -1,9 +1,13 @@
{{#let components.MenuItem as |MenuItem|}}
{{#let @components.MenuItem as |MenuItem|}}
<MenuItem
class={{if selected 'is-active'}}
class={{if this.selected 'is-active'}}
{{did-insert this.connect}}
{{did-insert (set this "selected" @selected)}}
{{did-update (set this "selected" @selected)}}
{{will-destroy this.disconnect}}
...attributes
@onclick={{action 'click'}}
@selected={{selected}}
@onclick={{action @onclick this}}
@selected={{this.selected}}
>
<BlockSlot @name="label">
{{yield}}

34
ui/packages/consul-ui/app/components/popover-select/option/index.js

@ -1,20 +1,16 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default Component.extend({
tagName: '',
dom: service('dom'),
didInsertElement: function() {
this._super(...arguments);
this.select.addOption(this);
},
willDestroyElement: function() {
this._super(...arguments);
this.select.removeOption(this);
},
actions: {
click: function(e) {
this.onclick(e, this.value);
},
},
});
export default class Option extends Component {
@tracked selected;
@action
connect() {
this.args.select.addOption(this);
}
@action
disconnect() {
this.args.select.removeOption(this);
}
}

61
ui/packages/consul-ui/app/components/search-bar/README.mdx

@ -1,61 +0,0 @@
## SearchBar
```handlebars
<SearchBar
@value={{"search term"}}
@onsearch={{action "search"}}
/>
```
### Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `String` | | The string `value` of the freetext search bar |
| `onsearch` | `Function` | | The action to fire when the freetext search bar changes. Emits a native event with a `target.value` property containing the text typed into the search bar |
| `options` | `Array` | | An array of Key/Values pairs to use for options for either a filter interface or a sort interface |
| `selected` | `Object` | | An object containing a Key/Value pair of the currently selected option |
| `onchange` | `Function` | | The action to fire when the filter/sort changes. Emits an Event-like object, when filtering this has a `target.value` property containg the key of the selected filter, when sorting this has a `target.selected` property containing the selected Key/Value pair |
| `secondary` | `string` | | String identifier to signify what type of secondary filter to show. Currently only value here is 'sort' |
`SearchBar` is used for a variety of searching behaviours, freetext searching, filtering and sorting. It is also slot based to enable you to completely overwrite the secondary search if need be.
### Examples
```handlebars
{{! Freetext only search bar}}
<SearchBar
@value={{"search term"}}
@onsearch={{action "search"}}
/>
```
```handlebars
{{! Freetext and filter search bar}}
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value='target.value'}}
@selected={{filter.selected}}
@options={{filter.items}}
@onchange={{action (mut filterBy) value='target.value'}}
/>
```
```handlebars
{{! Freetext and sort search bar}}
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value='target.value'}}
@secondary="sort"
@selected={{sort.selected}}
@options={{sort.items}}
@onchange={{action (mut sortBy) value='target.selected.key'}}
/>
```
### See
- [Component Source Code](./index.js)
- [Template Source Code](./index.hbs)
---

99
ui/packages/consul-ui/app/components/search-bar/index.hbs

@ -1,31 +1,68 @@
{{yield}}
<form class={{concat 'filter-bar' (if (eq secondary 'sort') ' with-sort')}} ...attributes>
{{#yield-slot name="primary"}}
<fieldset>
{{yield}}
</fieldset>
{{else}}
<FreetextFilter
@onsearch={{action onsearch}}
@value={{value}}
@placeholder={{or placeholder 'Search'}}
/>
{{/yield-slot}}
{{#yield-slot name="secondary"}}
<fieldset>
{{yield}}
</fieldset>
{{else}}
{{#if options}}
{{#if (eq secondary 'sort')}}
{{else}}
<RadioGroup
@keyboardAccess={{true}}
@value={{selected.key}}
@items={{options}}
@onchange={{action onchange}}
/>
{{/if}}
{{/if}}
{{/yield-slot}}
</form>
<div
class="search-bar"
...attributes
>
<form
class="filter-bar"
>
<div class="search">
{{yield (hash
Search=(component "freetext-filter")
Select=(component "popover-select")
) to="search"}}
</div>
<div class="filters">
{{yield (hash
Search=(component "freetext-filter")
Select=(component "popover-select")
) to="filter"}}
</div>
<div class="sort">
{{yield (hash
Search=(component "freetext-filter")
Select=(component "popover-select")
) to="sort"}}
</div>
</form>
{{#if this.isFiltered}}
<div class="search-bar-status">
<dl>
<dt>{{string-trim
(t "component.search-bar.header"
default="common.ui.filtered-by"
item=""
)
}}</dt>
<dd>
<ul>
{{#each this.filters as |filter|}}
{{yield (hash
RemoveFilter=(component "search-bar/remove-filter" onclick=(action
(get (get @filter filter.key) "change")
(hash
target=(hash
selectedItems=(join filter.selected ',')
)
))
)
status=(hash
key=filter.key
value=(lowercase filter.value)
)
)
to="status"
}}
{{/each}}
<li class="remove-all">
<Action
{{on "click" this.removeAllFilters}}
>
Remove filters
</Action>
</li>
</ul>
</dd>
</dl>
</div>
{{/if}}
</div>

38
ui/packages/consul-ui/app/components/search-bar/index.js

@ -1,6 +1,34 @@
import Component from '@ember/component';
import Slotted from 'block-slots';
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { diff, filters } from './utils';
export default Component.extend(Slotted, {
tagName: '',
});
export default class SearchBar extends Component {
// only show the filter status bar if we have searchproperty filters or
// normal types of filters, and we are currently filtering by either of those
get isFiltered() {
const searchproperty = this.args.filter.searchproperty || { default: [], value: [] };
return (
diff(searchproperty.default, searchproperty.value).length > 0 ||
Object.entries(this.args.filter).some(([key, value]) => {
return key !== 'searchproperty' && typeof value.value !== 'undefined';
})
);
}
// convert the object based filters to an array of iterable filters ready for
// rendering
get filters() {
return filters(this.args.filter);
}
@action
removeAllFilters() {
Object.values(this.args.filter).forEach((value, i) => {
// put in a little queue to ensure query params are unset properly
// ideally this would be done outside of the component
// TODO: Look to see if this can be moved to serializeQueryParam
// so we we aren't polluting components with queryParam related things
setTimeout(() => value.change(value.default || []), 1 * i);
});
}
}

60
ui/packages/consul-ui/app/components/search-bar/index.scss

@ -0,0 +1,60 @@
.search-bar {
&-status {
& {
border-bottom: $decor-border-100;
border-bottom-color: $gray-200;
}
.remove-all button {
@extend %anchor;
}
li:not(.remove-all) {
& {
@extend %pill-200;
border: $decor-border-100;
border-color: $gray-200;
color: $gray-600;
}
button {
cursor: pointer;
}
button::before {
@extend %with-cancel-plain-mask, %as-pseudo;
color: $gray-600;
margin-top: 1px;
margin-right: 0.2rem;
}
}
& {
padding: .5rem 0;
padding-left: .5rem;
}
dt::after {
content: ':';
padding-right: 0.3rem;
}
& > dl > dt {
float: left;
}
dt {
white-space: nowrap;
}
li {
display: inline-flex;
}
li:not(:last-child) {
margin-right: 0.3rem;
margin-bottom: 0.3rem;
}
li:not(.remove-all) {
& {
padding: 0 0.2rem;
}
dl {
display: flex;
}
button {
padding: 0;
}
}
}
}

7
ui/packages/consul-ui/app/components/search-bar/pageobject.js

@ -1,7 +0,0 @@
export default (search, secondary = () => {}) => scope => {
return {
scope: scope,
...search(),
...secondary(),
};
};

7
ui/packages/consul-ui/app/components/search-bar/remove-filter/index.hbs

@ -0,0 +1,7 @@
<li>
<Action
...attributes
{{on 'click' @onclick}}
/>
{{yield}}
</li>

38
ui/packages/consul-ui/app/components/search-bar/utils.js

@ -0,0 +1,38 @@
export const diff = (a, b) => {
return a.filter(item => !b.includes(item));
};
/**
* filters accepts the args.filter @attribute which is shaped like
* {filterName: {default: ['Node', 'Address'], value: ['Address']}, ...}
* It will turn this into an array of 'filters' shaped like
* [{key: 'filterName', value: 'Address', selected: ["Node"]}]
* importantly 'selected' isn't what is currently 'selected' it is what selected
* will be once you remove this filter
* There is more explanation in the unit tests for this function so thats worthwhile
* checking if you are in amongst this
*/
export const filters = filters => {
return Object.entries(filters)
.filter(([key, value]) => {
if (key === 'searchproperty') {
return diff(value.default, value.value).length > 0;
}
return (value.value || []).length > 0;
})
.reduce((prev, [key, value]) => {
return prev.concat(
value.value.map(item => {
const obj = {
key: key,
value: item,
};
if (key !== 'searchproperty') {
obj.selected = diff(value.value, [item]);
} else {
obj.selected = value.value.length === 1 ? value.default : diff(value.value, [item]);
}
return obj;
})
);
}, []);
};

2
ui/packages/consul-ui/app/filter/predicates/acl.js

@ -1,5 +1,5 @@
export default {
kinds: {
kind: {
management: (item, value) => item.Type === value,
client: (item, value) => item.Type === value,
},

6
ui/packages/consul-ui/app/filter/predicates/health-check.js

@ -1,14 +1,14 @@
export default {
statuses: {
status: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,
},
kinds: {
kind: {
service: (item, value) => item.Kind === value,
node: (item, value) => item.Kind === value,
},
checks: {
check: {
serf: (item, value) => item.Type === '',
script: (item, value) => item.Type === value,
http: (item, value) => item.Type === value,

2
ui/packages/consul-ui/app/filter/predicates/intention.js

@ -1,5 +1,5 @@
export default {
accesses: {
access: {
allow: (item, value) => item.Action === value,
deny: (item, value) => item.Action === value,
'app-aware': (item, value) => typeof item.Action === 'undefined',

2
ui/packages/consul-ui/app/filter/predicates/kv.js

@ -1,5 +1,5 @@
export default {
kinds: {
kind: {
folder: (item, value) => item.isFolder,
key: (item, value) => !item.isFolder,
},

2
ui/packages/consul-ui/app/filter/predicates/node.js

@ -1,5 +1,5 @@
export default {
statuses: {
status: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,

4
ui/packages/consul-ui/app/filter/predicates/policy.js

@ -1,11 +1,11 @@
import setHelpers from 'mnemonist/set';
export default {
kinds: {
kind: {
'global-management': (item, value) => item.isGlobalManagement,
standard: (item, value) => !item.isGlobalManagement,
},
dcs: (item, values) => {
datacenter: (item, values) => {
return (
typeof item.Datacenters === 'undefined' ||
setHelpers.intersectionSize(values, new Set(item.Datacenters)) > 0

5
ui/packages/consul-ui/app/filter/predicates/service-instance.js

@ -1,12 +1,13 @@
import setHelpers from 'mnemonist/set';
export default {
statuses: {
status: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,
empty: (item, value) => item.MeshChecks.length === 0,
},
sources: (item, values) => {
source: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
};

9
ui/packages/consul-ui/app/filter/predicates/service.js

@ -1,7 +1,7 @@
import setHelpers from 'mnemonist/set';
export default {
kinds: {
kind: {
'ingress-gateway': (item, value) => item.Kind === value,
'terminating-gateway': (item, value) => item.Kind === value,
'mesh-gateway': (item, value) => item.Kind === value,
@ -9,16 +9,17 @@ export default {
'in-mesh': (item, value) => item.InMesh,
'not-in-mesh': (item, value) => !item.InMesh,
},
statuses: {
status: {
passing: (item, value) => item.MeshStatus === value,
warning: (item, value) => item.MeshStatus === value,
critical: (item, value) => item.MeshStatus === value,
empty: (item, value) => item.MeshChecksTotal === 0,
},
instances: {
instance: {
registered: (item, value) => item.InstanceCount > 0,
'not-registered': (item, value) => item.InstanceCount === 0,
},
sources: (item, values) => {
source: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
};

2
ui/packages/consul-ui/app/filter/predicates/token.js

@ -1,5 +1,5 @@
export default {
kinds: {
kind: {
'global-management': (item, value) => item.isGlobalManagement,
global: (item, value) => !item.Local,
local: (item, value) => item.Local,

4
ui/packages/consul-ui/app/models/role.js

@ -17,7 +17,9 @@ export default class Role extends Model {
@attr('number') SyncTime;
@attr('number') CreateIndex;
@attr('number') ModifyIndex;
// frontend only for ordering where CreateIndex can't be used
// frontend only for ordering where CreateIndex can't be used i.e. for when
// we need to order items that aren't yet saved to the backend, for example
// in the role-selector
@attr('number') CreateTime;
// TODO: Figure out whether we need this or not
@attr() Datacenters; // string[]

10
ui/packages/consul-ui/app/models/service.js

@ -48,6 +48,16 @@ export default class Service extends Model {
@attr() meta; // {}
@computed('ChecksPassing', 'ChecksWarning', 'ChecksCritical')
get ChecksTotal() {
return this.ChecksPassing + this.ChecksWarning + this.ChecksCritical;
}
@computed('MeshChecksPassing', 'MeshChecksWarning', 'MeshChecksCritical')
get MeshChecksTotal() {
return this.MeshChecksPassing + this.MeshChecksWarning + this.MeshChecksCritical;
}
/* Mesh properties involve both the service and the associated proxy */
@computed('ConnectedWithProxy', 'ConnectedWithGateway')
get MeshEnabled() {

35
ui/packages/consul-ui/app/routes/dc/acls/index.js

@ -1,13 +1,11 @@
import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
import { get } from '@ember/object';
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
export default class IndexRoute extends Route.extend(WithAclActions) {
@service('repository/acl') repo;
@service('settings') settings;
queryParams = {
@ -19,24 +17,25 @@ export default class IndexRoute extends Route.extend(WithAclActions) {
},
};
beforeModel(transition) {
return this.settings.findBySlug('token').then(token => {
// If you don't have a token set or you have a
// token set with AccessorID set to not null (new ACL mode)
// then rewrite to the new acls
if (!token || get(token, 'AccessorID') !== null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls.tokens');
}
});
async beforeModel(transition) {
const token = await this.settings.findBySlug('token');
// If you don't have a token set or you have a
// token set with AccessorID set to not null (new ACL mode)
// then rewrite to the new acls
if (!token || get(token, 'AccessorID') !== null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls.tokens');
}
}
model(params) {
return hash({
items: this.repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
token: this.settings.findBySlug('token'),
});
async model(params) {
const _items = this.repo.findAllByDatacenter(this.modelFor('dc').dc.Name);
const _token = this.settings.findBySlug('token');
return {
items: await _items,
token: await _token,
};
}
setupController(controller, model) {

5
ui/packages/consul-ui/app/routes/dc/acls/policies/index.js

@ -9,7 +9,9 @@ export default class IndexRoute extends Route.extend(WithPolicyActions) {
queryParams = {
sortBy: 'sort',
dc: 'dc',
datacenter: {
as: 'dc',
},
kind: 'kind',
searchproperty: {
as: 'searchproperty',
@ -29,6 +31,7 @@ export default class IndexRoute extends Route.extend(WithPolicyActions) {
this.modelFor('nspace').nspace.substr(1)
),
}),
searchProperties: this.queryParams.searchproperty.empty[0],
});
}

1
ui/packages/consul-ui/app/routes/dc/acls/roles/index.js

@ -27,6 +27,7 @@ export default class IndexRoute extends Route.extend(WithRoleActions) {
this.modelFor('nspace').nspace.substr(1)
),
}),
searchProperties: this.queryParams.searchproperty.empty[0],
});
}

20
ui/packages/consul-ui/app/routes/dc/acls/tokens/index.js

@ -21,16 +21,15 @@ export default class IndexRoute extends Route.extend(WithTokenActions) {
},
};
beforeModel(transition) {
return this.settings.findBySlug('token').then(token => {
// If you have a token set with AccessorID set to null (legacy mode)
// then rewrite to the old acls
if (token && get(token, 'AccessorID') === null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls');
}
});
async beforeModel(transition) {
const token = await this.settings.findBySlug('token');
// If you have a token set with AccessorID set to null (legacy mode)
// then rewrite to the old acls
if (token && get(token, 'AccessorID') === null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls');
}
}
model(params) {
@ -43,6 +42,7 @@ export default class IndexRoute extends Route.extend(WithTokenActions) {
}),
nspace: this.modelFor('nspace').nspace.substr(1),
token: this.settings.findBySlug('token'),
searchProperties: this.queryParams.searchproperty.empty[0],
});
}

3
ui/packages/consul-ui/app/routes/dc/intentions/index.js

@ -14,10 +14,11 @@ export default class IndexRoute extends Route {
},
};
model(params) {
async model(params) {
return {
dc: this.modelFor('dc').dc.Name,
nspace: this.modelFor('nspace').nspace.substr(1),
searchProperties: this.queryParams.searchproperty.empty[0],
};
}

14
ui/packages/consul-ui/app/routes/dc/nodes/index.js

@ -1,6 +1,5 @@
import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class IndexRoute extends Route {
@service('data-source/service') data;
@ -18,13 +17,16 @@ export default class IndexRoute extends Route {
},
};
model(params) {
async model(params) {
const dc = this.modelFor('dc').dc.Name;
const nspace = this.modelFor('nspace').nspace.substr(1);
return hash({
items: this.data.source(uri => uri`/${nspace}/${dc}/nodes`),
leader: this.data.source(uri => uri`/${nspace}/${dc}/leader`),
});
const items = this.data.source(uri => uri`/${nspace}/${dc}/nodes`);
const leader = this.data.source(uri => uri`/${nspace}/${dc}/leader`);
return {
items: await items,
leader: await leader,
searchProperties: this.queryParams.searchproperty.empty[0],
};
}
setupController(controller, model) {

10
ui/packages/consul-ui/app/routes/dc/nspaces/index.js

@ -1,6 +1,5 @@
import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
export default class IndexRoute extends Route.extend(WithNspaceActions) {
@ -19,10 +18,11 @@ export default class IndexRoute extends Route.extend(WithNspaceActions) {
},
};
model(params) {
return hash({
items: this.data.source(uri => uri`/*/*/namespaces`),
});
async model(params) {
return {
items: await this.data.source(uri => uri`/*/*/namespaces`),
searchProperties: this.queryParams.searchproperty.empty[0],
};
}
setupController(controller, model) {

1
ui/packages/consul-ui/app/routes/dc/services/index.js

@ -27,6 +27,7 @@ export default class IndexRoute extends Route {
dc,
nspace,
items,
searchProperties: this.queryParams.searchproperty.empty[0],
};
}

1
ui/packages/consul-ui/app/routes/dc/services/instance/healthchecks.js

@ -4,7 +4,6 @@ export default class HealthchecksRoute extends Route {
queryParams = {
sortBy: 'sort',
status: 'status',
kind: 'kind',
check: 'check',
searchproperty: {
as: 'searchproperty',

3
ui/packages/consul-ui/app/routes/dc/services/show/intentions/index.js

@ -14,11 +14,12 @@ export default class IndexRoute extends Route {
},
};
model(params) {
async model(params) {
return {
dc: this.modelFor('dc').dc.Name,
nspace: this.modelFor('nspace').nspace.substr(1) || 'default',
slug: this.paramsFor('dc.services.show').name,
searchProperties: this.queryParams.searchproperty.empty[0],
};
}

7
ui/packages/consul-ui/app/routes/dc/services/show/services.js

@ -25,13 +25,12 @@ export default class ServicesRoute extends Route {
.slice(0, -1)
.join('.');
const name = this.modelFor(parent).slug;
const gatewayServices = await this.data.source(
uri => uri`/${nspace}/${dc}/gateways/for-service/${name}`
);
const items = await this.data.source(uri => uri`/${nspace}/${dc}/gateways/for-service/${name}`);
return {
dc,
nspace,
gatewayServices,
items,
searchProperties: this.queryParams.searchproperty.empty[0],
};
}

1
ui/packages/consul-ui/app/styles/components.scss

@ -53,6 +53,7 @@
@import 'consul-ui/components/freetext-filter';
@import 'consul-ui/components/informed-action';
@import 'consul-ui/components/tab-nav';
@import 'consul-ui/components/search-bar';
@import 'consul-ui/components/consul/tomography/graph';
@import 'consul-ui/components/consul/discovery-chain';

2
ui/packages/consul-ui/app/styles/layout.scss

@ -18,7 +18,7 @@ html[data-route$='edit'] .app-view > header + div > *:first-child {
/* if it is a filter bar and the thing after the filter bar is a p then it also */
/* needs a top margun :S */
%app-view-content .tab-section > *:first-child:not(.filter-bar):not(table),
%app-view-content .tab-section > .filter-bar + p,
%app-view-content .tab-section > .search-bar + p,
%app-view-content .tab-section .consul-health-check-list {
margin-top: 1.25em;
}

2
ui/packages/consul-ui/app/styles/typography.scss

@ -17,7 +17,7 @@ h3 {
%radio-card header,
fieldset > header,
%main-nav-horizontal-action,
%app-view-content div > dl > dt,
%definition-table dt,
%table caption,
%tbody-th,
%form-element > span {

164
ui/packages/consul-ui/app/templates/dc/acls/index.hbs

@ -1,83 +1,93 @@
{{page-title 'ACLs'}}
{{#let (hash
kinds=(if kind (split kind ',') undefined)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Acl::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
)
items
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
as |sort filters items|}}
@filter={{filters}}
@onfilter={{hash
kind=(action (mut kind) value="target.selectedItems")
}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="acl"
@sort={{sort}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Acl::List
@items={{collection.items}}
</BlockSlot>
<BlockSlot @name="header">
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Acl::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="acl"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Acl::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
@onuse={{route-action 'use'}}
@onclone={{route-action 'clone'}}
>
</Consul::Acl::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No ACLs found
{{else}}
Welcome to ACLs
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for.
{{else}}
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
@ondelete={{route-action 'delete'}}
@onuse={{route-action 'use'}}
@onclone={{route-action 'clone'}}
>
</Consul::Acl::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No ACLs found
{{else}}
Welcome to ACLs
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for.
{{else}}
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

208
ui/packages/consul-ui/app/templates/dc/acls/policies/index.hbs

@ -3,104 +3,118 @@
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
{{#let (hash
kinds=(if kind (split kind ',') undefined)
dcs=(if dc (split dc ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Description')
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
datacenter=(hash
value=(if datacenter (split datacenter ',') undefined)
change=(action (mut datacenter) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Access Controls
</h1>
</BlockSlot>
<BlockSlot @name="nav">
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Policy::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
items
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Access Controls
</h1>
</BlockSlot>
<BlockSlot @name="nav">
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Policy::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="policy"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Policy::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No policies found
{{else}}
Welcome to Policies
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/policy" rel="noopener noreferrer" target="_blank">Documentation on policies</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_LEARN_URL'}}/consul/security-networking/managing-acl-policies" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
dc=(action (mut dc) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="role"
@sort={{sort}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Policy::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No policies found
{{else}}
Welcome to Policies
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/policy" rel="noopener noreferrer" target="_blank">Documentation on policies</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_LEARN_URL'}}/consul/security-networking/managing-acl-policies" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

34
ui/packages/consul-ui/app/templates/dc/acls/roles/index.hbs

@ -4,13 +4,28 @@
{{page-title 'Access Controls'}}
{{/if}}
{{#let (hash
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Description' 'Policy')
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
items
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@ -43,19 +58,15 @@
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="role"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -99,5 +110,4 @@
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

218
ui/packages/consul-ui/app/templates/dc/acls/tokens/index.hbs

@ -4,110 +4,122 @@
{{page-title 'Access Controls'}}
{{/if}}
{{#let (hash
kinds=(if kind (split kind ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Description' 'Policy' 'Role')
{{#let
(hash
value=(or sortBy "CreateTime:desc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "CreateTime:desc") as |sort|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Access Controls
</h1>
</BlockSlot>
<BlockSlot @name="nav">
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.tokens.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Token::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
items
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Access Controls
</h1>
</BlockSlot>
<BlockSlot @name="nav">
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.tokens.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Token::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy items)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p data-test-notification-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 <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
</notice.Body>
</Notice>
{{/if}}
<DataCollection
@type="token"
@sort={{sort}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Token::List
@items={{collection.items}}
@token={{token}}
@onuse={{route-action 'use'}}
@ondelete={{route-action 'delete'}}
@onlogout={{route-action 'logout'}}
@onclone={{route-action 'clone'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No tokens found
{{else}}
Welcome to ACL Tokens
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy items)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p data-test-notification-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 <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
</notice.Body>
</Notice>
{{/if}}
<DataCollection
@type="token"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Token::List
@items={{collection.items}}
@token={{token}}
@onuse={{route-action 'use'}}
@ondelete={{route-action 'delete'}}
@onlogout={{route-action 'logout'}}
@onclone={{route-action 'clone'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No tokens found
{{else}}
Welcome to ACL Tokens
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}

191
ui/packages/consul-ui/app/templates/dc/intentions/index.hbs

@ -6,104 +6,115 @@
</BlockSlot>
<BlockSlot @name="loaded">
{{#let api.data as |items|}}
{{#let (hash
accesses=(if access (split access ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'SourceName' 'DestinationName')
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
) as |filters|}}
{{#let (or sortBy "Action:asc") as |sort|}}
<AppView>
<BlockSlot @name="header">
<h1>
Intentions <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
api.data
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
as |sort filters items|}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
access=(action (mut access) value="target.selectedItems")
}}
/>
{{/if}}
<AppView>
<BlockSlot @name="header">
<h1>
Intentions <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
</BlockSlot>
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
<DataCollection
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.Table />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No intentions found
{{else}}
Welcome to Intentions
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/intention" rel="noopener noreferrer" target="_blank">Documentation on intentions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/connect" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.Table />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No intentions found
{{else}}
Welcome to Intentions
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#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.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/intention" rel="noopener noreferrer" target="_blank">Documentation on intentions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/connect" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}
</DataWriter>
</BlockSlot>
</AppView>
{{/let}}
</BlockSlot>
</DataLoader>

28
ui/packages/consul-ui/app/templates/dc/kv/index.hbs

@ -1,8 +1,21 @@
{{page-title 'Key/Value'}}
{{#let (hash
kinds=(if kind (split kind ',') undefined)
) as |filters|}}
{{#let (or sortBy "Kind:asc") as |sort|}}
{{#let
(hash
value=(or sortBy "Kind:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
)
items
as |sort filters items|}}
<AppView>
{{#if (not-eq parent.Key '/') }}
<BlockSlot @name="breadcrumbs">
@ -31,12 +44,8 @@
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
kind=(action (mut kind) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
@ -57,7 +66,7 @@
<BlockSlot @name="content">
<DataCollection
@type="kv"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -104,5 +113,4 @@
</DataWriter>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

39
ui/packages/consul-ui/app/templates/dc/nodes/index.hbs

@ -1,14 +1,31 @@
{{page-title 'Nodes'}}
<EventSource @src={{items}} />
<EventSource @src={{leader}} />
{{#let (hash
statuses=(if status (split status ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Node' 'Address' 'Meta')
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
items
as |sort filters items|}}
<AppView>
<BlockSlot @name="header">
<h1>
@ -23,20 +40,15 @@
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="node"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -59,5 +71,4 @@
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

58
ui/packages/consul-ui/app/templates/dc/nodes/show/healthchecks.hbs

@ -1,40 +1,55 @@
{{#let (hash
statuses=(if status (split status ',') undefined)
kinds=(if kind (split kind ',') undefined)
checks=(if check (split check ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
item.Checks
as |sort filters items|}}
<div class="tab-section">
{{#if (gt item.Checks.length 0) }}
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
check=(action (mut check) value="target.selectedItems")
}}
/>
{{/if}}
<DataCollection
@type="health-check"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{item.Checks}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@ -45,12 +60,11 @@
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no health checks{{#if (gt item.Checks.length 0)}} matching that search{{/if}}.
This node has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
{{/let}}

47
ui/packages/consul-ui/app/templates/dc/nodes/show/services.hbs

@ -1,13 +1,32 @@
{{#let (hash
statuses=(if status (split status ',') undefined)
sources=(if source (split source ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
{{#let (reject-by 'Service.Kind' 'connect-proxy' item.Services) as |items|}}
(reject-by 'Service.Kind' 'connect-proxy' item.Services)
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
@ -18,20 +37,14 @@
@searchproperties={{searchProperties}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
source=(action (mut source) value="target.selectedItems")
}}
/>
{{/if}}
{{! filter out any sidecar proxies }}
<DataCollection
@type="service-instance"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -55,6 +68,4 @@
</collection.Empty>
</DataCollection>
</div>
{{/let}}
{{/let}}
{{/let}}

36
ui/packages/consul-ui/app/templates/dc/nspaces/index.hbs

@ -1,12 +1,27 @@
{{page-title 'Namespaces'}}
{{#let (hash
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Description' 'Policy' 'Role')
<EventSource @src={{items}} />
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
<EventSource @src={{items}} />
items
as |sort filters items|}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@ -30,19 +45,15 @@
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="nspace"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -86,5 +97,4 @@
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}

40
ui/packages/consul-ui/app/templates/dc/services/index.hbs

@ -4,15 +4,31 @@
{{#let
(or sortBy "Status:asc")
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
statuses=(if status (split status ',') undefined)
kinds=(if kind (split kind ',') undefined)
sources=(if source (split source ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Tags')
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
@ -36,22 +52,16 @@ as |sort filters items|}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
source=(action (mut source) value="target.selectedItems")
}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="service"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}

54
ui/packages/consul-ui/app/templates/dc/services/instance/healthchecks.hbs

@ -1,41 +1,52 @@
{{#let (hash
statuses=(if status (split status ',') undefined)
kinds=(if kind (split kind ',') undefined)
checks=(if check (split check ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
item.MeshChecks
as |sort filters items|}}
<div class="tab-section">
{{#if (gt item.MeshChecks.length 0) }}
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
check=(action (mut check) value="target.selectedItems")
}}
/>
{{/if}}
<DataCollection
@type="health-check"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{item.MeshChecks}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@ -46,7 +57,7 @@
<EmptyState>
<BlockSlot @name="body">
<p>
This instance has no health checks{{#if (gt item.MeshChecks.length 0)}} matching that search{{/if}}.
This instance has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
@ -54,5 +65,4 @@
</DataCollection>
</div>
{{/let}}
{{/let}}

39
ui/packages/consul-ui/app/templates/dc/services/instance/upstreams.hbs

@ -1,12 +1,26 @@
<div class="tab-section">
{{#let (hash
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
{{#let
(hash
value=(or sortBy "DestinationName:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "DestinationName:asc") as |sort|}}
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
proxy.Service.Proxy.Upstreams
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar
@search={{search}}
@ -14,20 +28,16 @@
@searchproperties={{searchProperties}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
}}
/>
{{/if}}
<DataCollection
@type="upstream-instance"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{proxy.Service.Proxy.Upstreams}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::UpstreamInstance::List
@ -40,12 +50,11 @@
<EmptyState>
<BlockSlot @name="body">
<p>
This service has no upstreams{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}} matching that search{{/if}}.
This service has no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
{{/let}}
</div>

46
ui/packages/consul-ui/app/templates/dc/services/show/instances.hbs

@ -1,36 +1,49 @@
<div class="tab-section">
{{#let (hash
statuses=(if status (split status ',') undefined)
sources=(if source (split source ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
items
as |sort filters items|}}
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::ServiceInstance::SearchBar
@sources={{get (collection items) 'ExternalSources'}}
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
status=(action (mut status) value="target.selectedItems")
source=(action (mut source) value="target.selectedItems")
}}
/>
{{/if}}
{{! Service > Service Instance view doesn't require filtering of proxies }}
<DataCollection
@type="service-instance"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
@ -51,6 +64,5 @@
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
{{/let}}
</div>

131
ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs

@ -12,74 +12,83 @@ as |api|>
<ErrorState @error={{api.error}} />
</BlockSlot>
<BlockSlot @name="loaded">
{{#let api.data as |items|}}
{{#let (hash
accesses=(if access (split access ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'SourceName' 'DestinationName')
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
) as |filters|}}
{{#let (or sortBy "Action:asc") as |sort|}}
<div class="tab-section">
<Portal @target="app-view-actions">
<a data-test-create href={{href-to 'dc.services.show.intentions.create'}} class="type-create">Create</a>
</Portal>
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
api.data
as |sort filters items|}}
<div class="tab-section">
<Portal @target="app-view-actions">
<a data-test-create href={{href-to 'dc.services.show.intentions.create'}} class="type-create">Create</a>
</Portal>
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@sort={{sort}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
access=(action (mut access) value="target.selectedItems")
}}
/>
@filter={{filters}}
/>
{{/if}}
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@check={{search}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.CheckNotice />
<list.Table @routeName="dc.services.show.intentions.edit" />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no intentions {{if (gt items.length 0) 'found '}} for this service.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</div>
{{/let}}
{{/let}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@check={{search}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.CheckNotice />
<list.Table @routeName="dc.services.show.intentions.edit" />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no intentions {{if (gt items.length 0) 'found '}} for this service.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</div>
{{/let}}
</BlockSlot>
</DataLoader>

47
ui/packages/consul-ui/app/templates/dc/services/show/services.hbs

@ -1,27 +1,39 @@
<EventSource @src={{gatewayServices}} />
<EventSource @src={{items}} />
<div class="tab-section">
{{#let (hash
instances=(if instance (split instance ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Tags')
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
{{#if (gt gatewayServices.length 0)}}
items
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
instance=(action (mut instance) value="target.selectedItems")
}}
/>
{{/if}}
<p>
@ -30,10 +42,10 @@
</p>
<DataCollection
@type="service"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{gatewayServices}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Service::List
@ -46,12 +58,11 @@
<EmptyState>
<BlockSlot @name="body">
<p>
There are no linked services{{#if (gt gatewayServices.length 0)}} matching that search{{/if}}.
There are no linked services{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
{{/let}}
</div>

47
ui/packages/consul-ui/app/templates/dc/services/show/upstreams.hbs

@ -1,27 +1,39 @@
<EventSource @src={{gatewayServices}} />
<EventSource @src={{items}} />
<div class="tab-section">
{{#let (hash
instances=(if instance (split instance ',') undefined)
searchproperties=(if (not-eq searchproperty undefined)
(split searchproperty ',')
(array 'Name' 'Tags')
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
) as |filters|}}
{{#let (or sortBy "Status:asc") as |sort|}}
{{#if (gt gatewayServices.length 0)}}
items
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@filter={{filters}}
@onfilter={{hash
searchproperty=(action (mut searchproperty) value="target.selectedItems")
instance=(action (mut instance) value="target.selectedItems")
}}
/>
{{/if}}
<p>
@ -29,10 +41,10 @@
</p>
<DataCollection
@type="service"
@sort={{sort}}
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{gatewayServices}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Upstream::List
@ -46,12 +58,11 @@
<EmptyState>
<BlockSlot @name="body">
<p>
There are no upstreams{{#if (gt gatewayServices.length 0)}} matching that search{{/if}}.
There are no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
{{/let}}
</div>

9
ui/packages/consul-ui/app/utils/intl/missing-message.js

@ -0,0 +1,9 @@
// if we can't find the message, take the last part of the identifier and
// ucfirst it so it looks human
export default function missingMessage(key, locales) {
const last = key
.split('.')
.pop()
.replaceAll('-', ' ');
return `${last.substr(0, 1).toUpperCase()}${last.substr(1)}`;
}

24
ui/packages/consul-ui/tests/integration/components/search-bar-test.js

@ -1,24 +0,0 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | search-bar', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
this.set('search', function(e) {});
await render(hbs`<SearchBar @onsearch={{action search}}/>`);
assert.equal(this.element.textContent.trim(), 'Search');
// Template block usage:
await render(hbs`
<SearchBar @onsearch={{action search}}></SearchBar>
`);
assert.equal(this.element.textContent.trim(), 'Search');
});
});

23
ui/packages/consul-ui/tests/pages.js

@ -6,7 +6,6 @@ import {
collection,
text,
isPresent,
triggerable,
} from 'ember-cli-page-object';
import { alias } from 'ember-cli-page-object/macros';
@ -26,9 +25,7 @@ import pageFactory from 'consul-ui/components/hashicorp-consul/pageobject';
import radiogroup from 'consul-ui/components/radio-group/pageobject';
import tabgroup from 'consul-ui/components/tab-nav/pageobject';
import authFormFactory from 'consul-ui/components/auth-form/pageobject';
import freetextFilterFactory from 'consul-ui/components/freetext-filter/pageobject';
import searchBarFactory from 'consul-ui/components/search-bar/pageobject';
import emptyStateFactory from 'consul-ui/components/empty-state/pageobject';
import policyFormFactory from 'consul-ui/components/policy-form/pageobject';
@ -82,11 +79,6 @@ const cancelable = createCancelable(clickable, is);
// components
const tokenList = tokenListFactory(clickable, attribute, collection, deletable);
const authForm = authFormFactory(submitable, clickable, attribute);
const freetextFilter = freetextFilterFactory(triggerable);
const catalogToolbar = searchBarFactory(freetextFilter);
const aclFilter = searchBarFactory(freetextFilter, () =>
radiogroup('type', ['', 'management', 'client'])
);
const policyForm = policyFormFactory(submitable, cancelable, radiogroup, text);
const policySelector = policySelectorFactory(clickable, deletable, collection, alias, policyForm);
const roleForm = roleFormFactory(submitable, cancelable, policySelector);
@ -160,18 +152,7 @@ export default {
radiogroup
)
),
service: create(
service(
visitable,
clickable,
attribute,
collection,
text,
consulIntentionList,
catalogToolbar,
tabgroup
)
),
service: create(service(visitable, clickable, attribute, collection, text, consulIntentionList, tabgroup)),
instance: create(
instance(
visitable,
@ -199,7 +180,7 @@ export default {
),
kvs: create(kvs(visitable, creatable, consulKvList)),
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)),
acls: create(acls(visitable, deletable, creatable, clickable, attribute, collection, aclFilter)),
acls: create(acls(visitable, deletable, creatable, clickable, attribute, collection)),
acl: create(acl(visitable, submitable, deletable, cancelable, clickable)),
policies: create(policies(visitable, creatable, consulPolicyList, popoverSelect)),
policy: create(policy(visitable, submitable, deletable, cancelable, clickable, tokenList)),

3
ui/packages/consul-ui/tests/pages/dc/acls/index.js

@ -1,4 +1,4 @@
export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
export default function(visitable, deletable, creatable, clickable, attribute, collection) {
return creatable({
visit: visitable('/:dc/acls'),
acls: collection(
@ -11,6 +11,5 @@ export default function(visitable, deletable, creatable, clickable, attribute, c
confirmUse: clickable('[data-test-confirm-use]'),
})
),
filter: filter('[data-test-acl-filter]'),
});
}

12
ui/packages/consul-ui/tests/pages/dc/services/show.js

@ -1,13 +1,4 @@
export default function(
visitable,
clickable,
attribute,
collection,
text,
intentions,
filter,
tabs
) {
export default function(visitable, clickable, attribute, collection, text, intentions, tabs) {
const page = {
visit: visitable('/:dc/services/:service'),
externalSource: attribute('data-test-external-source', '[data-test-external-source]', {
@ -28,7 +19,6 @@ export default function(
'routing',
'tags',
]),
filter: filter(),
// TODO: These need to somehow move to subpages
instances: collection('.consul-service-instance-list > ul > li:not(:first-child)', {
address: text('[data-test-address]'),

154
ui/packages/consul-ui/tests/unit/components/search-bar/filters-test.js

@ -0,0 +1,154 @@
import { filters } from 'consul-ui/components/search-bar/utils';
import { module, test } from 'qunit';
module('Unit | Component | search-bar/filters', function() {
test('it correctly reshapes the filter data', function(assert) {
[
// basic filter, returns a single filter button when clicked
// resets selected/queryparam to empty
{
filters: {
status: {
value: ['passing'],
},
},
expected: [
{
key: 'status',
value: 'passing',
selected: [],
},
],
},
// basic filters, returns multiple filter button when clicked
// sets selected/queryparam to the left over single filter
{
filters: {
status: {
value: ['passing', 'warning'],
},
},
expected: [
{
key: 'status',
value: 'passing',
selected: ['warning'],
},
{
key: 'status',
value: 'warning',
selected: ['passing'],
},
],
},
// basic filters, returns multiple filter button when clicked
// sets selected/queryparam to the left over multiple filters
{
filters: {
status: {
value: ['passing', 'warning', 'critical'],
},
},
expected: [
{
key: 'status',
value: 'passing',
selected: ['warning', 'critical'],
},
{
key: 'status',
value: 'warning',
selected: ['passing', 'critical'],
},
{
key: 'status',
value: 'critical',
selected: ['passing', 'warning'],
},
],
},
// basic filters, returns multiple filter button when clicked
// sets selected/queryparam to the left over multiple filters
// also search property multiple filter, sets the selected/queryparam to
// the left of single searchproperty filter
{
filters: {
status: {
value: ['passing', 'warning', 'critical'],
},
searchproperties: {
default: ['Node', 'Address', 'Meta'],
value: ['Node', 'Address'],
},
},
expected: [
{
key: 'status',
value: 'passing',
selected: ['warning', 'critical'],
},
{
key: 'status',
value: 'warning',
selected: ['passing', 'critical'],
},
{
key: 'status',
value: 'critical',
selected: ['passing', 'warning'],
},
{
key: 'searchproperties',
value: 'Node',
selected: ['Address'],
},
{
key: 'searchproperties',
value: 'Address',
selected: ['Node'],
},
],
},
// basic filters, returns multiple filter button when clicked
// sets selected/queryparam to the left over multiple filters
// also search property single filter, resets the selected/queryparam to
// empty
{
filters: {
status: {
value: ['passing', 'warning', 'critical'],
},
searchproperties: {
default: ['Node', 'Address', 'Meta'],
value: ['Node'],
},
},
expected: [
{
key: 'status',
value: 'passing',
selected: ['warning', 'critical'],
},
{
key: 'status',
value: 'warning',
selected: ['passing', 'critical'],
},
{
key: 'status',
value: 'critical',
selected: ['passing', 'warning'],
},
{
key: 'searchproperties',
value: 'Node',
selected: [],
},
],
},
].forEach(item => {
const actual = filters(item.filters);
assert.deepEqual(actual, item.expected);
});
});
});

6
ui/packages/consul-ui/tests/unit/filter/predicates/intention-test.js

@ -20,7 +20,7 @@ module('Unit | Filter | Predicates | intention', function() {
expected = [items[0]];
actual = items.filter(
predicate({
accesses: ['allow'],
access: ['allow'],
})
);
assert.deepEqual(actual, expected);
@ -28,7 +28,7 @@ module('Unit | Filter | Predicates | intention', function() {
expected = [items[1]];
actual = items.filter(
predicate({
accesses: ['deny'],
access: ['deny'],
})
);
assert.deepEqual(actual, expected);
@ -36,7 +36,7 @@ module('Unit | Filter | Predicates | intention', function() {
expected = items;
actual = items.filter(
predicate({
accesses: ['allow', 'deny'],
access: ['allow', 'deny'],
})
);
assert.deepEqual(actual, expected);

36
ui/packages/consul-ui/tests/unit/filter/predicates/service-test.js

@ -20,7 +20,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
instances: ['registered'],
instance: ['registered'],
})
);
assert.deepEqual(actual, expected);
@ -28,7 +28,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
instances: ['not-registered'],
instance: ['not-registered'],
})
);
assert.deepEqual(actual, expected);
@ -36,7 +36,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
instances: ['registered', 'not-registered'],
instance: ['registered', 'not-registered'],
})
);
assert.deepEqual(actual, expected);
@ -60,7 +60,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
statuses: ['passing'],
status: ['passing'],
})
);
assert.deepEqual(actual, expected);
@ -68,7 +68,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
statuses: ['warning'],
status: ['warning'],
})
);
assert.deepEqual(actual, expected);
@ -76,7 +76,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
statuses: ['passing', 'warning', 'critical'],
status: ['passing', 'warning', 'critical'],
})
);
assert.deepEqual(actual, expected);
@ -98,7 +98,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
kinds: ['ingress-gateway'],
kind: ['ingress-gateway'],
})
);
assert.deepEqual(actual, expected);
@ -106,7 +106,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
kinds: ['mesh-gateway'],
kind: ['mesh-gateway'],
})
);
assert.deepEqual(actual, expected);
@ -114,7 +114,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
kind: ['ingress-gateway', 'mesh-gateway', 'service'],
})
);
assert.deepEqual(actual, expected);
@ -142,9 +142,9 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
kinds: ['ingress-gateway'],
statuses: ['passing'],
instances: ['registered'],
kind: ['ingress-gateway'],
status: ['passing'],
instance: ['registered'],
})
);
assert.deepEqual(actual, expected);
@ -152,9 +152,9 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
kinds: ['mesh-gateway'],
statuses: ['warning'],
instances: ['registered'],
kind: ['mesh-gateway'],
status: ['warning'],
instance: ['registered'],
})
);
assert.deepEqual(actual, expected);
@ -162,9 +162,9 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
statuses: ['passing', 'warning', 'critical'],
instances: ['registered', 'not-registered'],
kind: ['ingress-gateway', 'mesh-gateway', 'service'],
status: ['passing', 'warning', 'critical'],
instance: ['registered', 'not-registered'],
})
);
assert.deepEqual(actual, expected);

157
ui/packages/consul-ui/translations/en-us.yaml

@ -1 +1,158 @@
common:
brand:
consul: Consul
terraform: Terraform
nomad: Nomad
vault: Vault
aws: AWS
kubernetes: Kubernetes
ui:
remove: Remove {item}
filtered-by: Filtered by {item}
name: Name
creation: Creation
consul:
name: Name
passing: Passing
warning: Warning
critical: Critical
registered: Registered
not-registered: Not Registered
empty: No checks
tags: Tags
service: Service
gateway: Gateway
mesh: Mesh
ingress-gateway: Ingress Gateway
terminating-gateway: Terminating Gateway
mesh-gateway: Mesh Gateway
status: Health Status
service-name: Service Name
node-name: Node Name
accessorid: AccessorID
datacenter: Datacenter
localbindaddress: Local Bind Address
localbindport: Local Bind Port
destinationname: Destination Name
sourcename: Source Name
search:
search: Search
searchproperty: Search Across
source: Source
critical: Failing
in-mesh: In service mesh
not-in-mesh: Not in service mesh
sort:
alpha:
asc: A to Z
desc: Z to A
numeric:
asc: Ascending
desc: Descending
age:
asc: Oldest to Newest
desc: Newest to Oldest
status:
asc: Unhealthy to Healthy
desc: Healthy to Unhealthy
components:
consul:
service:
search-bar:
kind: Service Type
in-mesh: In service mesh
not-in-mesh: Not in service mesh
upstream:
search-bar:
instance:
name: Type
service-instance:
search-bar:
sort:
name:
name: Service Name
health-check:
search-bar:
kind:
name: Kind
options:
service: Service Check
node: Node Check
check:
name: Type
options:
alias: alias
docker: docker
grpc: grpc
http: http
script: script
serf: serf
tcp: tcp
ttl: ttl
sort:
name:
name: Check Name
kind:
name: Check Type
asc: Service to Node
desc: Node to Service
acl:
search-bar:
kind:
name: Type
options:
management: Management
client: Client
token:
search-bar:
kind:
name: Type
options:
global-management: Global Management
global: Global Scope
local: Local Scope
policy:
search-bar:
kind:
name: Type
options:
global-management: Global Management
standard: Standard
kv:
search-bar:
kind:
name: Type
options:
folder: Folder
key: Key
sort:
kind:
asc: Folders to Keys
desc: Keys to Folders
intention:
search-bar:
access:
name: Permission
options:
allow: Allow
deny: Deny
app-aware: App aware
sort:
access:
name: Permission
asc: Allow to Deny
desc: Deny to Allow
source-name:
name: Source
asc: "Source: A to Z"
desc: "Source: Z to A"
destination-name:
name: Destination
asc: "Destination: A to Z"
desc: "Destination: Z to A"
precedence:
name: Precedence
asc: Ascending
desc: Descending

Loading…
Cancel
Save