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.
This commit is contained in:
John Cowen 2021-01-25 18:13:54 +00:00 committed by GitHub
parent e3f5a77ade
commit 148b18b28c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 3117 additions and 1946 deletions

4
.changelog/9442.txt Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,140 +1,190 @@
<form <SearchBar
class="consul-health-check-search-bar filter-bar" class="consul-healthcheck-search-bar"
...attributes ...attributes
@filter={{@filter}}
> >
<div class="search"> <:status as |search|>
<FreetextFilter
@onsearch={{action @onsearch}} {{#let
@value={{@search}}
@placeholder="Search" (t (concat "components.consul.health-check.search-bar." search.status.key ".name")
> default=(array
<PopoverSelect (concat "common.search." search.status.key)
class="type-search-properties" (concat "common.consul." search.status.key)
@position="right" )
@onchange={{action @onfilter.searchproperty}} )
(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}} @multiple={{true}}
as |components|> as |components|>
<BlockSlot @name="selected"> <BlockSlot @name="selected">
<span> <span>
Search across {{t "common.consul.status"}}
</span> </span>
</BlockSlot> </BlockSlot>
<BlockSlot @name="options"> <BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @searchproperties as |prop|}} {{#each (array "passing" "warning" "critical" "empty") as |state|}}
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{prop}}</Option> <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}} {{/each}}
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </search.Select>
</FreetextFilter> {{#if @filter.kind}}
</div> <search.Select
<div class="filters"> class="type-kind"
<PopoverSelect @position="left"
class="type-status" @onchange={{action @filter.kind.change}}
@position="left" @multiple={{true}}
@onchange={{action @onfilter.status}} as |components|>
@multiple={{true}} <BlockSlot @name="selected">
as |components|> <span>
<BlockSlot @name="selected"> {{t "components.consul.health-check.search-bar.kind.name"}}
<span> </span>
Health Status </BlockSlot>
</span> <BlockSlot @name="options">
</BlockSlot> {{#let components.Optgroup components.Option as |Optgroup Option|}}
<BlockSlot @name="options"> {{#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|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option> <Optgroup @label={{t "common.consul.status"}}>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option> <Option @value="Status:asc" @selected={{eq "Status:asc" @sort.value}}>{{t "common.sort.status.asc"}}</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option> <Option @value="Status:desc" @selected={{eq "Status:desc" @sort.value}}>{{t "common.sort.status.desc"}}</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">
{{#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>
<Optgroup @label="Check Name"> <Optgroup @label={{t "components.consul.health-check.search-bar.sort.name.name"}}>
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option> <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}}>Z to A</Option> <Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup> </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:asc" @selected={{eq "Kind:asc" @sort}}>Service to Node</Option>
<Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort}}>Node to Service</Option> <Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort}}>Node to Service</Option>
</Optgroup> </Optgroup>
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </search.Select>
</div> </:sort>
</form> </SearchBar>

View File

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

View File

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

View File

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

View File

@ -1,63 +1,98 @@
<form <SearchBar
class="consul-nspace-search-bar filter-bar" class="consul-nspace-search-bar"
...attributes ...attributes
@filter={{@filter}}
> >
<div class="search"> <:status as |search|>
<FreetextFilter
@onsearch={{action @onsearch}} {{#let
@value={{@search}}
@placeholder="Search" (t (concat "components.consul.nspace.search-bar." search.status.key)
> default=(array
<PopoverSelect (concat "common.search." search.status.key)
class="type-search-properties" (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" @position="right"
@onchange={{action @onfilter.searchproperty}} @onchange={{action @sort.change}}
@multiple={{true}} @multiple={{false}}
@required={{true}}
as |components|> as |components|>
<BlockSlot @name="selected"> <BlockSlot @name="selected">
<span> <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> </span>
</BlockSlot> </BlockSlot>
<BlockSlot @name="options"> <BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option> <Optgroup @label={{t "common.consul.name"}}>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option> <Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option> <Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
<Option @value="Role" @selected={{contains 'Role' @filter.searchproperties}}>Role</Option> </Optgroup>
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </search.Select>
</FreetextFilter> </:sort>
</div> </SearchBar>
<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>

View File

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

View File

@ -1,68 +1,104 @@
<form <SearchBar
class="consul-role-search-bar filter-bar" class="consul-role-search-bar"
...attributes ...attributes
@filter={{@filter}}
> >
<div class="search"> <:status as |search|>
<FreetextFilter
@onsearch={{action @onsearch}} {{#let
@value={{@search}}
@placeholder="Search" (t (concat "components.consul.role.search-bar." search.status.key ".name")
> default=(array
<PopoverSelect (concat "common.search." search.status.key)
class="type-search-properties" (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" @position="right"
@onchange={{action @onfilter.searchproperty}} @onchange={{action @sort.change}}
@multiple={{true}} @multiple={{false}}
@required={{true}}
as |components|> as |components|>
<BlockSlot @name="selected"> <BlockSlot @name="selected">
<span> <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> </span>
</BlockSlot> </BlockSlot>
<BlockSlot @name="options"> <BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option> <Optgroup @label={{t "common.ui.name"}}>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option> <Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option> <Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
</Optgroup>
<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}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </search.Select>
</FreetextFilter> </:sort>
</div> </SearchBar>
<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>
<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>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>

View File

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

View File

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

View File

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

View File

@ -1,63 +1,96 @@
<form <SearchBar
class="consul-upstream-instance-search-bar filter-bar" class="consul-upstream-instance-search-bar"
...attributes ...attributes
@filter={{@filter}}
> >
<div class="search"> <:status as |search|>
<FreetextFilter
@onsearch={{action @onsearch}} {{#let
@value={{@search}}
@placeholder="Search" (t (concat "components.consul.upstream-instance.search-bar." search.status.key ".name")
> default=(array
<PopoverSelect (concat "common.search." search.status.key)
class="type-search-properties" (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" @position="right"
@onchange={{action @onfilter.searchproperty}} @onchange={{action @sort.change}}
@multiple={{true}} @multiple={{false}}
@required={{true}}
as |components|> as |components|>
<BlockSlot @name="selected"> <BlockSlot @name="selected">
<span> <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> </span>
</BlockSlot> </BlockSlot>
<BlockSlot @name="options"> <BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each @searchproperties as |prop|}} <Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{prop}}</Option> <Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
{{/each}}
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </search.Select>
</FreetextFilter> </:sort>
</div> </SearchBar>
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,13 @@
{{#let components.MenuItem as |MenuItem|}} {{#let @components.MenuItem as |MenuItem|}}
<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 ...attributes
@onclick={{action 'click'}} @onclick={{action @onclick this}}
@selected={{selected}} @selected={{this.selected}}
> >
<BlockSlot @name="label"> <BlockSlot @name="label">
{{yield}} {{yield}}

View File

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

View File

@ -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)
---

View File

@ -1,31 +1,68 @@
{{yield}} <div
<form class={{concat 'filter-bar' (if (eq secondary 'sort') ' with-sort')}} ...attributes> class="search-bar"
{{#yield-slot name="primary"}} ...attributes
<fieldset> >
{{yield}} <form
</fieldset> class="filter-bar"
{{else}} >
<FreetextFilter <div class="search">
@onsearch={{action onsearch}} {{yield (hash
@value={{value}} Search=(component "freetext-filter")
@placeholder={{or placeholder 'Search'}} Select=(component "popover-select")
/> ) to="search"}}
{{/yield-slot}} </div>
{{#yield-slot name="secondary"}} <div class="filters">
<fieldset> {{yield (hash
{{yield}} Search=(component "freetext-filter")
</fieldset> Select=(component "popover-select")
{{else}} ) to="filter"}}
{{#if options}} </div>
{{#if (eq secondary 'sort')}} <div class="sort">
{{else}} {{yield (hash
<RadioGroup Search=(component "freetext-filter")
@keyboardAccess={{true}} Select=(component "popover-select")
@value={{selected.key}} ) to="sort"}}
@items={{options}} </div>
@onchange={{action onchange}} </form>
/> {{#if this.isFiltered}}
{{/if}} <div class="search-bar-status">
{{/if}} <dl>
{{/yield-slot}} <dt>{{string-trim
</form> (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>

View File

@ -1,6 +1,34 @@
import Component from '@ember/component'; import Component from '@glimmer/component';
import Slotted from 'block-slots'; import { action } from '@ember/object';
import { diff, filters } from './utils';
export default Component.extend(Slotted, { export default class SearchBar extends Component {
tagName: '', // 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);
});
}
}

View File

@ -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;
}
}
}
}

View File

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

View File

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

View File

@ -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;
})
);
}, []);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,9 @@ export default class Role extends Model {
@attr('number') SyncTime; @attr('number') SyncTime;
@attr('number') CreateIndex; @attr('number') CreateIndex;
@attr('number') ModifyIndex; @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; @attr('number') CreateTime;
// TODO: Figure out whether we need this or not // TODO: Figure out whether we need this or not
@attr() Datacenters; // string[] @attr() Datacenters; // string[]

View File

@ -48,6 +48,16 @@ export default class Service extends Model {
@attr() meta; // {} @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 */ /* Mesh properties involve both the service and the associated proxy */
@computed('ConnectedWithProxy', 'ConnectedWithGateway') @computed('ConnectedWithProxy', 'ConnectedWithGateway')
get MeshEnabled() { get MeshEnabled() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route'; import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class IndexRoute extends Route { export default class IndexRoute extends Route {
@service('data-source/service') data; @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 dc = this.modelFor('dc').dc.Name;
const nspace = this.modelFor('nspace').nspace.substr(1); const nspace = this.modelFor('nspace').nspace.substr(1);
return hash({ const items = this.data.source(uri => uri`/${nspace}/${dc}/nodes`);
items: this.data.source(uri => uri`/${nspace}/${dc}/nodes`), const leader = this.data.source(uri => uri`/${nspace}/${dc}/leader`);
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) { setupController(controller, model) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 */ /* if it is a filter bar and the thing after the filter bar is a p then it also */
/* needs a top margun :S */ /* needs a top margun :S */
%app-view-content .tab-section > *:first-child:not(.filter-bar):not(table), %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 { %app-view-content .tab-section .consul-health-check-list {
margin-top: 1.25em; margin-top: 1.25em;
} }

View File

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

View File

@ -1,83 +1,93 @@
{{page-title 'ACLs'}} {{page-title 'ACLs'}}
{{#let (hash {{#let
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"}}
@sort={{sort}} (hash
@onsort={{action (mut sortBy) value="target.selected"}} value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
@filter={{filters}} (hash
@onfilter={{hash kind=(hash
kind=(action (mut kind) value="target.selectedItems") value=(if kind (split kind ',') undefined)
}} change=(action (mut kind) value="target.selectedItems")
)
)
items
as |sort filters items|}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/> />
{{/if}} </BlockSlot>
</BlockSlot> <BlockSlot @name="header">
<BlockSlot @name="content"> <h1>
<DataCollection ACL Tokens <em>{{format-number items.length}} total</em>
@type="acl" </h1>
@sort={{sort}} <label for="toolbar-toggle"></label>
@filters={{filters}} </BlockSlot>
@search={{search}} <BlockSlot @name="actions">
@items={{items}} <a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
as |collection|> </BlockSlot>
<collection.Collection> <BlockSlot @name="toolbar">
<Consul::Acl::List {{#if (gt items.length 0) }}
@items={{collection.items}} <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}} {{/let}}

View File

@ -3,104 +3,118 @@
{{else}} {{else}}
{{page-title 'Access Controls'}} {{page-title 'Access Controls'}}
{{/if}} {{/if}}
{{#let (hash {{#let
kinds=(if kind (split kind ',') undefined)
dcs=(if dc (split dc ',') undefined) (hash
searchproperties=(if (not-eq searchproperty undefined) value=(or sortBy "Name:asc")
(split searchproperty ',') change=(action (mut sortBy) value="target.selected")
(array 'Name' 'Description')
) )
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}} (hash
<AppView kind=(hash
@authorized={{isAuthorized}} value=(if kind (split kind ',') undefined)
@enabled={{isEnabled}} change=(action (mut kind) value="target.selectedItems")
> )
<BlockSlot @name="notification" as |status type item error|> datacenter=(hash
<Consul::Policy::Notifications value=(if datacenter (split datacenter ',') undefined)
@type={{type}} change=(action (mut datacenter) value="target.selectedItems")
@status={{status}} )
@item={{item}} searchproperty=(hash
@error={{error}} value=(if (not-eq searchproperty undefined)
/> (split searchproperty ',')
</BlockSlot> searchProperties
<BlockSlot @name="header"> )
<h1> change=(action (mut searchproperty) value="target.selectedItems")
Access Controls default=searchProperties
</h1> )
</BlockSlot> )
<BlockSlot @name="nav">
{{#if isAuthorized }} items
{{partial 'dc/acls/nav'}}
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}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="actions"> <BlockSlot @name="content">
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a> <DataCollection
</BlockSlot> @type="policy"
<BlockSlot @name="toolbar"> @sort={{sort.value}}
{{#if (gt items.length 0) }} @filters={{filters}}
<Consul::Policy::SearchBar @search={{search}}
@search={{search}} @items={{items}}
@onsearch={{action (mut search) value="target.value"}} 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>
@sort={{sort}}
@onsort={{action (mut sortBy) value="target.selected"}}
@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}} {{/let}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)}`;
}

View File

@ -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');
});
});

View File

@ -6,7 +6,6 @@ import {
collection, collection,
text, text,
isPresent, isPresent,
triggerable,
} from 'ember-cli-page-object'; } from 'ember-cli-page-object';
import { alias } from 'ember-cli-page-object/macros'; 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 radiogroup from 'consul-ui/components/radio-group/pageobject';
import tabgroup from 'consul-ui/components/tab-nav/pageobject'; import tabgroup from 'consul-ui/components/tab-nav/pageobject';
import authFormFactory from 'consul-ui/components/auth-form/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 emptyStateFactory from 'consul-ui/components/empty-state/pageobject';
import policyFormFactory from 'consul-ui/components/policy-form/pageobject'; import policyFormFactory from 'consul-ui/components/policy-form/pageobject';
@ -82,11 +79,6 @@ const cancelable = createCancelable(clickable, is);
// components // components
const tokenList = tokenListFactory(clickable, attribute, collection, deletable); const tokenList = tokenListFactory(clickable, attribute, collection, deletable);
const authForm = authFormFactory(submitable, clickable, attribute); 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 policyForm = policyFormFactory(submitable, cancelable, radiogroup, text);
const policySelector = policySelectorFactory(clickable, deletable, collection, alias, policyForm); const policySelector = policySelectorFactory(clickable, deletable, collection, alias, policyForm);
const roleForm = roleFormFactory(submitable, cancelable, policySelector); const roleForm = roleFormFactory(submitable, cancelable, policySelector);
@ -160,18 +152,7 @@ export default {
radiogroup radiogroup
) )
), ),
service: create( service: create(service(visitable, clickable, attribute, collection, text, consulIntentionList, tabgroup)),
service(
visitable,
clickable,
attribute,
collection,
text,
consulIntentionList,
catalogToolbar,
tabgroup
)
),
instance: create( instance: create(
instance( instance(
visitable, visitable,
@ -199,7 +180,7 @@ export default {
), ),
kvs: create(kvs(visitable, creatable, consulKvList)), kvs: create(kvs(visitable, creatable, consulKvList)),
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)), 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)), acl: create(acl(visitable, submitable, deletable, cancelable, clickable)),
policies: create(policies(visitable, creatable, consulPolicyList, popoverSelect)), policies: create(policies(visitable, creatable, consulPolicyList, popoverSelect)),
policy: create(policy(visitable, submitable, deletable, cancelable, clickable, tokenList)), policy: create(policy(visitable, submitable, deletable, cancelable, clickable, tokenList)),

View File

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

View File

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

View File

@ -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);
});
});
});

View File

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

View File

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

View File

@ -1 +1,158 @@
common: 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