diff --git a/changelog/23942.txt b/changelog/23942.txt
new file mode 100644
index 000000000000..a4d43d48f091
--- /dev/null
+++ b/changelog/23942.txt
@@ -0,0 +1,3 @@
+```release-note:bug
+ui: fix broken GUI when accessing from listener with chroot_namespace defined
+```
diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js
index de173feb8897..7469e062cdff 100644
--- a/ui/app/adapters/cluster.js
+++ b/ui/app/adapters/cluster.js
@@ -80,6 +80,11 @@ export default ApplicationAdapter.extend({
performancestandbycode: 200,
},
unauthenticated: true,
+ }).catch(() => {
+ // sys/health will only fail when chroot set
+ // because it's allowed in root namespace only and
+ // configured to return a 200 response in other fail scenarios
+ return { has_chroot_namespace: true };
});
},
diff --git a/ui/app/models/cluster.js b/ui/app/models/cluster.js
index 543ed7bf0a60..6ac7f90af367 100644
--- a/ui/app/models/cluster.js
+++ b/ui/app/models/cluster.js
@@ -16,6 +16,8 @@ export default class ClusterModel extends Model {
@attr('boolean') standby;
@attr('string') type;
@attr('object') license;
+ // manually set on response when sys/health failure
+ @attr('boolean') hasChrootNamespace;
/* Licensing concerns */
get licenseExpiry() {
diff --git a/ui/app/models/node.js b/ui/app/models/node.js
index 520368c72d01..003d3f7ab99c 100644
--- a/ui/app/models/node.js
+++ b/ui/app/models/node.js
@@ -9,17 +9,16 @@ import { alias, and, equal } from '@ember/object/computed';
export default Model.extend({
name: attr('string'),
// https://developer.hashicorp.com/vault/api-docs/system/health
- initialized: attr('boolean'),
- sealed: attr('boolean'),
- isSealed: alias('sealed'),
standby: attr('boolean'),
isActive: equal('standby', false),
- clusterName: attr('string'),
clusterId: attr('string'),
isLeader: and('initialized', 'isActive'),
// https://developer.hashicorp.com/vault/api-docs/system/seal-status
+ initialized: attr('boolean'),
+ sealed: attr('boolean'),
+ isSealed: alias('sealed'),
// The "t" parameter is the threshold, and "n" is the number of shares.
t: attr('number'),
n: attr('number'),
diff --git a/ui/app/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js
index b50a68c1dbef..37b2cb88b7de 100644
--- a/ui/app/routes/vault/cluster/dashboard.js
+++ b/ui/app/routes/vault/cluster/dashboard.js
@@ -29,18 +29,20 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
model() {
const clusterModel = this.modelFor('vault.cluster');
- const replication = {
- dr: clusterModel.dr,
- performance: clusterModel.performance,
- };
-
+ const hasChroot = clusterModel?.hasChrootNamespace;
+ const replication = hasChroot
+ ? null
+ : {
+ dr: clusterModel.dr,
+ performance: clusterModel.performance,
+ };
return hash({
replication,
secretsEngines: this.store.query('secret-engine', {}),
license: this.store.queryRecord('license', {}).catch(() => null),
- isRootNamespace: this.namespace.inRootNamespace,
+ isRootNamespace: this.namespace.inRootNamespace && !hasChroot,
version: this.version,
- vaultConfiguration: this.getVaultConfiguration(),
+ vaultConfiguration: hasChroot ? null : this.getVaultConfiguration(),
});
}
diff --git a/ui/app/services/version.js b/ui/app/services/version.js
index fe1ec0aae891..cf99b759c7a8 100644
--- a/ui/app/services/version.js
+++ b/ui/app/services/version.js
@@ -44,7 +44,7 @@ export default class VersionService extends Service {
@task
*getVersion() {
if (this.version) return;
- const response = yield this.store.adapterFor('cluster').health();
+ const response = yield this.store.adapterFor('cluster').sealStatus();
this.version = response.version;
return;
}
diff --git a/ui/app/templates/error.hbs b/ui/app/templates/error.hbs
new file mode 100644
index 000000000000..9448637df46b
--- /dev/null
+++ b/ui/app/templates/error.hbs
@@ -0,0 +1,46 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+~}}
+
+
+
+
+
+
+
+
+
+
+ {{#if (eq this.model.httpStatus 403)}}
+ Not authorized
+ {{else if (eq this.model.httpStatus 404)}}
+ Page not found
+ {{else}}
+ Error
+ {{/if}}
+
+
Error {{this.model.httpStatus}}
+
+
+
+
+ {{this.model.message}}
+ {{join ". " this.model.errors}}
+
+
+
+
+
+ Go home
+
+
+ Learn more
+
+
+
+
\ No newline at end of file
diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs
index fad7cd54d79e..b7f212674a0d 100644
--- a/ui/app/templates/vault/cluster/dashboard.hbs
+++ b/ui/app/templates/vault/cluster/dashboard.hbs
@@ -4,12 +4,12 @@
~}}
\ No newline at end of file
diff --git a/ui/mirage/handlers/chroot-namespace.js b/ui/mirage/handlers/chroot-namespace.js
new file mode 100644
index 000000000000..97a17e10074a
--- /dev/null
+++ b/ui/mirage/handlers/chroot-namespace.js
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { Response } from 'miragejs';
+
+/*
+ These are mocked responses to mimic what we get from the server
+ when within a chrooted listener (assuming the namespace exists)
+ */
+export default function (server) {
+ server.get('sys/health', () => new Response(400, {}, { errors: ['unsupported path'] }));
+ server.get('sys/replication/status', () => new Response(400, {}, { errors: ['unsupported path'] }));
+}
diff --git a/ui/mirage/handlers/index.js b/ui/mirage/handlers/index.js
index 7ddae8563173..574b2e0405cc 100644
--- a/ui/mirage/handlers/index.js
+++ b/ui/mirage/handlers/index.js
@@ -6,6 +6,7 @@
// add all handlers here
// individual lookup done in mirage config
import base from './base';
+import chrootNamespace from './chroot-namespace';
import clients from './clients';
import db from './db';
import hcpLink from './hcp-link';
@@ -19,6 +20,7 @@ import reducedDisclosure from './reduced-disclosure';
export {
base,
+ chrootNamespace,
clients,
db,
hcpLink,
diff --git a/ui/package.json b/ui/package.json
index e872b1147edf..7e199ccd3df3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -26,6 +26,7 @@
"fmt:styles": "prettier --write app/styles/**/*.*",
"start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR",
"start2": "ember server --proxy=http://localhost:8202 --port=4202",
+ "start:chroot": "ember server --proxy=http://localhost:8300 --port=4300",
"start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start",
"test": "npm-run-all --print-name lint:js:quiet lint:hbs:quiet && node scripts/start-vault.js",
"test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js",
diff --git a/ui/tests/acceptance/chroot-namespace-test.js b/ui/tests/acceptance/chroot-namespace-test.js
new file mode 100644
index 000000000000..5bca573f31c1
--- /dev/null
+++ b/ui/tests/acceptance/chroot-namespace-test.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { currentRouteName } from '@ember/test-helpers';
+import authPage from 'vault/tests/pages/auth';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import ENV from 'vault/config/environment';
+
+module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.before(function () {
+ ENV['ember-cli-mirage'].handler = 'chrootNamespace';
+ });
+ hooks.after(function () {
+ ENV['ember-cli-mirage'].handler = null;
+ });
+
+ test('it should render normally when chroot namespace exists', async function (assert) {
+ await authPage.login();
+ assert.strictEqual(currentRouteName(), 'vault.cluster.dashboard', 'goes to dashboard page');
+ assert.dom('[data-test-badge-namespace]').includesText('root', 'Shows root namespace badge');
+ });
+});
diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js
index e34cfb8fccb3..cdf95e18208f 100644
--- a/ui/tests/acceptance/dashboard-test.js
+++ b/ui/tests/acceptance/dashboard-test.js
@@ -49,10 +49,11 @@ module('Acceptance | landing page dashboard', function (hooks) {
await visit('/vault/dashboard');
const version = this.owner.lookup('service:version');
const versionName = version.version;
- const versionNameEnd = version.isEnterprise ? versionName.indexOf('+') : versionName.length;
- assert
- .dom(SELECTORS.cardHeader('Vault version'))
- .hasText(`Vault v${versionName.slice(0, versionNameEnd)} root`);
+ const versionText = version.isEnterprise
+ ? `Vault v${versionName.slice(0, versionName.indexOf('+'))} root`
+ : `Vault v${versionName}`;
+
+ assert.dom(SELECTORS.cardHeader('Vault version')).hasText(versionText);
});
module('secrets engines card', function (hooks) {