diff --git a/ui/app/components/link-status.js b/ui/app/components/link-status.js
index 46e8020c2a..50f6407fc4 100644
--- a/ui/app/components/link-status.js
+++ b/ui/app/components/link-status.js
@@ -7,23 +7,66 @@ import { inject as service } from '@ember/service';
*
* @example
* ```js
- *
+ *
* ```
*
- * @param {string} status - cluster.hcpLinkStatus value from currentCluster service
+ * @param {string} status - cluster.hcpLinkStatus value from currentCluster service -- returned from seal-status endpoint
*/
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 state() {
+ if (!this.args.status) return null;
+ // connected state is returned with no further information
+ if (this.args.status === 'connected') return this.args.status;
+ // disconnected and connecting states are returned with a timestamp and error
+ // state is always the first word of the string
+ return this.args.status.split(' ', 1).toString();
}
- get bannerClass() {
- return this.args.status === 'connected' ? 'connected' : 'warning';
+ get timestamp() {
+ try {
+ return this.state !== 'connected' ? this.args.status.split('since')[1].split('m=')[0].trim() : null;
+ } catch {
+ return null;
+ }
+ }
+
+ get message() {
+ if (this.args.status) {
+ const error = this.args.status.split('error:')[1];
+ const time = `[${this.timestamp}]`;
+ if (this.state === 'disconnected') {
+ // if generally disconnected hide the banner
+ return !error || error.includes('UNKNOWN')
+ ? null
+ : `Vault has been disconnected from the Hashicorp Cloud Platform since ${time}. Error: ${error}`;
+ } else if (this.state === 'connecting') {
+ if (error.includes('connection refused')) {
+ return `Vault has been trying to connect to the Hashicorp Cloud Platform since ${time}, but the Scada provider is down. Vault will try again soon.`;
+ } else if (error.includes('principal does not have permission to register as provider')) {
+ return `Vault tried connecting to the Hashicorp Cloud Platform, but the Resource ID is invalid. Check your resource ID. ${time}`;
+ } else if (error.includes('cannot fetch token: 401 Unauthorized')) {
+ return `Vault tried connecting to the Hashicorp Cloud Platform, but the authorization information is wrong. Update it and try again. ${time}`;
+ } else {
+ // catch all for any unknown errors
+ return `Vault has been trying to connect to the Hashicorp Cloud Platform since ${time}. Vault will try again soon. Error: ${error}`;
+ }
+ }
+ }
+ return null;
+ }
+
+ get showStatus() {
+ // enterprise only feature at this time but will expand to OSS in future release
+ if (!this.version.isEnterprise || !this.args.status) {
+ return false;
+ }
+ if (this.state === 'disconnected' && !this.message) {
+ return false;
+ }
+ return true;
}
}
diff --git a/ui/app/templates/components/link-status.hbs b/ui/app/templates/components/link-status.hbs
index 1fbe0ea142..e12676e04e 100644
--- a/ui/app/templates/components/link-status.hbs
+++ b/ui/app/templates/components/link-status.hbs
@@ -1,21 +1,14 @@
-{{#if this.showBanner}}
-
+{{#if this.showStatus}}
+
- {{#if (eq @status "connected")}}
+ {{#if (eq this.state "connected")}}
This self-managed Vault is linked to the
HashiCorp Cloud Platform.
- {{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
-
- the status page
-
- for details.
+ {{else}}
+ {{this.message}}
{{/if}}
diff --git a/ui/mirage/handlers/hcp-link.js b/ui/mirage/handlers/hcp-link.js
index 2f7380f46b..e1aca516a9 100644
--- a/ui/mirage/handlers/hcp-link.js
+++ b/ui/mirage/handlers/hcp-link.js
@@ -1,3 +1,17 @@
+export const statuses = [
+ 'connected',
+ 'disconnected since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: UNKNOWN',
+ 'disconnected since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: some other error other than unknown',
+ 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: dial tcp [::1]:28083: connect: connection refused',
+ 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: principal does not have permission to register as provider: rpc error: code = PermissionDenied desc =',
+ 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: failed to get access token: oauth2: cannot fetch token: 401 Unauthorized. Response: {"error":"access_denied","error_description":"Unauthorized"}',
+ 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: connection error we are unaware of',
+ // the following were identified as dev only errors -- leaving in case they need to be handled
+ // 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: failed to get access token: Post "https://aauth.idp.hcp.dev/oauth2/token": x509: “*.hcp.dev” certificate name does not match input',
+ // 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: UNKNOWN',
+];
+let index = null;
+
export default function (server) {
const handleResponse = (req, props) => {
const xhr = req.passthrough();
@@ -16,10 +30,13 @@ export default function (server) {
};
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 });
+ // return next status from statuses array
+ if (index === null || index === statuses.length - 1) {
+ index = 0;
+ } else {
+ index++;
+ }
+ return handleResponse(req, { hcp_link_status: statuses[index] });
});
// enterprise only feature initially
server.get('sys/health', (schema, req) => handleResponse(req, { version: '1.12.0-dev1+ent' }));
diff --git a/ui/tests/integration/components/link-status-test.js b/ui/tests/integration/components/link-status-test.js
index 081a622581..d5b9787d4f 100644
--- a/ui/tests/integration/components/link-status-test.js
+++ b/ui/tests/integration/components/link-status-test.js
@@ -3,6 +3,9 @@ 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';
+import { statuses } from '../../../mirage/handlers/hcp-link';
+
+const timestamp = '[2022-09-13 14:45:40.666697 -0700 PDT]';
module('Integration | Component | link-status', function (hooks) {
setupRenderingTest(hooks);
@@ -11,16 +14,11 @@ module('Integration | Component | link-status', function (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');
+ this.statuses = statuses;
});
test('it renders connected status', async function (assert) {
- await render(hbs`
`);
+ await render(hbs`
`);
assert.dom('.navbar-status').hasClass('connected', 'Correct class renders for connected state');
assert
@@ -33,4 +31,72 @@ module('Integration | Component | link-status', function (hooks) {
.dom('[data-test-link-status] a')
.hasAttribute('href', 'https://portal.cloud.hashicorp.com/sign-in', 'HCP sign in link renders');
});
+
+ test('it does not render banner for disconnected state with unknown error', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state');
+ });
+
+ test('it should render for disconnected error state', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for disconnected error state');
+ assert
+ .dom('[data-test-link-status]')
+ .hasText(
+ `Vault has been disconnected from the Hashicorp Cloud Platform since ${timestamp}. Error: some other error other than unknown`,
+ 'Copy renders for disconnected error state'
+ );
+ });
+
+ test('it should render for connection refused error state', async function (assert) {
+ await render(hbs`
`);
+
+ assert
+ .dom('.navbar-status')
+ .hasClass('warning', 'Correct class renders for connection refused error state');
+ assert
+ .dom('[data-test-link-status]')
+ .hasText(
+ `Vault has been trying to connect to the Hashicorp Cloud Platform since ${timestamp}, but the Scada provider is down. Vault will try again soon.`,
+ 'Copy renders for connection refused error state'
+ );
+ });
+
+ test('it should render for resource id error state', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for resource id error state');
+ assert
+ .dom('[data-test-link-status]')
+ .hasText(
+ `Vault tried connecting to the Hashicorp Cloud Platform, but the Resource ID is invalid. Check your resource ID. ${timestamp}`,
+ 'Copy renders for resource id error state'
+ );
+ });
+
+ test('it should render for unauthorized error state', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for unauthorized error state');
+ assert
+ .dom('[data-test-link-status]')
+ .hasText(
+ `Vault tried connecting to the Hashicorp Cloud Platform, but the authorization information is wrong. Update it and try again. ${timestamp}`,
+ 'Copy renders for unauthorized error state'
+ );
+ });
+
+ test('it should render generic message for unknown error state', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for unknown error state');
+ assert
+ .dom('[data-test-link-status]')
+ .hasText(
+ `Vault has been trying to connect to the Hashicorp Cloud Platform since ${timestamp}. Vault will try again soon. Error: connection error we are unaware of`,
+ 'Copy renders for unknown error state'
+ );
+ });
});