mirror of https://github.com/hashicorp/consul
ui: New Empty States (#7940)
* ui: CSS and component changes to the <EmptyState /> component * ui: Reset the auth-form component back to its initial state Moving forwards we are going to have the auth-form on the page all the time, even when logged in (for relogging in purposes). This means the auth-form will not always be removed from the DOM when you log in. This sets the form back to its idle state before calling onsubmit * ui: Make a public api for modal-dialog with a single close method * ui : Move cache reset somewhere that makes more sense, + single refresh 1. Centralize cache resetting elsewhere, for now the store makes most sense, although I would prefer the Repository class, so using the store is temporary 2. We only need to refresh on login once, unless we have a differing nspace * ui: Ensure visibilitychange events are cleaned up * ui: Only cache DataSource data if we have any, + only clear the cache * ui: Add the modal login dialog to both unauth and auth views This means we can 'relogin' when already logged in * ui: Add new empty states * ui: CSS Tweaks * Remove marketing grayspull/8013/head
parent
c4b2fcbd38
commit
94dd1849b4
|
@ -93,7 +93,7 @@
|
|||
@nspace={{or value.Namespace nspace}}
|
||||
@type={{if value.Name 'oidc' 'secret'}}
|
||||
@value={{if value.Name value.Name value}}
|
||||
@onchange={{action onsubmit}}
|
||||
@onchange={{queue (action dispatch "RESET") (action onsubmit)}}
|
||||
@onerror={{queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR")}}
|
||||
/>
|
||||
</State>
|
||||
|
|
|
@ -8,11 +8,24 @@
|
|||
{{yield}}
|
||||
{{/yield-slot}}
|
||||
</header>
|
||||
<p>
|
||||
{{#yield-slot name="body"}}
|
||||
{{#yield-slot name="body"}}
|
||||
<div>
|
||||
{{yield}}
|
||||
{{/yield-slot}}
|
||||
</p>
|
||||
{{#if (and (env 'CONSUL_ACLS_ENABLED') allowLogin)}}
|
||||
<label for="login-toggle">
|
||||
<DataSource
|
||||
@src="settings://consul:token"
|
||||
@onchange={{action (mut token) value="data"}}
|
||||
/>
|
||||
{{#if token.AccessorID}}
|
||||
Log in with a different token
|
||||
{{else}}
|
||||
Log in
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/yield-slot}}
|
||||
{{#yield-slot name="actions"}}
|
||||
<ul>
|
||||
{{yield}}
|
||||
|
|
|
@ -127,14 +127,15 @@
|
|||
<AuthDialog
|
||||
@dc={{dc.Name}}
|
||||
@nspace={{nspace.Name}}
|
||||
@onchange={{action onchange}} as |authDialog components|
|
||||
@onchange={{action "reauthorize"}} as |authDialog components|
|
||||
>
|
||||
{{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}}
|
||||
<BlockSlot @name="unauthorized">
|
||||
<label tabindex="0" for="login-toggle" onkeypress={{action 'keypressClick'}}>
|
||||
<span>Log in</span>
|
||||
</label>
|
||||
<ModalDialog @name="login-toggle" @onclose={{action 'close'}} @onopen={{action 'open'}}>
|
||||
<ModalDialog @name="login-toggle" @onclose={{action 'close'}} @onopen={{action 'open'}} as |api|>
|
||||
<Ref @target={{this}} @name="modal" @value={{api}} />
|
||||
<BlockSlot @name="header">
|
||||
<h2>Log in to Consul</h2>
|
||||
</BlockSlot>
|
||||
|
@ -151,6 +152,22 @@
|
|||
</ModalDialog>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="authorized">
|
||||
<ModalDialog @name="login-toggle" @onclose={{action 'close'}} @onopen={{action 'open'}} as |api|>
|
||||
<Ref @target={{this}} @name="modal" @value={{api}} />
|
||||
<BlockSlot @name="header">
|
||||
<h2>Log in with a different token</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<AuthForm as |api|>
|
||||
<Ref @target={{this}} @name="authForm" @value={{api}} />
|
||||
</AuthForm>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions" as |close|>
|
||||
<button type="button" onclick={{action close}}>
|
||||
Continue without logging in
|
||||
</button>
|
||||
</BlockSlot>
|
||||
</ModalDialog>
|
||||
<PopoverMenu @position="right">
|
||||
<BlockSlot @name="trigger">
|
||||
Logout
|
||||
|
|
|
@ -28,6 +28,10 @@ export default Component.extend({
|
|||
close: function() {
|
||||
this.authForm.reset();
|
||||
},
|
||||
reauthorize: function(e) {
|
||||
this.modal.close();
|
||||
this.onchange(e);
|
||||
},
|
||||
change: function(e) {
|
||||
const win = this.dom.viewport();
|
||||
const $root = this.dom.root();
|
||||
|
|
|
@ -6,13 +6,25 @@
|
|||
<div>
|
||||
<header>
|
||||
<label for="modal_close">Close</label>
|
||||
<YieldSlot @name="header">{{yield}}</YieldSlot>
|
||||
<YieldSlot @name="header">
|
||||
{{yield (hash
|
||||
close=(action "close")
|
||||
)}}
|
||||
</YieldSlot>
|
||||
</header>
|
||||
<div>
|
||||
<YieldSlot @name="body">{{yield}}</YieldSlot>
|
||||
<YieldSlot @name="body">
|
||||
{{yield (hash
|
||||
close=(action "close")
|
||||
)}}
|
||||
</YieldSlot>
|
||||
</div>
|
||||
<footer>
|
||||
<YieldSlot @name="actions" @params={{block-params (action "close")}}>{{yield}}</YieldSlot>
|
||||
<YieldSlot @name="actions" @params={{block-params (action "close")}}>
|
||||
{{yield (hash
|
||||
close=(action "close")
|
||||
)}}
|
||||
</YieldSlot>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,9 +6,6 @@ import transitionable from 'consul-ui/utils/routing/transitionable';
|
|||
|
||||
export default Controller.extend({
|
||||
router: service('router'),
|
||||
http: service('repository/type/event-source'),
|
||||
dataSource: service('data-source/service'),
|
||||
client: service('client/http'),
|
||||
store: service('store'),
|
||||
feedback: service('feedback'),
|
||||
actions: {
|
||||
|
@ -23,12 +20,11 @@ export default Controller.extend({
|
|||
// used for the feedback service.
|
||||
this.feedback.execute(
|
||||
() => {
|
||||
// TODO: Centralize this elsewhere
|
||||
this.client.abort();
|
||||
this.http.resetCache();
|
||||
this.dataSource.resetCache();
|
||||
this.store.init();
|
||||
|
||||
// TODO: Currently we clear cache from the ember-data store
|
||||
// ideally this would be a static method of the abstract Repository class
|
||||
// once we move to proper classes for services take another look at this.
|
||||
this.store.clear();
|
||||
//
|
||||
const params = {};
|
||||
if (e.data) {
|
||||
const token = e.data;
|
||||
|
@ -42,22 +38,24 @@ export default Controller.extend({
|
|||
}
|
||||
}
|
||||
}
|
||||
const container = getOwner(this);
|
||||
const routeName = this.router.currentRoute.name;
|
||||
const route = getOwner(this).lookup(`route:${routeName}`);
|
||||
const router = this.router;
|
||||
const route = container.lookup(`route:${routeName}`);
|
||||
// Refresh the application route
|
||||
return getOwner(this)
|
||||
return container
|
||||
.lookup('route:application')
|
||||
.refresh()
|
||||
.promise.then(() => {
|
||||
// We use transitionable here as refresh doesn't work if you are on an error page
|
||||
// which is highly likely to happen here (403s)
|
||||
if (routeName !== router.currentRouteName || typeof params.nspace !== 'undefined') {
|
||||
.promise.then(res => {
|
||||
// Use transitionable if we need to change a section of the URL
|
||||
if (
|
||||
routeName !== this.router.currentRouteName ||
|
||||
typeof params.nspace !== 'undefined'
|
||||
) {
|
||||
return route.transitionTo(
|
||||
...transitionable(router.currentRoute, params, getOwner(this))
|
||||
...transitionable(this.router.currentRoute, params, container)
|
||||
);
|
||||
} else {
|
||||
return route.refresh();
|
||||
return res;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -69,19 +69,27 @@ export default Service.extend({
|
|||
settings: service('settings'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
const maxConnections = env('CONSUL_HTTP_MAX_CONNECTIONS');
|
||||
set(this, 'connections', getObjectPool(dispose, maxConnections));
|
||||
if (typeof maxConnections !== 'undefined') {
|
||||
set(this, 'maxConnections', maxConnections);
|
||||
const doc = this.dom.document();
|
||||
// when the user hides the tab, abort all connections
|
||||
doc.addEventListener('visibilitychange', e => {
|
||||
if (e.target.hidden) {
|
||||
this.connections.purge();
|
||||
}
|
||||
this._listeners.add(this.dom.document(), {
|
||||
visibilitychange: e => {
|
||||
if (e.target.hidden) {
|
||||
this.connections.purge();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
willDestroy: function() {
|
||||
this._listeners.remove();
|
||||
this.connections.purge();
|
||||
set(this, 'connections', undefined);
|
||||
this._super(...arguments);
|
||||
},
|
||||
url: function() {
|
||||
return url(...arguments);
|
||||
},
|
||||
|
@ -235,14 +243,18 @@ export default Service.extend({
|
|||
this.connections.purge();
|
||||
},
|
||||
whenAvailable: function(e) {
|
||||
const doc = this.dom.document();
|
||||
// if we are using a connection limited protocol and the user has hidden the tab (hidden browser/tab switch)
|
||||
// any aborted errors should restart
|
||||
const doc = this.dom.document();
|
||||
if (typeof this.maxConnections !== 'undefined' && doc.hidden) {
|
||||
return new Promise(function(resolve) {
|
||||
doc.addEventListener('visibilitychange', function listen(event) {
|
||||
doc.removeEventListener('visibilitychange', listen);
|
||||
resolve(e);
|
||||
return new Promise(resolve => {
|
||||
const remove = this._listeners.add(doc, {
|
||||
visibilitychange: function(event) {
|
||||
remove();
|
||||
// we resolve with the event that comes from
|
||||
// whenAvailable not visibilitychange
|
||||
resolve(e);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,22 +18,17 @@ export default Service.extend({
|
|||
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
if (cache === null) {
|
||||
this.resetCache();
|
||||
}
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
resetCache: function() {
|
||||
Object.entries(sources || {}).forEach(function([key, item]) {
|
||||
item.close();
|
||||
});
|
||||
cache = new Map();
|
||||
sources = new Map();
|
||||
usage = new MultiMap(Set);
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
resetCache: function() {
|
||||
cache = new Map();
|
||||
},
|
||||
willDestroy: function() {
|
||||
this._listeners.remove();
|
||||
Object.entries(sources || {}).forEach(function([key, item]) {
|
||||
sources.forEach(function(item) {
|
||||
item.close();
|
||||
});
|
||||
cache = null;
|
||||
|
@ -61,10 +56,15 @@ export default Service.extend({
|
|||
close: e => {
|
||||
const source = e.target;
|
||||
source.removeEventListener('close', close);
|
||||
cache.set(uri, {
|
||||
currentEvent: source.getCurrentEvent(),
|
||||
cursor: source.configuration.cursor,
|
||||
});
|
||||
const event = source.getCurrentEvent();
|
||||
const cursor = source.configuration.cursor;
|
||||
// only cache data if we have any
|
||||
if (typeof event !== 'undefined' && typeof cursor !== 'undefined') {
|
||||
cache.set(uri, {
|
||||
currentEvent: source.getCurrentEvent(),
|
||||
cursor: source.configuration.cursor,
|
||||
});
|
||||
}
|
||||
// the data is cached delete the EventSource
|
||||
sources.delete(uri);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
import Store from 'ember-data/store';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Store.extend({
|
||||
// TODO: This should eventually go on a static method
|
||||
// of the abstract Repository class
|
||||
http: service('repository/type/event-source'),
|
||||
dataSource: service('data-source/service'),
|
||||
client: service('client/http'),
|
||||
clear: function() {
|
||||
// Aborting the client will close all open http type sources
|
||||
this.client.abort();
|
||||
// once they are closed clear their caches
|
||||
this.http.resetCache();
|
||||
this.dataSource.resetCache();
|
||||
this.init();
|
||||
},
|
||||
//
|
||||
// TODO: These only exist for ACLs, should probably make sure they fail
|
||||
// nicely if you aren't on ACLs for good DX
|
||||
// cloning immediately refreshes the view
|
||||
|
|
|
@ -58,16 +58,7 @@ $cyan-600: #009fd9;
|
|||
$cyan-700: #0077a3;
|
||||
$cyan-800: #005574;
|
||||
$cyan-900: #003346;
|
||||
$gray-1: #191a1c;
|
||||
$gray-2: #323538;
|
||||
$gray-3: #4c4f54;
|
||||
$gray-4: #656a70;
|
||||
$gray-5: #7f858d;
|
||||
$gray-6: #9a9ea5;
|
||||
$gray-7: #b4b8bc;
|
||||
$gray-8: #d0d2d5;
|
||||
$gray-9: #ebecee;
|
||||
$gray-10: #f3f4f6;
|
||||
$gray-010: #fbfbfc;
|
||||
$gray-050: #f7f8fa;
|
||||
$gray-100: #ebeef2;
|
||||
$gray-200: #dce0e6;
|
||||
|
|
|
@ -16,3 +16,6 @@
|
|||
%empty-state > ul > li {
|
||||
@extend %with-popover-menu;
|
||||
}
|
||||
%empty-state label {
|
||||
@extend %primary-button;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,28 @@
|
|||
%empty-state,
|
||||
%empty-state > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
%empty-state-header {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
%empty-state {
|
||||
width: 320px;
|
||||
margin-top: 0 !important;
|
||||
padding-bottom: 2.8em;
|
||||
}
|
||||
%empty-state > * {
|
||||
width: 370px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
%empty-state label {
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
%empty-state-header {
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
%empty-state header {
|
||||
margin-top: 1.8em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
%empty-state > ul {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
%empty-state {
|
||||
color: $gray-500;
|
||||
background-color: $gray-010;
|
||||
}
|
||||
%empty-state > ul {
|
||||
border-color: $gray-300;
|
||||
|
@ -34,12 +35,16 @@
|
|||
%empty-state[class*='status-5'] header::before {
|
||||
@extend %with-alert-circle-outline-mask;
|
||||
}
|
||||
%empty-state .docs-link > *::before {
|
||||
@extend %with-docs-mask, %as-pseudo;
|
||||
%empty-state li[class*='-link'] > *::after {
|
||||
@extend %as-pseudo;
|
||||
margin-left: 5px;
|
||||
}
|
||||
%empty-state .back-link > *::before {
|
||||
@extend %with-chevron-left-mask, %as-pseudo;
|
||||
%empty-state .docs-link > *::after {
|
||||
@extend %with-docs-mask;
|
||||
}
|
||||
%empty-state .learn-link > *::before {
|
||||
@extend %with-learn-mask, %as-pseudo;
|
||||
%empty-state .back-link > *::after {
|
||||
@extend %with-chevron-left-mask;
|
||||
}
|
||||
%empty-state .learn-link > *::after {
|
||||
@extend %with-learn-mask;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<EmptyState class="status-403">
|
||||
<EmptyState class="status-403" @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>You are not authorized</h2>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
ACLs are not enabled. We strongly encourage the use of ACLs in production environments for the best security practices.
|
||||
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">
|
||||
|
|
|
@ -128,9 +128,24 @@
|
|||
</TabularCollection>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no ACLs.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>No ACLs</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}" rel="noopener noreferrer" target="_blank">Read the documentation</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/" rel="noopener noreferrer" target="_blank">Follow the guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -101,9 +101,24 @@
|
|||
</TabularCollection>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no Policies.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Policies</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any policies, or you may not have access to view policies yet.
|
||||
</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>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -96,9 +96,24 @@
|
|||
</TabularCollection>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no Roles.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Roles</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any roles, or you may not have access to view roles yet.
|
||||
</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 guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -26,9 +26,24 @@
|
|||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no intentions.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Intentions</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any intentions, or you may not have access to view intentions yet.
|
||||
</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>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -87,9 +87,24 @@
|
|||
</TabularCollection>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no Key / Value pairs.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Key/Value</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
You don't have any K/V pairs, or you may not have access to view K/V pairs yet.
|
||||
</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>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -64,9 +64,24 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
{{#if (and (eq healthy.length 0) (eq unhealthy.length 0)) }}
|
||||
<p>
|
||||
There are no nodes.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Nodes</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any nodes, or you may not have access to view nodes yet.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}" rel="noopener noreferrer" target="_blank">Documentation on nodes</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/" rel="noopener noreferrer" target="_blank">Read the guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</AppView>
|
|
@ -90,9 +90,24 @@
|
|||
</TabularCollection>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no Namespaces.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Namespaces</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any namespaces, or you may not have access to view namespaces yet.
|
||||
</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>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
|
@ -33,9 +33,24 @@
|
|||
<ConsulServiceList @routeName="dc.services.show" @items={{sort-by sort.selected.key filtered}} @proxies={{proxies}}/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
There are no services.
|
||||
</p>
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Services</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any registered services, or you may not have access to view services yet.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/services" rel="noopener noreferrer" target="_blank">Documentation on services</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/services" rel="noopener noreferrer" target="_blank">Read the guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</BlockSlot>
|
||||
|
|
Loading…
Reference in New Issue