ui: Namespaces Redesign (#8336)

* ui: Add new consul-nspace-list component

* ui: Use new consul-nspace-list component

* Fix up other components to use linkable list-collection action

* ui: Remove some dead CSS
This commit is contained in:
John Cowen 2020-07-20 18:12:34 +01:00 committed by GitHub
parent ac629cd51e
commit 59585f71a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 176 additions and 206 deletions

View file

@ -0,0 +1,24 @@
## ConsulNspaceList
```
<ConsulNspaceList
@items={{items}}
@ondelete={{action 'delete'}}
/>
```
A presentational component for rendering Consul Namespaces
### Arguments
| Argument/Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `array` | | An array of Namespaces |
| `ondelete` | `function` | | An action to execute when the `Delete` action is clicked |
### See
- [Component Source Code](./index.js)
- [Template Source Code](./index.hbs)
---

View file

@ -0,0 +1,55 @@
{{#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 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')}}
<ConsulTokenRulesetList @item={{item}} />
{{/if}}
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
<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>
</BlockSlot>
</ListCollection>
{{/if}}

View file

@ -0,0 +1,10 @@
import Component from '@ember/component';
export default Component.extend({
tagName: '',
actions: {
isLinkable: function(item) {
return !item.DeletedAt;
},
},
});

View file

@ -0,0 +1,7 @@
export default (collection, clickable, attribute, text, actions) => () => {
return collection('.consul-nspace-list li:not(:first-child)', {
nspace: clickable('a'),
description: text('[data-test-description]'),
...actions(['edit', 'delete']),
});
};

View file

@ -1,5 +1,5 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-service-list" as |item index|>
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-service-list" as |item index|>
<BlockSlot @name="header">
{{#let (get proxies item.Name) as |proxy|}}
{{#let (service/health-checks item proxy) as |health|}}
@ -23,6 +23,7 @@
</dl>
{{/let}}
{{/let}}
{{#if (gt item.InstanceCount 0)}}
{{#if (eq item.Kind 'terminating-gateway')}}
<a data-test-service-name href={{href-to "dc.services.show.services" item.Name}}>
{{item.Name}}
@ -36,6 +37,11 @@
{{item.Name}}
</a>
{{/if}}
{{else}}
<p data-test-service-name>
{{item.Name}}
</p>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#if (and nspace (env 'CONSUL_NSPACES_ENABLED'))}}

View file

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

View file

@ -1,4 +1,4 @@
{{#let (policy/group item.Policies) as |policies|}}
{{#let (policy/group (or item.Policies item.ACLs.PolicyDefaults (array))) as |policies|}}
{{#let (get policies 'management') as |management|}}
{{#if (gt management.length 0)}}
<dl>
@ -33,7 +33,7 @@
</dd>
</dl>
{{else}}
{{#let (append (get policies 'policies') (or item.Roles (array))) as |policies|}}
{{#let (append (get policies 'policies') (or item.Roles item.ACLs.RoleDefaults (array))) as |policies|}}
{{#if (gt policies.length 0)}}
<dl>
<dt>Rules</dt>

View file

@ -1,6 +1,6 @@
<ListCollection @items={{items}} class="consul-upstream-list" as |item index|>
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-upstream-list" as |item index|>
<BlockSlot @name="header">
{{#if (service/exists item)}}
{{#if (gt item.InstanceCount 0)}}
<dl class={{service/health-checks item}}>
<dt>
Health

View file

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

View file

@ -10,7 +10,13 @@
>
<li></li>
{{~#each _cells as |cell|~}}
<li onclick={{action 'click'}} style={{{cell.style}}} class={{if (service/exists cell.item) 'linkable'}}>
<li
onclick={{action 'click'}} style={{{cell.style}}}
class={{if
(compute (action (or linkable (noop)) cell.item))
'linkable'
}}
>
<YieldSlot @name="header"><div>{{yield cell.item cell.index}}</div></YieldSlot>
<YieldSlot @name="details"><div>{{yield cell.item cell.index}}</div></YieldSlot>
<YieldSlot @name="actions"

View file

@ -1,11 +0,0 @@
import { helper } from '@ember/component/helper';
export function serviceExists([item], hash) {
if (typeof item.InstanceCount === 'undefined') {
return false;
}
return item.InstanceCount > 0;
}
export default helper(serviceExists);

View file

@ -7,9 +7,7 @@
}
/* hoverable rows */
%composite-row.linkable,
.consul-gateway-service-list > ul > li:not(:first-child),
.consul-service-instance-list > ul > li:not(:first-child),
.consul-service-list > ul > li:not(:first-child),
.consul-token-list > ul > li:not(:first-child),
.consul-policy-list > ul > li:not(:first-child),
.consul-role-list > ul > li:not(:first-child) {
@ -17,6 +15,7 @@
}
/*TODO: This hides the icons-less dt's in the below lists as */
/* they don't have tooltips */
.consul-nspace-list > ul > li:not(:first-child) dt,
.consul-token-list > ul > li:not(:first-child) dt,
.consul-policy-list > ul li:not(:first-child) dl:not(.datacenter) dt,
.consul-role-list > ul > li:not(:first-child) dt {

View file

@ -23,16 +23,6 @@
--consul-icon: #{$consul-logo-color-svg};
--aws-icon: #{$aws-logo-color-svg};
}
html.template-node.template-show #services td:first-child a span {
@extend %with-external-source-icon;
float: left;
margin-right: 10px;
margin-top: 2px;
}
/* This nudges the th in for the external source icons */
html.template-node.template-show #services th:first-child {
text-indent: 28px;
}
td.folder::before {
@extend %with-folder-outline-mask, %as-pseudo;
@ -88,22 +78,11 @@ th span em {
}
/**/
/* ideally these would be in route css files, but left here as they */
/* accomplish the same thing (hide non-essential columns for tables) */
@media #{$--lt-medium-table} {
/* Policy > Datacenters */
html.template-policy.template-list tr > :nth-child(2) {
display: none;
}
}
@media #{$--lt-wide-table} {
/* hide actions on narrow screens, you can always click in do everything from there */
tr > .actions {
display: none;
}
html.template-node.template-show #services tr > :last-child {
display: none;
}
html.template-node.template-show #lock-sessions tr > :not(:first-child):not(:last-child) {
display: none;
}

View file

@ -69,15 +69,6 @@ table.has-actions tr > *:nth-last-child(5):first-child ~ * {
}
/*TODO: trs only live in tables, get rid of table */
html.template-nspace.has-acls.template-list main table tr {
@extend %with-acls-nspaces-row;
}
html.template-nspace:not(.has-acls).template-list main table tr {
@extend %nspaces-row;
}
html.template-role.template-list main table tr {
@extend %roles-row;
}
html.template-policy.template-edit [role='dialog'] table tr,
html.template-policy.template-edit main table tr,
html.template-role.template-edit [role='dialog'] table tr,
@ -100,23 +91,6 @@ html.template-node.template-show main table.sessions tr {
width: calc(100% - 240px) !important;
}
%roles-row > *:nth-child(1),
%roles-row > *:nth-child(2),
%with-acls-nspaces-row > *:nth-child(1),
%with-acls-nspaces-row > *:nth-child(2) {
width: calc(22% - 20px) !important;
}
%with-acls-nspaces-row > *:nth-child(3),
%roles-row > *:nth-child(3) {
width: calc(56% - 20px) !important;
}
%nspaces-row > *:nth-child(1) {
width: 30%;
}
%nspaces-row > *:nth-child(2) {
width: calc(70% - 60px);
}
@media #{$--horizontal-session-list} {
%node-sessions-row > * {
// (100% / 7) - (300px / 6) - (120px / 6)

View file

@ -21,78 +21,11 @@
/>
{{/if}}
<ChangeableSet @dispatcher={{searchable 'nspace' items}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<TabularCollection @items={{filtered}} as |item index|>
<BlockSlot @name="header">
<th>Name</th>
<th>Description</th>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<th>Roles &amp; Policies</th>
{{/if}}
</BlockSlot>
<BlockSlot @name="row">
{{#if item.DeletedAt}}
<td class="no-actions" colspan="3">
<p>
Deleting {{item.Name}}...
</p>
</td>
{{else}}
<td data-test-namespace={{item.Name}}>
<a href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
</td>
<td data-test-description>
<p>{{item.Description}}</p>
</td>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<td>
{{#each (compact (append item.ACLs.PolicyDefaults item.ACLs.RoleDefaults)) as |item|}}
<strong data-test-policy class={{policy/typeof item}}>{{item.Name}}</strong>
{{/each}}
</td>
{{/if}}
{{/if}}
</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 clickTrigger|>
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.nspaces.edit' item.Name}}>Edit</a>
</li>
{{#if (not-eq item.Name 'default') }}
<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 send 'delete' item) (queue clickTrigger)}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
</BlockSlot>
</PopoverMenu>
</BlockSlot>
</TabularCollection>
</BlockSlot>
<BlockSlot @name="empty">
<BlockSlot @name="content" as |filtered|>
<ConsulNspaceList
@items={{filtered}}
@ondelete={{queue (action send 'delete')}}
>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
@ -121,6 +54,7 @@
</li>
</BlockSlot>
</EmptyState>
</ConsulNspaceList>
</BlockSlot>
</ChangeableSet>
</BlockSlot>

View file

@ -5,7 +5,10 @@
The following services may receive traffic from external services through this gateway. Learn more about configuring gateways in our
<a href="{{env 'CONSUL_DOCS_URL'}}/connect/terminating-gateway" target="_blank" rel="noopener noreferrer">step-by-step guide</a>.
</p>
<ConsulServiceList @items={{gatewayServices}} @nspace={{nspace}} />
<ConsulServiceList
@items={{gatewayServices}}
@nspace={{nspace}}
/>
{{else}}
<EmptyState>
<BlockSlot @name="body">

View file

@ -1,17 +0,0 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | service/exists', function(hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests.
test('it renders', async function(assert) {
this.set('inputValue', { InstanceCount: 3 });
await render(hbs`{{service/exists inputValue}}`);
assert.equal(this.element.textContent.trim(), 'true');
});
});

View file

@ -41,6 +41,7 @@ import consulTokenListFactory from 'consul-ui/components/consul-token-list/pageo
import consulRoleListFactory from 'consul-ui/components/consul-role-list/pageobject';
import consulPolicyListFactory from 'consul-ui/components/consul-policy-list/pageobject';
import consulIntentionListFactory from 'consul-ui/components/consul-intention-list/pageobject';
import consulNspaceListFactory from 'consul-ui/components/consul-nspace-list/pageobject';
import consulKvListFactory from 'consul-ui/components/consul-kv-list/pageobject';
// pages
@ -97,6 +98,13 @@ const morePopoverMenu = morePopoverMenuFactory(clickable);
const popoverSelect = popoverSelectFactory(clickable, collection);
const consulIntentionList = consulIntentionListFactory(collection, clickable, attribute, deletable);
const consulNspaceList = consulNspaceListFactory(
collection,
clickable,
attribute,
text,
morePopoverMenu
);
const consulKvList = consulKvListFactory(collection, clickable, attribute, deletable);
const consulTokenList = consulTokenListFactory(
collection,
@ -166,9 +174,7 @@ export default {
),
intentions: create(intentions(visitable, creatable, consulIntentionList, intentionFilter)),
intention: create(intention(visitable, submitable, deletable, cancelable)),
nspaces: create(
nspaces(visitable, deletable, creatable, clickable, attribute, collection, text, freetextFilter)
),
nspaces: create(nspaces(visitable, creatable, consulNspaceList, freetextFilter)),
nspace: create(
nspace(visitable, submitable, deletable, cancelable, policySelector, roleSelector)
),

View file

@ -1,24 +1,7 @@
export default function(
visitable,
deletable,
creatable,
clickable,
attribute,
collection,
text,
filter
) {
export default function(visitable, creatable, nspaces, filter) {
return creatable({
visit: visitable('/:dc/namespaces'),
nspaces: collection(
'[data-test-tabular-row]',
deletable({
action: attribute('data-test-nspace-action', '[data-test-nspace-action]'),
description: text('[data-test-description]'),
nspace: clickable('a'),
actions: clickable('label'),
})
),
nspaces: nspaces(),
filter: filter(),
});
}