ui: Notifications re-organization/re-style (#11577)
- Moves where they appear up to the <App /> component. - Instead of a <Notification /> wrapping component to move whatever you use for a notification up to where they need to appear (via ember-cli-flash), we now use a {{notification}} modifier now we have modifiers. - Global notifications/flashes are no longer special styles of their own. You just use the {{notification}} modifier to hoist whatever component/element you want up to the top of the page. This means we can re-use our existing <Notice /> component for all our global UI notifications (this is the user visible change here)
This commit is contained in:
parent
f605689154
commit
124fa8f168
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: Update global notification styling
|
||||
```
|
|
@ -8,9 +8,7 @@ state: needs-love
|
|||
the app chrome), every 'top level main section/template' should have one of
|
||||
these.
|
||||
|
||||
It contains legacy authorization code (that can probably be removed now), and
|
||||
our flash messages (that should be moved to the `<App />` or `<HashicorpConsul
|
||||
/>` component and potentially be renamed to `Page` or `View` or similar now
|
||||
This component will potentially be renamed to `Page` or `View` or similar now
|
||||
that we don't need two words.
|
||||
|
||||
Other than that it provides the basic layout/slots for our main title, search
|
||||
|
@ -86,20 +84,12 @@ breadcrumbs and back again.
|
|||
</figure>
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `authorized` | `Boolean` | `true` | Whether the View is authorized or not |
|
||||
| `enabled` | `Boolean` | `true` | Whether ACLs are enabled or not |
|
||||
|
||||
## Slots
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `header` | The main title of the page, you probably want to put a `<h1>` in here |
|
||||
| `content` | The main content of the page, and potentially an `<Outlet />` somewhere |
|
||||
| `notification` | Old style notifications, also see `<Notification />` |
|
||||
| `breadcrumbs` | Any breadcrumbs, you probably want an `ol/li/a` in here |
|
||||
| `actions` | Any actions relevant for the entire page, probably using `<Action />` |
|
||||
| `nav` | Secondary navigation goes in here, also see `<TabNav />` |
|
||||
|
|
|
@ -4,74 +4,23 @@
|
|||
>
|
||||
{{yield}}
|
||||
<header>
|
||||
{{#each flashMessages.queue as |flash|}}
|
||||
<FlashMessage @flash={{flash}} as |component flash|>
|
||||
{{#if flash.dom}}
|
||||
{{{flash.dom}}}
|
||||
{{else}}
|
||||
{{#let (lowercase component.flashType) (lowercase flash.action) as |status type|}}
|
||||
{{! flashes automatically ucfirst the type }}
|
||||
|
||||
<p data-notification role="alert" class={{concat status ' notification-' type}}>
|
||||
<strong>
|
||||
{{capitalize status}}!
|
||||
</strong>
|
||||
{{#yield-slot name="notification" params=(block-params status type flash.item flash.error)}}
|
||||
{{yield}}
|
||||
{{#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}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#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}}
|
||||
{{/if}}
|
||||
{{/yield-slot}}
|
||||
</p>
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</FlashMessage>
|
||||
{{/each}}
|
||||
<div>
|
||||
<div>
|
||||
{{#if authorized}}
|
||||
<nav aria-label="Breadcrumb" data-test-breadcrumbs>
|
||||
<YieldSlot @name="breadcrumbs">
|
||||
{{document-attrs class="with-breadcrumbs"}}
|
||||
{{yield}}
|
||||
</YieldSlot>
|
||||
</nav>
|
||||
{{/if}}
|
||||
<div class="title">
|
||||
<YieldSlot @name="header">
|
||||
{{yield}}
|
||||
</YieldSlot>
|
||||
<div class="actions">
|
||||
{{#if authorized}}
|
||||
<YieldSlot @name="actions">
|
||||
<PortalTarget @name="app-view-actions" />
|
||||
{{yield}}
|
||||
</YieldSlot>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<YieldSlot @name="nav">
|
||||
|
@ -79,42 +28,12 @@
|
|||
</YieldSlot>
|
||||
</div>
|
||||
</div>
|
||||
{{#if authorized}}
|
||||
<YieldSlot @name="toolbar">
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
{{yield}}
|
||||
</YieldSlot>
|
||||
{{/if}}
|
||||
</header>
|
||||
<div>
|
||||
{{#if (not enabled) }}
|
||||
<EmptyState data-test-acls-disabled>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to ACLs</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
ACLs are not enabled in this Consul cluster. We strongly encourage the use of ACLs in production environments for the best security practices.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}/acl/index.html" rel="noopener noreferrer" target="_blank">Read the documentation</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/security-networking/production-acls" rel="noopener noreferrer" target="_blank">Follow the guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
{{else if (not authorized)}}
|
||||
<ErrorState
|
||||
@error={{hash
|
||||
status='403'
|
||||
}}
|
||||
@login={{login}}
|
||||
/>
|
||||
{{else}}
|
||||
<YieldSlot @name="content">{{yield}}</YieldSlot>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,4 @@ import Component from '@ember/component';
|
|||
import SlotsMixin from 'block-slots';
|
||||
export default Component.extend(SlotsMixin, {
|
||||
tagName: '',
|
||||
authorized: true,
|
||||
enabled: true,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{{#let (hash
|
||||
main=(concat guid '-main')
|
||||
Notification=(component 'app/notification')
|
||||
) as |exported|}}
|
||||
|
||||
<div
|
||||
|
@ -13,7 +14,7 @@
|
|||
>
|
||||
<PortalTarget
|
||||
@name="app-before-skip-links"
|
||||
@mutiple={{true}}
|
||||
@multiple={{true}}
|
||||
></PortalTarget>
|
||||
<a href={{concat '#' exported.main}}>{{t 'components.app.skip_to_content'}}</a>
|
||||
{{!--
|
||||
|
@ -25,7 +26,7 @@
|
|||
--}}
|
||||
<PortalTarget
|
||||
@name="app-after-skip-links"
|
||||
@mutiple={{true}}
|
||||
@multiple={{true}}
|
||||
></PortalTarget>
|
||||
</div>
|
||||
|
||||
|
@ -78,6 +79,13 @@
|
|||
</div>
|
||||
</header>
|
||||
<main id={{concat guid '-main'}}>
|
||||
<div class="notifications">
|
||||
{{yield exported to="notifications"}}
|
||||
<PortalTarget
|
||||
@name="app-notifications"
|
||||
@multiple={{true}}
|
||||
></PortalTarget>
|
||||
</div>
|
||||
{{yield exported to="main"}}
|
||||
</main>
|
||||
<footer
|
||||
|
|
|
@ -1,6 +1,33 @@
|
|||
.app .skip-links {
|
||||
@extend %skip-links;
|
||||
}
|
||||
.app .notifications {
|
||||
@extend %app-notifications;
|
||||
}
|
||||
%app-notifications {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
top: -45px;
|
||||
left: 0;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
%app-notifications .app-notification > * {
|
||||
min-width: 400px;
|
||||
}
|
||||
%app-notifications .app-notification {
|
||||
@extend %with-transition-500;
|
||||
transition-property: opacity;
|
||||
width: fit-content;
|
||||
max-width: 80%;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
[role='banner'] {
|
||||
@extend %main-header-horizontal;
|
||||
}
|
||||
|
@ -32,6 +59,7 @@
|
|||
@extend %main-nav-horizontal-action-active;
|
||||
}
|
||||
%main-nav-sidebar,
|
||||
%main-notifications,
|
||||
main {
|
||||
@extend %transition-pushover;
|
||||
}
|
||||
|
@ -39,34 +67,50 @@ main {
|
|||
transition-property: left;
|
||||
z-index: 10;
|
||||
}
|
||||
%app-notifications,
|
||||
main {
|
||||
margin-top: var(--chrome-height, 64px);
|
||||
transition-property: margin-left;
|
||||
}
|
||||
%app-notifications {
|
||||
transition-property: margin-left, width;
|
||||
}
|
||||
|
||||
@media #{$--sidebar-open} {
|
||||
%main-nav-horizontal-toggle ~ main .notifications {
|
||||
width: calc(100% - var(--chrome-width));
|
||||
}
|
||||
%main-nav-horizontal-toggle:checked ~ main .notifications {
|
||||
width: 100%;
|
||||
}
|
||||
%main-nav-horizontal-toggle + header > div > nav:first-of-type {
|
||||
left: 0;
|
||||
}
|
||||
%main-nav-horizontal-toggle:checked + header > div > nav:first-of-type {
|
||||
left: calc(var(--chrome-width, 300px) * -1);
|
||||
}
|
||||
%main-nav-horizontal-toggle ~ main .notifications,
|
||||
%main-nav-horizontal-toggle ~ main,
|
||||
%main-nav-horizontal-toggle ~ footer {
|
||||
margin-left: var(--chrome-width, 300px);
|
||||
}
|
||||
%main-nav-horizontal-toggle:checked ~ main .notifications,
|
||||
%main-nav-horizontal-toggle:checked ~ main,
|
||||
%main-nav-horizontal-toggle:checked ~ footer {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@media #{$--lt-sidebar-open} {
|
||||
%main-nav-horizontal-toggle ~ main .notifications {
|
||||
width: 100%;
|
||||
}
|
||||
%main-nav-horizontal-toggle:checked + header > div > nav:first-of-type {
|
||||
left: 0;
|
||||
}
|
||||
%main-nav-horizontal-toggle + header > div > nav:first-of-type {
|
||||
left: calc(var(--chrome-width, 300px) * -1);
|
||||
}
|
||||
%main-nav-horizontal-toggle ~ main .notifications,
|
||||
%main-nav-horizontal-toggle ~ main,
|
||||
%main-nav-horizontal-toggle ~ footer {
|
||||
margin-left: 0;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<div
|
||||
class="app-notification"
|
||||
...attributes
|
||||
{{style
|
||||
(array
|
||||
(array 'opacity' '1')
|
||||
(array 'transition-delay' (concat @delay 'ms'))
|
||||
)
|
||||
}}
|
||||
{{style
|
||||
(array
|
||||
(array 'opacity' (if @sticky '1' '0'))
|
||||
)
|
||||
delay=0
|
||||
}}
|
||||
>
|
||||
{{yield}}
|
||||
</div>
|
||||
|
|
@ -14,21 +14,45 @@
|
|||
@onsubmit={{action this.onsubmit}}
|
||||
as |api|>
|
||||
|
||||
<BlockSlot @name="error" as |Notification|>
|
||||
<Notification>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
{{#if (string-starts-with api.error.detail 'duplicate intention found:')}}
|
||||
<strong>Intention exists</strong>
|
||||
<BlockSlot @name="error" as |after|>
|
||||
{{#if (string-starts-with api.error.detail 'duplicate intention found:')}}
|
||||
<Notice
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Intention exists!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An intention already exists for this Source-Destination pair. Please enter a different combination of Services, or search the intentions to edit an existing intention.
|
||||
{{else}}
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else}}
|
||||
<Notice
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
There was an error saving your intention.
|
||||
{{#if (and api.error.status api.error.detail)}}
|
||||
<br />{{api.error.status}}: {{api.error.detail}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="form">
|
||||
|
|
|
@ -49,16 +49,26 @@
|
|||
<State @matches={{array "idle" "disconnected"}}>
|
||||
|
||||
<State @matches="disconnected">
|
||||
{{#yield-slot name="disconnected" params=(block-params (component 'notification' after=(action dispatch "RESET")))}}
|
||||
{{#yield-slot name="disconnected" params=(block-params (action dispatch "RESET"))}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
{{#if (not eq error.status '401')}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
{{/yield-slot}}
|
||||
</State>
|
||||
|
|
|
@ -33,46 +33,89 @@
|
|||
</State>
|
||||
|
||||
<State @matches="removed">
|
||||
{{#yield-slot name="removed" params=(block-params (component 'notification' after=(queue (action dispatch "RESET") (action ondelete))))}}
|
||||
{{#let
|
||||
(queue (action dispatch "RESET") (action ondelete))
|
||||
as |after|}}
|
||||
{{#yield-slot name="removed" params=(block-params after)}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
<Notification @after={{queue (action dispatch "RESET") (action ondelete)}}>
|
||||
<p data-notification role="alert" class="success notification-delete">
|
||||
<Notice
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
class="notification-delete"
|
||||
@type="success"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Success!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
Your {{or label type}} has been deleted.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/yield-slot}}
|
||||
{{/let}}
|
||||
</State>
|
||||
|
||||
<State @matches="persisted">
|
||||
<Notification @after={{action onchange}}>
|
||||
{{#yield-slot name="persisted"}}
|
||||
{{#let
|
||||
(action onchange)
|
||||
as |after|}}
|
||||
{{#yield-slot name="persisted" params=(block-params after)}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
<p data-notification role="alert" class="success notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="success"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Success!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
Your {{or label type}} has been saved.
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/yield-slot}}
|
||||
</Notification>
|
||||
{{/let}}
|
||||
</State>
|
||||
|
||||
<State @matches="error">
|
||||
{{#yield-slot name="error" params=(block-params (component 'notification' after=(action dispatch "RESET")))}}
|
||||
{{#let
|
||||
(action dispatch "RESET")
|
||||
as |after|}}
|
||||
{{#yield-slot name="error" params=(block-params after)}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
<Notification @after={{action dispatch "RESET"}}>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
There was an error saving your {{or label type}}.
|
||||
{{#if (and api.error.status api.error.detail)}}
|
||||
<br />{{api.error.status}}: {{api.error.detail}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/yield-slot}}
|
||||
{{/let}}
|
||||
</State>
|
||||
|
||||
<YieldSlot @name="content">
|
||||
{{yield api}}
|
||||
</YieldSlot>
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
class: css
|
||||
state: needs-love
|
||||
---
|
||||
# flash-message
|
||||
|
||||
CSS component for styling our flash messages
|
||||
|
||||
```hbs preview-template
|
||||
<div class="flash-message">
|
||||
<p
|
||||
role="alert"
|
||||
class={{or this.type 'success'}}
|
||||
>
|
||||
<strong>
|
||||
{{capitalize (or this.type 'success')}}!
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<figure>
|
||||
<figcaption>Provide a widget to change the <code>class</code></figcaption>
|
||||
|
||||
<select
|
||||
onchange={{action (mut this.type) value="target.value"}}
|
||||
>
|
||||
<option>success</option>
|
||||
<option>warning</option>
|
||||
<option>error</option>
|
||||
<option>exists</option>
|
||||
</select>
|
||||
|
||||
</figure>
|
||||
```
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
@import './skin';
|
||||
@import './layout';
|
||||
.flash-message {
|
||||
@extend %flash-message;
|
||||
}
|
||||
%flash-message.exiting {
|
||||
@extend %blink-in-fade-out;
|
||||
}
|
||||
/* This is for the flash message that appears */
|
||||
/* when you save an intention that already exists */
|
||||
/* once we have refactored app-view with data-source with nicer */
|
||||
/* flash message usage we should be able to remove this */
|
||||
%flash-message p.exists strong::before {
|
||||
@extend %with-cancel-square-fill-mask;
|
||||
color: rgb(var(--tone-red-500));
|
||||
}
|
||||
%flash-message p.exists {
|
||||
@extend %frame-red-500;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
%flash-message {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
justify-content: center;
|
||||
margin: 0 15%;
|
||||
}
|
||||
%flash-message p {
|
||||
top: -46px;
|
||||
position: absolute;
|
||||
padding: 9px 15px;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
%flash-message p {
|
||||
border-width: 1px;
|
||||
border-radius: var(--decor-radius-100);
|
||||
}
|
||||
%flash-message p strong::before {
|
||||
@extend %as-pseudo;
|
||||
}
|
||||
%flash-message p.success strong::before {
|
||||
@extend %with-check-circle-fill-mask;
|
||||
color: rgb(var(--tone-green-500));
|
||||
}
|
||||
%flash-message p.warning strong::before {
|
||||
@extend %with-alert-triangle-mask;
|
||||
color: rgb(var(--tone-orange-500));
|
||||
}
|
||||
%flash-message p.error strong::before {
|
||||
@extend %with-cancel-square-fill-mask;
|
||||
color: rgb(var(--tone-red-500));
|
||||
}
|
||||
%flash-message p.success {
|
||||
@extend %frame-green-500;
|
||||
}
|
||||
%flash-message p.warning {
|
||||
@extend %frame-yellow-500;
|
||||
}
|
||||
%flash-message p.error {
|
||||
@extend %frame-red-500;
|
||||
}
|
|
@ -3,7 +3,81 @@
|
|||
class="hashicorp-consul"
|
||||
...attributes
|
||||
>
|
||||
<: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}}
|
||||
{{#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}}
|
||||
{{else}}
|
||||
{{#if (eq flash.model 'token')}}
|
||||
<Consul::Token::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}}
|
||||
/>
|
||||
{{else if (eq flash.model 'nspace')}}
|
||||
<Consul::Nspace::Notifications
|
||||
@type={{type}}
|
||||
@status={{status}}
|
||||
@item={{flash.item}}
|
||||
@error={{flash.error}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</app.Notification>
|
||||
{{/each}}
|
||||
|
||||
</:notifications>
|
||||
<:home-nav>
|
||||
<a
|
||||
href={{href-to 'index'}}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
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();
|
||||
|
|
|
@ -24,9 +24,12 @@
|
|||
background-color: rgb(var(--tone-green-050));
|
||||
border-color: rgb(var(--tone-green-500));
|
||||
}
|
||||
%notice-success header * {
|
||||
color: rgb(var(--tone-green-800));
|
||||
}
|
||||
%notice-info {
|
||||
border-color: rgb(var(--tone-blue-100));
|
||||
background-color: rgb(var(--tone-gray-010));
|
||||
background-color: rgb(var(--tone-blue-010));
|
||||
}
|
||||
%notice-info header * {
|
||||
color: rgb(var(--tone-blue-700));
|
||||
|
@ -36,7 +39,7 @@
|
|||
border-color: rgb(var(--tone-gray-300));
|
||||
}
|
||||
%notice-info header * {
|
||||
color: rgb(var(--tone-gray-700));
|
||||
color: rgb(var(--tone-blue-700));
|
||||
}
|
||||
%notice-warning {
|
||||
border-color: rgb(var(--tone-yellow-100));
|
||||
|
@ -49,6 +52,9 @@
|
|||
background-color: rgb(var(--tone-red-050));
|
||||
border-color: rgb(var(--tone-red-500));
|
||||
}
|
||||
%notice-error header * {
|
||||
color: rgb(var(--tone-red-500));
|
||||
}
|
||||
%notice-success::before {
|
||||
@extend %with-check-circle-fill-mask;
|
||||
color: rgb(var(--tone-green-500));
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<div id={{guid}}>
|
||||
{{yield}}
|
||||
</div>
|
|
@ -1,40 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
notify: service('flashMessages'),
|
||||
dom: service('dom'),
|
||||
oncomplete: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.guid = this.dom.guid(this);
|
||||
},
|
||||
didInsertElement: function() {
|
||||
const $el = this.dom.element(`#${this.guid}`);
|
||||
const options = {
|
||||
timeout: 6000,
|
||||
extendedTimeout: 300,
|
||||
dom: $el.innerHTML,
|
||||
};
|
||||
if (this.sticky) {
|
||||
options.sticky = true;
|
||||
}
|
||||
$el.remove();
|
||||
this.notify.clearMessages();
|
||||
if (typeof this.after === 'function') {
|
||||
Promise.resolve(this.after())
|
||||
.catch(e => {
|
||||
if (e.name !== 'TransitionAborted') {
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.notify.add(options);
|
||||
});
|
||||
} else {
|
||||
this.notify.add(options);
|
||||
}
|
||||
},
|
||||
});
|
|
@ -1,6 +1,8 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set, get } from '@ember/object';
|
||||
import { singularize } from 'ember-inflector';
|
||||
|
||||
/** With Blocking Actions
|
||||
* This mixin contains common write actions (Create Update Delete) for routes.
|
||||
* It could also be an Route to extend but decoration seems to be more sense right now.
|
||||
|
@ -25,7 +27,11 @@ export default Mixin.create({
|
|||
const route = this;
|
||||
set(this, 'feedback', {
|
||||
execute: function(cb, type, error) {
|
||||
return feedback.execute(cb, type, error, route.controller);
|
||||
const temp = route.routeName.split('.');
|
||||
temp.pop();
|
||||
const routeName = singularize(temp.pop());
|
||||
|
||||
return feedback.execute(cb, type, error, routeName);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import Modifier from 'ember-modifier';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class NotificationModifier extends Modifier {
|
||||
@service('flashMessages') notify;
|
||||
|
||||
didInstall() {
|
||||
this.element.setAttribute('role', 'alert');
|
||||
this.element.dataset['notification'] = null;
|
||||
const options = {
|
||||
timeout: 6000,
|
||||
extendedTimeout: 300,
|
||||
...this.args.named,
|
||||
};
|
||||
options.dom = this.element.outerHTML;
|
||||
this.element.remove();
|
||||
this.notify.clearMessages();
|
||||
if (typeof options.after === 'function') {
|
||||
Promise.resolve().then(_ => options.after())
|
||||
.catch(e => {
|
||||
if (e.name !== 'TransitionAborted') {
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.notify.add(options);
|
||||
});
|
||||
} else {
|
||||
this.notify.add(options);
|
||||
}
|
||||
}
|
||||
willDestroy() {
|
||||
if(this.args.named.sticky) {
|
||||
this.notify.clearMessages();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
# notification
|
||||
|
||||
Consul UIs notification modifier is used to 'hoist' DOM elements into the
|
||||
global notification area for the UI. The most common usage will be something
|
||||
like the below:
|
||||
|
||||
```hbs preview-template
|
||||
<figure>
|
||||
<figcaption>Attach a Warning notice to the top of the app ^^^</figcaption>
|
||||
|
||||
<Notice
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
This service has been deregistered and no longer exists in the catalog.
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
|
||||
</figure>
|
||||
```
|
||||
|
||||
Currently this is backed by `ember-cli-flash` and the named options are
|
||||
currently options that can be accepted by `ember-cli-flash`. Be aware that this
|
||||
is likely to change in the future.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import Modifier from 'ember-modifier';
|
||||
import { assert } from '@ember/debug';
|
||||
|
||||
export default class StyleModifier extends Modifier {
|
||||
setStyles(newStyles = []) {
|
||||
const rulesToRemove = this._oldStyles || new Set();
|
||||
if(!Array.isArray(newStyles)) {
|
||||
newStyles = Object.entries(newStyles)
|
||||
}
|
||||
newStyles.forEach(([property, value]) => {
|
||||
assert(
|
||||
`Your given value for property '${property}' is ${value} (${typeof value}). Accepted types are string and undefined. Please change accordingly.`,
|
||||
typeof value === 'undefined' || typeof value === 'string'
|
||||
);
|
||||
|
||||
// priority must be specified as separate argument
|
||||
// value must not contain "!important"
|
||||
let priority = '';
|
||||
if (value.length > 0 && value.includes('!important')) {
|
||||
priority = 'important';
|
||||
value = value.replace('!important', '');
|
||||
}
|
||||
|
||||
// update CSSOM
|
||||
this.element.style.setProperty(property, value, priority);
|
||||
|
||||
// should not remove rules that have been updated in this cycle
|
||||
rulesToRemove.delete(property);
|
||||
});
|
||||
|
||||
// remove rules that were present in last cycle but aren't present in this one
|
||||
rulesToRemove.forEach((rule) => this.element.style.removeProperty(rule));
|
||||
|
||||
// cache styles that in this rendering cycle for the next one
|
||||
this._oldStyles = new Set(newStyles.map((e) => e[0]));
|
||||
}
|
||||
|
||||
didReceiveArguments() {
|
||||
if(typeof this.args.named.delay !== 'undefined') {
|
||||
setTimeout(
|
||||
_ => {
|
||||
if(typeof this !== this.args.positional[0]) {
|
||||
this.setStyles(this.args.positional[0]);
|
||||
}
|
||||
},
|
||||
this.args.named.delay
|
||||
)
|
||||
} else {
|
||||
this.setStyles(this.args.positional[0]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ const notificationDefaults = function() {
|
|||
return {
|
||||
timeout: 6000,
|
||||
extendedTimeout: 300,
|
||||
destroyOnClick: true
|
||||
};
|
||||
};
|
||||
export default class FeedbackService extends Service {
|
||||
|
@ -23,7 +24,7 @@ export default class FeedbackService extends Service {
|
|||
};
|
||||
}
|
||||
|
||||
success(item, action, status = defaultStatus) {
|
||||
success(item, action, status = defaultStatus, model) {
|
||||
const getAction = callableType(action);
|
||||
const getStatus = callableType(status);
|
||||
// returning exactly `false` for a feedback action means even though
|
||||
|
@ -38,11 +39,12 @@ export default class FeedbackService extends Service {
|
|||
// here..
|
||||
action: getAction(),
|
||||
item: item,
|
||||
model: model
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
error(e, action, status = defaultStatus) {
|
||||
error(e, action, status = defaultStatus, model) {
|
||||
const getAction = callableType(action);
|
||||
const getStatus = callableType(status);
|
||||
this.notify.clearMessages();
|
||||
|
@ -53,6 +55,7 @@ export default class FeedbackService extends Service {
|
|||
type: getStatus(TYPE_SUCCESS),
|
||||
// and here
|
||||
action: getAction(),
|
||||
model: model
|
||||
});
|
||||
} else {
|
||||
this.notify.add({
|
||||
|
@ -60,17 +63,18 @@ export default class FeedbackService extends Service {
|
|||
type: getStatus(TYPE_ERROR, e),
|
||||
action: getAction(),
|
||||
error: e,
|
||||
model: model
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async execute(handle, action, status) {
|
||||
async execute(handle, action, status, routeName) {
|
||||
let result;
|
||||
try {
|
||||
result = await handle();
|
||||
this.success(result, action, status);
|
||||
this.success(result, action, status, routeName);
|
||||
} catch (e) {
|
||||
this.error(e, action, status);
|
||||
this.error(e, action, status, routeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
@import 'consul-ui/components/empty-state';
|
||||
@import 'consul-ui/components/expanded-single-select';
|
||||
@import 'consul-ui/components/form-elements';
|
||||
@import 'consul-ui/components/flash-message';
|
||||
@import 'consul-ui/components/icon-definition';
|
||||
@import 'consul-ui/components/list-row';
|
||||
@import 'consul-ui/components/inline-alert';
|
||||
|
|
|
@ -38,14 +38,6 @@ as |dc partition nspace id item create|}}
|
|||
<AppView
|
||||
@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>
|
||||
|
|
|
@ -55,14 +55,6 @@ as |route|>
|
|||
<AppView
|
||||
@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>
|
||||
<route.Title @title="Policies" />
|
||||
|
|
|
@ -36,14 +36,6 @@ as |dc partition nspace item create|}}
|
|||
<AppView
|
||||
@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>
|
||||
|
|
|
@ -49,14 +49,6 @@ as |route|>
|
|||
<AppView
|
||||
@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>
|
||||
<route.Title @title="Roles" />
|
||||
|
|
|
@ -36,14 +36,6 @@ as |dc partition nspace item create|}}
|
|||
<AppView
|
||||
@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>
|
||||
|
|
|
@ -52,14 +52,6 @@ as |route|>
|
|||
<AppView
|
||||
@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>
|
||||
<route.Title @title="Tokens" />
|
||||
|
|
|
@ -28,28 +28,58 @@ as |route|>
|
|||
@login={{route.model.app.login.open}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="disconnected" as |Notification|>
|
||||
<BlockSlot @name="disconnected" as |after|>
|
||||
{{#if (eq loader.error.status "404")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
This KV or parent of this KV was deleted.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else if (eq loader.error.status "403")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
You no longer have access to this KV.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
|
||||
|
|
|
@ -26,28 +26,58 @@ as |route|>
|
|||
/>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="disconnected" as |Notification|>
|
||||
<BlockSlot @name="disconnected" as |after|>
|
||||
{{#if (eq loader.error.status "404")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
This node no longer exists in the catalog.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else if (eq loader.error.status "403")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
You no longer have access to this node
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="loaded">
|
||||
|
|
|
@ -21,28 +21,58 @@ as |route|>
|
|||
/>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="disconnected" as |Notification|>
|
||||
<BlockSlot @name="disconnected" as |after|>
|
||||
{{#if (eq loader.error.status "404")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
This service has been deregistered and no longer exists in the catalog.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else if (eq loader.error.status "403")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
You no longer have access to this service
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
|
||||
|
|
|
@ -19,28 +19,58 @@ as |route|>
|
|||
/>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="disconnected" as |Notification|>
|
||||
<BlockSlot @name="disconnected" as |after|>
|
||||
{{#if (eq loader.error.status "404")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
This service has been deregistered and no longer exists in the catalog.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else if (eq loader.error.status "403")}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="error notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="error"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Error!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
You no longer have access to this service
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{else}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<Notice
|
||||
{{notification
|
||||
sticky=true
|
||||
}}
|
||||
class="notification-update"
|
||||
@type="warning"
|
||||
as |notice|>
|
||||
<notice.Header>
|
||||
<strong>Warning!</strong>
|
||||
</notice.Header>
|
||||
<notice.Body>
|
||||
<p>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
|
||||
|
|
|
@ -2,6 +2,20 @@
|
|||
|
||||
{{document-attrs class="is-debug"}}
|
||||
<App class="docs" id="wrapper">
|
||||
|
||||
<:notifications as |app|>
|
||||
{{#each flashMessages.queue as |flash|}}
|
||||
{{#if flash.dom}}
|
||||
<app.Notification
|
||||
@delay={{sub flash.timeout flash.extendedTimeout}}
|
||||
@sticky={{flash.sticky}}
|
||||
>
|
||||
{{{flash.dom}}}
|
||||
</app.Notification>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</:notifications>
|
||||
|
||||
<:main-nav>
|
||||
|
||||
<DocfyOutput as |node|>
|
||||
|
|
Loading…
Reference in New Issue