From 3079b45e6bbb0e828dfd84a046c20d1dfa669eb9 Mon Sep 17 00:00:00 2001 From: Jordan Reimer Date: Wed, 7 Sep 2022 10:21:23 -0600 Subject: [PATCH] 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 --- changelog/16959.txt | 3 + ui/app/components/link-status.js | 29 ++++++++ ui/app/components/nav-header.js | 1 + ui/app/models/cluster.js | 1 + ui/app/models/node.js | 1 + ui/app/styles/core/navbar.scss | 40 +++++++++-- ui/app/templates/components/link-status.hbs | 22 ++++++ ui/app/templates/components/nav-header.hbs | 68 ++++++++++--------- ui/mirage/handlers/hcp-link.js | 26 +++++++ ui/mirage/handlers/index.js | 3 +- .../components/link-status-test.js | 36 ++++++++++ 11 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 changelog/16959.txt create mode 100644 ui/app/components/link-status.js create mode 100644 ui/app/templates/components/link-status.hbs create mode 100644 ui/mirage/handlers/hcp-link.js create mode 100644 ui/tests/integration/components/link-status-test.js diff --git a/changelog/16959.txt b/changelog/16959.txt new file mode 100644 index 0000000000..aabfac3cae --- /dev/null +++ b/changelog/16959.txt @@ -0,0 +1,3 @@ +```release-note:feature +ui: adds HCP link status banner +``` \ No newline at end of file diff --git a/ui/app/components/link-status.js b/ui/app/components/link-status.js new file mode 100644 index 0000000000..46e8020c2a --- /dev/null +++ b/ui/app/components/link-status.js @@ -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 + * + * ``` + * + * @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'; + } +} diff --git a/ui/app/components/nav-header.js b/ui/app/components/nav-header.js index 7697c6fc1a..7f67b8f258 100644 --- a/ui/app/components/nav-header.js +++ b/ui/app/components/nav-header.js @@ -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', diff --git a/ui/app/models/cluster.js b/ui/app/models/cluster.js index 07e259a284..1f24ca9dc8 100644 --- a/ui/app/models/cluster.js +++ b/ui/app/models/cluster.js @@ -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'), diff --git a/ui/app/models/node.js b/ui/app/models/node.js index 2acd3a05e2..efc9bd60a9 100644 --- a/ui/app/models/node.js +++ b/ui/app/models/node.js @@ -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'), diff --git a/ui/app/styles/core/navbar.scss b/ui/app/styles/core/navbar.scss index a981fcf12b..48d9cc7d1f 100644 --- a/ui/app/styles/core/navbar.scss +++ b/ui/app/styles/core/navbar.scss @@ -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 { diff --git a/ui/app/templates/components/link-status.hbs b/ui/app/templates/components/link-status.hbs new file mode 100644 index 0000000000..1fbe0ea142 --- /dev/null +++ b/ui/app/templates/components/link-status.hbs @@ -0,0 +1,22 @@ +{{#if this.showBanner}} + +{{/if}} \ No newline at end of file diff --git a/ui/app/templates/components/nav-header.hbs b/ui/app/templates/components/nav-header.hbs index b7c138967a..ba37e51b97 100644 --- a/ui/app/templates/components/nav-header.hbs +++ b/ui/app/templates/components/nav-header.hbs @@ -1,38 +1,42 @@ \ No newline at end of file diff --git a/ui/mirage/handlers/hcp-link.js b/ui/mirage/handlers/hcp-link.js new file mode 100644 index 0000000000..2f7380f46b --- /dev/null +++ b/ui/mirage/handlers/hcp-link.js @@ -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' })); +} diff --git a/ui/mirage/handlers/index.js b/ui/mirage/handlers/index.js index cd134b5823..3659f74abe 100644 --- a/ui/mirage/handlers/index.js +++ b/ui/mirage/handlers/index.js @@ -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 }; diff --git a/ui/tests/integration/components/link-status-test.js b/ui/tests/integration/components/link-status-test.js new file mode 100644 index 0000000000..081a622581 --- /dev/null +++ b/ui/tests/integration/components/link-status-test.js @@ -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``); + + assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state'); + }); + + test('it renders connected status', async function (assert) { + await render(hbs``); + + 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'); + }); +});