ui: Add 'Search Across' for finer grained searching (#9282)

This commit is contained in:
John Cowen 2020-12-01 15:45:09 +00:00 committed by GitHub
parent c4eff420be
commit cf38309f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 2442 additions and 2084 deletions

View File

@ -0,0 +1,122 @@
<div
class="consul-acl-list"
...attributes
>
<TabularCollection
@items={{@items}}
as |item index|
>
<BlockSlot @name="header">
<th>Name</th>
<th>Type</th>
</BlockSlot>
<BlockSlot @name="row">
<td data-test-acl={{item.Name}}>
<a href={{href-to 'dc.acls.edit' item.ID}}>{{item.Name}}</a>
</td>
<td>
{{#if (eq item.Type 'management')}}
<strong>{{item.Type}}</strong>
{{else}}
<span>{{item.Type}}</span>
{{/if}}
</td>
</BlockSlot>
<BlockSlot @name="actions" as |index change checked|>
<PopoverMenu @expanded={{if (eq checked index) true false}} @onchange={{action change index}} @keyboardAccess={{false}} @submenus={{array "logout" "use" "delete"}}>
<BlockSlot @name="trigger">
More
</BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.edit' item.ID}}>Edit</a>
</li>
{{#if (eq item.ID token.SecretID) }}
<li role="none">
<label for={{concat confirm 'logout'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-logout>Stop using</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm logout
</header>
<p>
Are you sure you want to stop using this ACL token? This will log you out.
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" onclick={{action send 'logout' item}}>Logout</button>
</li>
<li>
<label for={{concat confirm 'logout'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{else}}
<li role="none">
<label for={{concat confirm 'use'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-use>Use</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm use
</header>
<p>
Are you sure you want to use this ACL token?
</p>
</div>
<ul>
<li class="dangerous">
<button
{{on 'click' (fn @onuse item)}}
data-test-confirm-use
tabindex="-1"
type="button"
>
Use
</button>
</li>
<li>
<label for={{concat confirm 'use'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
<li role="none">
<button role="menuitem" tabindex="-1" type="button" data-test-clone {{action @onclone item}}>Duplicate</button>
</li>
{{# if (not-eq item.ID 'anonymous') }}
<li role="none" class="dangerous">
<label for={{concat confirm 'delete'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this token?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action @ondelete item}}>Delete</button>
</li>
<li>
<label for={{concat confirm 'delete'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
</BlockSlot>
</PopoverMenu>
</BlockSlot>
</TabularCollection>
</div>

View File

@ -0,0 +1,59 @@
<form
class="consul-acl-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
/>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="management" @selected={{contains 'management' @filter.kinds}}>Management</Option>
<Option @value="client" @selected={{contains 'service' @filter.kinds}}>Client</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Name:asc" "A to Z")
(array "Name:desc" "Z to A")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort}}>A to Z</Option>
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort}}>Z to A</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>

View File

@ -3,15 +3,8 @@
...attributes
{{did-update this.updateCRDManagement @items}}
>
<DataWriter
@sink={{concat '/' @dc '/' @nspace '/intention/'}}
@type="intention"
@ondelete={{action @ondelete}}
as |writer|>
<BlockSlot @name="content">
{{#let (hash
Table=(component 'consul/intention/list/table' delete=writer.delete items=this.items)
{{yield (hash
Table=(component 'consul/intention/list/table' delete=@delete items=this.items)
CheckNotice=(if this.checkedItem
(component 'consul/intention/list/check' item=this.checkedItem)
''
@ -20,15 +13,5 @@
(component 'consul/intention/notice/custom-resource')
''
)
) as |api|}}
{{#if (gt this.items.length 0)}}
{{yield api to="idle"}}
{{else}}
{{yield api to="empty"}}
{{/if}}
{{/let}}
</BlockSlot>
</DataWriter>
)}}
</div>

View File

@ -5,13 +5,8 @@ import { tracked } from '@glimmer/tracking';
import { sort } from '@ember/object/computed';
export default class ConsulIntentionList extends Component {
@service('filter') filter;
@service('sort') sort;
@service('search') search;
@service('repository/intention') repo;
@sort('searched', 'comparator') sorted;
@tracked isManagedByCRDs;
constructor(owner, args) {
@ -19,25 +14,11 @@ export default class ConsulIntentionList extends Component {
this.updateCRDManagement(args.items);
}
get items() {
return this.sorted;
}
get filtered() {
const predicate = this.filter.predicate('intention');
return this.args.items.filter(predicate(this.args.filters));
}
get searched() {
if (typeof this.args.search === 'undefined') {
return this.filtered;
}
const predicate = this.search.predicate('intention');
return this.filtered.filter(predicate(this.args.search));
}
get comparator() {
return [this.args.sort];
return this.args.items || [];
}
get checkedItem() {
if (this.searched.length === 1) {
return this.searched[0].SourceName === this.args.search ? this.searched[0] : null;
if (this.items.length === 1 && this.args.check) {
return this.items[0].SourceName === this.args.check ? this.items[0] : null;
}
return null;
}

View File

@ -2,27 +2,48 @@
class="consul-intention-search-bar filter-bar"
...attributes
>
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="SourceName" @selected={{contains 'SourceName' @filter.searchproperties}}>Source Name</Option>
<Option @value="DestinationName" @selected={{contains 'DestinationName' @filter.searchproperties}}>Destination Name</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action onfilter.access}}
@onchange={{action @onfilter.access}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Intent
Permission
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-allow" @value="allow" @selected={{contains 'allow' filter.accesses}}>Allow</Option>
<Option class="value-deny" @value="deny" @selected={{contains 'deny' filter.accesses}}>Deny</Option>
<Option class="value-" @value="app-aware" @selected={{contains 'app-aware' filter.accesses}}>App aware</Option>
<Option class="value-allow" @value="allow" @selected={{contains 'allow' @filter.accesses}}>Allow</Option>
<Option class="value-deny" @value="deny" @selected={{contains 'deny' @filter.accesses}}>Deny</Option>
<Option class="value-" @value="app-aware" @selected={{contains 'app-aware' @filter.accesses}}>App aware</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
@ -32,7 +53,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -49,27 +70,27 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>
<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>
<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>

View File

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

View File

@ -1,9 +1,12 @@
.consul-intention-search-bar {
.value-allow button::before {
@extend %with-arrow-right-color-mask, %as-pseudo;
@extend %with-arrow-right-mask, %as-pseudo;
color: $green-500;
}
.value-deny button::before {
@extend %with-deny-color-icon, %as-pseudo;
}
.value- button::before {
@extend %with-layers-mask, %as-pseudo;
}
}

View File

@ -1,58 +1,56 @@
<DataWriter
@sink={{concat '/' dc '/' nspace '/kv/'}}
@type="kv"
@label="key"
@ondelete={{action ondelete}}
as |writer|>
<BlockSlot @name="content">
{{#if (gt items.length 0)}}
<TabularCollection class="consul-kv-list" @items={{items}} as |item index|>
<BlockSlot @name="header">
<th>Name</th>
</BlockSlot>
<BlockSlot @name="row">
<td data-test-kv={{item.Key}} class={{if item.isFolder 'folder' 'file' }}>
<a href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{right-trim (left-trim item.Key parent.Key) '/'}}</a>
</td>
</BlockSlot>
<BlockSlot @name="actions" as |index change checked|>
<PopoverMenu @expanded={{if (eq checked index) true false}} @onchange={{action change index}} @keyboardAccess={{false}}>
<BlockSlot @name="trigger">
More
</BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{if item.isFolder 'View' 'Edit'}}</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this key?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{queue (action change) (action writer.delete item)}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
</BlockSlot>
</PopoverMenu>
</BlockSlot>
</TabularCollection>
{{else}}
{{yield}}
{{/if}}
</BlockSlot>
</DataWriter>
<TabularCollection
class="consul-kv-list"
...attributes
@items={{@items}}
as |item index|>
<BlockSlot @name="header">
<th>Name</th>
</BlockSlot>
<BlockSlot @name="row">
<td data-test-kv={{item.Key}} class={{if item.isFolder 'folder' 'file'}}>
<a href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{right-trim (left-trim item.Key @parent.Key) '/'}}</a>
</td>
</BlockSlot>
<BlockSlot @name="actions" as |index change checked|>
<PopoverMenu @expanded={{if (eq checked index) true false}} @onchange={{action change index}} @keyboardAccess={{false}}>
<BlockSlot @name="trigger">
More
</BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{if item.isFolder 'View' 'Edit'}}</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this key?
</p>
</div>
<ul>
<li class="dangerous">
<button
tabindex="-1"
type="button"
class="type-delete"
onclick={{queue (action change) (action @delete item)}}
>
Delete
</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
</BlockSlot>
</PopoverMenu>
</BlockSlot>
</TabularCollection>

View File

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

View File

@ -0,0 +1,68 @@
<form
class="consul-kv-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
/>
</div>
<div class="filters">
<PopoverSelect
class="type-kind"
@position="left"
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Type
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="folder" @selected={{contains 'folder' @filter.kinds}}>Folder</Option>
<Option @value="key" @selected={{contains 'key' @filter.kinds}}>Key</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Key:asc" "A to Z")
(array "Key:desc" "Z to A")
(array "Kind:asc" "Folders to Keys")
(array "Kind:desc" "Keys to Folders")
))
as |selectable|
}}
{{get selectable @sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Key:asc" @selected={{eq "Key:asc" @sort}}>A to Z</Option>
<Option @value="Key:desc" @selected={{eq "Key:desc" @sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Type">
<Option @value="Kind:asc" @selected={{eq "Kind:asc" @sort}}>Folders to Keys</Option>
<Option @value="Kind:desc" @selected={{eq "Kind:desc" @sort}}>Keys to Folders</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</div>
</form>

View File

@ -1,5 +1,7 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-node-list" as |item index|>
<ListCollection
class="consul-node-list"
@items={{@items}}
as |item index|>
<BlockSlot @name="header">
<dl class={{item.Status}}>
<dt>
@ -24,8 +26,8 @@
</a>
</BlockSlot>
<BlockSlot @name="details">
{{#if (eq item.Address leader.Address)}}
<span class="leader" data-test-leader={{leader.Address}}>Leader</span>
{{#if (eq item.Address @leader.Address)}}
<span class="leader" data-test-leader={{@leader.Address}}>Leader</span>
{{/if}}
{{#if (gt item.Services.length 0)}}
<span>
@ -46,4 +48,3 @@
</dl>
</BlockSlot>
</ListCollection>
{{/if}}

View File

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

View File

@ -1,14 +1,39 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-node-list filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Node" @selected={{contains 'Node' @filter.searchproperties}}>Node Name</Option>
<Option @value="Address" @selected={{contains 'Address' @filter.searchproperties}}>Address</Option>
<Option @value="Meta" @selected={{contains 'Meta' @filter.searchproperties}}>Node Meta</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action onfilter.status}}
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -18,10 +43,10 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' filter.statuses}}>No checks</Option>
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
@ -31,7 +56,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -44,19 +69,19 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>

View File

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

View File

@ -1,59 +1,60 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-nspace-list" as |item|>
<BlockSlot @name="header">
{{#if item.DeletedAt}}
<p>
Deleting {{item.Name}}...
</p>
{{else}}
<a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
<dl>
<dt>Description</dt>
<dd data-test-description>
{{item.Description}}
</dd>
</dl>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<Consul::Token::Ruleset::List @item={{item}} />
{{/if}}
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
{{#if (not item.DeletedAt)}}
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label">
Edit
</BlockSlot>
</Action>
{{#if (not-eq item.Name 'default') }}
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
<BlockSlot @name="confirmation" as |Confirmation|>
<Confirmation class="warning">
<BlockSlot @name="header">
Confirm delete
</BlockSlot>
<BlockSlot @name="body">
<p>
Are you sure you want to delete this namespace?
</p>
</BlockSlot>
<BlockSlot @name="confirm" as |Confirm|>
<Confirm>Delete</Confirm>
</BlockSlot>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
</Actions>
{{/if}}
</BlockSlot>
</ListCollection>
<ListCollection
class="consul-nspace-list"
...attributes
@items={{@items}}
@linkable={{action this.isLinkable}}
as |item|>
<BlockSlot @name="header">
{{#if item.DeletedAt}}
<p>
Deleting {{item.Name}}...
</p>
{{else}}
{{yield to="empty"}}
{{/if}}
<a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
<dl>
<dt>Description</dt>
<dd data-test-description>
{{item.Description}}
</dd>
</dl>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<Consul::Token::Ruleset::List @item={{item}} />
{{/if}}
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
{{#if (not item.DeletedAt)}}
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label">
Edit
</BlockSlot>
</Action>
{{#if (not-eq item.Name 'default') }}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
<BlockSlot @name="confirmation" as |Confirmation|>
<Confirmation class="warning">
<BlockSlot @name="header">
Confirm delete
</BlockSlot>
<BlockSlot @name="body">
<p>
Are you sure you want to delete this namespace?
</p>
</BlockSlot>
<BlockSlot @name="confirm" as |Confirm|>
<Confirm>Delete</Confirm>
</BlockSlot>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
</Actions>
{{/if}}
</BlockSlot>
</ListCollection>

View File

@ -1,10 +1,8 @@
import Component from '@ember/component';
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default Component.extend({
tagName: '',
actions: {
isLinkable: function(item) {
return !item.DeletedAt;
},
},
});
export default class ConsulNspaceList extends Component {
isLinkable(item) {
return !item.DeletedAt;
}
}

View File

@ -1,15 +1,41 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-nspace-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
<Option @value="Role" @selected={{contains 'Role' @filter.searchproperties}}>Role</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -20,15 +46,15 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>

View File

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

View File

@ -1,5 +1,7 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-policy-list" as |item|>
<ListCollection
class="consul-policy-list"
@items={{@items}}
as |item|>
<BlockSlot @name="header">
{{#if (eq (policy/typeof item) 'policy-management')}}
<dl class="policy-management">
@ -40,7 +42,7 @@
</BlockSlot>
</Action>
{{#if (not-eq (policy/typeof item) 'policy-management')}}
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
@ -63,5 +65,4 @@
{{/if}}
</Actions>
</BlockSlot>
</ListCollection>
{{/if}}
</ListCollection>

View File

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

View File

@ -1,14 +1,38 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-policy-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="select-dcs"
@position="left"
@onchange={{action onfilter.dc}}
@onchange={{action @onfilter.dc}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -19,16 +43,16 @@
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#each dcs as |dc|}}
<Option @value={{dc.Name}} @selected={{contains dc.Name filter.dcs}}>{{dc.Name}}</Option>
<Option @value={{@dc.Name}} @selected={{contains dc.Name @filter.dcs}}>{{dc.Name}}</Option>
{{/each}}
{{/let}}
<DataSource @src="/*/*/datacenters" @loading="lazy" @onchange={{action (mut dcs) value="data"}} />
<DataSource @src="/*/*/datacenters" @loading="lazy" @onchange={{action (mut this.dcs) value="data"}} />
</BlockSlot>
</PopoverSelect>
<PopoverSelect
class="select-type"
@position="left"
@onchange={{action onfilter.kind}}
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -38,8 +62,8 @@
</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>
<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>
@ -49,7 +73,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -60,15 +84,15 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>

View File

@ -1,3 +1,3 @@
import Component from '@ember/component';
import Component from '@glimmer/component';
export default Component.extend({});
export default class ConsulPolicySearchBarComponent extends Component {}

View File

@ -1,5 +1,8 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-role-list" as |item|>
<ListCollection
class="consul-role-list"
...attributes
@items={{@items}}
as |item|>
<BlockSlot @name="header">
<a data-test-role={{item.Name}} href={{href-to 'dc.acls.roles.edit' item.ID}}>{{item.Name}}</a>
</BlockSlot>
@ -19,7 +22,7 @@
Edit
</BlockSlot>
</Action>
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
@ -41,5 +44,4 @@
</Action>
</Actions>
</BlockSlot>
</ListCollection>
{{/if}}
</ListCollection>

View File

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

View File

@ -1,15 +1,40 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-role-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="sort">
<PopoverSelect
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -22,19 +47,19 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>

View File

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

View File

@ -1,84 +1,86 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-service-instance-list" as |item index|>
<BlockSlot @name="header">
{{#if (eq routeName "dc.services.show")}}
<a data-test-service-name href={{href-to routeName item.Service}}>
{{item.ID}}
</a>
{{else}}
<a data-test-service-name href={{href-to routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
{{item.Service.ID}}
</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#if checks}}
<Consul::ExternalSource @item={{item}} />
<Consul::InstanceChecks @type="service" @items={{get checks item.Service}} />
<ListCollection
class="consul-service-instance-list"
...attributes
@items={{@items}}
as |item index|>
<BlockSlot @name="header">
{{#if (eq @routeName "dc.services.show")}}
<a data-test-service-name href={{href-to @routeName item.Service}}>
{{item.ID}}
</a>
{{else}}
<Consul::ExternalSource @item={{item.Service}} />
<Consul::InstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
<Consul::InstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
<a data-test-service-name href={{href-to @routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
{{item.Service.ID}}
</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#if @checks}}
<Consul::ExternalSource @item={{item}} />
<Consul::InstanceChecks @type="service" @items={{get @checks item.Service}} />
{{else}}
<Consul::ExternalSource @item={{item.Service}} />
<Consul::InstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
<Consul::InstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
{{/if}}
{{#if item.ProxyInstance}}
<dl class="mesh">
<dt>
<Tooltip>
This service uses a proxy for the Consul service mesh
</Tooltip>
</dt>
<dd data-test-mesh>
in service mesh with proxy
</dd>
</dl>
<dl class="mesh">
<dt>
<Tooltip>
This service uses a proxy for the Consul service mesh
</Tooltip>
</dt>
<dd data-test-mesh>
in service mesh with proxy
</dd>
</dl>
{{/if}}
{{#if (gt item.Node.Node.length 0)}}
<dl class="node">
<dt>
<Tooltip>
Node
</Tooltip>
</dt>
<dd>
<a href={{href-to 'dc.nodes.show' item.Node.Node}}>{{item.Node.Node}}</a>
</dd>
</dl>
<dl class="node">
<dt>
<Tooltip>
Node
</Tooltip>
</dt>
<dd>
<a href={{href-to 'dc.nodes.show' item.Node.Node}}>{{item.Node.Node}}</a>
</dd>
</dl>
{{/if}}
{{#if item.Service.Port}}
<dl class="address" data-test-address>
<dt>
<Tooltip>
IP Address and Port
</Tooltip>
</dt>
<dd>
{{#if (not-eq item.Service.Address '')}}
{{item.Service.Address}}:{{item.Service.Port}}
{{else}}
{{item.Node.Address}}:{{item.Service.Port}}
{{/if}}
</dd>
</dl>
<dl class="address" data-test-address>
<dt>
<Tooltip>
IP Address and Port
</Tooltip>
</dt>
<dd>
{{#if (not-eq item.Service.Address '')}}
{{item.Service.Address}}:{{item.Service.Port}}
{{else}}
{{item.Node.Address}}:{{item.Service.Port}}
{{/if}}
</dd>
</dl>
{{/if}}
{{#if (and checks item.Port)}}
<dl class="port">
<dt>
Port
</dt>
<dd data-test-service-port={{item.Port}}>
<CopyButton
@value={{item.Port}}
@name="Port"
/>
:{{item.Port}}
</dd>
</dl>
{{#if (and @checks item.Port)}}
<dl class="port">
<dt>
Port
</dt>
<dd data-test-service-port={{item.Port}}>
<CopyButton
@value={{item.Port}}
@name="Port"
/>
:{{item.Port}}
</dd>
</dl>
{{/if}}
{{#if checks}}
<TagList @item={{item}} />
<TagList @item={{item}} />
{{else}}
<TagList @item={{item.Service}} />
<TagList @item={{item.Service}} />
{{/if}}
</BlockSlot>
</ListCollection>
{{/if}}
</BlockSlot>
</ListCollection>

View File

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

View File

@ -1,14 +1,43 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-service-instance-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Tags" @selected={{contains 'Tags' @filter.searchproperties}}>Tags</Option>
<Option @value="ID" @selected={{contains 'ID' @filter.searchproperties}}>Service ID</Option>
<Option @value="Address" @selected={{contains 'Address' @filter.searchproperties}}>Address</Option>
<Option @value="Port" @selected={{contains 'Port' @filter.searchproperties}}>Port</Option>
<Option @value="Service.Meta" @selected={{contains 'Service.Meta' @filter.searchproperties}}>Service Meta</Option>
<Option @value="Node.Meta" @selected={{contains 'Node.Meta' @filter.searchproperties}}>Node Meta</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action onfilter.status}}
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -18,18 +47,18 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' filter.statuses}}>No checks</Option>
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
{{#if (gt sources.length 0)}}
{{#if (gt @sources.length 0)}}
<PopoverSelect
class="type-source"
@position="left"
@onchange={{action onfilter.source}}
@onchange={{action @onfilter.source}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -39,8 +68,8 @@
</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 @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.sources}}>{{source}}</Option>
{{/each}}
{{/let}}
</BlockSlot>
@ -52,7 +81,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -65,19 +94,19 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>

View File

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

View File

@ -1,5 +1,10 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-service-list" as |item index|>
<ListCollection
class="consul-service-list"
...attributes
@items={{@items}}
@linkable={{action this.isLinkable}}
as |item index|
>
<BlockSlot @name="header">
<dl class={{item.MeshStatus}}>
<dt>
@ -84,5 +89,4 @@
{{/if}}
<TagList @item={{item}} />
</BlockSlot>
</ListCollection>
{{/if}}
</ListCollection>

View File

@ -1,11 +1,9 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default Component.extend({
tagName: '',
actions: {
isLinkable: function(item) {
return get(item, 'InstanceCount') > 0;
},
},
});
export default class ConsulServiceList extends Component {
@action
isLinkable(item) {
return item.InstanceCount > 0;
}
}

View File

@ -1,14 +1,38 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-service-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Tags" @selected={{contains 'Tags' @filter.searchproperties}}>Tags</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
class="type-status"
@position="left"
@onchange={{action onfilter.status}}
@onchange={{action @onfilter.status}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -18,16 +42,16 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-passing" @value="passing" @selected={{contains 'passing' filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' filter.statuses}}>No checks</Option>
<Option class="value-passing" @value="passing" @selected={{contains 'passing' @filter.statuses}}>Passing</Option>
<Option class="value-warning" @value="warning" @selected={{contains 'warning' @filter.statuses}}>Warning</Option>
<Option class="value-critical" @value="critical" @selected={{contains 'critical' @filter.statuses}}>Failing</Option>
<Option class="value-empty" @value="empty" @selected={{contains 'empty' @filter.statuses}}>No checks</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
<PopoverSelect
@position="left"
@onchange={{action onfilter.kind}}
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -37,24 +61,24 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="service" @selected={{contains 'service' filter.kinds}}>Service</Option>
<Option @value="service" @selected={{contains 'service' @filter.kinds}}>Service</Option>
<Optgroup @label="Gateway">
<Option @value="ingress-gateway" @selected={{contains 'ingress-gateway' filter.kinds}}>Ingress Gateway</Option>
<Option @value="terminating-gateway" @selected={{contains 'terminating-gateway' filter.kinds}}>Terminating Gateway</Option>
<Option @value="mesh-gateway" @selected={{contains 'mesh-gateway' filter.kinds}}>Mesh Gateway</Option>
<Option @value="ingress-gateway" @selected={{contains 'ingress-gateway' @filter.kinds}}>Ingress Gateway</Option>
<Option @value="terminating-gateway" @selected={{contains 'terminating-gateway' @filter.kinds}}>Terminating Gateway</Option>
<Option @value="mesh-gateway" @selected={{contains 'mesh-gateway' @filter.kinds}}>Mesh Gateway</Option>
</Optgroup>
<Optgroup @label="Mesh">
<Option @value="in-mesh" @selected={{contains 'in-mesh' filter.kinds}}>In service mesh</Option>
<Option @value="not-in-mesh" @selected={{contains 'not-in-mesh' filter.kinds}}>Not in service mesh</Option>
<Option @value="in-mesh" @selected={{contains 'in-mesh' @filter.kinds}}>In service mesh</Option>
<Option @value="not-in-mesh" @selected={{contains 'not-in-mesh' @filter.kinds}}>Not in service mesh</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
{{#if (gt sources.length 0)}}
{{#if (gt @sources.length 0)}}
<PopoverSelect
class="type-source"
@position="left"
@onchange={{action onfilter.source}}
@onchange={{action @onfilter.source}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -64,8 +88,8 @@
</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 @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.sources}}>{{source}}</Option>
{{/each}}
{{/let}}
</BlockSlot>
@ -77,7 +101,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -90,19 +114,19 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>

View File

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

View File

@ -1,7 +1,9 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-token-list" as |item|>
<ListCollection
class="consul-token-list"
@items={{@items}}
as |item|>
<BlockSlot @name="header">
{{#if (eq item.AccessorID token.AccessorID)}}
{{#if (eq item.AccessorID @token.AccessorID)}}
<dl rel="me">
<dd>
<Tooltip @position="top-start">
@ -35,14 +37,14 @@
</BlockSlot>
</Action>
{{#if (not (token/is-legacy item))}}
<Action data-test-clone-action @onclick={{action onclone item}}>
<Action data-test-clone-action @onclick={{action @onclone item}}>
<BlockSlot @name="label">
Duplicate
</BlockSlot>
</Action>
{{/if}}
{{#if (eq item.AccessorID token.AccessorID)}}
<Action data-test-logout-action class="dangerous" @onclick={{action onlogout item}}>
<Action data-test-logout-action class="dangerous" @onclick={{action @onlogout item}}>
<BlockSlot @name="label">
Logout
</BlockSlot>
@ -63,7 +65,7 @@
</BlockSlot>
</Action>
{{else}}
<Action data-test-use-action @onclick={{action onuse item}}>
<Action data-test-use-action @onclick={{action @onuse item}}>
<BlockSlot @name="label">
Use
</BlockSlot>
@ -84,8 +86,8 @@
</BlockSlot>
</Action>
{{/if}}
{{#if (not (or (token/is-anonymous item) (eq item.AccessorID token.AccessorID)))}}
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
{{#if (not (or (token/is-anonymous item) (eq item.AccessorID @token.AccessorID)))}}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
@ -108,5 +110,4 @@
{{/if}}
</Actions>
</BlockSlot>
</ListCollection>
{{/if}}
</ListCollection>

View File

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

View File

@ -1,13 +1,39 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-token-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="AccessorID" @selected={{contains 'AccessorID' @filter.searchproperties}}>AccessorID</Option>
<Option @value="Description" @selected={{contains 'Description' @filter.searchproperties}}>Description</Option>
<Option @value="Policy" @selected={{contains 'Policy' @filter.searchproperties}}>Policy</Option>
<Option @value="Role" @selected={{contains 'Role' @filter.searchproperties}}>Role</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action onfilter.kind}}
@onchange={{action @onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -17,9 +43,9 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="global-management" @selected={{contains 'global-management' filter.kinds}}>Global Management</Option>
<Option @value="global" @selected={{contains 'global' filter.kinds}}>Global</Option>
<Option @value="local" @selected={{contains 'local' filter.kinds}}>Local</Option>
<Option @value="global-management" @selected={{contains 'global-management' @filter.kinds}}>Global Management</Option>
<Option @value="global" @selected={{contains 'global' @filter.kinds}}>Global Scope</Option>
<Option @value="local" @selected={{contains 'local' @filter.kinds}}>Local Scope</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
@ -29,7 +55,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -40,15 +66,15 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>

View File

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

View File

@ -1,4 +1,9 @@
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-upstream-list" as |item index|>
<ListCollection
class="consul-upstream-list"
...attributes
@items={{@items}}
@linkable={{action this.isLinkable}}
as |item index|>
<BlockSlot @name="header">
{{#if (gt item.InstanceCount 0)}}
<dl class={{item.MeshStatus}}>
@ -19,8 +24,9 @@
</Tooltip>
</dd>
</dl>
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (not-eq item.Namespace nspace))}}
<a data-test-service-name href={{href-to 'nspace.dc.services.show' (concat '~' item.Namespace) dc item.Name }}>
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (not-eq item.Namespace @nspace))}}
{{item.Namespace}}
<a data-test-service-name href={{href-to 'nspace.dc.services.show' (concat '~' item.Namespace) @dc item.Name }}>
{{item.Name}}
</a>
{{else}}
@ -35,11 +41,11 @@
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (not-eq item.Namespace nspace))}}
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (not-eq item.Namespace @nspace))}}
<dl class="nspace">
<dt>
<Tooltip>
Namespace
Namespace
</Tooltip>
</dt>
<dd>
@ -47,18 +53,19 @@
</dd>
</dl>
{{/if}}
{{#if (gt item.GatewayConfig.Addresses.length 0)}}
{{#each item.GatewayConfig.Addresses as |address|}}
<dl>
<dt>
<span>Address</span>
</dt>
<dd>
<CopyButton
@value={{address}}
@name="Address"
/>
</dt>
<dd>{{address}}</dd>
{{address}}
</dd>
</dl>
{{/each}}
{{/if}}
</BlockSlot>
</ListCollection>

View File

@ -1,11 +1,9 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default Component.extend({
tagName: '',
actions: {
isLinkable: function(item) {
return get(item, 'InstanceCount') > 0;
},
},
});
export default class ConsulServiceList extends Component {
@action
isLinkable(item) {
return item.InstanceCount > 0;
}
}

View File

@ -1,13 +1,37 @@
<form class="filter-bar">
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@placeholder="Search"
/>
<form
class="consul-upstream-search-bar filter-bar"
...attributes
>
<div class="search">
<FreetextFilter
@onsearch={{action @onsearch}}
@value={{@search}}
@placeholder="Search"
>
<PopoverSelect
class="type-search-properties"
@position="right"
@onchange={{action @onfilter.searchproperty}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{contains 'Name' @filter.searchproperties}}>Name</Option>
<Option @value="Tags" @selected={{contains 'Tags' @filter.searchproperties}}>Tags</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>
</div>
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action onfilter.instance}}
@onchange={{action @onfilter.instance}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -17,8 +41,8 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="registered" @selected={{contains 'registered' filter.instances}}>Registered</Option>
<Option @value="not-registered" @selected={{contains 'not-registered' filter.instances}}>Not Registered</Option>
<Option @value="registered" @selected={{contains 'registered' @filter.instances}}>Registered</Option>
<Option @value="not-registered" @selected={{contains 'not-registered' @filter.instances}}>Not Registered</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
@ -28,7 +52,7 @@
class="type-sort"
data-test-sort-control
@position="right"
@onchange={{action onsort}}
@onchange={{action @onsort}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
@ -41,19 +65,19 @@
))
as |selectable|
}}
{{get selectable sort}}
{{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>
<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>
<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>

View File

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

View File

@ -0,0 +1,5 @@
{{yield (hash
items=this.items
Collection=(if (gt this.items.length 0) (component 'anonymous') '')
Empty=(if (eq this.items.length 0) (component 'anonymous') '')
)}}

View File

@ -0,0 +1,56 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { sort } from '@ember/object/computed';
import { defineProperty } from '@ember/object';
export default class DataCollectionComponent extends Component {
@service('filter') filter;
@service('sort') sort;
@service('search') search;
get type() {
return this.args.type;
}
get items() {
// the ember sort computed accepts either:
// 1. The name of a property (as a string) returning an array properties to sort by
// 2. A function to use for sorting
let comparator = 'comparator';
if (typeof this.comparator === 'function') {
comparator = this.comparator;
}
defineProperty(this, 'sorted', sort('searched', comparator));
return this.sorted;
}
get searched() {
if (typeof this.args.search === 'undefined') {
return this.filtered;
}
const predicate = this.search.predicate(this.type);
const options = {};
if (typeof this.args.filters.searchproperties !== 'undefined') {
options.properties = this.args.filters.searchproperties;
}
return this.filtered.filter(predicate(this.args.search, options));
}
get filtered() {
if (typeof this.args.filters === 'undefined') {
return this.args.items;
}
const predicate = this.filter.predicate(this.type);
if (typeof predicate === 'undefined') {
return this.args.items;
}
return this.args.items.filter(predicate(this.args.filters));
}
get comparator() {
if (typeof this.args.sort === 'undefined') {
return [];
}
return this.sort.comparator(this.type)(this.args.sort);
}
}

View File

@ -0,0 +1,5 @@
@import './skin';
@import './layout';
%filter-bar-reversed {
color: inherit;
}

View File

@ -0,0 +1,45 @@
.filter-bar {
&,
> div {
display: flex;
}
& {
padding: 4px 8px;
}
.sort {
margin-left: auto;
}
.popover-select {
position: relative;
z-index: 3;
}
.popover-menu > [type='checkbox'] + label button {
padding-left: 1.5rem !important;
padding-right: 1.5rem !important;
}
.popover-menu [role='menuitem'] {
justify-content: normal !important;
}
}
@media #{$--lt-horizontal-filters} {
.filter-bar {
&,
& > div {
flex-wrap: wrap;
}
.search {
position: relative;
z-index: 4;
width: 100%;
margin-bottom: .3rem;
}
}
}
@media #{$--lt-horizontal-selects} {
.filter-bar {
.filters, .sort {
display: none;
}
}
}

View File

@ -0,0 +1,13 @@
.filter-bar {
& {
background-color: $gray-010;
border-bottom: $decor-border-100;
border-color: $gray-200;
}
.filters, .sort {
.popover-menu > [type='checkbox']:checked + label button {
color: $blue-500;
background-color: $gray-100;
}
}
}

View File

@ -0,0 +1,45 @@
import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
<Meta title="Components/FreetextFilter" />
# FreetextFilter
<Canvas>
<Story
name="Basic"
argTypes={{
}}
>{(args) => ({
template: hbs`<FreetextFilter />`,
context: args
})}
</Story>
<Story
name="Search Across"
argTypes={{
}}
>{(args) => ({
template: hbs`<FreetextFilter>
<PopoverSelect
@position="right"
@multiple={{true}}
as |popover|>
<BlockSlot @name="selected">
<span>
Search across
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let popover.Optgroup popover.Option as |Optgroup Option|}}
<Option @value="Name" @selected={{true}}>Name</Option>
<Option @value="Tags">Tags</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>
</FreetextFilter>`,
context: args
})}
</Story>
</Canvas>

View File

@ -1,6 +1,19 @@
<fieldset class="freetext-filter">
<label class="type-search">
<span>Search</span>
<input type="search" onsearch={{action "change"}} oninput={{action "change"}} onkeydown={{action "keydown"}} name="s" value={{value}} placeholder={{placeholder}} autofocus="autofocus" />
</label>
<fieldset
class="freetext-filter"
...attributes
>
<label class="type-search">
<span class="freetext-filter_label">Search</span>
<input
class="freetext-filter_input"
type="search"
onsearch={{action this.change}}
oninput={{action this.change}}
onkeydown={{action this.keydown}}
name="s" value={{@value}}
placeholder={{this.placeholder}}
autofocus="autofocus"
/>
</label>
{{yield}}
</fieldset>

View File

@ -1,19 +1,25 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';
import { action } from '@ember/object';
const ENTER = 13;
export default Component.extend({
dom: service('dom'),
tagName: '',
actions: {
change: function(e) {
this.onsearch(
this.dom.setEventTargetProperty(e, 'value', value => (value === '' ? undefined : value))
);
},
keydown: function(e) {
if (e.keyCode === ENTER) {
e.preventDefault();
}
},
},
});
export default class FreetextFilter extends Component {
get placeholder() {
return this.args.placeholder || 'Search';
}
get onsearch() {
return this.args.onsearch || (() => {});
}
@action
change(e) {
this.onsearch(e);
}
@action
keydown(e) {
if (e.keyCode === ENTER) {
e.preventDefault();
}
}
}

View File

@ -0,0 +1,28 @@
.freetext-filter {
--height: 2.2rem;
& {
display: flex;
position: relative;
height: var(--height);
}
&_input,
& > label {
flex-grow: 1;
}
&_input,
&_label {
height: 100%;
}
&_input {
padding: 8px 10px;
padding-left: var(--height);
min-width: 12.7rem;
width: 100%;
}
&_label {
visibility: hidden;
position: absolute;
z-index: 1;
width: var(--height);
}
}

View File

@ -0,0 +1,49 @@
.freetext-filter {
& {
border: $decor-border-100;
border-radius: $decor-radius-100;
background-color: $white;
border-color: $gray-200;
color: $gray-400;
}
&:hover,
&:hover * {
border-color: $gray-400;
}
& *,
&_input::placeholder {
cursor: inherit;
color: inherit;
border-color: inherit;
}
&_input {
-webkit-appearance: none;
border: none;
}
&_label::after {
@extend %as-pseudo;
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin-left: -8px;
margin-top: -8px;
}
&_label::after {
@extend %with-search-mask;
}
.popover-menu {
background-color: $gray-050;
color: $gray-800;
}
.popover-menu {
border-left: 1px solid;
border-color: inherit;
}
.popover-menu > [type='checkbox']:checked + label button {
background-color: $gray-200;
}
}

View File

@ -1,5 +1,12 @@
<div class="more-popover-menu">
<PopoverMenu @expanded={{expanded}} @onchange={{action onchange}} @keyboardAccess={{false}} as |components api|>
<div
class="more-popover-menu"
...attributes
>
<PopoverMenu
@expanded={{expanded}}
@onchange={{action onchange}}
@keyboardAccess={{false}}
as |components api|>
<BlockSlot @name="trigger">
More
</BlockSlot>

View File

@ -1,6 +1,9 @@
{{yield}}
<div class="popover-menu" ...attributes>
<AriaMenu @keyboardAccess={{keyboardAccess}} as |change keypress keypressClick aria|>
{{yield}}
<div
class="popover-menu"
...attributes
>
<AriaMenu @keyboardAccess={{keyboardAccess}} as |change keypress keypressClick aria|>
{{#let (hash
MenuItem=(component
@ -18,46 +21,46 @@
)
)
as |components|}}
{{#let (hash
toggle=this.toggle.click
)
as |api|}}
{{#let (hash
toggle=this.toggle.click
)
as |api|}}
<ToggleButton
@checked={{if keyboardAccess aria.expanded expanded}}
@onchange={{queue change (action "change")}}
as |toggle|>
<Ref @target={{this}} @name="toggle" @value={{toggle}} />
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{this.toggle.click}} id={{aria.labelledBy}} aria-controls={{aria.controls}}>
<YieldSlot @name="trigger">
{{yield components api}}
</YieldSlot>
</button>
</ToggleButton>
<ToggleButton
@checked={{if keyboardAccess aria.expanded expanded}}
@onchange={{queue change (action "change")}}
as |toggle|>
<Ref @target={{this}} @name="toggle" @value={{toggle}} />
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{this.toggle.click}} id={{aria.labelledBy}} aria-controls={{aria.controls}}>
<YieldSlot @name="trigger">
{{yield components api}}
</YieldSlot>
</button>
</ToggleButton>
<MenuPanel @position={{position}} id={{aria.controls}} aria-labelledby={{aria.labelledBy}} aria-expanded={{aria.expanded}} as |menu|>
<Ref @target={{this}} @name="menu" @value={{menu}} />
<BlockSlot @name="controls">
<input type="checkbox" id={{concat 'popover-menu-' guid '-'}} />
{{#each submenus as |sub|}}
<input type="checkbox" id={{concat 'popover-menu-' guid '-' sub}} onchange={{menu.change}} />
{{/each}}
<MenuPanel @position={{position}} id={{aria.controls}} aria-labelledby={{aria.labelledBy}} aria-expanded={{aria.expanded}} as |menu|>
<Ref @target={{this}} @name="menu" @value={{menu}} />
<BlockSlot @name="controls">
<input type="checkbox" id={{concat 'popover-menu-' guid '-'}} />
{{#each submenus as |sub|}}
<input type="checkbox" id={{concat 'popover-menu-' guid '-' sub}} onchange={{menu.change}} />
{{/each}}
</BlockSlot>
{{#if hasHeader}}
<BlockSlot @name="header">
{{yield components api}}
{{#yield-slot name="header"}}{{else}}{{/yield-slot}}
</BlockSlot>
{{#if hasHeader}}
<BlockSlot @name="header">
{{yield components api}}
{{#yield-slot name="header"}}{{else}}{{/yield-slot}}
</BlockSlot>
{{/if}}
<BlockSlot @name="menu">
<YieldSlot @name="menu" @params={{block-params (concat "popover-menu-" guid "-") send keypressClick this.toggle.click}}>
{{yield components api}}
</YieldSlot>
</BlockSlot>
</MenuPanel>
{{/if}}
<BlockSlot @name="menu">
<YieldSlot @name="menu" @params={{block-params (concat "popover-menu-" guid "-") send keypressClick this.toggle.click}}>
{{yield components api}}
</YieldSlot>
</BlockSlot>
</MenuPanel>
{{/let}}
{{/let}}
{{/let}}
</AriaMenu>
</div>
</AriaMenu>
</div>

View File

@ -7,7 +7,7 @@
<div role="menu">
<YieldSlot @name="confirmation" @params={{
block-params (component 'confirmation-alert'
onclick=(queue (action onclick) (action menu.clickTrigger))
onclick=(queue (action menu.clickTrigger) (action onclick))
name=(concat menu.confirm guid)
)
}}>{{yield}}</YieldSlot>

View File

@ -1,4 +1,9 @@
<PopoverMenu @position={{or position "left"}} class="popover-select" ...attributes as |components menu|>
<PopoverMenu
class="popover-select"
...attributes
@position={{or position "left"}}
as |components menu|
>
{{yield}}
{{#let
(component 'popover-select/optgroup' components=components)
@ -28,4 +33,4 @@
</YieldSlot>
</BlockSlot>
{{/let}}
</PopoverMenu>
</PopoverMenu>

View File

@ -40,10 +40,7 @@ export default Component.extend(Slotted, {
this.dom.setEventTargetProperties(e, {
selected: target => value,
selectedItems: target => {
const opts = [...options];
if (opts.length > 0) {
return opts.join(',');
}
return [...options].join(',');
},
})
);

View File

@ -1,12 +1,15 @@
<input
...attributes
{{ref this "input"}}
{{did-insert (set this 'input')}}
type="checkbox"
checked={{if checked 'checked' undefined}}
id={{concat 'toggle-button-' guid}}
onchange={{action 'change'}}
/>
<label {{ref this "label"}} for={{concat 'toggle-button-' guid}}>
<label
{{did-insert (set this 'label')}}
for={{concat 'toggle-button-' guid}}
>
{{yield (hash
click=(action 'click')
)}}

View File

@ -1,18 +0,0 @@
import { action } from '@ember/object';
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
filterBy: {
as: 'type',
},
search: {
as: 'filter',
replace: true,
},
};
@action
sendClone(item) {
this.send('clone', item);
}
}

View File

@ -1,13 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
dc: 'dc',
kind: 'kind',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,10 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,12 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
kind: 'kind',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,12 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
access: 'access',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,10 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,12 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
status: 'status',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,10 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
search: {
as: 'filter',
},
};
}

View File

@ -2,17 +2,6 @@ import { computed } from '@ember/object';
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
status: 'status',
source: 'source',
kind: 'kind',
search: {
as: 'filter',
replace: true,
},
};
@computed('items.[]')
get services() {
return this.items.filter(function(item) {

View File

@ -2,16 +2,6 @@ import { computed } from '@ember/object';
import Controller from '@ember/controller';
export default class InstancesController extends Controller {
queryParams = {
sortBy: 'sort',
status: 'status',
source: 'source',
search: {
as: 'filter',
replace: true,
},
};
@computed('items')
get externalSources() {
const sources = this.items.reduce(function(prev, item) {

View File

@ -1,11 +0,0 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
access: 'access',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,12 +0,0 @@
import Controller from '@ember/controller';
export default class ServicesController extends Controller {
queryParams = {
sortBy: 'sort',
instance: 'instance',
search: {
as: 'filter',
replace: true,
},
};
}

View File

@ -1,12 +0,0 @@
import Controller from '@ember/controller';
export default class UpstreamsController extends Controller {
queryParams = {
sortBy: 'sort',
instance: 'instance',
search: {
as: 'filter',
replace: true,
},
};
}

View File

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

View File

@ -1,9 +1,7 @@
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
accesses: {
allow: (item, value) => item.Action === value,
deny: (item, value) => item.Action === value,
'app-aware': (item, value) => typeof item.Action === 'undefined',
},
});
};

View File

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

View File

@ -1,10 +1,7 @@
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
statuses: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,
},
});
};

View File

@ -1,7 +1,4 @@
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
kinds: {
'global-management': (item, value) => item.isGlobalManagement,
standard: (item, value) => !item.isGlobalManagement,
@ -12,4 +9,4 @@ export default andOr({
setHelpers.intersectionSize(values, new Set(item.Datacenters)) > 0
);
},
});
};

View File

@ -1,7 +1,6 @@
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
statuses: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
@ -10,4 +9,4 @@ export default andOr({
sources: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
});
};

View File

@ -1,7 +1,6 @@
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
kinds: {
'ingress-gateway': (item, value) => item.Kind === value,
'terminating-gateway': (item, value) => item.Kind === value,
@ -22,4 +21,4 @@ export default andOr({
sources: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
});
};

View File

@ -1,10 +1,7 @@
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
export default {
kinds: {
'global-management': (item, value) => item.isGlobalManagement,
global: (item, value) => !item.Local,
local: (item, value) => item.Local,
},
});
};

View File

@ -25,6 +25,11 @@ export default class Kv extends Model {
@attr('number') ModifyIndex;
@attr('string') Session;
@computed('isFolder')
get Kind() {
return this.isFolder ? 'folder' : 'key';
}
@computed('Key')
get isFolder() {
return isFolder(this.Key || '');

View File

@ -20,6 +20,8 @@ export default class ServiceInstance extends Model {
@attr() meta;
@or('Service.ID', 'Service.Service') Name;
@or('Service.Address', 'Node.Service') Address;
@alias('Service.Tags') Tags;
@alias('Service.Meta') Meta;
@alias('Service.Namespace') Namespace;

View File

@ -6,13 +6,13 @@ import { get } from '@ember/object';
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
export default class IndexRoute extends Route.extend(WithAclActions) {
@service('repository/acl')
repo;
@service('repository/acl') repo;
@service('settings')
settings;
@service('settings') settings;
queryParams = {
sortBy: 'sort',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -11,6 +11,10 @@ export default class IndexRoute extends Route.extend(WithPolicyActions) {
sortBy: 'sort',
dc: 'dc',
kind: 'kind',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Description']],
},
search: {
as: 'filter',
replace: true,

View File

@ -9,6 +9,10 @@ export default class IndexRoute extends Route.extend(WithRoleActions) {
queryParams = {
sortBy: 'sort',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Description', 'Policy']],
},
search: {
as: 'filter',
replace: true,

View File

@ -11,6 +11,10 @@ export default class IndexRoute extends Route.extend(WithTokenActions) {
queryParams = {
sortBy: 'sort',
kind: 'kind',
searchproperty: {
as: 'searchproperty',
empty: [['AccessorID', 'Description', 'Role', 'Policy']],
},
search: {
as: 'filter',
replace: true,

View File

@ -4,6 +4,10 @@ export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
access: 'access',
searchproperty: {
as: 'searchproperty',
empty: [['SourceName', 'DestinationName']],
},
search: {
as: 'filter',
replace: true,

View File

@ -9,6 +9,7 @@ export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -8,6 +8,10 @@ export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
status: 'status',
searchproperty: {
as: 'searchproperty',
empty: [['Node', 'Address', 'Meta']],
},
search: {
as: 'filter',
replace: true,

View File

@ -9,6 +9,10 @@ export default class IndexRoute extends Route.extend(WithNspaceActions) {
queryParams = {
sortBy: 'sort',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Description', 'Role', 'Policy']],
},
search: {
as: 'filter',
replace: true,

View File

@ -10,6 +10,10 @@ export default class IndexRoute extends Route {
status: 'status',
source: 'source',
kind: 'kind',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Tags']],
},
search: {
as: 'filter',
replace: true,
@ -17,26 +21,11 @@ export default class IndexRoute extends Route {
};
model(params) {
let terms = params.s || '';
// we check for the old style `status` variable here
// and convert it to the new style filter=status:critical
let status = params.status;
if (status) {
status = `status:${status}`;
if (terms.indexOf(status) === -1) {
terms = terms
.split('\n')
.concat(status)
.join('\n')
.trim();
}
}
const nspace = this.modelFor('nspace').nspace.substr(1);
const dc = this.modelFor('dc').dc.Name;
return hash({
nspace: nspace,
dc: dc,
terms: terms !== '' ? terms.split('\n') : [],
items: this.data.source(uri => uri`/${nspace}/${dc}/services`),
});
}

View File

@ -5,6 +5,10 @@ export default class InstancesRoute extends Route {
sortBy: 'sort',
status: 'status',
source: 'source',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Tags', 'ID', 'Address', 'Port', 'Service.Meta', 'Node.Meta']],
},
search: {
as: 'filter',
replace: true,

View File

@ -4,6 +4,10 @@ export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
access: 'access',
searchproperty: {
as: 'searchproperty',
empty: [['SourceName', 'DestinationName']],
},
search: {
as: 'filter',
replace: true,

View File

@ -3,8 +3,20 @@ import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class ServicesRoute extends Route {
@service('data-source/service')
data;
@service('data-source/service') data;
queryParams = {
sortBy: 'sort',
instance: 'instance',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Tags']],
},
search: {
as: 'filter',
replace: true,
},
};
model() {
const dc = this.modelFor('dc').dc.Name;

View File

@ -1,5 +1,17 @@
import Route from './services';
export default class UpstreamsRoute extends Route {
queryParams = {
sortBy: 'sort',
instance: 'instance',
searchproperty: {
as: 'searchproperty',
empty: [['Name', 'Tags']],
},
search: {
as: 'filter',
replace: true,
},
};
templateName = 'dc/services/show/upstreams';
}

View File

@ -1,5 +1,5 @@
import Route from '@ember/routing/route';
import { setProperties } from '@ember/object';
import { get, setProperties } from '@ember/object';
// paramsFor
import { routes } from 'consul-ui/router';
@ -7,6 +7,37 @@ import wildcard from 'consul-ui/utils/routing/wildcard';
const isWildcard = wildcard(routes);
export default class BaseRoute extends Route {
/**
* By default any empty string query parameters should remove the query
* parameter from the URL. This is the most common behavior if you don't
* require this behavior overwrite this method in the specific Route for the
* specific queryParam key.
* If the behaviour should be different add an empty: [] parameter to the
* queryParameter configuration to configure what is deemed 'empty'
*/
serializeQueryParam(value, key, type) {
if(typeof value !== 'undefined') {
const empty = get(this, `queryParams.${key}.empty`);
if(typeof empty === 'undefined') {
// by default any queryParams when an empty string mean undefined,
// therefore remove the queryParam from the URL
if(value === '') {
value = undefined;
}
} else {
const possible = empty[0];
let actual = value;
if(Array.isArray(actual)) {
actual = actual.split(',');
}
const diff = possible.filter(item => !actual.includes(item))
if(diff.length === 0) {
value = undefined;
}
}
}
return value;
}
/**
* Set the routeName for the controller so that it is available in the template
* for the route/controller.. This is mainly used to give a route name to the

View File

@ -1,14 +0,0 @@
import { get } from '@ember/object';
export default function(filterable) {
return filterable(function(item, { s = '' }) {
const sLower = s.toLowerCase();
return (
get(item, 'Name')
.toLowerCase()
.indexOf(sLower) !== -1 ||
get(item, 'ID')
.toLowerCase()
.indexOf(sLower) !== -1
);
});
}

View File

@ -1,10 +0,0 @@
import { get } from '@ember/object';
import rightTrim from 'consul-ui/utils/right-trim';
export default function(filterable) {
return filterable(function(item, { s = '' }) {
const key = rightTrim(get(item, 'Key'), '/')
.split('/')
.pop();
return key.toLowerCase().indexOf(s.toLowerCase()) !== -1;
});
}

View File

@ -1,14 +0,0 @@
import { get } from '@ember/object';
export default function(filterable) {
return filterable(function(item, { s = '' }) {
const sLower = s.toLowerCase();
return (
get(item, 'Node')
.toLowerCase()
.indexOf(sLower) !== -1 ||
get(item, 'Address')
.toLowerCase()
.indexOf(sLower) !== -1
);
});
}

View File

@ -1,20 +0,0 @@
import { get } from '@ember/object';
export default function(filterable) {
return filterable(function(item, { s = '' }) {
const sLower = s.toLowerCase();
return (
get(item, 'Name')
.toLowerCase()
.indexOf(sLower) !== -1 ||
get(item, 'Description')
.toLowerCase()
.indexOf(sLower) !== -1 ||
(get(item, 'ACLs.PolicyDefaults') || []).some(function(item) {
return item.Name.toLowerCase().indexOf(sLower) !== -1;
}) ||
(get(item, 'ACLs.RoleDefaults') || []).some(function(item) {
return item.Name.toLowerCase().indexOf(sLower) !== -1;
})
);
});
}

Some files were not shown because too many files have changed in this diff Show More