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:
parent
ac629cd51e
commit
59585f71a7
24
ui-v2/app/components/consul-nspace-list/README.mdx
Normal file
24
ui-v2/app/components/consul-nspace-list/README.mdx
Normal 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)
|
||||
|
||||
---
|
55
ui-v2/app/components/consul-nspace-list/index.hbs
Normal file
55
ui-v2/app/components/consul-nspace-list/index.hbs
Normal 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}}
|
10
ui-v2/app/components/consul-nspace-list/index.js
Normal file
10
ui-v2/app/components/consul-nspace-list/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
actions: {
|
||||
isLinkable: function(item) {
|
||||
return !item.DeletedAt;
|
||||
},
|
||||
},
|
||||
});
|
7
ui-v2/app/components/consul-nspace-list/pageobject.js
Normal file
7
ui-v2/app/components/consul-nspace-list/pageobject.js
Normal 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']),
|
||||
});
|
||||
};
|
|
@ -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'))}}
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 & 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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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)
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue