UI/license banners (#11759)
This commit is contained in:
parent
c6c0424a8e
commit
f9ccd941ad
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: show site-wide banners for license warnings if applicable
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* @module LicenseBanners
|
||||
* LicenseBanners components are used to display Vault-specific license expiry messages
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <LicenseBanners @expiry={expiryDate} />
|
||||
* ```
|
||||
* @param {string} expiry - RFC3339 date timestamp
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import isAfter from 'date-fns/isAfter';
|
||||
import differenceInDays from 'date-fns/differenceInDays';
|
||||
|
||||
export default class LicenseBanners extends Component {
|
||||
get licenseExpired() {
|
||||
if (!this.args.expiry) return false;
|
||||
return isAfter(new Date(), new Date(this.args.expiry));
|
||||
}
|
||||
|
||||
get licenseExpiringInDays() {
|
||||
// Anything more than 30 does not render a warning
|
||||
if (!this.args.expiry) return 99;
|
||||
return differenceInDays(new Date(this.args.expiry), new Date());
|
||||
}
|
||||
}
|
|
@ -12,6 +12,11 @@ export default Model.extend({
|
|||
status: attr('string'),
|
||||
standby: attr('boolean'),
|
||||
type: attr('string'),
|
||||
license: attr('object'),
|
||||
|
||||
/* Licensing concerns */
|
||||
licenseExpiry: alias('license.expiry'),
|
||||
licenseState: alias('license.state'),
|
||||
|
||||
needsInit: computed('nodes', 'nodes.@each.initialized', function() {
|
||||
// needs init if no nodes are initialized
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.license-banner-wrapper {
|
||||
width: 100%;
|
||||
max-width: 1344px;
|
||||
margin: $spacing-l auto 0;
|
||||
padding: 0 1.5rem;
|
||||
}
|
|
@ -64,6 +64,7 @@
|
|||
@import './components/input-hint';
|
||||
@import './components/kmip-role-edit';
|
||||
@import './components/known-secondaries-card.scss';
|
||||
@import './components/license-banners';
|
||||
@import './components/linked-block';
|
||||
@import './components/list-item-row';
|
||||
@import './components/list-pagination';
|
||||
|
|
|
@ -174,6 +174,9 @@
|
|||
.has-top-margin-s {
|
||||
margin-top: $spacing-s;
|
||||
}
|
||||
.has-top-margin-l {
|
||||
margin-top: $spacing-l;
|
||||
}
|
||||
.has-top-margin-xl {
|
||||
margin-top: $spacing-xl;
|
||||
}
|
||||
|
|
|
@ -132,3 +132,7 @@
|
|||
.has-text-highlight {
|
||||
color: $yellow-500;
|
||||
}
|
||||
|
||||
.message.message-marginless {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{{#if this.licenseExpired}}
|
||||
<div class="license-banner-wrapper" data-test-license-banner data-test-license-banner-expired>
|
||||
<AlertBanner
|
||||
@type="danger"
|
||||
@title="License expired"
|
||||
@message="Your Vault license expired on {{date-format @expiry "MMM d, yyyy"}}. Add a new license to your configuration and restart Vault."
|
||||
@marginless={{true}}
|
||||
>
|
||||
<a href="https://learn.hashicorp.com/tutorials/nomad/hashicorp-enterprise-license" target="_blank" rel="noreferrer noopener">Read documentation</a>
|
||||
</AlertBanner>
|
||||
</div>
|
||||
{{else if (lte this.licenseExpiringInDays 30)}}
|
||||
<div class="license-banner-wrapper" data-test-license-banner data-test-license-banner-warning>
|
||||
<AlertBanner
|
||||
@type="warning"
|
||||
@title="Vault license expiring"
|
||||
@message="Your Vault license will expire in {{this.licenseExpiringInDays}} days at {{date-format @expiry "hh:mm:ss a"}} on {{date-format @expiry "MMM d, yyyy"}}. Add a new license to your configuration."
|
||||
@marginless={{true}}
|
||||
>
|
||||
<a href="https://learn.hashicorp.com/tutorials/nomad/hashicorp-enterprise-license" target="_blank" rel="noreferrer noopener">Read documentation</a>
|
||||
</AlertBanner>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -101,6 +101,7 @@
|
|||
</Nav.items>
|
||||
</NavHeader>
|
||||
{{/if}}
|
||||
<LicenseBanners @expiry={{activeCluster.licenseExpiry}} />
|
||||
<div class="global-flash">
|
||||
{{#each flashMessages.queue as |flash|}}
|
||||
{{#flash-message data-test-flash-message=true flash=flash as |customComponent flash close|}}
|
||||
|
|
|
@ -28,10 +28,12 @@ export default Component.extend({
|
|||
secondIconType: null,
|
||||
progressBar: null,
|
||||
yieldWithoutColumn: false,
|
||||
marginless: false,
|
||||
classNameBindings: ['containerClass'],
|
||||
|
||||
containerClass: computed('type', function() {
|
||||
return 'message ' + messageTypes([this.type]).class;
|
||||
containerClass: computed('type', 'marginless', function() {
|
||||
const base = this.marginless ? 'message message-marginless ' : 'message ';
|
||||
return base + messageTypes([this.type]).class;
|
||||
}),
|
||||
|
||||
alertType: computed('type', function() {
|
||||
|
|
|
@ -29,5 +29,25 @@ export default function() {
|
|||
};
|
||||
});
|
||||
|
||||
this.get('/sys/health', function() {
|
||||
return {
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
standby: false,
|
||||
license: {
|
||||
expiry: '2021-05-12T23:20:50.52Z',
|
||||
state: 'stored',
|
||||
},
|
||||
performance_standby: false,
|
||||
replication_performance_mode: 'disabled',
|
||||
replication_dr_mode: 'disabled',
|
||||
server_time_utc: 1622562585,
|
||||
version: '1.9.0+ent',
|
||||
cluster_name: 'vault-cluster-e779cd7c',
|
||||
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
|
||||
last_wal: 121,
|
||||
};
|
||||
});
|
||||
|
||||
this.passthrough();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { visit } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import Pretender from 'pretender';
|
||||
import formatRFC3339 from 'date-fns/formatRFC3339';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
|
||||
const generateHealthResponse = state => {
|
||||
let expiry;
|
||||
switch (state) {
|
||||
case 'expired':
|
||||
expiry = subDays(new Date(), 2);
|
||||
break;
|
||||
case 'expiring':
|
||||
expiry = addDays(new Date(), 10);
|
||||
break;
|
||||
default:
|
||||
expiry = addDays(new Date(), 33);
|
||||
break;
|
||||
}
|
||||
return {
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
standby: false,
|
||||
license: {
|
||||
expiry: formatRFC3339(expiry),
|
||||
state: 'stored',
|
||||
},
|
||||
performance_standby: false,
|
||||
replication_performance_mode: 'disabled',
|
||||
replication_dr_mode: 'disabled',
|
||||
server_time_utc: 1622562585,
|
||||
version: '1.9.0+ent',
|
||||
cluster_name: 'vault-cluster-e779cd7c',
|
||||
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
|
||||
last_wal: 121,
|
||||
};
|
||||
};
|
||||
|
||||
module('Acceptance | Enterprise | License banner warnings', function(hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
test('it shows no license banner if license expires in > 30 days', async function(assert) {
|
||||
const healthResp = generateHealthResponse();
|
||||
this.server = new Pretender(function() {
|
||||
this.get('/v1/sys/health', response => {
|
||||
return [response, { 'Content-Type': 'application/json' }, JSON.stringify(healthResp)];
|
||||
});
|
||||
this.get('/v1/sys/internal/ui/feature-flags', this.passthrough);
|
||||
// this.get('/v1/sys/health', this.passthrough);
|
||||
this.get('/v1/sys/seal-status', this.passthrough);
|
||||
this.get('/v1/sys/license/features', this.passthrough);
|
||||
});
|
||||
await visit('/vault/auth');
|
||||
assert.dom('[data-test-license-banner]').doesNotExist('license banner does not show');
|
||||
this.server.shutdown();
|
||||
});
|
||||
test('it shows license banner warning if license expires within 30 days', async function(assert) {
|
||||
const healthResp = generateHealthResponse('expiring');
|
||||
this.server = new Pretender(function() {
|
||||
this.get('/v1/sys/health', response => {
|
||||
return [response, { 'Content-Type': 'application/json' }, JSON.stringify(healthResp)];
|
||||
});
|
||||
this.get('/v1/sys/internal/ui/feature-flags', this.passthrough);
|
||||
this.get('/v1/sys/seal-status', this.passthrough);
|
||||
this.get('/v1/sys/license/features', this.passthrough);
|
||||
});
|
||||
await visit('/vault/auth');
|
||||
assert.dom('[data-test-license-banner-warning]').exists('license warning shows');
|
||||
this.server.shutdown();
|
||||
});
|
||||
|
||||
test('it shows license banner alert if license has already expired', async function(assert) {
|
||||
const healthResp = generateHealthResponse('expired');
|
||||
this.server = new Pretender(function() {
|
||||
this.get('/v1/sys/health', response => {
|
||||
return [response, { 'Content-Type': 'application/json' }, JSON.stringify(healthResp)];
|
||||
});
|
||||
this.get('/v1/sys/internal/ui/feature-flags', this.passthrough);
|
||||
this.get('/v1/sys/seal-status', this.passthrough);
|
||||
this.get('/v1/sys/license/features', this.passthrough);
|
||||
});
|
||||
await visit('/vault/auth');
|
||||
assert.dom('[data-test-license-banner-expired]').exists('expired license message shows');
|
||||
this.server.shutdown();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import addDays from 'date-fns/addDays';
|
||||
import formatRFC3339 from 'date-fns/formatRFC3339';
|
||||
|
||||
module('Integration | Component | license-banners', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it does not render if no expiry', async function(assert) {
|
||||
await render(hbs`<LicenseBanners />`);
|
||||
assert.dom('[data-test-license-banner]').doesNotExist('License banner does not render');
|
||||
});
|
||||
|
||||
test('it renders an error if expiry is before now', async function(assert) {
|
||||
const yesterday = subDays(new Date(), 1);
|
||||
this.set('expiry', formatRFC3339(yesterday));
|
||||
await render(hbs`<LicenseBanners @expiry={{expiry}} />`);
|
||||
assert.dom('[data-test-license-banner-expired]').exists('Expired license banner renders');
|
||||
assert.dom('.message-title').hasText('License expired', 'Shows correct title on alert');
|
||||
});
|
||||
|
||||
test('it renders a warning if expiry is within 30 days', async function(assert) {
|
||||
const nextMonth = addDays(new Date(), 30);
|
||||
this.set('expiry', formatRFC3339(nextMonth));
|
||||
await render(hbs`<LicenseBanners @expiry={{expiry}} />`);
|
||||
assert.dom('[data-test-license-banner-warning]').exists('Warning license banner renders');
|
||||
assert.dom('.message-title').hasText('Vault license expiring', 'Shows correct title on alert');
|
||||
});
|
||||
|
||||
test('it does not render a banner if expiry is outside 30 days', async function(assert) {
|
||||
const outside30 = addDays(new Date(), 32);
|
||||
this.set('expiry', formatRFC3339(outside30));
|
||||
await render(hbs`<LicenseBanners @expiry={{expiry}} />`);
|
||||
assert.dom('[data-test-license-banner]').doesNotExist('License banner does not render');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue