ui: Upgrade AuthDialog (#11913)

- Move AuthDialog to use a Glimmer Component plus native named blocks/slots.
- Unravel the Auth* contextual components, there wasn't a lot of point having them as contextual components and now the AuthDialog (non-view-specific state machine component) can be used entirely separately from the view-specific components (AuthForm and AuthProfile).
- Move all the ACL related components that are in the main app chrome/navigation (our HashicorpConsul component) in our consul-acls sub package/module (which will eventually be loaded on demand only when ACLs are enabled)
This commit is contained in:
John Cowen 2022-01-07 19:08:25 +00:00 committed by GitHub
parent a08f2927fd
commit 86e885fc5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 614 additions and 563 deletions

View File

@ -0,0 +1,79 @@
<li
class="acls-separator"
role="separator"
>
Access Controls
{{#if (not (can "use acls"))}}
<span
{{tooltip "ACLs are not currently enabled in this cluster"}}
></span>
{{/if}}
</li>
<li
data-test-main-nav-tokens
class={{if (is-href 'dc.acls.tokens' @dc.Name) 'is-active'}}
>
<a
href={{href-to 'dc.acls.tokens' @dc.Name}}
>
Tokens
</a>
</li>
{{#if (can "read acls")}}
<li
data-test-main-nav-policies
class={{if (is-href 'dc.acls.policies' @dc.Name) 'is-active'}}
>
<a
href={{href-to 'dc.acls.policies' @dc.Name}}
>
Policies
</a>
</li>
<li
data-test-main-nav-roles
class={{if (is-href 'dc.acls.roles' @dc.Name) 'is-active'}}
>
<a
href={{href-to 'dc.acls.roles' @dc.Name}}
>
Roles
</a>
</li>
<li
data-test-main-nav-auth-methods
class={{if (is-href 'dc.acls.auth-methods' @dc.Name) 'is-active'}}
>
<a
href={{href-to 'dc.acls.auth-methods' @dc.Name}}
>
Auth Methods
</a>
</li>
{{else if (not (can "use acls"))}}
<li
data-test-main-nav-policies
class={{if (is-href 'dc.acls.policies' @dc.Name) 'is-active'}}
>
<span>
Policies
</span>
</li>
<li
data-test-main-nav-roles
class={{if (is-href 'dc.acls.roles' @dc.Name) 'is-active'}}
>
<span>
Roles
</span>
</li>
<li
data-test-main-nav-auth-methods
class={{if (is-href 'dc.acls.auth-methods' @dc.Name) 'is-active'}}
>
<span>
Auth Methods
</span>
</li>
{{/if}}

View File

@ -0,0 +1,158 @@
{{#if (can 'use acls')}}
<li data-test-main-nav-auth>
<AuthDialog
@src={{uri 'settings://consul:token'}}
@sink={{uri 'settings://consul:token'}}
@onchange={{this.reauthorize}}
>
<:unauthorized as |authDialog|>
<Portal @target="app-before-skip-links">
<Action
{{on "click" (optional this.modal.open)}}
>
Login
</Action>
</Portal>
<Action
{{on "click" (optional this.modal.open)}}
>
Log in
</Action>
<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>
</BlockSlot>
<BlockSlot @name="body">
<AuthForm
@dc={{@dc.Name}}
@partition={{@partition}}
@nspace={{@nspace}}
@onsubmit={{action authDialog.login value="data"}}
as |authForm|>
<Ref
@target={{this}}
@name="authForm"
@value={{authForm}}
/>
{{#if (can "use SSO")}}
<authForm.Method @matches="sso">
<OidcSelect
@dc={{@dc.Name}}
@nspace={{@nspace}}
@disabled={{authForm.disabled}}
@onchange={{authForm.submit}}
@onerror={{authForm.error}}
/>
</authForm.Method>
{{/if}}
</AuthForm>
</BlockSlot>
<BlockSlot @name="actions">
<Action
{{on "click" modal.close}}
>
Continue without logging in
</Action>
</BlockSlot>
</ModalDialog>
</:unauthorized>
<:authorized as |authDialog|>
<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>
</BlockSlot>
<BlockSlot @name="body">
<AuthForm
@dc={{@dc.Name}}
@nspace={{@nspace}}
@partition={{@partition}}
@onsubmit={{action authDialog.login value="data"}}
as |authForm|>
<Ref
@target={{this}}
@name="authForm"
@value={{authForm}}
/>
</AuthForm>
</BlockSlot>
<BlockSlot @name="actions">
<Action
{{on 'click' modal.close}}
>
Continue without logging in
</Action>
</BlockSlot>
</ModalDialog>
<Portal @target="app-before-skip-links">
<Action
{{on "click" (optional authDialog.logout)}}
>
Logout
</Action>
</Portal>
<PopoverMenu @position="right" as |components api|>
<BlockSlot @name="trigger">
Logout
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
{{!TODO: It might be nice to use one of our recursive components here}}
{{#if @token.AccessorID}}
<li role="none">
<AuthProfile
@item={{@token}}
/>
</li>
<MenuSeparator />
{{/if}}
<MenuItem
class="dangerous"
@onclick={{action authDialog.logout}}
>
<BlockSlot @name="label">
Logout
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
</:authorized>
</AuthDialog>
</li>
{{yield
(hash
modal=this.modal
)
}}
{{/if}}

View File

@ -0,0 +1,20 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class ConsulAclsTokensSelector extends Component {
@action
open() {
this.authForm.focus();
}
@action
close() {
this.authForm.reset();
}
@action
reauthorize(e) {
this.modal.close();
this.args.onchange(e);
}
}

View File

@ -1,8 +0,0 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
export default class AuthenticateAbility extends BaseAbility {
@service('env') env;
get can() {
return this.env.var('CONSUL_ACLS_ENABLED');
}
}

View File

@ -1,63 +1,55 @@
---
class: ember
---
# AuthDialog
```hbs preview-template
<AuthDialog
@dc={{'dc-1'}}
@nspace={{'default'}}
@partition={{'default'}}
@onchange={{action (noop)}}
as |api components|>
{{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}}
<BlockSlot @name="unauthorized">
Here's the login form:
<AuthForm />
</BlockSlot>
<BlockSlot @name="authorized">
Here's your profile:
<AuthProfile />
<button onclick={{action api.logout}}>Logout</button>
</BlockSlot>
{{/let}}
</AuthDialog>
```
### Arguments
A component to help orchestrate a login/logout flow.
```hbs preview-template
<AuthDialog
@src={{uri 'settings://consul:token'}}
@sink={{uri 'settings://consul:token'}}
@onchange={{action (noop)}}
>
<:unauthorized as |api|>
<AuthForm
@onsubmit={{action api.login value="data"}}
/>
</:unauthorized>
<:authorized as |api|>
<AuthProfile
@item={{api.token}}
/>
<button
{{on 'click' (fn api.logout)}}
>
Logout
</button>
</:authorized>
</AuthDialog>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `dc` | `String` | | The name of the current datacenter |
| `nspace` | `String` | | The name of the current namespace |
| `partition` | `String` | | The name of the current partition |
| `onchange` | `Function` | | An action to fire when the users token has changed (logged in/logged out/token changed) |
| `src` | `URI` | | DataSource URI used to retrive/watch for changes on the users token |
| `sink` | `URI` | | DataSink URI used to save the users token to |
### Methods/Actions/api
## Exports
| Method/Action | Description |
| --- | --- |
| `login` | Login with a specified token |
| `logout` | Logout (delete token) |
| `token` | The current token itself (as a property not a method) |
| Name | Type | Description |
| --- | --- | --- |
| `login` | `Function` | Login with a specified token |
| `logout` | `Function` | Logout (delete token) |
| `token` | `Token` | The current token itself |
### Components
| Name | Description |
| --- | --- |
| [`AuthForm`](../auth-form/README.mdx) | Renders an Authorization form |
| [`AuthProfile`](../auth-profile/README.mdx) | Renders a User Profile |
### Slots
## Slots
| Name | Description |
| --- | --- |
| `unauthorized` | This slot is only rendered when the user doesn't have a token |
| `authorized` | This slot is only rendered whtn the user has a token.|
| `authorized` | This slot is only rendered when the user has a token.|
### See
## See
- [Component Source Code](./index.js)
- [Template Source Code](./index.hbs)

View File

@ -18,39 +18,29 @@ as |State Guard Action dispatch state|>
{{! This DataSource just permanently listens to any changes to the users }}
{{! token, whether thats a new token, a changed token or a deleted token }}
<DataSource
@src={{uri 'settings://consul:token'}}
@src={{@src}}
@onchange={{queue (action (mut token) value="data") (action dispatch "CHANGE") (action (mut previousToken) value="data")}}
/>
{{! This DataSink is just used for logging in from the form, }}
{{! or logging out via the exposed logout function }}
<DataSink
@sink={{uri "settings://consul:token"}}
@sink={{@sink}}
as |sink|
>
{{yield}}
{{#let (hash
login=(action sink.open)
logout=(action sink.open null)
token=token
) (hash
AuthProfile=(component 'auth-profile' item=token)
AuthForm=(component 'auth-form'
dc=dc
partition=partition
nspace=nspace
onsubmit=(action sink.open value="data"))
) as |api components|}}
) as |api|}}
<State @matches="authorized">
{{#yield-slot name="authorized"}}
{{yield api components}}
{{/yield-slot}}
{{yield api to="authorized"}}
</State>
<State @matches="unauthorized">
{{#yield-slot name="unauthorized"}}
{{yield api components}}
{{/yield-slot}}
{{yield api to="unauthorized"}}
</State>
{{/let}}
</DataSink>
</StateChart>

View File

@ -1,42 +1,45 @@
import Component from '@ember/component';
import Slotted from 'block-slots';
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import { get, action } from '@ember/object';
import chart from './chart.xstate';
export default Component.extend(Slotted, {
tagName: '',
repo: service('repository/oidc-provider'),
init: function() {
this._super(...arguments);
export default class AuthDialog extends Component {
@service('repository/oidc-provider') repo;
constructor() {
super(...arguments);
this.chart = chart;
},
actions: {
hasToken: function() {
return typeof this.token.AccessorID !== 'undefined';
},
login: function() {
let prev = get(this, 'previousToken.AccessorID');
let current = get(this, 'token.AccessorID');
if (prev === null) {
prev = get(this, 'previousToken.SecretID');
}
if (current === null) {
current = get(this, 'token.SecretID');
}
let type = 'authorize';
if (typeof prev !== 'undefined' && prev !== current) {
type = 'use';
}
this.onchange({ data: get(this, 'token'), type: type });
},
logout: function() {
if (typeof get(this, 'previousToken.AuthMethod') !== 'undefined') {
// we are ok to fire and forget here
this.repo.logout(get(this, 'previousToken.SecretID'));
}
this.previousToken = null;
this.onchange({ data: null, type: 'logout' });
},
},
});
}
@action
hasToken() {
return typeof this.token.AccessorID !== 'undefined';
}
@action
login() {
let prev = get(this, 'previousToken.AccessorID');
let current = get(this, 'token.AccessorID');
if (prev === null) {
prev = get(this, 'previousToken.SecretID');
}
if (current === null) {
current = get(this, 'token.SecretID');
}
let type = 'authorize';
if (typeof prev !== 'undefined' && prev !== current) {
type = 'use';
}
this.args.onchange({ data: get(this, 'token'), type: type });
}
@action
logout() {
if (typeof get(this, 'previousToken.AuthMethod') !== 'undefined') {
// we are ok to fire and forget here
this.repo.logout(get(this, 'previousToken.SecretID'));
}
this.previousToken = null;
this.args.onchange({ data: null, type: 'logout' });
}
}

View File

@ -1,23 +1,22 @@
---
class: ember
---
# AuthProfile
```hbs preview-template
<AuthProfile @item={{hash AccessorID='123456-1234567-123456'}} />
```
A straightforward partial-like component for rendering a user profile.
Only the last 8 characters are shown.
### Arguments
```hbs preview-template
<AuthProfile
@item={{hash AccessorID='123456-1234567-123456'}}
/>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `item` | `Object` | | A Consul shaped token object (currently only requires an AccessorID property to be set |
| `item` | `Token` | | A token object (currently only requires an AccessorID property to be set |
### See
## See
- [Component Source Code](./index.js)
- [Template Source Code](./index.hbs)

View File

@ -1,9 +1,12 @@
<dl>
<dl
class="auth-profile"
...attributes
>
<dt>
<span>My ACL Token</span><br />
AccessorID
</dt>
<dd>
{{substr item.AccessorID -8}}
{{string-substring @item.AccessorID (sub @item.AccessorID.length 8)}}
</dd>
</dl>

View File

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

View File

@ -0,0 +1,19 @@
.auth-profile {
padding: 0.9em 1em;
}
.auth-profile {
@extend %p2;
}
.auth-profile dt span {
font-weight: var(--typo-weight-normal);
}
.auth-profile dt {
font-weight: var(--typo-weight-bold);
}
.auth-profile dt,
.auth-profile dd {
color: rgb(var(--tone-gray-800));
}
.auth-profile dt span {
color: rgb(var(--tone-gray-600));
}

View File

@ -1,397 +1,263 @@
{{#let (unique-id) as |guid|}}
<App
class="hashicorp-consul"
...attributes
<App
class="hashicorp-consul"
...attributes
>
<:notifications as |app|>
{{#each flashMessages.queue as |flash|}}
<app.Notification
@delay={{sub flash.timeout flash.extendedTimeout}}
@sticky={{flash.sticky}}
>
<:notifications as |app|>
{{#each flashMessages.queue as |flash|}}
<app.Notification
@delay={{sub flash.timeout flash.extendedTimeout}}
@sticky={{flash.sticky}}
>
{{#if flash.dom}}
{{{flash.dom}}}
{{else}}
{{#if flash.dom}}
{{{flash.dom}}}
{{else}}
{{#let (lowercase flash.type) (lowercase flash.action) as |status type|}}
<Notice
role="alert"
class={{concat status ' notification-' type}}
data-notification
@type={{status}}
as |notice|>
<notice.Header>
<strong>
{{capitalize status}}!
</strong>
</notice.Header>
<notice.Body>
<p>
{{#if (eq type 'logout')}}
{{#if (eq status 'success') }}
You are now logged out.
{{else}}
There was an error logging out.
{{/if}}
{{else if (eq type 'authorize')}}
{{#if (eq status 'success') }}
You are now logged in.
{{else}}
There was an error, please check your SecretID/Token
{{/if}}
<Notice
role="alert"
class={{concat status ' notification-' type}}
data-notification
@type={{status}}
as |notice|>
<notice.Header>
<strong>
{{capitalize status}}!
</strong>
</notice.Header>
<notice.Body>
<p>
{{#if (eq type 'logout')}}
{{#if (eq status 'success') }}
You are now logged out.
{{else}}
{{#if (or (eq type 'use') (eq flash.model 'token'))}}
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'intention')}}
<Consul::Intention::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'role')}}
<Consul::Role::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'policy')}}
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{/if}}
There was an error logging out.
{{/if}}
</p>
</notice.Body>
</Notice>
{{else if (eq type 'authorize')}}
{{#if (eq status 'success') }}
You are now logged in.
{{else}}
There was an error, please check your SecretID/Token
{{/if}}
{{else}}
{{#if (or (eq type 'use') (eq flash.model 'token'))}}
<Consul::Token::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'intention')}}
<Consul::Intention::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'role')}}
<Consul::Role::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{else if (eq flash.model 'policy')}}
<Consul::Policy::Notifications
@type={{type}}
@status={{status}}
@item={{flash.item}}
@error={{flash.error}}
/>
{{/if}}
{{/if}}
</p>
</notice.Body>
</Notice>
{{/let}}
{{/if}}
</app.Notification>
{{/each}}
{{/if}}
</app.Notification>
{{/each}}
</:notifications>
<:home-nav>
<a
href={{href-to 'index'}}
><Consul::Logo /></a>
</:home-nav>
</:notifications>
<:main-nav>
<ul>
<:home-nav>
<a
href={{href-to 'index'}}
><Consul::Logo /></a>
</:home-nav>
<:main-nav>
<ul>
<li
class="dcs"
data-test-datacenter-menu
>
<PopoverMenu
aria-label="Datacenter"
@position="left"
as |components|>
<BlockSlot @name="trigger">
{{@dc.Name}}
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<DataSource
@src={{uri '/*/*/*/datacenters'}}
@onchange={{action (mut @dcs) value="data"}}
@loading="lazy"
/>
{{#each (sort-by 'Name' @dcs) as |item|}}
<MenuItem
data-test-datacenter-picker
class={{concat
(if (eq @dc.Name item.Name) 'is-active')
(if item.Local ' is-local')
(if item.Primary ' is-primary')
}}
@href={{href-to '.' params=(hash
dc=item.Name
partition=undefined
nspace=(if (gt @nspace.length 0) @nspace undefined)
)}}
>
<BlockSlot @name="label">
{{item.Name}}
{{#if item.Primary}}
<span>Primary</span>
{{/if}}
{{#if item.Local}}
<span>Local</span>
{{/if}}
</BlockSlot>
</MenuItem>
{{/each}}
{{/let}}
</BlockSlot>
</PopoverMenu>
</li>
<Consul::Partition::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@partitions={{this.partitions}}
@onchange={{action (mut this.partitions) value="data"}}
/>
<Consul::Nspace::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@nspaces={{this.nspaces}}
@onchange={{action (mut this.nspaces) value="data"}}
/>
{{#if (can "read services")}}
<li data-test-main-nav-services class={{if (is-href 'dc.services' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.services' @dc.Name}}>Services</a>
</li>
{{/if}}
{{#if (can "read nodes")}}
<li data-test-main-nav-nodes class={{if (is-href 'dc.nodes' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.nodes' @dc.Name}}>Nodes</a>
</li>
{{/if}}
{{#if (can "read kv")}}
<li data-test-main-nav-kvs class={{if (is-href 'dc.kv' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.kv' @dc.Name}}>Key/Value</a>
</li>
{{/if}}
{{#if (can "read intentions")}}
<li data-test-main-nav-intentions class={{if (is-href 'dc.intentions' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.intentions' @dc.Name}}>Intentions</a>
</li>
{{/if}}
<Consul::Acl::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
/>
</ul>
</:main-nav>
<:complementary-nav>
<ul>
<Debug::Navigation />
<li
class="dcs"
data-test-datacenter-menu
data-test-main-nav-help
>
<PopoverMenu
aria-label="Datacenter"
@position="left"
as |components|>
<PopoverMenu @position="right" as |components|>
<BlockSlot @name="trigger">
{{@dc.Name}}
Help
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<DataSource
@src={{uri '/*/*/*/datacenters'}}
@onchange={{action (mut @dcs) value="data"}}
@loading="lazy"
/>
{{#each (sort-by 'Name' @dcs) as |item|}}
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<MenuSeparator>
<BlockSlot @name="label">
Consul v{{env 'CONSUL_VERSION'}}
</BlockSlot>
</MenuSeparator>
<MenuItem
data-test-datacenter-picker
class={{concat
(if (eq @dc.Name item.Name) 'is-active')
(if item.Local ' is-local')
(if item.Primary ' is-primary')
}}
@href={{href-to '.' params=(hash
dc=item.Name
partition=undefined
nspace=(if (gt @nspace.length 0) @nspace undefined)
)}}
class="docs-link"
@href={{env 'CONSUL_DOCS_URL'}}
>
<BlockSlot @name="label">
{{item.Name}}
{{#if item.Primary}}
<span>Primary</span>
{{/if}}
{{#if item.Local}}
<span>Local</span>
{{/if}}
Documentation
</BlockSlot>
</MenuItem>
{{/each}}
{{/let}}
<MenuItem
class="learn-link"
@href={{concat (env 'CONSUL_DOCS_LEARN_URL') '/consul'}}
>
<BlockSlot @name="label">
HashiCorp Learn
</BlockSlot>
</MenuItem>
<MenuSeparator />
<MenuItem
class="learn-link"
@href={{env 'CONSUL_REPO_ISSUES_URL'}}
>
<BlockSlot @name="label">
Provide Feedback
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
</li>
<Consul::Partition::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@partitions={{this.partitions}}
@onchange={{action (mut this.partitions) value="data"}}
/>
<Consul::Nspace::Selector
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@nspaces={{this.nspaces}}
@onchange={{action (mut this.nspaces) value="data"}}
/>
{{#if (can "read services")}}
<li data-test-main-nav-services class={{if (is-href 'dc.services' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.services' @dc.Name}}>Services</a>
</li>
{{/if}}
{{#if (can "read nodes")}}
<li data-test-main-nav-nodes class={{if (is-href 'dc.nodes' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.nodes' @dc.Name}}>Nodes</a>
</li>
{{/if}}
{{#if (can "read kv")}}
<li data-test-main-nav-kvs class={{if (is-href 'dc.kv' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.kv' @dc.Name}}>Key/Value</a>
</li>
{{/if}}
{{#if (can "read intentions")}}
<li data-test-main-nav-intentions class={{if (is-href 'dc.intentions' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.intentions' @dc.Name}}>Intentions</a>
</li>
{{/if}}
<li class="acls-separator" role="separator">
Access Controls
{{#if (not (can "use acls"))}}
<span
{{tooltip "ACLs are not currently enabled in this cluster"}}
></span>
{{/if}}
</li>
<li data-test-main-nav-tokens class={{if (is-href 'dc.acls.tokens' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.acls.tokens' @dc.Name}}>Tokens</a>
</li>
{{#if (can "read acls")}}
<li data-test-main-nav-policies class={{if (is-href 'dc.acls.policies' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.acls.policies' @dc.Name}}>Policies</a>
</li>
<li data-test-main-nav-roles class={{if (is-href 'dc.acls.roles' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.acls.roles' @dc.Name}}>Roles</a>
</li>
<li data-test-main-nav-auth-methods class={{if (is-href 'dc.acls.auth-methods' @dc.Name) 'is-active'}}>
<a href={{href-to 'dc.acls.auth-methods' @dc.Name}}>Auth Methods</a>
</li>
{{else if (not (can "use acls"))}}
<li data-test-main-nav-policies class={{if (is-href 'dc.acls.policies' @dc.Name) 'is-active'}}>
<span>Policies</span>
</li>
<li data-test-main-nav-roles class={{if (is-href 'dc.acls.roles' @dc.Name) 'is-active'}}>
<span>Roles</span>
</li>
<li data-test-main-nav-auth-methods class={{if (is-href 'dc.acls.auth-methods' @dc.Name) 'is-active'}}>
<span>Auth Methods</span>
</li>
{{/if}}
</ul>
</:main-nav>
<:complementary-nav>
<ul>
<Debug::Navigation />
<li data-test-main-nav-help>
<PopoverMenu @position="right" as |components|>
<BlockSlot @name="trigger">
Help
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
<MenuSeparator>
<BlockSlot @name="label">
Consul v{{env 'CONSUL_VERSION'}}
</BlockSlot>
</MenuSeparator>
<MenuItem
class="docs-link"
@href={{env 'CONSUL_DOCS_URL'}}
>
<BlockSlot @name="label">
Documentation
</BlockSlot>
</MenuItem>
<MenuItem
class="learn-link"
@href={{concat (env 'CONSUL_DOCS_LEARN_URL') '/consul'}}
>
<BlockSlot @name="label">
HashiCorp Learn
</BlockSlot>
</MenuItem>
<MenuSeparator />
<MenuItem
class="learn-link"
@href={{env 'CONSUL_REPO_ISSUES_URL'}}
>
<BlockSlot @name="label">
Provide Feedback
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
</li>
<li data-test-main-nav-settings class={{if (is-href 'settings') 'is-active'}}>
<a href={{href-to 'settings' params=(hash
<li
data-test-main-nav-settings
class={{if (is-href 'settings') 'is-active'}}
>
<a
href={{href-to 'settings' params=(hash
nspace=undefined
partition=undefined
)}}>Settings</a>
</li>
{{#if (can 'authenticate')}}
<li data-test-main-nav-auth>
<AuthDialog
@dc={{@dc.Name}}
@nspace={{@nspace}}
@partition={{@partition}}
@onchange={{this.reauthorize}} as |authDialog components|
)}}
>
{{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}}
<BlockSlot @name="unauthorized">
<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)}}
>
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>
</BlockSlot>
<BlockSlot @name="body">
<AuthForm as |authForm|>
<Ref
@target={{this}}
@name="authForm"
@value={{authForm}}
/>
{{#if (can "use SSO")}}
<authForm.Method @matches="sso">
<OidcSelect
@dc={{@dc.Name}}
@nspace={{@nspace}}
@disabled={{authForm.disabled}}
@onchange={{authForm.submit}}
@onerror={{authForm.error}}
/>
</authForm.Method>
{{/if}}
</AuthForm>
</BlockSlot>
<BlockSlot @name="actions">
<button type="button"
{{on "click" modal.close}}
>
Continue without logging in
</button>
</BlockSlot>
</ModalDialog>
</BlockSlot>
<BlockSlot @name="authorized">
<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>
</BlockSlot>
<BlockSlot @name="body">
<AuthForm as |authForm|>
<Ref @target={{this}} @name="authForm" @value={{authForm}} />
</AuthForm>
</BlockSlot>
<BlockSlot @name="actions">
<button type="button" onclick={{action modal.close}}>
Continue without logging in
</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
</BlockSlot>
<BlockSlot @name="menu">
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
{{!TODO: It might be nice to use one of our recursive components here}}
{{#if authDialog.token.AccessorID}}
<li role="none">
<AuthProfile />
</li>
<MenuSeparator />
{{/if}}
<MenuItem
class="dangerous"
@onclick={{action authDialog.logout}}
>
<BlockSlot @name="label">
Logout
</BlockSlot>
</MenuItem>
{{/let}}
</BlockSlot>
</PopoverMenu>
</BlockSlot>
{{/let}}
</AuthDialog>
</li>
{{/if}}
</ul>
</:complementary-nav>
Settings
</a>
</li>
<Consul::Token::Selector
@token={{@user.token}}
@dc={{@dc}}
@partition={{@partition}}
@nspace={{@nspace}}
@onchange={{@onchange}}
as |modal|>
{{did-insert (set this 'modal')}}
</Consul::Token::Selector>
</ul>
</:complementary-nav>
<:main>
{{yield (hash
login=(if (env 'CONSUL_ACLS_ENABLED') this.modal (hash open=undefined))
)}}
</:main>
<:main>
{{yield (hash
login=(if this.modal this.modal (hash open=undefined))
)}}
</:main>
<:content-info>
<p>
Consul v{{env 'CONSUL_VERSION'}}
</p>
{{{concat '<!-- ' (env 'CONSUL_GIT_SHA') '-->'}}}
</:content-info>
</App>
{{/let}}
<:content-info>
<p>
Consul v{{env 'CONSUL_VERSION'}}
</p>
{{{concat '<!-- ' (env 'CONSUL_GIT_SHA') '-->'}}}
</:content-info>
</App>

View File

@ -1,28 +1,6 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class HashiCorpConsul extends Component {
@service('flashMessages') flashMessages;
@action
open() {
this.authForm.focus();
}
@action
close() {
this.authForm.reset();
}
@action
reauthorize(e) {
this.modal.close();
this.args.onchange(e);
}
@action
keypressClick(e) {
e.target.dispatchEvent(new MouseEvent('click'));
}
}

View File

@ -45,9 +45,6 @@
display: block;
}
/**/
%menu-panel dl {
padding: 0.9em 1em;
}
%menu-panel > ul > li > div[role='menu'] {
@extend %menu-panel-sub-panel;
}

View File

@ -6,10 +6,6 @@
%menu-panel > ul > li {
list-style-type: none;
}
%menu-panel dt {
font-weight: var(--typo-weight-bold);
}
%menu-panel dl,
%menu-panel-header {
@extend %p2;
}
@ -18,9 +14,6 @@
text-transform: uppercase;
font-weight: var(--typo-weight-medium);
}
%menu-panel dt span {
font-weight: var(--typo-weight-normal);
}
%menu-panel-header + ul,
%menu-panel-separator:not(:first-child) {
border-top: var(--decor-border-100);
@ -32,13 +25,6 @@
border-color: rgb(var(--tone-gray-300));
background-color: rgb(var(--tone-gray-000));
}
%menu-panel dt,
%menu-panel dd {
color: rgb(var(--tone-gray-800));
}
%menu-panel dt span {
color: rgb(var(--tone-gray-600));
}
%menu-panel-separator {
color: rgb(var(--tone-gray-600));
}

View File

@ -3,6 +3,7 @@
@import 'consul-ui/components/anchors';
@import 'consul-ui/components/auth-form';
@import 'consul-ui/components/auth-modal';
@import 'consul-ui/components/auth-profile';
@import 'consul-ui/components/breadcrumbs';
@import 'consul-ui/components/buttons';
@import 'consul-ui/components/card';

View File

@ -78,7 +78,6 @@ pre code,
/**/
/* resets */
%menu-panel dt span,
%empty-state-subheader,
%main-content label a[rel*='help'],
%pill,

View File

@ -1,26 +0,0 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | auth-dialog', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<AuthDialog />`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
<AuthDialog>
template block text
</AuthDialog>
`);
assert.equal(this.element.textContent.trim(), 'template block text');
});
});