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)}}
>
Login
</button>
</Portal>
<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|>
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,56 +1,59 @@
<Portal @target="modal">
{{yield}}
<input
id={{@name}}
type="checkbox"
{{on 'change' (action 'change')}}
/>
<div
class="modal-dialog"
aria-hidden="true"
...attributes
{{did-insert (action "connect")}}
{{will-destroy (action "disconnect")}}
>
<div tabindex="-1" data-a11y-dialog-hide></div>
{{#let (hash
labelledby=(unique-id)
) as |aria|}}
<Portal @target="modal">
{{yield}}
<div
class="modal-dialog-modal"
role="dialog"
class="modal-dialog"
aria-hidden="true"
...attributes
{{did-insert (action "connect")}}
{{will-destroy (action "disconnect")}}
>
<div tabindex="-1" data-a11y-dialog-hide></div>
<div
role="document"
class="modal-dialog-modal"
role="dialog"
aria-label={{@aria.label}}
>
<header class="modal-dialog-header">
<button
type="button"
data-a11y-dialog-hide
aria-label="Close dialog"
>
</button>
<YieldSlot @name="header">
{{yield (hash
open=(action "open")
close=(action "close")
)}}
</YieldSlot>
</header>
<div class="modal-dialog-body">
<YieldSlot @name="body">
{{yield (hash
open=(action "open")
close=(action "close")
)}}
</YieldSlot>
<div
role="document"
>
<header class="modal-dialog-header">
<button
type="button"
data-a11y-dialog-hide
aria-label="Close dialog"
>
</button>
<YieldSlot @name="header">
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</header>
<div class="modal-dialog-body">
<YieldSlot @name="body">
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</div>
<footer class="modal-dialog-footer">
<YieldSlot @name="actions" @params={{block-params (action "close")}}>
{{yield (hash
open=(action "open")
close=(action "close")
aria=aria
)}}
</YieldSlot>
</footer>
</div>
<footer class="modal-dialog-footer">
<YieldSlot @name="actions" @params={{block-params (action "close")}}>
{{yield (hash
open=(action "open")
close=(action "close")
)}}
</YieldSlot>
</footer>
</div>
</div>
</div>
</Portal>
</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 @@
{{outlet}}
<Route
@name={{routeName}}
as |route|>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</Route>

View File

@ -1,102 +1,104 @@
{{#if isAuthorized }}
{{page-title 'Auth Methods'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Auth Methods"
as |route|>
{{#let
{{#let
(hash
value=(or sortBy "MethodName:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
(hash
value=(or sortBy "MethodName:asc")
change=(action (mut sortBy) value="target.selected")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="header">
<h1>
Access Controls
</h1>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::AuthMethod::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="auth-method"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::AuthMethod::List @items={{collection.items}} />
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No auth methods found
{{else}}
Welcome to Auth Methods
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No auth methods where found matching that search, or you may not have access to view the auth methods you are searching for.
{{else}}
There don't seem to be any auth methods, or you may not have access to view auth methods yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/security/acl/auth-methods" rel="noopener noreferrer" target="_blank">Documentation on auth methods</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_API_URL'}}/acl/auth-methods.html" rel="noopener noreferrer" target="_blank">Read the API Docs</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h1>
{{route.model.hi}}
Auth Methods
</h1>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::AuthMethod::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="auth-method"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::AuthMethod::List @items={{collection.items}} />
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No auth methods found
{{else}}
Welcome to Auth Methods
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No auth methods where found matching that search, or you may not have access to view the auth methods you are searching for.
{{else}}
There don't seem to be any auth methods, or you may not have access to view auth methods yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/security/acl/auth-methods" rel="noopener noreferrer" target="_blank">Documentation on auth methods</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_API_URL'}}/acl/auth-methods.html" rel="noopener noreferrer" target="_blank">Read the API Docs</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,48 +1,53 @@
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls'}}>All Tokens</a></li>
</ol>
<Route
@name={{routeName}}
@title={{if create 'New ACL' 'Edit ACL'}}
as |route|>
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.Name }}
{{item.Name}}
{{else}}
New token
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not create) }}
<CopyButton @value={{item.ID}} @name="token ID">
Copy token ID
</CopyButton>
{{#if (can "duplicate acl" item=item)}}
<button type="button" {{ action "clone" item }}>Clone token</button>
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|>
<button data-test-use type="button" {{ action confirm 'use' item }}>Use token</button>
</BlockSlot>
<BlockSlot @name="dialog" as |execute cancel message|>
<p>
{{message}}
</p>
<button type="button" class="type-delete" {{action execute}}>Confirm Use</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot>
</ConfirmationDialog>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls'}}>All Tokens</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.Name }}
{{item.Name}}
{{else}}
New token
{{/if}}
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{ partial 'dc/acls/form'}}
</BlockSlot>
</AppView>
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not create) }}
<CopyButton @value={{item.ID}} @name="token ID">
Copy token ID
</CopyButton>
{{#if (can "duplicate acl" item=item)}}
<button type="button" {{ action "clone" item }}>Clone token</button>
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|>
<button data-test-use type="button" {{ action confirm 'use' item }}>Use token</button>
</BlockSlot>
<BlockSlot @name="dialog" as |execute cancel message|>
<p>
{{message}}
</p>
<button type="button" class="type-delete" {{action execute}}>Confirm Use</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot>
</ConfirmationDialog>
{{/if}}
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{ partial 'dc/acls/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,95 +1,101 @@
{{page-title 'ACLs'}}
{{#let
<Route
@name={{routeName}}
@title="ACLs"
as |route|>
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
)
items
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
)
as |sort filters items|}}
items
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create acls")}}
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Acl::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
as |sort filters items|}}
@sort={{sort}}
@filter={{filters}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Acl::Notifications
@status={{status}}
@type={{type}}
@error={{error}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="acl"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Acl::List
@items={{collection.items}}
</BlockSlot>
<BlockSlot @name="header">
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create acls")}}
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Acl::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@ondelete={{route-action 'delete'}}
@onuse={{route-action 'use'}}
@onclone={{route-action 'clone'}}
>
</Consul::Acl::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No ACLs found
{{else}}
Welcome to ACLs
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for.
{{else}}
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
@sort={{sort}}
{{/let}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="acl"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Acl::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
@onuse={{route-action 'use'}}
@onclone={{route-action 'clone'}}
>
</Consul::Acl::List>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No ACLs found
{{else}}
Welcome to ACLs
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for.
{{else}}
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</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,61 +1,58 @@
{{#if isAuthorized }}
{{#if create }}
{{page-title 'New Policy'}}
{{else}}
{{page-title 'Edit Policy'}}
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.policies'}}>All Policies</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Policy
{{else}}
{{#if (can "write policy" item=item)}}
Edit Policy
<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
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.policies'}}>All Policies</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Policy
{{else}}
View Policy
{{#if (can "write policy" item=item)}}
Edit Policy
{{else}}
View Policy
{{/if}}
{{/if}}
{{else}}
Access Controls
{{/if}}
{{else}}
Access Controls
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>Policy ID</dt>
<dd>
<CopyButton @value={{item.ID}} @name="Policy ID" @position="top-start" /> {{item.ID}}
</dd>
</dl>
</div>
{{/if}}
{{#if (eq (policy/typeof item) 'policy-management')}}
{{ partial 'dc/acls/policies/view'}}
{{else}}
{{ partial 'dc/acls/policies/form'}}
{{/if}}
</BlockSlot>
</AppView>
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>Policy ID</dt>
<dd>
<CopyButton @value={{item.ID}} @name="Policy ID" @position="top-start" /> {{item.ID}}
</dd>
</dl>
</div>
{{/if}}
{{#if (eq (policy/typeof item) 'policy-management')}}
{{ partial 'dc/acls/policies/view'}}
{{else}}
{{ partial 'dc/acls/policies/form'}}
{{/if}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,115 +1,118 @@
{{#if isAuthorized }}
{{page-title 'Policies'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
{{#let
<Route
@name={{routeName}}
@title="Policies"
as |route|>
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
datacenter=(hash
value=(if datacenter (split datacenter ',') undefined)
change=(action (mut datacenter) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
datacenter=(hash
value=(if datacenter (split datacenter ',') undefined)
change=(action (mut datacenter) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Policies
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Policy::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
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
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Policies
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Policy::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="policy"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Policy::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No policies found
{{else}}
Welcome to Policies
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No policies where found matching that search, or you may not have access to view the policies you are searching for.
{{else}}
There don't seem to be any policies, or you may not have access to view policies yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/policy" rel="noopener noreferrer" target="_blank">Documentation on policies</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_LEARN_URL'}}/consul/security-networking/managing-acl-policies" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="policy"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Policy::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No policies found
{{else}}
Welcome to Policies
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No policies where found matching that search, or you may not have access to view the policies you are searching for.
{{else}}
There don't seem to be any policies, or you may not have access to view policies yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/policy" rel="noopener noreferrer" target="_blank">Documentation on policies</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_LEARN_URL'}}/consul/security-networking/managing-acl-policies" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/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,53 +1,50 @@
{{#if isAuthorized }}
{{#if item.ID}}
{{page-title 'Edit Role'}}
<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
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.roles'}}>All Roles</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Role
{{else}}
Edit Role
{{/if}}
{{else}}
{{page-title 'New Role'}}
Access Controls
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Role::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.roles'}}>All Roles</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Role
{{else}}
Edit Role
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>Role ID</dt>
<dd>
<CopyButton @value={{item.ID}} @name="Role ID" @position="top-start" /> {{item.ID}}
</dd>
</dl>
</div>
{{/if}}
{{else}}
Access Controls
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>Role ID</dt>
<dd>
<CopyButton @value={{item.ID}} @name="Role ID" @position="top-start" /> {{item.ID}}
</dd>
</dl>
</div>
{{/if}}
{{ partial 'dc/acls/roles/form'}}
</BlockSlot>
</AppView>
{{ partial 'dc/acls/roles/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,108 +1,110 @@
{{#if isAuthorized }}
{{page-title 'Roles'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Roles"
as |route|>
{{#let
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
)
items
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
as |sort filters items|}}
items
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Role::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Roles
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.roles.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Role::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
as |sort filters items|}}
@sort={{sort}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Role::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Roles
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.acls.roles.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Role::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="role"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Role::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No roles found
{{else}}
Welcome to Roles
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No roles where found matching that search, or you may not have access to view the roles you are searching for.
{{else}}
There don't seem to be any roles, or you may not have access to view roles yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/role" rel="noopener noreferrer" target="_blank">Documentation on roles</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_API_URL'}}/acl/roles.html" rel="noopener noreferrer" target="_blank">Read the API Docs</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="role"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Role::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No roles found
{{else}}
Welcome to Roles
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No roles where found matching that search, or you may not have access to view the roles you are searching for.
{{else}}
There don't seem to be any roles, or you may not have access to view roles yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/acl/role" rel="noopener noreferrer" target="_blank">Documentation on roles</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_API_URL'}}/acl/roles.html" rel="noopener noreferrer" target="_blank">Read the API Docs</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,98 +1,95 @@
{{#if isAuthorized }}
{{#if create}}
{{page-title 'New Token'}}
{{else}}
{{page-title 'Edit Token'}}
{{/if}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
<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
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.tokens'}}>All Tokens</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Token
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.acls.tokens'}}>All Tokens</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if isAuthorized }}
{{#if create }}
New Token
{{else}}
Edit Token
{{/if}}
{{else}}
Edit Token
Access Controls
{{/if}}
{{else}}
Access Controls
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not create)}}
{{#if (not-eq item.AccessorID token.AccessorID)}}
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|>
<button data-test-use type="button" {{ action confirm 'use' item }}>Use</button>
</BlockSlot>
<BlockSlot @name="dialog" as |execute cancel message|>
<p>
{{message}}
</p>
<button type="button" class="type-delete" {{action execute}}>Confirm Use</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot>
</ConfirmationDialog>
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (not create)}}
{{#if (not-eq item.AccessorID token.AccessorID)}}
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|>
<button data-test-use type="button" {{ action confirm 'use' item }}>Use</button>
</BlockSlot>
<BlockSlot @name="dialog" as |execute cancel message|>
<p>
{{message}}
</p>
<button type="button" class="type-delete" {{action execute}}>Confirm Use</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot>
</ConfirmationDialog>
{{/if}}
{{#if (can "duplicate token" item=item)}}
<button data-test-clone type="button" {{ action "clone" item }}>Duplicate</button>
{{/if}}
{{/if}}
{{#if (can "duplicate token" item=item)}}
<button data-test-clone type="button" {{ action "clone" item }}>Duplicate</button>
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy item)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p>
We have upgraded our ACL system by allowing you to create reusable policies which you can then apply to tokens. Don't worry, even though this token was written in the old style, it is still valid. However, we do recommend upgrading your old tokens to the new style. Learn how in our <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.
</p>
</notice.Body>
</Notice>
{{/if}}
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>AccessorID</dt>
<dd>
<CopyButton @value={{item.AccessorID}} @name="AccessorID" @position="top-start" /> {{item.AccessorID}}
</dd>
<dt>Token</dt>
<dd>
<CopyButton @value={{item.SecretID}} @name="Token" @position="top-start" /> <SecretButton>{{item.SecretID}}</SecretButton>
</dd>
{{#if (and (not (token/is-legacy item)) (not create))}}
<dt>Scope</dt>
<dd>
{{if item.Local 'local' 'global' }}
</dd>
{{/if}}
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy item)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p>
We have upgraded our ACL system by allowing you to create reusable policies which you can then apply to tokens. Don't worry, even though this token was written in the old style, it is still valid. However, we do recommend upgrading your old tokens to the new style. Learn how in our <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.
</p>
</notice.Body>
</Notice>
{{/if}}
{{#if (not create) }}
<div class="definition-table">
<dl>
<dt>AccessorID</dt>
<dd>
<CopyButton @value={{item.AccessorID}} @name="AccessorID" @position="top-start" /> {{item.AccessorID}}
</dd>
<dt>Token</dt>
<dd>
<CopyButton @value={{item.SecretID}} @name="Token" @position="top-start" /> <SecretButton>{{item.SecretID}}</SecretButton>
</dd>
{{#if (and (not (token/is-legacy item)) (not create))}}
<dt>Scope</dt>
<dd>
{{if item.Local 'local' 'global' }}
</dd>
{{/if}}
</dl>
</div>
{{/if}}
{{ partial 'dc/acls/tokens/form'}}
</BlockSlot>
</AppView>
</dl>
</div>
{{/if}}
{{ partial 'dc/acls/tokens/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,122 +1,124 @@
{{#if isAuthorized }}
{{page-title 'Tokens'}}
{{else}}
{{page-title 'Access Controls'}}
{{/if}}
<Route
@name={{routeName}}
@title="Tokens"
as |route|>
{{#let
{{#let
(hash
value=(or sortBy "CreateTime:desc")
change=(action (mut sortBy) value="target.selected")
)
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
(hash
value=(or sortBy "CreateTime:desc")
change=(action (mut sortBy) value="target.selected")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
as |sort filters items|}}
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Tokens
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create tokens")}}
<a data-test-create href="{{href-to 'dc.acls.tokens.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Token::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
<AppView
@authorized={{isAuthorized}}
@enabled={{isEnabled}}
@login={{route.model.app.login.open}}
>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{item}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Tokens
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create tokens")}}
<a data-test-create href="{{href-to 'dc.acls.tokens.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy items)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p data-test-notification-update>We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
</notice.Body>
</Notice>
{{/if}}
<DataCollection
@type="token"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Token::List
@items={{collection.items}}
@token={{token}}
@onuse={{route-action 'use'}}
@ondelete={{route-action 'delete'}}
@onlogout={{route-action 'logout'}}
@onclone={{route-action 'clone'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No tokens found
{{else}}
Welcome to ACL Tokens
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No tokens where found matching that search, or you may not have access to view the tokens you are searching for.
{{else}}
There don't seem to be any tokens, or you may not have access to view tokens yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Token::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#if (token/is-legacy items)}}
<Notice
@type="info"
as |notice|>
<notice.Header>
<h2>Update</h2>
</notice.Header>
<notice.Body>
<p data-test-notification-update>We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
</notice.Body>
</Notice>
{{/if}}
<DataCollection
@type="token"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Token::List
@items={{collection.items}}
@token={{token}}
@onuse={{route-action 'use'}}
@ondelete={{route-action 'delete'}}
@onlogout={{route-action 'logout'}}
@onclone={{route-action 'clone'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No tokens found
{{else}}
Welcome to ACL Tokens
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No tokens where found matching that search, or you may not have access to view the tokens you are searching for.
{{else}}
There don't seem to be any tokens, or you may not have access to view tokens yet.
{{/if}}
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,33 +1,33 @@
{{#if item.ID}}
{{page-title 'Edit Intention'}}
{{else}}
{{page-title 'New Intention'}}
{{/if}}
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.intentions'}}>All Intentions</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.IsEditable}}
{{#if item.ID}}
Edit Intention
<Route
@name={{routeName}}
@title={{if item.ID 'Edit Intention' 'New Intention'}}
as |route|>
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.intentions'}}>All Intentions</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.IsEditable}}
{{#if item.ID}}
Edit Intention
{{else}}
New Intention
{{/if}}
{{else}}
New Intention
View Intention
{{/if}}
{{else}}
View Intention
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="content">
<Consul::Intention::Form
@item={{item}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{transition-to 'dc.intentions.index' dc}}
/>
</BlockSlot>
</AppView>
</h1>
</BlockSlot>
<BlockSlot @name="content">
<Consul::Intention::Form
@item={{item}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{transition-to 'dc.intentions.index' dc}}
/>
</BlockSlot>
</AppView>
</Route>

View File

@ -1,122 +1,131 @@
{{page-title 'Intentions'}}
<DataLoader @src={{concat '/' nspace '/' dc '/intentions'}} as |api|>
<Route
@name={{routeName}}
@title="Intentions"
as |route|>
<DataLoader @src={{concat '/' nspace '/' dc '/intentions'}} as |api|>
<BlockSlot @name="error">
<AppError @error={{api.error}} />
</BlockSlot>
<BlockSlot @name="loaded">
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
api.data
as |sort filters items|}}
<AppView>
<BlockSlot @name="header">
<h1>
Intentions <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can 'create intentions')}}
<a data-test-create href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
<BlockSlot @name="error">
<AppError
@error={{api.error}}
@login={{route.model.app.login.open}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.Table />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No intentions found
{{else}}
Welcome to Intentions
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No intentions where found matching that search, or you may not have access to view the intentions you are searching for.
{{else}}
There don't seem to be any intentions, or you may not have access to view intentions yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/intention" rel="noopener noreferrer" target="_blank">Documentation on intentions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/connect" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</BlockSlot>
</AppView>
{{/let}}
</BlockSlot>
</DataLoader>
<BlockSlot @name="loaded">
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
api.data
as |sort filters items|}}
<AppView>
<BlockSlot @name="header">
<h1>
Intentions <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can 'create intentions')}}
<a data-test-create href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.Table />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No intentions found
{{else}}
Welcome to Intentions
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No intentions where found matching that search, or you may not have access to view the intentions you are searching for.
{{else}}
There don't seem to be any intentions, or you may not have access to view intentions yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/intention" rel="noopener noreferrer" target="_blank">Documentation on intentions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/connect" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</BlockSlot>
</AppView>
{{/let}}
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,57 +1,57 @@
{{#if item.Key }}
{{page-title 'Edit Key/Value'}}
{{else}}
{{page-title 'New Key/Value'}}
{{/if}}
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.kv.index'}}>Key / Values</a></li>
{{#if (not-eq parent.Key '/')}}
{{#each (slice 0 -1 (split parent.Key '/')) as |breadcrumb index|}}
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
{{/each}}
{{/if}}
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.Key}}
{{left-trim item.Key parent.Key}}
{{else}}
New Key / Value
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{! if a KV has a session `Session` will always be populated despite any specific session permissions }}
{{#if item.Session}}
<Notice
@type="warning"
data-test-session-warning
as |notice|>
<notice.Body>
<p>
<strong>Warning.</strong> This KV has a lock session. You can edit KV's with lock sessions, but we recommend doing so with care, or not doing so at all. It may negatively impact the active node it's associated with. See below for more details on the Lock Session and see <a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" target="_blank" rel="noopener noreferrer">our documentation</a> for more information.
</p>
</notice.Body>
</Notice>
<Route
@name={{routeName}}
@title={{if item.Key 'Edit Key/Value' 'New Key/Value'}}
as |route|>
<AppView>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.kv.index'}}>Key / Values</a></li>
{{#if (not-eq parent.Key '/')}}
{{#each (slice 0 -1 (split parent.Key '/')) as |breadcrumb index|}}
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
{{/each}}
{{/if}}
<Consul::Kv::Form
@item={{item}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{if (eq parent.Key '/') (transition-to 'dc.kv.index') (transition-to 'dc.kv.folder' parent.Key)}}
@parent={{parent}}
/>
{{! session is slightly different to item.Session as we only have session if you have session:read perms}}
{{#if session}}
<Consul::LockSession::Form
@item={{session}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{action (mut session) undefined}}
/>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if item.Key}}
{{left-trim item.Key parent.Key}}
{{else}}
New Key / Value
{{/if}}
</BlockSlot>
</AppView>
</h1>
</BlockSlot>
<BlockSlot @name="content">
{{! if a KV has a session `Session` will always be populated despite any specific session permissions }}
{{#if item.Session}}
<Notice
@type="warning"
data-test-session-warning
as |notice|>
<notice.Body>
<p>
<strong>Warning.</strong> This KV has a lock session. You can edit KV's with lock sessions, but we recommend doing so with care, or not doing so at all. It may negatively impact the active node it's associated with. See below for more details on the Lock Session and see <a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" target="_blank" rel="noopener noreferrer">our documentation</a> for more information.
</p>
</notice.Body>
</Notice>
{{/if}}
<Consul::Kv::Form
@item={{item}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{if (eq parent.Key '/') (transition-to 'dc.kv.index') (transition-to 'dc.kv.folder' parent.Key)}}
@parent={{parent}}
/>
{{! session is slightly different to item.Session as we only have session if you have session:read perms}}
{{#if session}}
<Consul::LockSession::Form
@item={{session}}
@dc={{dc}}
@nspace={{nspace}}
@onsubmit={{action (mut session) undefined}}
/>
{{/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)}}
@ -115,4 +120,5 @@ as |sort filters items|}}
</DataWriter>
</BlockSlot>
</AppView>
{{/let}}
{{/let}}
</Route>

View File

@ -1,74 +1,78 @@
{{page-title 'Nodes'}}
<EventSource @src={{items}} />
<EventSource @src={{leader}} />
{{#let
<Route
@name={{routeName}}
@title="Nodes"
as |route|>
<EventSource @src={{items}} />
<EventSource @src={{leader}} />
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
<AppView>
<BlockSlot @name="header">
<h1>
Nodes <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Node::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
as |sort filters items|}}
<AppView>
<BlockSlot @name="header">
<h1>
Nodes <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
<Consul::Node::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="node"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Node::List
@items={{collection.items}}
@leader={{leader}}
@filter={{filters}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There don't seem to be any registered nodes, or you may not have access to view nodes yet.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="node"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Node::List
@items={{collection.items}}
@leader={{leader}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There don't seem to be any registered nodes, or you may not have access to view nodes yet.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
</Route>

View File

@ -1,82 +1,90 @@
{{page-title item.Node}}
<DataLoader as |loader|>
<Route
@name={{routeName}}
@title={{item.Node}}
as |route|>
<DataLoader as |loader|>
<BlockSlot @name="data">
<EventSource @src={{item}} @onerror={{action loader.dispatchError}} />
<EventSource @src={{tomography}} />
</BlockSlot>
<BlockSlot @name="data">
<EventSource @src={{item}} @onerror={{action loader.dispatchError}} />
<EventSource @src={{tomography}} />
</BlockSlot>
<BlockSlot @name="error">
<AppError @error={{loader.error}} />
</BlockSlot>
<BlockSlot @name="error">
<AppError
@error={{loader.error}}
@login={{route.model.app.login.open}}
/>
</BlockSlot>
<BlockSlot @name="disconnected" as |Notification|>
{{#if (eq loader.error.status "404")}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="warning notification-update">
<strong>Warning!</strong>
This node no longer exists in the catalog.
</p>
</Notification>
{{else if (eq loader.error.status "403")}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="error notification-update">
<strong>Error!</strong>
You no longer have access to this node
</p>
</Notification>
{{else}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="warning notification-update">
<strong>Warning!</strong>
An error was returned whilst loading this data, refresh to try again.
</p>
</Notification>
{{/if}}
</BlockSlot>
<BlockSlot @name="disconnected" as |Notification|>
{{#if (eq loader.error.status "404")}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="warning notification-update">
<strong>Warning!</strong>
This node no longer exists in the catalog.
</p>
</Notification>
{{else if (eq loader.error.status "403")}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="error notification-update">
<strong>Error!</strong>
You no longer have access to this node
</p>
</Notification>
{{else}}
<Notification @sticky={{true}}>
<p data-notification role="alert" class="warning notification-update">
<strong>Warning!</strong>
An error was returned whilst loading this data, refresh to try again.
</p>
</Notification>
{{/if}}
</BlockSlot>
<BlockSlot @name="loaded">
<AppView>
<BlockSlot @name="notification" as |status type|>
<Consul::LockSession::Notifications
@type={{type}}
@status={{status}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.nodes'}}>All Nodes</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{ item.Node }}
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="nav">
<TabNav @items={{
compact
(array
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))
)
}}/>
</BlockSlot>
<BlockSlot @name="actions">
<CopyButton @value={{item.Address}} @name="Address">{{item.Address}}</CopyButton>
</BlockSlot>
<BlockSlot @name="content">
<Outlet
@name={{routeName}}
as |o|>
{{outlet}}
</Outlet>
</BlockSlot>
</AppView>
<BlockSlot @name="loaded">
<AppView>
<BlockSlot @name="notification" as |status type|>
<Consul::LockSession::Notifications
@type={{type}}
@status={{status}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.nodes'}}>All Nodes</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{ item.Node }}
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="nav">
<TabNav @items={{
compact
(array
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))
)
}}/>
</BlockSlot>
<BlockSlot @name="actions">
<CopyButton @value={{item.Address}} @name="Address">{{item.Address}}</CopyButton>
</BlockSlot>
<BlockSlot @name="content">
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</BlockSlot>
</AppView>
</BlockSlot>
</DataLoader>
</BlockSlot>
</DataLoader>
</Route>

View File

@ -1,70 +1,74 @@
{{#let
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
kind=(hash
value=(if kind (split kind ',') undefined)
change=(action (mut kind) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
item.Checks
item.Checks
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
<DataCollection
@type="health-check"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@items={{collection.items}}
@filter={{filters}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
{{/if}}
<DataCollection
@type="health-check"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@items={{collection.items}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
</Route>

View File

@ -1,13 +1,17 @@
<div class="tab-section">
{{#if item.Meta}}
<Consul::Metadata::List @items={{entries item.Meta}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no metadata.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if item.Meta}}
<Consul::Metadata::List @items={{entries item.Meta}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no metadata.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,26 +1,29 @@
<div class="tab-section">
<div class="definition-table">
<dl>
<dt>
Minimum
</dt>
<dd>
{{format-number tomography.min maximumFractionDigits=2}}ms
</dd>
<dt>
Median
</dt>
<dd>
{{format-number tomography.median maximumFractionDigits=2}}ms
</dd>
<dt>
Maximum
</dt>
<dd>
{{format-number tomography.max maximumFractionDigits=2}}ms
</dd>
</dl>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
<div class="definition-table">
<dl>
<dt>
Minimum
</dt>
<dd>
{{format-number tomography.min maximumFractionDigits=2}}ms
</dd>
<dt>
Median
</dt>
<dd>
{{format-number tomography.median maximumFractionDigits=2}}ms
</dd>
<dt>
Maximum
</dt>
<dd>
{{format-number tomography.max maximumFractionDigits=2}}ms
</dd>
</dl>
</div>
<Consul::Tomography::Graph @distances={{tomography.distances}} />
</div>
<Consul::Tomography::Graph @distances={{tomography.distances}} />
</div>
</Route>

View File

@ -1,71 +1,75 @@
{{#let
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
source=(hash
value=(if source (split source ',') undefined)
change=(action (mut source) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
item.MeshServiceInstances
item.MeshServiceInstances
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::ServiceInstance::SearchBar
@sources={{get (collection items) 'ExternalSources'}}
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::ServiceInstance::SearchBar
@sources={{get (collection items) 'ExternalSources'}}
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
{{! filter out any sidecar proxies }}
<DataCollection
@type="service-instance"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::ServiceInstance::List
@node={{item}}
@routeName="dc.services.show"
@items={{collection.items}}
@checks={{checks}}
@filter={{filters}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no service instances{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
{{/if}}
{{! filter out any sidecar proxies }}
<DataCollection
@type="service-instance"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::ServiceInstance::List
@node={{item}}
@routeName="dc.services.show"
@items={{collection.items}}
@checks={{checks}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This node has no service instances{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
</Route>

View File

@ -1,30 +1,36 @@
<EventSource @src={{sessions}} />
<div class="tab-section">
{{#if (gt sessions.length 0)}}
<Consul::LockSession::List
@items={{sessions}}
@onInvalidate={{action send 'invalidateSession'}}
/>
{{else}}
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
Welcome to Lock Sessions
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between nodes, health checks, and key/value data. There are currently no lock sessions present, or you may not have permission to view lock sessions.
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" rel="noopener noreferrer" target="_blank">Documentation on sessions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{sessions}} />
<div class="tab-section">
{{#if (gt sessions.length 0)}}
<Consul::LockSession::List
@items={{sessions}}
@onInvalidate={{action send 'invalidateSession'}}
/>
{{else}}
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
Welcome to Lock Sessions
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between nodes, health checks, and key/value data. There are currently no lock sessions present, or you may not have permission to view lock sessions.
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" rel="noopener noreferrer" target="_blank">Documentation on sessions</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,33 +1,33 @@
{{#if create }}
{{page-title 'New Namespace'}}
{{else}}
{{page-title 'Edit Namespace'}}
{{/if}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@type={{type}}
@status={{status}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.nspaces'}}>All Namespaces</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if create }}
New Namespace
{{else}}
Edit {{item.Name}}
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="actions">
</BlockSlot>
<BlockSlot @name="content">
{{ partial 'dc/nspaces/form'}}
</BlockSlot>
</AppView>
<Route
@name={{routeName}}
@title={{if create 'New Namespace' 'Edit Namespace'}}
as |route|>
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@type={{type}}
@status={{status}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.nspaces'}}>All Namespaces</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{#if create }}
New Namespace
{{else}}
Edit {{item.Name}}
{{/if}}
</h1>
</BlockSlot>
<BlockSlot @name="actions">
</BlockSlot>
<BlockSlot @name="content">
{{ partial 'dc/nspaces/form'}}
</BlockSlot>
</AppView>
</Route>

View File

@ -1,100 +1,106 @@
{{page-title 'Namespaces'}}
<EventSource @src={{items}} />
{{#let
<Route
@name={{routeName}}
@title='Namespaces'
as |route|>
<EventSource @src={{items}} />
{{#let
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
(hash
value=(or sortBy "Name:asc")
change=(action (mut sortBy) value="target.selected")
)
)
items
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
as |sort filters items|}}
items
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@type={{type}}
@status={{status}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Namespaces
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.nspaces.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Nspace::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
as |sort filters items|}}
@sort={{sort}}
<AppView>
<BlockSlot @name="notification" as |status type item error|>
<Consul::Nspace::Notifications
@type={{type}}
@status={{status}}
@error={{error}}
/>
</BlockSlot>
<BlockSlot @name="header">
<h1>
Namespaces
</h1>
</BlockSlot>
<BlockSlot @name="actions">
<a data-test-create href="{{href-to 'dc.nspaces.create'}}" class="type-create">Create</a>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}
<Consul::Nspace::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="nspace"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Nspace::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No namespaces found
{{else}}
Welcome to Namespaces
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No namespaces where found matching that search, or you may not have access to view the namespaces you are searching for.
{{else}}
There don't seem to be any namespaces, or you may not have access to view namespaces yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/namespace" rel="noopener noreferrer" target="_blank">Documentation on namespaces</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/namespaces/secure-namespaces" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</AppView>
{{/let}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
<DataCollection
@type="nspace"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Nspace::List
@items={{collection.items}}
@ondelete={{route-action 'delete'}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState
@login={{route.model.app.login.open}}
>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No namespaces found
{{else}}
Welcome to Namespaces
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No namespaces where found matching that search, or you may not have access to view the namespaces you are searching for.
{{else}}
There don't seem to be any namespaces, or you may not have access to view namespaces yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/namespace" rel="noopener noreferrer" target="_blank">Documentation on namespaces</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/namespaces/secure-namespaces" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</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,10 +94,12 @@
}}/>
<Outlet
@name={{routeName}}
@model={{route.model}}
as |o|>
{{outlet}}
</Outlet>
</BlockSlot>
</AppView>
</BlockSlot>
</DataLoader>
</DataLoader>
</Route>

View File

@ -1,28 +1,32 @@
<div class="tab-section">
{{#if item.Service.TaggedAddresses }}
<TabularCollection
data-test-addresses
class="consul-tagged-addresses"
@items={{entries item.Service.TaggedAddresses}} as |taggedAddress index|
>
<BlockSlot @name="header">
<th>Tag</th>
<th>Address</th>
</BlockSlot>
<BlockSlot @name="row">
{{#with (object-at 1 taggedAddress) as |address|}}
<td>
{{object-at 0 taggedAddress}}{{#if (and (eq address.Address item.Address) (eq address.Port item.Port))}}&nbsp;<em data-test-address-default>(default)</em>{{/if}}
</td>
<td data-test-address>
{{address.Address}}:{{address.Port}}
</td>
{{/with}}
</BlockSlot>
</TabularCollection>
{{else}}
<p>
There are no additional addresses.
</p>
{{/if}}
</div>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if item.Service.TaggedAddresses }}
<TabularCollection
data-test-addresses
class="consul-tagged-addresses"
@items={{entries item.Service.TaggedAddresses}} as |taggedAddress index|
>
<BlockSlot @name="header">
<th>Tag</th>
<th>Address</th>
</BlockSlot>
<BlockSlot @name="row">
{{#with (object-at 1 taggedAddress) as |address|}}
<td>
{{object-at 0 taggedAddress}}{{#if (and (eq address.Address item.Address) (eq address.Port item.Port))}}&nbsp;<em data-test-address-default>(default)</em>{{/if}}
</td>
<td data-test-address>
{{address.Address}}:{{address.Port}}
</td>
{{/with}}
</BlockSlot>
</TabularCollection>
{{else}}
<p>
There are no additional addresses.
</p>
{{/if}}
</div>
</Route>

View File

@ -1,16 +1,20 @@
<div class="tab-section">
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
<p>
The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation.
</p>
<Consul::ExposedPath::List @items={{proxy.Service.Proxy.Expose.Paths}} @address={{proxy.Address}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our <Action @href={{concat (env 'CONSUL_DOCS_URL') '/connect/registration/service-registration#expose-paths-configuration-reference'}} @external={{true}}>documentation</Action>.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
<p>
The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation.
</p>
<Consul::ExposedPath::List @items={{proxy.Service.Proxy.Expose.Paths}} @address={{proxy.Address}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our <Action @href={{concat (env 'CONSUL_DOCS_URL') '/connect/registration/service-registration#expose-paths-configuration-reference'}} @external={{true}}>documentation</Action>.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</div>
</Route>

View File

@ -1,68 +1,72 @@
{{#let
<Route
@name={{routeName}}
as |route|>
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
status=(hash
value=(if status (split status ',') undefined)
change=(action (mut status) value="target.selectedItems")
)
check=(hash
value=(if check (split check ',') undefined)
change=(action (mut check) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
item.MeshChecks
item.MeshChecks
as |sort filters items|}}
<div class="tab-section">
as |sort filters items|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::HealthCheck::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
<DataCollection
@type="health-check"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@items={{collection.items}}
@filter={{filters}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This instance has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/if}}
</div>
{{/let}}
<DataCollection
@type="health-check"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::HealthCheck::List
@items={{collection.items}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This instance has no health checks{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</div>
{{/let}}
</Route>

View File

@ -1,30 +1,34 @@
<div class="tab-section">
<section class="tags">
<h2>Tags</h2>
{{#if (gt item.Tags.length 0) }}
<TagList @item={{item}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no tags.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</section>
<section class="metadata">
<h2>Meta</h2>
{{#if item.Meta}}
<Consul::Metadata::List @items={{entries item.Meta}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
This instance has no metadata.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</section>
</div>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
<section class="tags">
<h2>Tags</h2>
{{#if (gt item.Tags.length 0) }}
<TagList @item={{item}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no tags.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</section>
<section class="metadata">
<h2>Meta</h2>
{{#if item.Meta}}
<Consul::Metadata::List @items={{entries item.Meta}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
This instance has no metadata.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</section>
</div>
</Route>

View File

@ -1,60 +1,64 @@
<div class="tab-section">
{{#let
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#let
(hash
value=(or sortBy "DestinationName:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
(hash
value=(or sortBy "DestinationName:asc")
change=(action (mut sortBy) value="target.selected")
)
)
proxy.Service.Proxy.Upstreams
(hash
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
proxy.Service.Proxy.Upstreams
@sort={{sort}}
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@searchproperties={{searchProperties}}
@filter={{filters}}
/>
{{/if}}
<DataCollection
@type="upstream-instance"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::UpstreamInstance::List
@items={{collection.items}}
@dc={{dc}}
@nspace={{nspace}}
@sort={{sort}}
@filter={{filters}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This service has no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
</div>
{{/if}}
<DataCollection
@type="upstream-instance"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::UpstreamInstance::List
@items={{collection.items}}
@dc={{dc}}
@nspace={{nspace}}
/>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
This service has no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</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>
</Route>
{{/let}}

View File

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

View File

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

View File

@ -1,9 +1,13 @@
<Consul::Intention::Form
@nspace={{nspace}}
@dc={{dc}}
@src={{src}}
@autofill={{hash
DestinationName=service
}}
@onsubmit={{transition-to 'dc.services.show.intentions.index'}}
/>
<Route
@name={{routeName}}
as |route|>
<Consul::Intention::Form
@nspace={{nspace}}
@dc={{dc}}
@src={{src}}
@autofill={{hash
DestinationName=service
}}
@onsubmit={{transition-to 'dc.services.show.intentions.index'}}
/>
</Route>

View File

@ -1,96 +1,100 @@
<DataLoader
@src={{uri
'/${nspace}/${dc}/intentions/for-service/${slug}'
(hash
nspace=nspace
dc=dc
slug=slug
)
}}
as |api|>
<BlockSlot @name="error">
<ErrorState @error={{api.error}} />
</BlockSlot>
<BlockSlot @name="loaded">
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
<Route
@name={{routeName}}
as |route|>
<DataLoader
@src={{uri
'/${nspace}/${dc}/intentions/for-service/${slug}'
(hash
nspace=nspace
dc=dc
slug=slug
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
}}
as |api|>
<BlockSlot @name="error">
<ErrorState @error={{api.error}} />
</BlockSlot>
<BlockSlot @name="loaded">
{{#let
(hash
value=(or sortBy "Action:asc")
change=(action (mut sortBy) value="target.selected")
)
)
api.data
(hash
access=(hash
value=(if access (split access ',') undefined)
change=(action (mut access) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
as |sort filters items|}}
<div class="tab-section">
{{#if (can 'create intentions')}}
<Portal @target="app-view-actions">
<a data-test-create href={{href-to 'dc.services.show.intentions.create'}} class="type-create">Create</a>
</Portal>
{{/if}}
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
api.data
@sort={{sort}}
as |sort filters items|}}
<div class="tab-section">
{{#if (can 'create intentions')}}
<Portal @target="app-view-actions">
<a data-test-create href={{href-to 'dc.services.show.intentions.create'}} class="type-create">Create</a>
</Portal>
{{/if}}
{{#if (gt items.length 0) }}
<Consul::Intention::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@filter={{filters}}
/>
{{/if}}
@sort={{sort}}
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@check={{search}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.CheckNotice />
<list.Table @routeName="dc.services.show.intentions.edit" />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no intentions {{if (gt items.length 0) 'found '}} for this service.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</div>
{{/let}}
</BlockSlot>
</DataLoader>
@filter={{filters}}
/>
{{/if}}
<DataWriter
@sink={{concat '/' dc '/' nspace '/intention/'}}
@type="intention"
@ondelete={{refresh-route}}
as |writer|>
<BlockSlot @name="content">
<DataCollection
@type="intention"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Intention::List
@items={{collection.items}}
@check={{search}}
@delete={{writer.delete}}
as |list|>
<list.CustomResourceNotice />
<list.CheckNotice />
<list.Table @routeName="dc.services.show.intentions.edit" />
</Consul::Intention::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no intentions {{if (gt items.length 0) 'found '}} for this service.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
</BlockSlot>
</DataWriter>
</div>
{{/let}}
</BlockSlot>
</DataLoader>
</Route>

View File

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

View File

@ -1,68 +1,72 @@
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
<p>
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>
<DataCollection
@type="service"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Service::List
@nspace={{nspace}}
@items={{collection.items}}
>
</Consul::Service::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no linked services{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
</div>
@filter={{filters}}
/>
{{/if}}
<p>
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>
<DataCollection
@type="service"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Service::List
@nspace={{nspace}}
@items={{collection.items}}
>
</Consul::Service::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no linked services{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
</div>
</Route>

View File

@ -1,15 +1,19 @@
<div class="tab-section">
{{#let (flatten (map-by "Tags" items)) as |tags|}}
{{#if (gt tags.length 0) }}
<TagList @item={{hash Tags=tags}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no tags.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
{{/let}}
</div>
<Route
@name={{routeName}}
as |route|>
<div class="tab-section">
{{#let (flatten (map-by "Tags" items)) as |tags|}}
{{#if (gt tags.length 0) }}
<TagList @item={{hash Tags=tags}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
There are no tags.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
{{/let}}
</div>
</Route>

View File

@ -1,39 +1,43 @@
<EventSource @src={{topology}} />
<div class="tab-section">
{{#if (and (eq topology.Upstreams.length 0) (eq topology.Downstreams.length 0))}}
<EmptyState>
<BlockSlot @name="header">
<h2>
No dependencies
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
This service has neither downstreams nor upstreams, which means that no services are configured to connect with it. Add upstreams and intentions to ensure this service is connected with the rest of your service mesh.
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#complete-configuration-example" rel="noopener noreferrer" target="_blank">Documentation on upstreams</a>
</li>
</BlockSlot>
</EmptyState>
{{else}}
{{#if topology.FilteredByACLs}}
<TopologyMetrics::Notice::LimitedAccess />
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{topology}} />
<div class="tab-section">
{{#if (and (eq topology.Upstreams.length 0) (eq topology.Downstreams.length 0))}}
<EmptyState>
<BlockSlot @name="header">
<h2>
No dependencies
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
This service has neither downstreams nor upstreams, which means that no services are configured to connect with it. Add upstreams and intentions to ensure this service is connected with the rest of your service mesh.
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#complete-configuration-example" rel="noopener noreferrer" target="_blank">Documentation on upstreams</a>
</li>
</BlockSlot>
</EmptyState>
{{else}}
{{#if topology.FilteredByACLs}}
<TopologyMetrics::Notice::LimitedAccess />
{{/if}}
<TopologyMetrics
@nspace={{nspace}}
@dc={{dc.Name}}
@service={{items.firstObject}}
@topology={{topology}}
@metricsHref={{render-template urls.service (hash
Datacenter=dc.Name
Service=items.firstObject
)}}
@isRemoteDC={{not dc.Local}}
@hasMetricsProvider={{hasMetricsProvider}}
@oncreate={{route-action 'createIntention'}}
/>
{{/if}}
<TopologyMetrics
@nspace={{nspace}}
@dc={{dc.Name}}
@service={{items.firstObject}}
@topology={{topology}}
@metricsHref={{render-template urls.service (hash
Datacenter=dc.Name
Service=items.firstObject
)}}
@isRemoteDC={{not dc.Local}}
@hasMetricsProvider={{hasMetricsProvider}}
@oncreate={{route-action 'createIntention'}}
/>
{{/if}}
</div>
</div>
</Route>

View File

@ -1,68 +1,72 @@
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
<Route
@name={{routeName}}
as |route|>
<EventSource @src={{items}} />
<div class="tab-section">
{{#let
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
(hash
value=(or sortBy "Status:asc")
change=(action (mut sortBy) value="target.selected")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
(hash
instance=(hash
value=(if instance (split instance ',') undefined)
change=(action (mut instance) value="target.selectedItems")
)
searchproperty=(hash
value=(if (not-eq searchproperty undefined)
(split searchproperty ',')
searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
change=(action (mut searchproperty) value="target.selectedItems")
default=searchProperties
)
)
items
items
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
<p>
Upstreams are services that may receive traffic from this gateway. Learn more about configuring gateways in our <a href="{{env 'CONSUL_DOCS_URL'}}/connect/ingress-gateway" target="_blank" rel="noopener noreferrer">documentation</a>.
</p>
<DataCollection
@type="service"
@sort={{sort.value}}
@filters={{filters}}
as |sort filters items|}}
{{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Upstream::List
@items={{collection.items}}
@dc={{dc}}
@nspace={{nspace}}
>
</Consul::Upstream::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
</div>
@onsearch={{action (mut search) value="target.value"}}
@sort={{sort}}
@filter={{filters}}
/>
{{/if}}
<p>
Upstreams are services that may receive traffic from this gateway. Learn more about configuring gateways in our <a href="{{env 'CONSUL_DOCS_URL'}}/connect/ingress-gateway" target="_blank" rel="noopener noreferrer">documentation</a>.
</p>
<DataCollection
@type="service"
@sort={{sort.value}}
@filters={{filters}}
@search={{search}}
@items={{items}}
as |collection|>
<collection.Collection>
<Consul::Upstream::List
@items={{collection.items}}
@dc={{dc}}
@nspace={{nspace}}
>
</Consul::Upstream::List>
</collection.Collection>
<collection.Empty>
<EmptyState>
<BlockSlot @name="body">
<p>
There are no upstreams{{#if (gt items.length 0)}} matching that search{{/if}}.
</p>
</BlockSlot>
</EmptyState>
</collection.Empty>
</DataCollection>
{{/let}}
</div>
</Route>

View File

@ -1,5 +1,10 @@
<Outlet
<Route
@name={{routeName}}
as |o|>
{{outlet}}
</Outlet>
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