mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-07 07:07:05 +02:00
HCP Link Status (#16959)
* adds LinkStatus component to NavHeader to display banner with HCP link status * adds changelog entry * adds period to connected status message * updates hcp link status to current cluster polling to automatically update state
This commit is contained in:
parent
ea6d22c2a7
commit
3079b45e6b
3
changelog/16959.txt
Normal file
3
changelog/16959.txt
Normal file
@ -0,0 +1,3 @@
|
||||
```release-note:feature
|
||||
ui: adds HCP link status banner
|
||||
```
|
29
ui/app/components/link-status.js
Normal file
29
ui/app/components/link-status.js
Normal file
@ -0,0 +1,29 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
/**
|
||||
* @module LinkStatus
|
||||
* LinkStatus components are used to indicate link status to the hashicorp cloud platform
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <LinkStatus @status={{this.currentCluser.cluster.hcpLinkStatus}} />
|
||||
* ```
|
||||
*
|
||||
* @param {string} status - cluster.hcpLinkStatus value from currentCluster service
|
||||
*/
|
||||
|
||||
export default class LinkStatus extends Component {
|
||||
@service store;
|
||||
@service version;
|
||||
|
||||
get showBanner() {
|
||||
// enterprise only feature at this time but will expand to OSS in future release
|
||||
// there are plans to handle connection failure states -- only alert if connected until further states are returned
|
||||
return this.version.isEnterprise && this.args.status === 'connected';
|
||||
}
|
||||
|
||||
get bannerClass() {
|
||||
return this.args.status === 'connected' ? 'connected' : 'warning';
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
router: service(),
|
||||
currentCluster: service(),
|
||||
'data-test-navheader': true,
|
||||
attributeBindings: ['data-test-navheader'],
|
||||
classNameBindings: 'consoleFullscreen:panel-fullscreen',
|
||||
|
@ -43,6 +43,7 @@ export default Model.extend({
|
||||
sealProgress: alias('leaderNode.progress'),
|
||||
sealType: alias('leaderNode.type'),
|
||||
storageType: alias('leaderNode.storageType'),
|
||||
hcpLinkStatus: alias('leaderNode.hcpLinkStatus'),
|
||||
hasProgress: gte('sealProgress', 1),
|
||||
usingRaft: equal('storageType', 'raft'),
|
||||
|
||||
|
@ -24,6 +24,7 @@ export default Model.extend({
|
||||
version: attr('string'),
|
||||
type: attr('string'),
|
||||
storageType: attr('string'),
|
||||
hcpLinkStatus: attr('string'),
|
||||
|
||||
//https://www.vaultproject.io/docs/http/sys-leader.html
|
||||
haEnabled: attr('boolean'),
|
||||
|
@ -1,13 +1,45 @@
|
||||
.navbar {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@include from($mobile) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-status {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: $size-7;
|
||||
font-weight: $font-weight-semibold;
|
||||
|
||||
&.connected {
|
||||
background-color: $ui-gray-800;
|
||||
color: #c2c5cb;
|
||||
|
||||
a {
|
||||
color: #c2c5cb;
|
||||
}
|
||||
}
|
||||
&.warning {
|
||||
background-color: #fcf6ea;
|
||||
color: #975b06;
|
||||
|
||||
a {
|
||||
color: #975b06;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-actions {
|
||||
background-color: $black;
|
||||
display: flex;
|
||||
height: $header-height;
|
||||
justify-content: flex-start;
|
||||
left: 0;
|
||||
padding: $spacing-xs $spacing-s $spacing-xs 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
|
22
ui/app/templates/components/link-status.hbs
Normal file
22
ui/app/templates/components/link-status.hbs
Normal file
@ -0,0 +1,22 @@
|
||||
{{#if this.showBanner}}
|
||||
<div class="navbar-status {{this.bannerClass}}">
|
||||
<Icon @name="info" />
|
||||
<p data-test-link-status>
|
||||
{{#if (eq @status "connected")}}
|
||||
This self-managed Vault is linked to the
|
||||
<a href="https://portal.cloud.hashicorp.com/sign-in" target="_blank" rel="noopener noreferrer">
|
||||
HashiCorp Cloud Platform.
|
||||
</a>
|
||||
{{else if (eq @status "401")}}
|
||||
{{! roughing in 401 and 500 connection statuses -- update strings once they are exposed by the API }}
|
||||
Vault can’t connect to HashiCorp Cloud Portal. Check your config file to ensure that credentials are correct.
|
||||
{{else if (eq @status "500")}}
|
||||
Vault’s connection to HashiCorp Cloud Portal is down. Check
|
||||
<a href="https://status.hashicorp.com" target="_blank" rel="noopener noreferrer">
|
||||
the status page
|
||||
</a>
|
||||
for details.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
@ -1,38 +1,42 @@
|
||||
<nav class="navbar">
|
||||
<div class="navbar-brand" data-test-navheader-home>
|
||||
{{yield (hash home=(component "nav-header/home"))}}
|
||||
</div>
|
||||
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
||||
|
||||
{{#unless this.navDrawerOpen}}
|
||||
<button type="button" class="navbar-drawer-toggle is-hidden-tablet" {{action "toggleNavDrawer"}}>
|
||||
<Icon @name="more-vertical" />
|
||||
Menu
|
||||
</button>
|
||||
{{/unless}}
|
||||
|
||||
{{#unless this.hideLinks}}
|
||||
<div class="navbar-drawer{{if this.navDrawerOpen ' is-active'}}">
|
||||
<div class="navbar-drawer-scroll">
|
||||
<div data-test-navheader-main>
|
||||
{{yield (hash main=(component "nav-header/main") closeDrawer=(action "toggleNavDrawer" false))}}
|
||||
</div>
|
||||
<div class="navbar-end" data-test-navheader-items>
|
||||
{{yield (hash items=(component "nav-header/items") closeDrawer=(action "toggleNavDrawer" false))}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.navDrawerOpen}}
|
||||
<button class="navbar-drawer-toggle is-hidden-tablet" type="button" {{action "toggleNavDrawer" false}}>
|
||||
<Icon @name="x" />
|
||||
</button>
|
||||
{{/if}}
|
||||
<div class="navbar-actions">
|
||||
<div class="navbar-brand" data-test-navheader-home>
|
||||
{{yield (hash home=(component "nav-header/home"))}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div
|
||||
class="navbar-drawer-overlay{{if this.navDrawerOpen ' is-active'}}"
|
||||
role="button"
|
||||
onclick={{action "toggleNavDrawer" (not this.navDrawerOpen)}}
|
||||
></div>
|
||||
{{#unless this.navDrawerOpen}}
|
||||
<button type="button" class="navbar-drawer-toggle is-hidden-tablet" {{action "toggleNavDrawer"}}>
|
||||
<Icon @name="more-vertical" />
|
||||
Menu
|
||||
</button>
|
||||
{{/unless}}
|
||||
|
||||
{{#unless this.hideLinks}}
|
||||
<div class="navbar-drawer{{if this.navDrawerOpen ' is-active'}}">
|
||||
<div class="navbar-drawer-scroll">
|
||||
<div data-test-navheader-main>
|
||||
{{yield (hash main=(component "nav-header/main") closeDrawer=(action "toggleNavDrawer" false))}}
|
||||
</div>
|
||||
<div class="navbar-end" data-test-navheader-items>
|
||||
{{yield (hash items=(component "nav-header/items") closeDrawer=(action "toggleNavDrawer" false))}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.navDrawerOpen}}
|
||||
<button class="navbar-drawer-toggle is-hidden-tablet" type="button" {{action "toggleNavDrawer" false}}>
|
||||
<Icon @name="x" />
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div
|
||||
class="navbar-drawer-overlay{{if this.navDrawerOpen ' is-active'}}"
|
||||
role="button"
|
||||
onclick={{action "toggleNavDrawer" (not this.navDrawerOpen)}}
|
||||
></div>
|
||||
</div>
|
||||
</nav>
|
||||
<Console::UiPanel @isFullscreen={{this.consoleFullscreen}} />
|
26
ui/mirage/handlers/hcp-link.js
Normal file
26
ui/mirage/handlers/hcp-link.js
Normal file
@ -0,0 +1,26 @@
|
||||
export default function (server) {
|
||||
const handleResponse = (req, props) => {
|
||||
const xhr = req.passthrough();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status < 300) {
|
||||
// XMLHttpRequest response prop only has a getter -- redefine as writable and set value
|
||||
Object.defineProperty(xhr, 'response', {
|
||||
writable: true,
|
||||
value: JSON.stringify({
|
||||
...JSON.parse(xhr.responseText),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
server.get('sys/seal-status', (schema, req) => {
|
||||
// randomly return one of the various states to test polling
|
||||
// 401 and 500 are stubs -- update with actual API values once determined
|
||||
const hcp_link_status = ['connected', 'disconnected', '401', '500'][Math.floor(Math.random() * 2)];
|
||||
return handleResponse(req, { hcp_link_status });
|
||||
});
|
||||
// enterprise only feature initially
|
||||
server.get('sys/health', (schema, req) => handleResponse(req, { version: '1.12.0-dev1+ent' }));
|
||||
}
|
@ -7,5 +7,6 @@ import clients from './clients';
|
||||
import db from './db';
|
||||
import kms from './kms';
|
||||
import mfaConfig from './mfa-config';
|
||||
import hcpLink from './hcp-link';
|
||||
|
||||
export { base, activity, mfaLogin, mfaConfig, clients, db, kms };
|
||||
export { base, activity, mfaLogin, mfaConfig, clients, db, kms, hcpLink };
|
||||
|
36
ui/tests/integration/components/link-status-test.js
Normal file
36
ui/tests/integration/components/link-status-test.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
|
||||
module('Integration | Component | link-status', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
// this can be removed once feature is released for OSS
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.lookup('service:version').set('isEnterprise', true);
|
||||
});
|
||||
|
||||
test('it does not render disconnected status', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status="disconnected" />`);
|
||||
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state');
|
||||
});
|
||||
|
||||
test('it renders connected status', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status="connected" />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('connected', 'Correct class renders for connected state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
'This self-managed Vault is linked to the HashiCorp Cloud Platform.',
|
||||
'Copy renders for connected state'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-link-status] a')
|
||||
.hasAttribute('href', 'https://portal.cloud.hashicorp.com/sign-in', 'HCP sign in link renders');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user