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:
parent
af78561018
commit
489b60105f
|
@ -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>
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
@error={{hash
|
||||
status='403'
|
||||
}}
|
||||
@allowLogin={{true}}
|
||||
@login={{login}}
|
||||
/>
|
||||
{{else}}
|
||||
<YieldSlot @name="content">{{yield}}</YieldSlot>
|
||||
|
|
|
@ -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"}}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
```
|
|
@ -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}}
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
%empty-state > ul > li > label > button {
|
||||
@extend %empty-state-anchor;
|
||||
}
|
||||
%empty-state label {
|
||||
%empty-state div > button {
|
||||
@extend %primary-button;
|
||||
}
|
|
@ -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;
|
|
@ -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}}
|
||||
/>
|
||||
```
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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>
|
||||
|
||||
```
|
|
@ -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}}
|
|
@ -21,8 +21,5 @@ export default Component.extend(Slotted, {
|
|||
close: function() {
|
||||
this.dialog.hide();
|
||||
},
|
||||
change: function(e) {
|
||||
this.actions.open.call(this);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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 />
|
||||
```
|
|
@ -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 |
|
|
@ -1,5 +1,6 @@
|
|||
{{yield}}
|
||||
<fieldset
|
||||
class="policy-form"
|
||||
disabled={{if (not (can "write policy" item=item)) "disabled"}}
|
||||
...attributes
|
||||
>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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]',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
)}}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
color: $white;
|
||||
background-color: $blue-500;
|
||||
}
|
||||
%skip-links button,
|
||||
%skip-links a {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import './empty-state/index';
|
||||
@import 'consul-ui/components/empty-state/index';
|
||||
.empty-state {
|
||||
@extend %empty-state;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
{{outlet}}
|
||||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
<Outlet
|
||||
@name={{routeName}}
|
||||
@model={{route.model}}
|
||||
as |o|>
|
||||
{{outlet}}
|
||||
</Outlet>
|
||||
</Route>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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)}}
|
||||
|
|
|
@ -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>
|
|
@ -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))}} <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))}} <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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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}}
|
|
@ -1,6 +1,6 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
>
|
||||
as |route|>
|
||||
<div class="tab-section">
|
||||
{{#let
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue