Add Empty State component

This commit is contained in:
Joshua Ogle 2018-11-02 14:17:02 -06:00 committed by Matthew Irish
parent e99957aed9
commit 272b4e2f77
16 changed files with 266 additions and 195 deletions

View file

@ -0,0 +1,5 @@
import Component from '@ember/component';
export default Component.extend({
classNames: ['empty-state box is-sideless is-marginless is-fullwidth is-bottomless']
});

View file

@ -0,0 +1,37 @@
.empty-state {
align-items: center;
color: $grey;
display: flex;
background: $ui-gray-050;
justify-content: center;
padding: $spacing-xxl $spacing-s;
}
.empty-state-content {
max-width: 320px;
}
.empty-state-title {
color: $grey;
font-size: $size-4;
font-weight: $font-weight-semibold;
line-height: 1.2;
margin-bottom: $spacing-xs;
}
.empty-state-actions {
margin-top: $spacing-xs;
a,
.link,
a:not(.button):not(.file-delete-button):not(.tag) {
color: $blue;
font-size: $size-8;
font-weight: $font-weight-semibold;
text-decoration: none;
}
> * + * {
margin-left: $spacing-s;
}
}

View file

@ -49,6 +49,7 @@
@import "./components/console-ui-panel";
@import "./components/control-group";
@import "./components/doc-link";
@import "./components/empty-state";
@import "./components/env-banner";
@import "./components/features-selection";
@import "./components/form-section";

View file

@ -21,7 +21,7 @@ $ui-gray-050: #F7F8FA;
$ui-gray-100: #EBEEF2;
$ui-gray-200: #DCE0E6;
$ui-gray-300: #BAC1CC;
$ui-gray-500: #6a7786;
$ui-gray-500: #6F7682;
$ui-gray-700: #525761;
$ui-gray-800: #373A42;
$ui-gray-900: #1F2124;

View file

@ -0,0 +1,15 @@
<div class="empty-state-content">
<h3 class="empty-state-title">
{{{title}}}
</h3>
{{#if message}}
<p class="empty-state-message">
{{{message}}}
</p>
{{/if}}
{{#if hasBlock}}
<div class="empty-state-actions">
{{yield}}
</div>
{{/if}}
</div>

View file

@ -12,7 +12,7 @@
{{/link-to}}
{{/if}}
{{#link-to "vault.cluster.access.identity.create" (pluralize identityType) class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true}}
Create {{identityType}}
Add {{identityType}}
{{i-con glyph="chevron-right" size=11}}
{{/link-to}}
</p.levelRight>
@ -33,10 +33,12 @@
</ul>
</nav>
</div>
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{identity/lookup-input type=identityType}}
{{#if model.meta.total}}
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{identity/lookup-input type=identityType}}
</div>
</div>
</div>
</div>
{{/if}}

View file

@ -17,15 +17,14 @@
</div>
</div>
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
There is no metadata associated with {{model.name}}.
</p>
</div>
</div>
</div>
</div>
<EmptyState
@title="No metadata for {{model.name}} yet"
@message="You can store custom data that you want to associate with a {{lowercase (humanize model.identityType)}}. Edit this {{lowercase (humanize model.identityType)}} to get started.">
{{#link-to "vault.cluster.access.identity.aliases.edit" model.id tagName="button" class="link"}}
Edit {{lowercase (humanize model.identityType)}}
{{/link-to}}
<DocLink @href="https://learn.hashicorp.com/vault/identity-access-management/iam-identity">
Learn More
</DocLink>
</EmptyState>
{{/each-in}}

View file

@ -1,4 +1,4 @@
{{identity/entity-nav identityType=identityType}}
{{identity/entity-nav identityType=identityType model=model}}
{{#if model.meta.total}}
{{#each model as |item|}}
{{#linked-block
@ -35,23 +35,22 @@
</div>
{{/linked-block}}
{{/each}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.access.identity.aliases.index"
}}
{{/if}}
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
There are currently no {{identityType}} aliases.
</p>
</div>
</div>
</div>
</div>
{{/if}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.access.identity.aliases.index"
}}
<EmptyState
@title="No {{identityType}} aliases yet"
@message="A list of {{identityType}} aliases in this namespace will be listed here. Choose one of the {{pluralize identityType}} and click &quot;Add Alias&quot; to get started.">
{{#link-to "vault.cluster.access.identity.create" (pluralize identityType) tagName="button" class="link"}}
Add {{identityType}}
{{/link-to}}
<DocLink @href="https://learn.hashicorp.com/vault/identity-access-management/iam-identity">
Learn More
</DocLink>
</EmptyState>
{{/if}}

View file

@ -106,23 +106,22 @@
</div>
{{/linked-block}}
{{/each}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.access.identity.index"
}}
{{/if}}
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
There are currently no {{pluralize identityType}}.
</p>
</div>
</div>
</div>
</div>
{{/if}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.access.identity.index"
}}
<EmptyState
@title="No {{pluralize identityType}} yet"
@message="A list of {{pluralize identityType}} in this namespace will be listed here. Add your first {{identityType}} to get started.">
{{#link-to "vault.cluster.access.identity.create" (pluralize identityType) tagName="button" class="link"}}
Add {{identityType}}
{{/link-to}}
<DocLink @href="https://learn.hashicorp.com/vault/identity-access-management/iam-identity">
Learn More
</DocLink>
</EmptyState>
{{/if}}

View file

@ -115,25 +115,19 @@
</div>
{{/each}}
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
{{#if (eq baseKey.id '')}}
There are currently no leases.
{{else}}
{{#if filterIsFolder}}
{{#if (eq filter baseKey.id)}}
There are no leases under <code>{{filter}}</code>.
{{else}}
We couldn't find a prefix matching <code>{{filter}}</code>.
{{/if}}
{{/if}}
{{/if}}
</p>
</div>
</div>
<div class="empty-state">
<div class="empty-state-title">
{{#if (eq baseKey.id '')}}
There are currently no leases.
{{else}}
{{#if filterIsFolder}}
{{#if (eq filter baseKey.id)}}
There are no leases under <code>{{filter}}</code>.
{{else}}
We couldn't find a prefix matching <code>{{filter}}</code>.
{{/if}}
{{/if}}
{{/if}}
</div>
</div>
{{/if}}

View file

@ -12,40 +12,40 @@
</p.levelLeft>
<p.levelRight>
{{#link-to "vault.cluster.policies.create" class="button has-icon-right is-ghost is-compact" data-test-policy-create-link=true}}
Create {{uppercase policyType}} policy
Add {{uppercase policyType}} policy
{{i-con glyph="chevron-right" size=11}}
{{/link-to}}
</p.levelRight>
</PageHeader>
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{navigate-input
filterFocusDidChange=(action "setFilterFocus")
filterDidChange=(action "setFilter")
filter=filter
filterMatchesKey=filterMatchesKey
firstPartialMatch=firstPartialMatch
extraNavParams=policyType
placeholder="Filter policies"
mode="policy"
}}
{{#if filterFocused}}
{{#if filterMatchesKey}}
<p class="input-hint">
<kbd>ENTER</kbd> to go to <code>{{or pageFilter filter}}</code>
</p>
{{#if model.meta.total}}
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{navigate-input
filterFocusDidChange=(action "setFilterFocus")
filterDidChange=(action "setFilter")
filter=filter
filterMatchesKey=filterMatchesKey
firstPartialMatch=firstPartialMatch
extraNavParams=policyType
placeholder="Filter policies"
mode="policy"
}}
{{#if filterFocused}}
{{#if filterMatchesKey}}
<p class="input-hint">
<kbd>ENTER</kbd> to go to <code>{{or pageFilter filter}}</code>
</p>
{{/if}}
{{#if firstPartialMatch}}
<p class="input-hint">
<kbd>TAB</kbd> to complete <code>{{firstPartialMatch.id}}</code>
</p>
{{/if}}
{{/if}}
{{#if firstPartialMatch}}
<p class="input-hint">
<kbd>TAB</kbd> to complete <code>{{firstPartialMatch.id}}</code>
</p>
{{/if}}
{{/if}}
</div>
</div>
</div>
</div>
{{#if model.meta.total}}
{{#each model as |item|}}
{{#if (eq item.id "root")}}
<div class="list-item-row is-flex" data-test-policy-item>
@ -139,30 +139,29 @@
</div>
{{/linked-block}}
{{/if}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.policies.index"
}}
{{/if}}
{{else}}
<div class="box is-sideless">
<p> There are no policies matching <code>{{pageFilter}}</code>. </p>
</div>
<EmptyState
@title="No policies matching <code>{{pageFilter}}</code>"
/>
{{/each}}
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey" data-test-no-policies-text>
There are currently no {{uppercase policyType}} policies.
</p>
</div>
</div>
</div>
</div>
{{/if}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link="vault.cluster.policies.index"
}}
<EmptyState
@title="No {{uppercase policyType}} policies yet"
@message="A list of policies will be listed here. Add your first {{uppercase policyType}} policy to get started.">
{{#link-to "vault.cluster.policies.create" tagName="button" class="link"}}
Add {{uppercase policyType}} policy
{{/link-to}}
<DocLink @href="https://learn.hashicorp.com/vault/getting-started/policies">
Learn More
</DocLink>
</EmptyState>
{{/if}}
{{else}}
{{upgrade-page title="Sentinel" minimumEdition="Vault Enterprise Premium"}}

View file

@ -6,11 +6,9 @@
</h1>
</p.levelLeft>
</PageHeader>
<div class="box is-sideless has-background-white-bis has-text-grey has-text-centered">
<p>
The current cluster configuration does not support replication.
</p>
</div>
<EmptyState
@title="The current cluster configuration does not support replication"
/>
{{else}}
{{replication-summary
cluster=model

View file

@ -46,17 +46,18 @@
<hr class="is-marginless" />
{{/each}}
{{else}}
<div class="box is-bottomless has-background-white-bis is-marginless">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">
There are currently no known {{performanceMode}} secondary clusters associated with this cluster.
</p>
</div>
</div>
</div>
</div>
<EmptyState
@title="No known {{performanceMode}} secondary clusters associated with this cluster"
@message="A list of secondary clusters will be listed here. Add your first secondary cluster to get started.">
{{#if model.canAddSecondary}}
{{#link-to 'vault.cluster.replication.mode.secondaries.add' model.name replicationMode tagName="button" class="link" }}
Add secondary
{{/link-to}}
{{/if}}
<DocLink @href="https://www.vaultproject.io/docs/internals/replication.html">
Learn More
</DocLink>
</EmptyState>
{{/if}}
<div class="field is-grouped box is-shadowless is-fullwidth">
{{#if model.canAddSecondary}}

View file

@ -1,39 +1,39 @@
{{secret-list-header isCertTab=(eq tab "certs") model=backendModel baseKey=baseKey backendCrumb=backendCrumb filter=filter}}
{{#with (options-for-backend backendType tab) as |options|}}
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{navigate-input
enterpriseProduct="vault"
filterFocusDidChange=(action "setFilterFocus")
filterDidChange=(action "setFilter")
filter=filter
filterMatchesKey=filterMatchesKey
firstPartialMatch=firstPartialMatch
baseKey=(get baseKey "id")
shouldNavigateTree=options.navigateTree
placeholder=options.searchPlaceholder
mode=(if (eq tab 'certs') 'secrets-cert' 'secrets')
}}
{{#if filterFocused}}
{{#if filterMatchesKey}}
{{#unless filterIsFolder}}
{{#if model.meta.total}}
<div class="box is-sideless has-background-grey-lighter has-short-padding is-marginless">
<div class="level">
<div class="level-left">
{{navigate-input
enterpriseProduct="vault"
filterFocusDidChange=(action "setFilterFocus")
filterDidChange=(action "setFilter")
filter=filter
filterMatchesKey=filterMatchesKey
firstPartialMatch=firstPartialMatch
baseKey=(get baseKey "id")
shouldNavigateTree=options.navigateTree
placeholder=options.searchPlaceholder
mode=(if (eq tab 'certs') 'secrets-cert' 'secrets')
}}
{{#if filterFocused}}
{{#if filterMatchesKey}}
{{#unless filterIsFolder}}
<p class="input-hint">
<kbd>Enter</kbd> to view {{filter}}
</p>
{{/unless}}
{{/if}}
{{#if firstPartialMatch}}
<p class="input-hint">
<kbd>Enter</kbd> to view {{filter}}
<kbd>Tab</kbd> to autocomplete
</p>
{{/unless}}
{{/if}}
{{/if}}
{{#if firstPartialMatch}}
<p class="input-hint">
<kbd>Tab</kbd> to autocomplete
</p>
{{/if}}
{{/if}}
</div>
</div>
</div>
</div>
{{#if model.meta.total}}
{{#each model as |item|}}
{{partial options.listItemPartial}}
{{else}}
@ -45,27 +45,29 @@
{{/if}}
</div>
{{/each}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link=(concat "vault.cluster.secrets.backend.list" (if (not baseKey.id) "-root"))
model=(compact (array backend (if baseKey.id baseKey.id)))
}}
{{/if}}
{{else}}
<BlockEmpty>
{{#if (eq baseKey.id '')}}
There are currently no {{pluralize options.item}} in this backend.
{{else}}
{{#if filterIsFolder}}
{{#if (eq filter baseKey.id)}}
There are no {{pluralize options.item}} under <code>{{or filter}}</code>.
{{else}}
We couldn't find a folder matching <code>{{filter}}</code>.
{{/if}}
<div class="empty-state">
<div class="empty-state-title">
{{#if (eq baseKey.id '')}}
There are currently no {{pluralize options.item}} in this backend.
{{else}}
{{#if filterIsFolder}}
{{#if (eq filter baseKey.id)}}
There are no {{pluralize options.item}} under <code>{{or filter}}</code>.
{{else}}
We couldn't find a folder matching <code>{{filter}}</code>.
{{/if}}
{{/if}}
{{/if}}
{{/if}}
</BlockEmpty>
</div>
</div>
{{/if}}
{{/with}}
{{#if (gt model.meta.lastPage 1) }}
{{list-pagination
page=model.meta.currentPage
lastPage=model.meta.lastPage
link=(concat "vault.cluster.secrets.backend.list" (if (not baseKey.id) "-root"))
model=(compact (array backend (if baseKey.id baseKey.id)))
}}
{{/if}}

View file

@ -29,13 +29,7 @@
{{/confirm-action}}
</div>
{{else}}
<div class="box is-bottomless has-background-white-bis">
<div class="columns is-centered">
<div class="column is-half has-text-centered">
<div class="box is-shadowless has-background-white-bis">
<p class="has-text-grey">The token you are currently authenticated with does not have sufficient capabilities to seal this vault.</p>
</div>
</div>
</div>
</div>
<EmptyState
@title="The token you are currently authenticated with does not have sufficient capabilities to seal this vault"
/>
{{/if}}

View file

@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | empty-state', 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`{{empty-state}}`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
{{#empty-state}}
template block text
{{/empty-state}}
`);
assert.equal(this.element.textContent.trim(), 'template block text');
});
});