From eb1d3edfb019ba98c83f0afbeed4947efd65e889 Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Thu, 29 Jan 2026 14:19:42 -0500 Subject: [PATCH] UI: update namespace empty state (#11924) (#12082) * update namespace empty state add in refresh button to empty state and update tests update button design * update copy * update when exit button is shown * update css class * revert state changes Co-authored-by: lane-wetmore --- ui/app/components/page/namespaces.hbs | 218 +++++++++++------- ui/app/components/page/namespaces.ts | 23 ++ .../wizard/namespaces/namespace-wizard.hbs | 4 +- .../wizard/namespaces/namespace-wizard.ts | 12 +- ui/app/components/wizard/namespaces/step-3.ts | 2 +- .../access/namespaces/index-test.js | 18 +- 6 files changed, 178 insertions(+), 99 deletions(-) diff --git a/ui/app/components/page/namespaces.hbs b/ui/app/components/page/namespaces.hbs index 6181b3f74a..46b88b326b 100644 --- a/ui/app/components/page/namespaces.hbs +++ b/ui/app/components/page/namespaces.hbs @@ -16,90 +16,150 @@ @breadcrumbs={{array (hash label="Vault" route="vault.cluster.dashboard" icon="vault") (hash label="Namespaces")}} /> + <:badges> + + + <:actions> + {{#unless @model.namespaces}} + + + {{/unless}} + - - - - - - - - Create namespace - - - - - - {{#if @model.namespaces.length}} - - - {{list.item.id}} - - - - - {{#let (concat this.namespace.path (if this.namespace.path "/") list.item.id) as |targetNamespace|}} - {{#if (includes targetNamespace this.namespace.accessibleNamespaces)}} - Switch to namespace - {{/if}} - {{/let}} - Delete - - {{#if (eq this.nsToDelete list.item)}} - - {{/if}} - - - {{else}} - - + + - + + + + + Create namespace + + + + + + {{#if @model.namespaces.length}} + + + {{list.item.id}} + + + + + {{#let (concat this.namespace.path (if this.namespace.path "/") list.item.id) as |targetNamespace|}} + {{#if (includes targetNamespace this.namespace.accessibleNamespaces)}} + Switch to namespace + {{/if}} + {{/let}} + Delete + + {{#if (eq this.nsToDelete list.item)}} + + {{/if}} + + + {{else}} + + + + {{/if}} + + {{else}} + {{#if this.showSetupAlert}} + + Your current setup is 1 namespace. + Based on your answer about your security policy in the Guided start, no new namespaces are required. + {{/if}} - + + + + + + + + + + + + {{/if}} + {{/if}} + {{else}} {{/if}} \ No newline at end of file diff --git a/ui/app/components/page/namespaces.ts b/ui/app/components/page/namespaces.ts index 2ce1b7b61c..0976b8e81e 100644 --- a/ui/app/components/page/namespaces.ts +++ b/ui/app/components/page/namespaces.ts @@ -10,6 +10,7 @@ import Component from '@glimmer/component'; import keys from 'core/utils/keys'; import type ApiService from 'vault/services/api'; +import type FlagsService from 'vault/services/flags'; import type FlashMessageService from 'vault/services/flash-messages'; import type NamespaceService from 'vault/services/namespace'; import type RouterService from '@ember/routing/router-service'; @@ -45,6 +46,7 @@ interface NamespaceModel { export default class PageNamespacesComponent extends Component { @service declare readonly api: ApiService; @service declare readonly router: RouterService; + @service declare readonly flags: FlagsService; @service declare readonly flashMessages: FlashMessageService; @service declare namespace: NamespaceService; @@ -53,6 +55,7 @@ export default class PageNamespacesComponent extends Component { // browser query param to prevent unnecessary re-renders. @tracked query; @tracked nsToDelete = null; + @tracked showSetupAlert = false; @tracked hasDismissedWizard = false; wizardId = 'namespace'; @@ -68,6 +71,21 @@ export default class PageNamespacesComponent extends Component { } } + // show the full available namespace path e.g. "root/ns1/child2", "admin/ns1/child2" + get namespacePath() { + if (this.namespace.inRootNamespace) { + return 'root'; + } + + // For nested namespaces, show "root/" prefix if not HVD managed and no separate user root + if (!this.namespace.userRootNamespace && !this.flags.isHvdManaged) { + return `root/${this.namespace.path}`; + } + + // If there is a userRootNamespace or it is HVD managed, then the path alone will suffice + return this.namespace.path; + } + get showWizard() { // Show when there are no existing namespaces and it is not in a dismissed state return !this.hasDismissedWizard && !this.args.model.namespaces?.length; @@ -123,6 +141,11 @@ export default class PageNamespacesComponent extends Component { } } + @action + enterGuidedStart() { + this.hasDismissedWizard = false; + } + @action handlePageChange() { this.args.onRefresh(); } diff --git a/ui/app/components/wizard/namespaces/namespace-wizard.hbs b/ui/app/components/wizard/namespaces/namespace-wizard.hbs index f5c0b36287..3068b35da7 100644 --- a/ui/app/components/wizard/namespaces/namespace-wizard.hbs +++ b/ui/app/components/wizard/namespaces/namespace-wizard.hbs @@ -17,9 +17,9 @@ <:exit> - {{#unless (eq this.wizardState.creationMethod this.methods.UI)}} + {{#if this.shouldShowExitButton}} - {{/unless}} + {{/if}} <:submit> {{#if (eq this.wizardState.securityPolicyChoice this.policy.FLEXIBLE)}} diff --git a/ui/app/components/wizard/namespaces/namespace-wizard.ts b/ui/app/components/wizard/namespaces/namespace-wizard.ts index 853da7f53a..098e01d73c 100644 --- a/ui/app/components/wizard/namespaces/namespace-wizard.ts +++ b/ui/app/components/wizard/namespaces/namespace-wizard.ts @@ -72,9 +72,17 @@ export default class WizardNamespacesWizardComponent extends Component { } } + get isFinalStep() { + return this.currentStep === this.steps.length - 1; + } + + get shouldShowExitButton() { + // Show exit button unless we're on the final step with UI creation method + return !(this.wizardState.creationMethod === CreationMethod.UI && this.isFinalStep); + } + get exitText() { - return this.currentStep === this.steps.length - 1 && - this.wizardState.securityPolicyChoice === SecurityPolicy.STRICT + return this.isFinalStep && this.wizardState.securityPolicyChoice === SecurityPolicy.STRICT ? 'Done & Exit' : 'Exit'; } diff --git a/ui/app/components/wizard/namespaces/step-3.ts b/ui/app/components/wizard/namespaces/step-3.ts index 4b3cdd8cd2..c8061e4536 100644 --- a/ui/app/components/wizard/namespaces/step-3.ts +++ b/ui/app/components/wizard/namespaces/step-3.ts @@ -70,7 +70,7 @@ export default class WizardNamespacesStep3 extends Component { icon: 'sidebar', label: CreationMethod.UI, description: - 'Apply changes immediately. Note: Changes made here may be overwritten if you also use Infrastructure as Code (Terraform).', + 'Apply changes immediately. Note: Changes made in the UI will be overwritten by any future updates made via Infrastructure as Code (Terraform).', }, ]; tabOptions = ['API', 'CLI']; diff --git a/ui/tests/acceptance/access/namespaces/index-test.js b/ui/tests/acceptance/access/namespaces/index-test.js index f4b2230c42..208081b715 100644 --- a/ui/tests/acceptance/access/namespaces/index-test.js +++ b/ui/tests/acceptance/access/namespaces/index-test.js @@ -74,21 +74,9 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { const testNS = 'test-create-ns-ui'; // Verify test-create-ns does not exist in the Manage Namespace page - await fillIn(GENERAL.filterInputExplicit, testNS); - await click(GENERAL.button('Search')); - await waitFor(GENERAL.emptyStateTitle, { - timeout: 2000, - timeoutMessage: 'timed out waiting for empty state title to render', - }); - assert - .dom(GENERAL.emptyStateTitle) - .hasText( - 'No namespaces yet', - 'Empty state is displayed when searching for the namespace we have created in the UI but have not refreshed the list yet' - ); // Create a new namespace in the UI - await click(GENERAL.linkTo('create-namespace')); + await click(GENERAL.button('create-namespace')); await fillIn(GENERAL.inputByAttr('path'), testNS); await click(GENERAL.submitButton); @@ -113,11 +101,11 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { // Setup: Create namespace(s) via the CLI const testNS = 'asdf'; await runCmd(createNS(testNS), false); + await click(GENERAL.button('refresh-namespace-list')); // Search for created namespace// Enter search text await fillIn(GENERAL.filterInputExplicit, testNS); await click(GENERAL.button('Search')); - await click(GENERAL.button('refresh-namespace-list')); // Verify the menu options await waitFor(GENERAL.menuTrigger, { @@ -135,11 +123,11 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { // Setup: Create namespace(s) via the CLI const testNS = 'test-create-ns-switch'; await runCmd(createNS(testNS), false); + await click(GENERAL.button('refresh-namespace-list')); // Search for created namespace await fillIn(GENERAL.filterInputExplicit, testNS); await click(GENERAL.button('Search')); - await click(GENERAL.button('refresh-namespace-list')); // Switch namespace await waitFor(GENERAL.menuTrigger);