ui: Move control of login modal to use JS rather than HTML (label/id) (#9883)

* Add before and after skip links portals

* Move EmptyState and ErrorState to use a @login action/function

* Move page title setting to the Route component

* Add Routes and Outlets everywhere, and use those to access login modal

* Add some aria-labels to the modals

* Docs

* Remove the label/input now we no longer need it, fixup pageobject

* Add basic modal docs

* Switch out old toggle names for ids

* Wrap nspace Route template in a Route component

* type > class
This commit is contained in:
John Cowen 2021-04-06 13:40:40 +01:00 committed by GitHub
parent af78561018
commit 489b60105f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 2493 additions and 2028 deletions

View File

@ -5,6 +5,9 @@
</h1>
</BlockSlot>
<BlockSlot @name="content">
<ErrorState @error={{@error}} @allowLogin={{eq @error.status "403"}} />
<ErrorState
@error={{@error}}
@login={{if (eq @error.status "403") @login}}
/>
</BlockSlot>
</AppView>

View File

@ -108,7 +108,7 @@
@error={{hash
status='403'
}}
@allowLogin={{true}}
@login={{login}}
/>
{{else}}
<YieldSlot @name="content">{{yield}}</YieldSlot>

View File

@ -6,12 +6,15 @@
class="app"
...attributes
>
<ModalLayer />
<div
class="skip-links"
{{on "click" this.focus}}
>
<PortalTarget
@name="app-before-skip-links"
@mutiple={{true}}
></PortalTarget>
<a href={{concat '#' exported.main}}>{{t 'components.app.skip_to_content'}}</a>
{{!--
In order to add further skip links from within other templates use:
@ -21,11 +24,13 @@
from within your template
--}}
<PortalTarget
@name="app-skip-links"
@name="app-after-skip-links"
@mutiple={{true}}
></PortalTarget>
</div>
<ModalLayer />
<input
type="checkbox"
id={{concat guid "-main-nav-toggle"}}

View File

@ -193,6 +193,9 @@
<ModalDialog
class="consul-intention-permission-modal"
@onclose={{action (mut permission) undefined}}
@aria={{hash
label="Edit Permission"
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">

View File

@ -38,6 +38,9 @@ as |api|>
<ModalDialog
class="consul-intention-action-warn-modal warning"
data-test-action-warning
@aria={{hash
label=(concat "Set intention to " newAction)
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">

View File

@ -0,0 +1,81 @@
---
class: ember
---
# EmptyState
Consul UIs default 'empty state' used for when we retrive an empty result set,
whether that set is successful or erroneous. This is mainly used via the
`ErrorState` component, so also consider using that directly instead of this
component if dealing with errors.
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `login` | `Function` | `undefined` | A login action to call when the login button is pressed (if not provided no login button will be shown |
Icons are controlled via a `status-xxx` class. `xxx` should be some sort of
3 digit error code, special icons are used for `404` and `403`, otherwise a
generic icon will be used. To add any further special icons please add to the
component's `skin` file.
If the `@login` attribute is provided, a button will be shown directly
underneath the body text clicking on which will fire the provided `@login`
function.
```hbs preview-template
<EmptyState
class="status-404"
@login={{noop}}
>
<BlockSlot @name="header">
<h2>
Header
</h2>
</BlockSlot>
<BlockSlot @name="subheader">
<h3>
Subheader
</h3>
</BlockSlot>
<BlockSlot @name="body">
<p>
Body text
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a
href="{{env 'CONSUL_DOCS_URL'}}/agent/kv"
rel="noopener noreferrer"
target="_blank"
>
Documentation on K/V
</a>
</li>
<li class="learn-link">
<a
href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/kv"
rel="noopener noreferrer"
target="_blank"
>
Read the guide
</a>
</li>
</BlockSlot>
</EmptyState>
```
The component has four slots for specifying header, subheader, body and
'actions', although the component will show a minimal empty slot if only the
body slot is specified:
```hbs preview-template
<EmptyState>
<BlockSlot @name="body">
<p>
Minimal text
</p>
</BlockSlot>
</EmptyState>
```

View File

@ -16,8 +16,11 @@
{{#yield-slot name="body"}}
<div>
{{yield}}
{{#if (and (env 'CONSUL_ACLS_ENABLED') allowLogin)}}
<label for="login-toggle" data-test-empty-state-login>
{{#if login}}
<Action
data-test-empty-state-login
{{on "click" login}}
>
<DataSource
@src="settings://consul:token"
@onchange={{action (mut token) value="data"}}
@ -27,7 +30,7 @@
{{else}}
Log in
{{/if}}
</label>
</Action>
{{/if}}
</div>
{{/yield-slot}}

View File

@ -13,6 +13,6 @@
%empty-state > ul > li > label > button {
@extend %empty-state-anchor;
}
%empty-state label {
%empty-state div > button {
@extend %primary-button;
}

View File

@ -15,8 +15,9 @@
width: 370px;
margin: 0 auto;
}
%empty-state label {
margin: 0 auto !important;
%empty-state button {
margin: 0 auto;
display: inline;
}
%empty-state-header {
margin-bottom: -3px;

View File

@ -0,0 +1,34 @@
# ErrorState
Consul UIs default 'error state' used when an error is returned form the
backend. This component used `EmptyState` internally, so please refer to that
for more details.
Using this component for all of our errors means we can show a consistent
error page for generic errors.
This component show slighltly different visuals and copy depending on the
`status` of the error (the status is generally a HTTP error code)
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `login` | `Function` | `undefined` | A login action to call when the login button is pressed (if not provided no login button will be shown |
| `error` | `Object` | `undefined` | 'Consul UI error shaped' JSON `{status: String, message: String, detail: String}` |
```hbs preview-template
<ErrorState
@error={{hash status='403'}}
/>
```
As with `EmptyState` you can optionally chose to show a login button using the
`@login` argument.
```hbs preview-template
<ErrorState
@error={{hash status='403'}}
@login={{noop}}
/>
```

View File

@ -1,38 +0,0 @@
import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
<Meta title="Components/ErrorState" />
# ErrorState
<Canvas>
<Story
name="Basic"
argTypes={{
allowLogin: {
defaultValue: true,
control: {
type: 'boolean'
}
},
status: {
defaultValue: '403',
control: {
type: 'select',
options: [
'404',
'403',
'500'
]
}
}
}}
>{(args) => ({
template: hbs`<ErrorState
@allowLogin={{allowLogin}}
@error={{hash status=status}}
/>`,
context: args
})}
</Story>
</Canvas>

View File

@ -1,7 +1,7 @@
{{#if (not-eq @error.status "403")}}
<EmptyState
class={{concat "status-" @error.status}}
@allowLogin={{@allowLogin}}
@login={{@login}}
>
<BlockSlot @name="header">
<h2>{{or @error.message "Consul returned an error"}}</h2>
@ -34,7 +34,7 @@
{{else}}
<EmptyState
class="status-403"
@allowLogin={{@allowLogin}}
@login={{@login}}
>
<BlockSlot @name="header">
<h2 data-test-status={{@error.status}}>You are not authorized</h2>

View File

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

View File

@ -218,14 +218,28 @@
>
{{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}}
<BlockSlot @name="unauthorized">
<label
tabindex="0"
{{on 'keypress' this.keypressClick}}
<Portal @target="app-before-skip-links">
<button
type="button"
{{on "click" (optional this.modal.open)}}
>
<span>Log in</span>
</label>
<ModalDialog @name="login-toggle" @onclose={{this.close}} @onopen={{this.open}} as |modal|>
Login
</button>
</Portal>
<button
type="button"
{{on "click" (optional this.modal.open)}}
>
Log in
</button>
<ModalDialog
@name="login-toggle"
@onclose={{this.close}}
@onopen={{this.open}}
@aria={{hash
label="Log in to Consul"
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">
<h2>Log in to Consul</h2>
@ -245,7 +259,14 @@
</ModalDialog>
</BlockSlot>
<BlockSlot @name="authorized">
<ModalDialog @name="login-toggle" @onclose={{this.close}} @onopen={{this.open}} as |modal|>
<ModalDialog
@name="login-toggle"
@onclose={{this.close}}
@onopen={{this.open}}
@aria={{hash
label="Log in with a different token"
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">
<h2>Log in with a different token</h2>
@ -261,6 +282,14 @@
</button>
</BlockSlot>
</ModalDialog>
<Portal @target="app-before-skip-links">
<button
type="button"
{{on "click" (optional authDialog.logout)}}
>
Logout
</button>
</Portal>
<PopoverMenu @position="right" as |components api|>
<BlockSlot @name="trigger">
Logout
@ -295,7 +324,7 @@
<:main>
{{yield (hash
modal=this.modal
login=(if (env 'CONSUL_ACLS_ENABLED') this.modal (hash open=undefined))
)}}
</:main>

View File

@ -42,7 +42,7 @@ export default (collection, clickable, attribute, is, authForm, emptyState) => s
status: attribute('data-test-status', '[data-test-status]'),
},
};
page.navigation.login = clickable('[data-test-main-nav-auth] label');
page.navigation.login = clickable('[data-test-main-nav-auth] button');
page.navigation.dc = clickable('[data-test-datacenter-menu] button');
page.navigation.nspace = clickable('[data-test-nspace-menu] button');
page.navigation.manageNspaces = clickable('[data-test-main-nav-nspaces] a');

View File

@ -4,13 +4,13 @@
/* things that should look like nav buttons */
%main-nav-horizontal > ul > li > a,
%main-nav-horizontal > ul > li > span,
%main-nav-horizontal > ul > li > label,
%main-nav-horizontal > ul > li > button,
%main-nav-horizontal > ul > li > .popover-menu > label > button {
@extend %main-nav-horizontal-action;
}
%main-nav-horizontal .popover-menu [type='checkbox']:checked + label > *,
%main-nav-horizontal > ul > li.is-active > a,
%main-nav-horizontal > ul > li.is-active > label > * {
%main-nav-horizontal > ul > li.is-active > button {
@extend %main-nav-horizontal-action-active;
}
/* Whilst we want spans to look the same as actions */

View File

@ -0,0 +1,69 @@
---
class: ember
---
# ModalDialog
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `onopen` | `Function` | `undefined` | A function to call when the modal has opened |
| `onclose` | `Function` | `undefined` | A function to call when the modal has closed |
| `aria` | `Object` | `undefined` | A `hash` of aria properties used in the component, currently only label is supported |
## Exports
| Name | Type | Description |
| --- | --- | --- |
| `open` | `Function` | Opens the modal dialog |
| `close` | `Function` | Closes the modal dialog |
Works in tandem with `<ModalLayer />` to render modals. First of all ensure
you have a modal layer on the page (it doesn't have to be in the same
template)
```hbs
<ModalLayer />
```
Then all modals will be rendered into the `<ModalLayer />` for example:
```hbs preview-template
<ModalDialog
@onclose={{noop}}
@onopen={{noop}}
@aria={{hash
label="Screenread name of the modal"
}}
as |modal|>
<!-- Save a reference to the modal component so we can call its methods -->
{{did-insert (set this 'modal' modal)}}
<BlockSlot @name="header">
<h2>
Modal Header
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
Modal body
</p>
</BlockSlot>
<BlockSlot @name="actions">
<button type="button"
{{on "click" modal.close}}
>
Close modal
</button>
</BlockSlot>
</ModalDialog>
<button
type="button"
{{on 'click' (optional this.modal.open)}}
>
Open Modal
</button>
```

View File

@ -1,10 +1,8 @@
{{#let (hash
labelledby=(unique-id)
) as |aria|}}
<Portal @target="modal">
{{yield}}
<input
id={{@name}}
type="checkbox"
{{on 'change' (action 'change')}}
/>
<div
class="modal-dialog"
aria-hidden="true"
@ -16,6 +14,7 @@
<div
class="modal-dialog-modal"
role="dialog"
aria-label={{@aria.label}}
>
<div
role="document"
@ -31,6 +30,7 @@
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</header>
@ -39,6 +39,7 @@
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</div>
@ -47,6 +48,7 @@
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</footer>
@ -54,3 +56,4 @@
</div>
</div>
</Portal>
{{/let}}

View File

@ -21,8 +21,5 @@ export default Component.extend(Slotted, {
close: function() {
this.dialog.hide();
},
change: function(e) {
this.actions.open.call(this);
},
},
});

View File

@ -0,0 +1,8 @@
# ModalLayer
A component to give you control over where `<ModalDialog />` components are
rendered. Please see `<ModalDialog />` for more details.
```hbs
<ModalLayer />
```

View File

@ -0,0 +1,43 @@
# Outlet
The `Outlet` component should be used to wrap *every* ember `{{outlet}}`. It
provides/will provide functionality (along with the `<Route />` component)
for setting and announcing the title of the page, passing data down through
the route/template hierarchy, automatic orchestration of nested routing and
visual animating/transitioning between routes.
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `String` | `undefined` | The name of the route in ember routeName format e.g. `dc.services.index`. This is generally the `routeName` variable that is available to you in all Consul UI route/page level templates.|
| `model` | `Object` | `undefined` | Arbitrary hash of data to pass down to the child route (available in the `<Route as |route|>` export). |
```hbs
<Outlet
@name={{routeName}}
@model={{hash
dc=(hash
Name="dc-1"
)
}}
>
{{outlet}}
</Outlet>
```
Currently, using the `<Outlet />` component means that every single page/route
template is wrapped in a HTML `<section>` element. This `<section>` element
has various data attributes attached to indiciate the loading state of the
outlet. These can be used to specifically target every individual outlet via
CSS.
## Attributes
| Data Attribute | Description |
| --- | --- |
| `data-outlet` | The name of this outlet in ember routeName format e.g. `dc.services.index` |
| `data-route` | The name of the current child route of this outlet in ember routeName format e.g. `dc.services.show` |
| `data-state` | The current state of this outlet, `idle` or `loading` |
| `data-transition` | A combination of `idle` and `loading` states |

View File

@ -1,5 +1,6 @@
{{yield}}
<fieldset
class="policy-form"
disabled={{if (not (can "write policy" item=item)) "disabled"}}
...attributes
>

View File

@ -27,8 +27,11 @@
{{!the modal has to go here so that if you provide a slot to trigger it doesn't get rendered}}
<ModalDialog
data-test-policy-form
id="new-policy"
@onopen={{action "open"}}
@name="new-policy-toggle"
@aria={{hash
label='New Policy'
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">

View File

@ -5,7 +5,7 @@ export default (clickable, deletable, collection, alias, policyForm) => (
return {
scope: scope,
create: clickable(createSelector),
form: policyForm('#new-policy-toggle + div'),
form: policyForm('#new-policy'),
policies: alias('selectedOptions'),
selectedOptions: collection(
'[data-test-policies] [data-test-tabular-row]',

View File

@ -1,8 +1,9 @@
{{yield}}
<fieldset
disabled={{if (not (can "write role" item=item)) "disabled"}}
class="role-form"
disabled={{if (not (can "write role" item=item)) "disabled"}}
data-test-role-form
...attributes
>
<label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span>

View File

@ -1,8 +1,11 @@
<ModalDialog
class="role-selector"
data-test-role-form
id="new-role"
@onclose={{action (mut state) "role"}}
@name="new-role-toggle"
@aria={{hash
label=(if (eq state 'role') 'New Role' 'New Policy')
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">

View File

@ -0,0 +1,35 @@
# Route
The `Route` component should be used for the top-level component for *every*
route/page template. It provides/will provide functionality (along with the
`<Outlet />` component) for setting and announcing the title of the page,
passing data down through the route/template hierarchy, automatic
orchestration of nested routing and visual animating/transitioning between
routes.
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `String` | `undefined` | The name of the route in ember routeName format e.g. `dc.services.index`. This is generally the `routeName` variable that is available to you in all Consul UI route/page level templates.|
| `title` | `String` | `undefined` | The title for this page (eventually passed through to the `{{page-title}}` helper. This argument should be omitted if a title change isn't required. |
| `titleSeparator` | `String` | `undefined` | This can be used in the top-level route to configure the separator for the `{{page-title}}` helper |
## Exports
| export | Type | Default | Description |
| --- | --- | --- | --- |
| `model` | `Object` | `undefined` | Arbitrary hash of data passed down from the parent route/outlet |
```hbs
<Route
@name={{routeName}}
@title="Page Title"
@titleSeparator=" - "
as |route|>
{{route.model.dc.Name}}
</Route>
```
Every page/route template has a `routeName` variable exposed specifically to
allow you to use this to set the `@name` of the route.

View File

@ -1,5 +1,10 @@
{{did-insert this.connect}}
{{will-destroy this.disconnect}}
{{#if this.title}}
{{page-title this.title separator=@titleSeparator}}
{{/if}}
{{yield (hash
model=model
)}}

View File

@ -8,10 +8,15 @@ export default class RouteComponent extends Component {
@tracked model;
get title() {
return this.args.title;
}
@action
connect() {
this.routlet.addRoute(this.args.name, this);
}
@action
disconnect() {
this.routlet.removeRoute(this.args.name, this);

View File

@ -2,11 +2,20 @@
display: flex;
flex-direction: column;
position: absolute;
z-index: 10;
left: 50%;
padding: 20px;
top: -100px;
transform: translateX(-50%);
}
%skip-links div,
%skip-links button,
%skip-links a {
display: block;
width: 100%;
text-align: center;
box-sizing: border-box;
}
%skip-links:focus-within {
top: 0px;
}

View File

@ -3,6 +3,7 @@
color: $white;
background-color: $blue-500;
}
%skip-links button,
%skip-links a {
color: inherit;
}

View File

@ -43,6 +43,9 @@
<ModalDialog
class="sparkline-key"
@aria={{hash
label="Metrics Key"
}}
as |modal|>
<Ref @target={{this}} @name="modal" @value={{modal}} />
<BlockSlot @name="header">

View File

@ -1,9 +1,12 @@
%visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
position: absolute;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
clip: rect(0 0 0 0);
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
}
%visually-hidden-text {
text-indent: -9000px;

View File

@ -1,4 +1,4 @@
@import './empty-state/index';
@import 'consul-ui/components/empty-state/index';
.empty-state {
@extend %empty-state;
}

View File

@ -1,7 +1,8 @@
<Route
@name={{routeName}}
@title='Consul'
@titleSeparator=" - "
>
{{page-title 'Consul' separator=' - '}}
{{#if (env 'CONSUL_ACLS_ENABLED')}}
{{document-attrs class="has-acls"}}
@ -28,9 +29,12 @@ as |source|>
@nspaces={{nspaces}}
@nspace={{or nspace nspaces.firstObject}}
@onchange={{action "reauthorize"}}
>
as |consul|>
<Outlet
@name="application"
@model={{hash
app=consul
}}
as |o|>
{{outlet}}
</Outlet>

View File

@ -1 +1,10 @@
<Route
@name={{routeName}}
as |route|>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</Route>

View File

@ -1,9 +1,7 @@
{{#if isAuthorized }}
{{page-title 'Auth Methods'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Auth Methods"
as |route|>
{{#let
(hash
@ -38,10 +36,12 @@ as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h1>
Access Controls
{{route.model.hi}}
Auth Methods
</h1>
</BlockSlot>
<BlockSlot @name="toolbar">
@ -66,7 +66,9 @@ as |sort filters items|}}
<Consul::AuthMethod::List @items={{collection.items}} />
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -99,4 +101,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,3 +1,7 @@
<Route
@name={{routeName}}
@title={{if create 'New ACL' 'Edit ACL'}}
as |route|>
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@ -46,3 +50,4 @@
{{ partial 'dc/acls/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,4 +1,7 @@
{{page-title 'ACLs'}}
<Route
@name={{routeName}}
@title="ACLs"
as |route|>
{{#let
(hash
@ -67,7 +70,9 @@ as |sort filters items|}}
</Consul::Acl::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -93,3 +98,4 @@ as |sort filters items|}}
</AppView>
{{/let}}
</Route>

View File

@ -26,6 +26,9 @@
<ModalDialog
data-test-delete-modal
@onclose={{action cancel}}
@aria={{hash
label="Policy in Use"
}}
>
<BlockSlot @name="header">
<h2>Policy in Use</h2>

View File

@ -1,15 +1,11 @@
{{#if isAuthorized }}
{{#if create }}
{{page-title 'New Policy'}}
{{else}}
{{page-title 'Edit Policy'}}
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if isAuthorized (if create 'New Policy' 'Edit Policy') 'Access Controls'}}
as |route|>
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@ -59,3 +55,4 @@
{{/if}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,8 +1,7 @@
{{#if isAuthorized }}
{{page-title 'Policies'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Policies"
as |route|>
{{#let
(hash
@ -35,6 +34,7 @@ as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@ -79,7 +79,9 @@ as |sort filters items|}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -113,3 +115,4 @@ as |sort filters items|}}
</AppView>
{{/let}}
</Route>

View File

@ -24,7 +24,12 @@
</BlockSlot>
<BlockSlot @name="dialog" as |execute cancel message|>
{{#if (gt items.length 0)}}
<ModalDialog @onclose={{action cancel}}>
<ModalDialog
@onclose={{action cancel}}
@aria={{hash
label="Role in Use"
}}
>
<BlockSlot @name="header">
<h2>Role in Use</h2>
</BlockSlot>

View File

@ -1,15 +1,11 @@
{{#if isAuthorized }}
{{#if item.ID}}
{{page-title 'Edit Role'}}
{{else}}
{{page-title 'New Role'}}
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if isAuthorized (if create 'New Role' 'Edit Role') 'Access Controls'}}
as |route|>
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Role::Notifications
@ -51,3 +47,4 @@
{{ partial 'dc/acls/roles/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,9 +1,7 @@
{{#if isAuthorized }}
{{page-title 'Roles'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Roles"
as |route|>
{{#let
(hash
@ -29,6 +27,7 @@ as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Role::Notifications
@ -73,7 +72,9 @@ as |sort filters items|}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -106,3 +107,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,15 +1,11 @@
{{#if isAuthorized }}
{{#if create}}
{{page-title 'New Token'}}
{{else}}
{{page-title 'Edit Token'}}
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if isAuthorized (if create 'New Token' 'Edit Token') 'Access Controls'}}
as |route|>
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@ -96,3 +92,4 @@
{{ partial 'dc/acls/tokens/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,9 +1,7 @@
{{#if isAuthorized }}
{{page-title 'Tokens'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Tokens"
as |route|>
{{#let
(hash
@ -33,6 +31,7 @@ as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@ -95,7 +94,9 @@ as |sort filters items|}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -120,3 +121,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,8 +1,7 @@
{{#if item.ID}}
{{page-title 'Edit Intention'}}
{{else}}
{{page-title 'New Intention'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if item.ID 'Edit Intention' 'New Intention'}}
as |route|>
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
@ -31,3 +30,4 @@
/>
</BlockSlot>
</AppView>
</Route>

View File

@ -1,8 +1,14 @@
{{page-title 'Intentions'}}
<Route
@name={{routeName}}
@title="Intentions"
as |route|>
<DataLoader @src={{concat '/' nspace '/' dc '/intentions'}} as |api|>
<BlockSlot @name="error">
<AppError @error={{api.error}} />
<AppError
@error={{api.error}}
@login={{route.model.app.login.open}}
/>
</BlockSlot>
<BlockSlot @name="loaded">
@ -82,7 +88,9 @@ as |sort filters items|}}
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -120,3 +128,4 @@ as |sort filters items|}}
{{/let}}
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,8 +1,7 @@
{{#if item.Key }}
{{page-title 'Edit Key/Value'}}
{{else}}
{{page-title 'New Key/Value'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if item.Key 'Edit Key/Value' 'New Key/Value'}}
as |route|>
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
@ -55,3 +54,4 @@
{{/if}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,4 +1,7 @@
{{page-title 'Key/Value'}}
<Route
@name={{routeName}}
@title="Key/Value"
as |route|>
{{#let
(hash
@ -81,7 +84,9 @@ as |sort filters items|}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -116,3 +121,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,4 +1,7 @@
{{page-title 'Nodes'}}
<Route
@name={{routeName}}
@title="Nodes"
as |route|>
<EventSource @src={{items}} />
<EventSource @src={{leader}} />
{{#let
@ -72,3 +75,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,4 +1,7 @@
{{page-title item.Node}}
<Route
@name={{routeName}}
@title={{item.Node}}
as |route|>
<DataLoader as |loader|>
<BlockSlot @name="data">
@ -7,7 +10,10 @@
</BlockSlot>
<BlockSlot @name="error">
<AppError @error={{loader.error}} />
<AppError
@error={{loader.error}}
@login={{route.model.app.login.open}}
/>
</BlockSlot>
<BlockSlot @name="disconnected" as |Notification|>
@ -72,6 +78,7 @@
<BlockSlot @name="content">
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
@ -80,3 +87,4 @@
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
@ -68,3 +71,4 @@ as |sort filters items|}}
</DataCollection>
</div>
{{/let}}
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if item.Meta}}
<Consul::Metadata::List @items={{entries item.Meta}} />
@ -11,3 +14,4 @@
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
<div class="definition-table">
<dl>
@ -23,4 +26,4 @@
</div>
<Consul::Tomography::Graph @distances={{tomography.distances}} />
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
@ -69,3 +72,4 @@ as |sort filters items|}}
</DataCollection>
</div>
{{/let}}
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{sessions}} />
<div class="tab-section">
{{#if (gt sessions.length 0)}}
@ -6,7 +9,9 @@
@onInvalidate={{action send 'invalidateSession'}}
/>
{{else}}
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
Welcome to Lock Sessions
@ -28,3 +33,4 @@
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,8 +1,7 @@
{{#if create }}
{{page-title 'New Namespace'}}
{{else}}
{{page-title 'Edit Namespace'}}
{{/if}}
<Route
@name={{routeName}}
@title={{if create 'New Namespace' 'Edit Namespace'}}
as |route|>
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@ -31,3 +30,4 @@
{{ partial 'dc/nspaces/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,4 +1,7 @@
{{page-title 'Namespaces'}}
<Route
@name={{routeName}}
@title='Namespaces'
as |route|>
<EventSource @src={{items}} />
{{#let
@ -65,7 +68,9 @@ as |sort filters items|}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
@ -98,3 +103,4 @@ as |sort filters items|}}
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,7 +1,7 @@
<Route
@name={{routeName}}
>
{{page-title 'Services'}}
@title="Services"
as |route|>
<EventSource @src={{items}} />
@ -76,7 +76,9 @@ as |sort filters items|}}
</Consul::Service::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt services.length 0)}}

View File

@ -1,4 +1,7 @@
{{page-title item.Service.ID}}
<Route
@name={{routeName}}
@title={{item.Service.ID}}
as |route|>
<DataLoader as |loader|>
<BlockSlot @name="data">
@ -10,7 +13,10 @@
</BlockSlot>
<BlockSlot @name="error">
<AppError @error={{loader.error}} />
<AppError
@error={{loader.error}}
@login={{route.model.app.login.open}}
/>
</BlockSlot>
<BlockSlot @name="disconnected" as |Notification|>
@ -88,6 +94,7 @@
}}/>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
@ -95,3 +102,4 @@
</AppView>
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if item.Service.TaggedAddresses }}
<TabularCollection
@ -26,3 +29,4 @@
</p>
{{/if}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
<p>
@ -14,3 +17,4 @@
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
@ -66,3 +69,4 @@ as |sort filters items|}}
</div>
{{/let}}
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
<section class="tags">
<h2>Tags</h2>
@ -28,3 +31,4 @@
{{/if}}
</section>
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#let
@ -58,3 +61,4 @@ as |sort filters items|}}
</DataCollection>
{{/let}}
</div>
</Route>

View File

@ -1,8 +1,8 @@
{{#let items.firstObject as |item|}}
<Route
@name={{routeName}}
@title={{item.Service.Service}}
as |route|>
{{#let items.firstObject as |item|}}
{{page-title item.Service.Service}}
<DataLoader as |loader|>
<BlockSlot @name="data">
@ -14,7 +14,10 @@ as |route|>
</BlockSlot>
<BlockSlot @name="error">
<AppError @error={{loader.error}} />
<AppError
@error={{loader.error}}
@login={{route.model.app.login.open}}
/>
</BlockSlot>
<BlockSlot @name="disconnected" as |Notification|>
@ -107,6 +110,7 @@ as |route|>
<BlockSlot @name="content">
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
@ -114,5 +118,5 @@ as |route|>
</AppView>
</BlockSlot>
</DataLoader>
{{/let}}
</Route>
{{/let}}

View File

@ -1,6 +1,6 @@
<Route
@name={{routeName}}
>
as |route|>
<div class="tab-section">
{{#let

View File

@ -1,5 +1,10 @@
<Route
@name={{routeName}}
as |route|>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<Consul::Intention::Form
@nspace={{nspace}}
@dc={{dc}}
@ -7,3 +10,4 @@
}}
@onsubmit={{transition-to 'dc.services.show.intentions.index'}}
/>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<DataLoader
@src={{uri
'/${nspace}/${dc}/intentions/for-service/${slug}'
@ -94,3 +97,4 @@ as |sort filters items|}}
{{/let}}
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,7 +1,10 @@
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{chain}} />
<div class="tab-section">
<Consul::DiscoveryChain
@chain={{chain.Chain}}
/>
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
@ -66,3 +69,4 @@ as |sort filters items|}}
</DataCollection>
{{/let}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#let (flatten (map-by "Tags" items)) as |tags|}}
{{#if (gt tags.length 0) }}
@ -13,3 +16,4 @@
{{/if}}
{{/let}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{topology}} />
<div class="tab-section">
{{#if (and (eq topology.Upstreams.length 0) (eq topology.Downstreams.length 0))}}
@ -37,3 +40,4 @@
/>
{{/if}}
</div>
</Route>

View File

@ -1,3 +1,6 @@
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
@ -66,3 +69,4 @@ as |sort filters items|}}
</DataCollection>
{{/let}}
</div>
</Route>

View File

@ -1,5 +1,10 @@
<Route
@name={{routeName}}
as |route|>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</Route>

View File

@ -51,7 +51,7 @@ Feature: dc / acls / roles / as-many / add-new: Add new
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
Scenario: Add Role that has an existing Policy
And I click "#new-role-toggle + div .ember-power-select-trigger"
And I click "#new-role .ember-power-select-trigger"
And I click ".ember-power-select-option:first-child"
And I click submit on the roles.form
Then a PUT request was made to "/v1/acl/role?dc=datacenter" from yaml