ui: Add Optgroups and selectedItems to multiple select dropdown and use (#8476)

* ui: Switch selects to use more HTML-like approach for optgroups

* Add KV comparator

* Use new option/optgroup approach for sort/select

* Fix up tests for new order of menu items
This commit is contained in:
John Cowen 2020-08-11 18:02:51 +01:00 committed by GitHub
parent 3962f35905
commit 10fe22c9b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 636 additions and 320 deletions

View File

@ -27,7 +27,12 @@
</a>
{{/let}}
{{else}}
<button role="menuitem" tabindex="-1" type="button" onclick={{action this.onclick}}>
<button
type="button"
role="menuitem"
aria-selected={{if selected 'true'}}
tabindex="-1"
onclick={{action (or this.onclick (noop))}}>
<YieldSlot @name="label">
{{yield}}
</YieldSlot>

View File

@ -1,28 +1,31 @@
<div class="popover-select" ...attributes>
<PopoverMenu as |components menu|>
<PopoverMenu @position={{or position "left"}} class="popover-select" ...attributes as |components menu|>
{{yield}}
{{#let
(component 'popover-select/optgroup' components=components)
(component 'popover-select/option'
select=this components=components
onclick=(queue
(action "click")
(if multiple (noop) menu.toggle)
)
)
as |Optgroup Option|
}}
<BlockSlot @name="trigger">
<span>
{{selected.value}}
</span>
<YieldSlot @name="selected">
{{yield (hash
Optgroup=Optgroup
Option=Option
)}}
</YieldSlot>
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<MenuSeparator>
<BlockSlot @name="label">
{{title}}
</BlockSlot>
</MenuSeparator>
{{#each options as |option|}}
<MenuItem
class={{if (eq selected.key option.key) 'is-active'}}
@onclick={{action (queue (action 'change' option) (if multiple (noop) menu.toggle))}}
>
<BlockSlot @name="label">
{{option.value}}
</BlockSlot>
</MenuItem>
{{/each}}
{{/let}}
<YieldSlot @name="options">
{{yield (hash
Optgroup=Optgroup
Option=Option
)}}
</YieldSlot>
</BlockSlot>
{{/let}}
</PopoverMenu>
</div>

View File

@ -1,12 +1,53 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import Slotted from 'block-slots';
export default Component.extend({
export default Component.extend(Slotted, {
tagName: '',
dom: service('dom'),
multiple: false,
subtractive: true,
onchange: function() {},
addOption: function(option) {
if (typeof this._options === 'undefined') {
this._options = new Set();
}
if (this.subtractive) {
if (!option.selected) {
this._options.add(option.value);
}
} else {
if (option.selected) {
this._options.add(option.value);
}
}
},
removeOption: function(option) {
this._options.delete(option.value);
},
actions: {
click: function(e, value) {
let options = [value];
if (this.multiple) {
if (this._options.has(value)) {
this._options.delete(value);
} else {
this._options.add(value);
}
options = this._options;
}
this.onchange(
this.dom.setEventTargetProperties(e, {
selected: target => value,
selectedItems: target => {
const opts = [...options];
if (opts.length > 0) {
return opts.join(',');
}
},
})
);
},
change: function(option, e) {
this.onchange(this.dom.setEventTargetProperty(e, 'selected', selected => option));
},

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{{#let components.MenuItem as |MenuItem|}}
<MenuItem
class={{if selected 'is-active'}}
@onclick={{action 'click'}}
@selected={{selected}}
>
<BlockSlot @name="label">
{{yield}}
</BlockSlot>
</MenuItem>
{{/let}}

View File

@ -0,0 +1,20 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
export default Component.extend({
tagName: '',
dom: service('dom'),
didInsertElement: function() {
this._super(...arguments);
this.select.addOption(this);
},
willDestroyElement: function() {
this._super(...arguments);
this.select.removeOption(this);
},
actions: {
click: function(e) {
this.onclick(e, this.value);
},
},
});

View File

@ -1,24 +1,23 @@
{{yield}}
<form class={{concat 'filter-bar' (if (eq secondary 'sort') ' with-sort')}} ...attributes>
<FreetextFilter
@onsearch={{action onsearch}}
@value={{value}}
@placeholder={{or placeholder 'Search'}}
/>
{{#yield-slot name="primary"}}
<fieldset>
{{yield}}
</fieldset>
{{else}}
<FreetextFilter
@onsearch={{action onsearch}}
@value={{value}}
@placeholder={{or placeholder 'Search'}}
/>
{{/yield-slot}}
{{#yield-slot name="secondary"}}
{{yield}}
<fieldset>
{{yield}}
</fieldset>
{{else}}
{{#if options}}
{{#if (eq secondary 'sort')}}
<fieldset>
<PopoverSelect
data-popover-select
@selected={{selected}}
@options={{options}}
@onchange={{action onchange}}
@title="Sort By"
/>
</fieldset>
{{else}}
<RadioGroup
@keyboardAccess={{true}}

View File

@ -5,6 +5,7 @@ import { alias } from '@ember/object/computed';
export default Controller.extend({
items: alias('item.Nodes'),
queryParams: {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,

View File

@ -1,9 +1,7 @@
import Controller from '@ember/controller';
export default Controller.extend({
queryParams: {
filterBy: {
as: 'action',
},
sortBy: 'sort',
search: {
as: 'filter',
replace: true,

View File

@ -1,4 +1,5 @@
import service from 'consul-ui/sort/comparators/service';
import kv from 'consul-ui/sort/comparators/kv';
import check from 'consul-ui/sort/comparators/check';
import intention from 'consul-ui/sort/comparators/intention';
import token from 'consul-ui/sort/comparators/token';
@ -11,6 +12,7 @@ export function initialize(container) {
const Sort = container.resolveRegistration('service:sort');
const comparators = {
service: service(),
kv: kv(),
check: check(),
intention: intention(),
token: token(),

View File

@ -69,6 +69,24 @@ export default Service.extend({
},
});
},
setEventTargetProperties: function(e, propObj) {
const target = e.target;
return new Proxy(e, {
get: function(obj, prop, receiver) {
if (prop === 'target') {
return new Proxy(target, {
get: function(obj, prop, receiver) {
if (typeof propObj[prop] !== 'undefined') {
return propObj[prop](e.target);
}
return target[prop];
},
});
}
return Reflect.get(...arguments);
},
});
},
listeners: createListeners,
root: function() {
return this.doc.documentElement;

View File

@ -0,0 +1,3 @@
export default () => key => {
return key;
};

View File

@ -5,7 +5,7 @@
border-bottom: $decor-border-200;
}
%app-view-content h2,
%app-view-content fieldset {
%app-view-content form:not(.filter-bar) fieldset {
border-bottom: $decor-border-200;
}
%app-view-content fieldset h2 {
@ -22,7 +22,7 @@
}
%app-view-title,
%app-view-content h2,
%app-view-content fieldset {
%app-view-content form:not(.filter-bar) fieldset {
border-color: $gray-200;
}
// We know that any sibling navs might have a top border

View File

@ -5,7 +5,6 @@ a.type-create {
// TODO: Once we move action-groups to use aria menu we can get rid of
// some of this and just use not(aria-haspopup)
button[type='reset'],
%app-view-content form button[type='button']:not([aria-haspopup='menu']),
header .actions button[type='button']:not(.copy-btn),
button.type-cancel,
html.template-error div > a {

View File

@ -4,14 +4,7 @@
{{title 'Access Controls'}}
{{/if}}
{{#let (selectable-key-values
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "Name:asc") as |sort|}}
<AppView
@class="policy list"
@loading={{isLoading}}
@ -45,15 +38,41 @@
<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'}}
/>
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@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>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'policy' sort.selected.key) items) as |sorted|}}
{{#let (sort-by (comparator 'policy' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'policy' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulPolicyList

View File

@ -4,16 +4,7 @@
{{title 'Access Controls'}}
{{/if}}
{{#let (selectable-key-values
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
(array "CreateIndex:asc" "Newest to oldest")
(array "CreateIndex:desc" "Oldest to newest")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "Name:asc") as |sort|}}
<AppView
@class="role list"
@loading={{isLoading}}
@ -47,15 +38,47 @@
<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'}}
/>
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@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>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'role' sort.selected.key) items) as |sorted|}}
{{#let (sort-by (comparator 'role' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'role' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulRoleList

View File

@ -4,13 +4,7 @@
{{title 'Access Controls'}}
{{/if}}
{{#let (selectable-key-values
(array "CreateTime:desc" "Newest to oldest")
(array "CreateTime:asc" "Oldest to newest")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "CreateTime:desc") as |sort|}}
<AppView
@class="token list"
@loading={{isLoading}}
@ -41,22 +35,47 @@
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<SearchBar
data-test-intention-filter="true"
@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'}}
/>
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@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>
{{/let}}
</BlockSlot>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy items)}}
<p data-test-notification-update class="notice info"><strong>Update.</strong> 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}}
{{#let (sort-by (comparator 'token' sort.selected.key) items) as |sorted|}}
{{#let (sort-by (comparator 'token' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'token' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulTokenList

View File

@ -6,21 +6,7 @@
</BlockSlot>
<BlockSlot @name="loaded">
{{#let (selectable-key-values
(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: Asc")
(array "Precedence:desc" "Precedence: Desc")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "Action:asc") as |sort|}}
<AppView @class="intention list">
<BlockSlot @name="header">
<h1>
@ -34,18 +20,61 @@
<BlockSlot @name="toolbar">
{{#if (gt api.data.length 0) }}
<SearchBar
data-test-intention-filter="true"
@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'}}
/>
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Action:asc" "Allow to Deny")
(array "Action:desc" "Deny to Allow")
(array "SourceName:asc" "Source: A to Z")
(array "SourceName:desc" "Source: Z to A")
(array "DestinationName:asc" "Destination: A to Z")
(array "DestinationName:desc" "Destination: Z to A")
(array "Precedence:asc" "Precedence: Ascending")
(array "Precedence:desc" "Precedence: Descending")
))
as |selectable|
}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Permission">
<Option @value="Action:asc" @selected={{eq "Action:asc" sort}}>Allow to Deny</Option>
<Option @value="Action:desc" @selected={{eq "Action:desc" sort}}>Deny to Allow</Option>
</Optgroup>
<Optgroup @label="Source">
<Option @value="SourceName:asc" @selected={{eq "SourceName:asc" sort}}>A to Z</Option>
<Option @value="SourceName:desc" @selected={{eq "SourceName:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Destination">
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" sort}}>A to Z</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Precedence">
<Option @value="Precedence:asc" @selected={{eq "Precedence:asc" sort}}>Ascending</Option>
<Option @value="Precedence:desc" @selected={{eq "Precedence:desc" sort}}>Descending</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'intention' sort.selected.key) api.data) as |sorted|}}
{{#let (sort-by (comparator 'intention' sort) api.data) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'intention' sorted}} @terms={{search}}>
<BlockSlot @name="content" as |filtered|>
<ConsulIntentionList

View File

@ -1,79 +1,118 @@
{{title 'Key/Value'}}
<AppView @class="kv list">
<BlockSlot @name="breadcrumbs">
<ol>
{{#if (not-eq parent.Key '/') }}
<li><a href={{href-to 'dc.kv'}}>Key / Values</a></li>
{{/if}}
{{#each (slice 0 -2 (split parent.Key '/')) as |breadcrumb index|}}
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
{{/each}}
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if (eq parent.Key '/')}}
Key / Value
{{else}}
{{take 1 (drop 1 (reverse (split parent.Key '/')))}}
{{/if}}
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<SearchBar
@placeholder="Search by name"
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not-eq parent.Key '/') }}
<a data-test-create href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
{{else}}
<a data-test-create href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<ChangeableSet @dispatcher={{searchable 'kv' items}} @terms={{search}}>
<BlockSlot @name="content" as |filtered|>
<ConsulKvList
@items={{sort-by "isFolder:desc" "Key:asc" filtered}}
@parent={{parent}}
@ondelete={{refresh-route}}
>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No K/V pairs found
{{else}}
Welcome to Key/Value
{{/if}}
</h2>
{{#let (or sortBy "isFolder:asc") as |sort|}}
<AppView @class="kv list">
<BlockSlot @name="breadcrumbs">
<ol>
{{#if (not-eq parent.Key '/') }}
<li><a href={{href-to 'dc.kv'}}>Key / Values</a></li>
{{/if}}
{{#each (slice 0 -2 (split parent.Key '/')) as |breadcrumb index|}}
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
{{/each}}
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if (eq parent.Key '/')}}
Key / Value
{{else}}
{{take 1 (drop 1 (reverse (split parent.Key '/')))}}
{{/if}}
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Key:asc" "A to Z")
(array "Key:desc" "Z to A")
(array "isFolder:desc" "Folders to Keys")
(array "isFolder:asc" "Keys to Folders")
))
as |selectable|
}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No K/V pairs where found matching that search, or you may not have access to view the K/V pairs you are searching for.
{{else}}
You don't have any K/V pairs, or you may not have access to view K/V pairs yet.
{{/if}}
</p>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Key:asc" @selected={{eq "Key:asc" sort}}>A to Z</Option>
<Option @value="Key:desc" @selected={{eq "Key:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Type">
<Option @value="isFolder:desc" @selected={{eq "isFolder:desc" sort}}>Folders to Keys</Option>
<Option @value="isFolder:asc" @selected={{eq "isFolder:asc" sort}}>Keys to Folders</Option>
</Optgroup>
{{/let}}
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/agent/kv" rel="noopener noreferrer" target="_blank">Documentation on K/V</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/kv" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</ConsulKvList>
</BlockSlot>
</ChangeableSet>
</BlockSlot>
</AppView>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not-eq parent.Key '/') }}
<a data-test-create href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
{{else}}
<a data-test-create href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'kv' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'kv' sorted}} @terms={{search}}>
<BlockSlot @name="content" as |filtered|>
<ConsulKvList
@items={{filtered}}
@parent={{parent}}
@ondelete={{refresh-route}}
>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No K/V pairs found
{{else}}
Welcome to Key/Value
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No K/V pairs where found matching that search, or you may not have access to view the K/V pairs you are searching for.
{{else}}
You don't have any K/V pairs, or you may not have access to view K/V pairs yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/agent/kv" rel="noopener noreferrer" target="_blank">Documentation on K/V</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/kv" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</ConsulKvList>
</BlockSlot>
</ChangeableSet>
{{/let}}
</BlockSlot>
</AppView>
{{/let}}

View File

@ -1,11 +1,5 @@
{{title 'Namespaces'}}
{{#let (selectable-key-values
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "Name:asc") as |sort|}}
<EventSource @src={{items}} />
<AppView @class="nspace list" @loading={{isLoading}}>
<BlockSlot @name="notification" as |status type subject|>
@ -21,18 +15,44 @@
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<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'}}
/>
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@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>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'nspace' sort.selected.key) items) as |sorted|}}
{{#let (sort-by (comparator 'nspace' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'nspace' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulNspaceList

View File

@ -1,14 +1,6 @@
{{title 'Services'}}
<EventSource @src={{items}} />
{{#let (selectable-key-values
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
(array "Status:asc" "Unhealthy to Healthy")
(array "Status:desc" "Healthy to Unhealthy")
selected=sortBy
)
as |sort|
}}
{{#let (or sortBy "Name:asc") as |sort|}}
<AppView @class="service list">
<BlockSlot @name="notification" as |status type|>
{{partial 'dc/services/notifications'}}
@ -24,15 +16,47 @@
<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'}}
/>
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
(array "Status:asc" "Unhealthy to Healthy")
(array "Status:desc" "Healthy to Unhealthy")
))
as |selectable|
}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" sort}}>Healthy to Unhealthy</Option>
</Optgroup>
<Optgroup @label="Service Name">
<Option @value="Name:asc" @selected={{eq "Name:asc" sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" sort}}>Z to A</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (sort-by (comparator 'service' sort.selected.key) services) as |sorted|}}
{{#let (sort-by (comparator 'service' sort) services) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'service' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulServiceList @items={{filtered}} @proxies={{proxies}}/>

View File

@ -3,38 +3,68 @@
<ErrorState @error={{api.error}} />
</BlockSlot>
<BlockSlot @name="loaded">
{{#let (filter-by "Action" "deny" api.data) as |denied|}}
{{#let (selectable-key-values
(array "" (concat "All (" api.data.length ")"))
(array "allow" (concat "Allow (" (sub api.data.length denied.length) ")"))
(array "deny" (concat "Deny (" denied.length ")"))
selected=filterBy
)
as |filter|
}}
{{#let (or sortBy "Action:asc") as |sort|}}
<div id="intentions" class="tab-section">
<div role="tabpanel">
<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 api.data.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
@selected={{filter.selected}}
@options={{filter.items}}
@onchange={{action (mut filterBy) value='target.value'}}
/>
<SearchBar
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Action:asc" "Allow to Deny")
(array "Action:desc" "Deny to Allow")
(array "SourceName:asc" "Source: A to Z")
(array "SourceName:desc" "Source: Z to A")
(array "DestinationName:asc" "Destination: A to Z")
(array "DestinationName:desc" "Destination: Z to A")
(array "Precedence:asc" "Precedence: Ascending")
(array "Precedence:desc" "Precedence: Descending")
))
as |selectable|
}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Permission">
<Option @value="Action:asc" @selected={{eq "Action:asc" sort}}>Allow to Deny</Option>
<Option @value="Action:desc" @selected={{eq "Action:desc" sort}}>Deny to Allow</Option>
</Optgroup>
<Optgroup @label="Source">
<Option @value="SourceName:asc" @selected={{eq "SourceName:asc" sort}}>A to Z</Option>
<Option @value="SourceName:desc" @selected={{eq "SourceName:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Destination">
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" sort}}>A to Z</Option>
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Precedence">
<Option @value="Precedence:asc" @selected={{eq "Precedence:asc" sort}}>Ascending</Option>
<Option @value="Precedence:desc" @selected={{eq "Precedence:desc" sort}}>Descending</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
<ChangeableSet
@dispatcher={{
searchable
'intention'
(if (eq filter.selected.key "") api.data (filter-by "Action" filter.selected.key api.data))
}}
@terms={{search}}
>
{{#let (sort-by (comparator 'intention' sort) api.data) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'intention' sorted}} @terms={{search}}>
<BlockSlot @name="content" as |filtered|>
<ConsulIntentionList
@items={{filtered}}
@ -51,9 +81,9 @@
</ConsulIntentionList>
</BlockSlot>
</ChangeableSet>
{{/let}}
</div>
</div>
{{/let}}
{{/let}}
</BlockSlot>
</DataLoader>

View File

@ -1,57 +1,57 @@
@setupApplicationTest
Feature: dc / acls / roles / sorting
Scenario: Sorting Roles
Given 1 datacenter model with the value "dc-1"
And 4 role models from yaml
---
- Name: "system-A"
CreateIndex: 3
- Name: "system-D"
CreateIndex: 2
- Name: "system-C"
CreateIndex: 1
- Name: "system-B"
CreateIndex: 4
---
When I visit the roles page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/acls/roles
Then I see 4 role models
When I click selected on the sort
When I click options.1.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-D"
- "system-C"
- "system-B"
- "system-A"
---
When I click selected on the sort
When I click options.0.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-A"
- "system-B"
- "system-C"
- "system-D"
---
When I click selected on the sort
When I click options.2.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-C"
- "system-D"
- "system-A"
- "system-B"
---
When I click selected on the sort
When I click options.3.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-B"
- "system-A"
- "system-D"
- "system-C"
---
@setupApplicationTest
Feature: dc / acls / roles / sorting
Scenario: Sorting Roles
Given 1 datacenter model with the value "dc-1"
And 4 role models from yaml
---
- Name: "system-A"
CreateIndex: 3
- Name: "system-D"
CreateIndex: 2
- Name: "system-C"
CreateIndex: 1
- Name: "system-B"
CreateIndex: 4
---
When I visit the roles page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/acls/roles
Then I see 4 role models
When I click selected on the sort
When I click options.1.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-D"
- "system-C"
- "system-B"
- "system-A"
---
When I click selected on the sort
When I click options.0.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-A"
- "system-B"
- "system-C"
- "system-D"
---
When I click selected on the sort
When I click options.3.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-C"
- "system-D"
- "system-A"
- "system-B"
---
When I click selected on the sort
When I click options.2.button on the sort
Then I see name on the roles vertically like yaml
---
- "system-B"
- "system-A"
- "system-D"
- "system-C"
---

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / services / intentions: Intentions per service
Feature: dc / services / show / intentions: Intentions per service
Background:
Given 1 datacenter model with the value "dc1"
And 1 node models
@ -26,6 +26,6 @@ Feature: dc / services / intentions: Intentions per service
And I click actions on the intentions
And I click delete on the intentions
And I click confirmDelete on the intentions
Then a DELETE request was made to "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc1"
Then a DELETE request was made to "/v1/connect/intentions/755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0?dc=dc1"
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class

View File

@ -40,7 +40,7 @@ Feature: dc / services / sorting
dc: dc-1
---
When I click selected on the sort
When I click options.1.button on the sort
When I click options.3.button on the sort
Then I see name on the services vertically like yaml
---
- Service-F
@ -51,20 +51,20 @@ Feature: dc / services / sorting
- Service-A
---
When I click selected on the sort
When I click options.0.button on the sort
Then I see name on the services vertically like yaml
---
- Service-A
- Service-B
- Service-C
- Service-D
- Service-E
- Service-F
---
When I click selected on the sort
When I click options.2.button on the sort
Then I see name on the services vertically like yaml
---
- Service-A
- Service-B
- Service-C
- Service-D
- Service-E
- Service-F
---
When I click selected on the sort
When I click options.0.button on the sort
Then I see name on the services vertically like yaml
---
- Service-B
- Service-C
- Service-A
@ -73,7 +73,7 @@ Feature: dc / services / sorting
- Service-E
---
When I click selected on the sort
When I click options.3.button on the sort
When I click options.1.button on the sort
Then I see name on the services vertically like yaml
---
- Service-E