mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
* adds constants util for wizards and updates service to use WizardId type * updates wizards to use WIZARD_ID_MAP values * updates wizard tests to use the service for dismissal * updates playwright setup to add all wizard ids as dismissed in localStorage * removes wizard dismissal step from existing playwright tests * fixes issues accessing owner in beforeEach hooks of namespaces acceptance tests Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
186 lines
5.7 KiB
TypeScript
186 lines
5.7 KiB
TypeScript
/**
|
|
* Copyright IBM Corp. 2016, 2025
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import { service } from '@ember/service';
|
|
import { action } from '@ember/object';
|
|
import { tracked } from '@glimmer/tracking';
|
|
import Component from '@glimmer/component';
|
|
import { SecurityPolicy } from 'vault/components/wizard/namespaces/step-1';
|
|
import { CreationMethod } from 'vault/components/wizard/namespaces/step-3';
|
|
import { WIZARD_ID_MAP } from 'vault/utils/constants/wizard';
|
|
|
|
import type ApiService from 'vault/services/api';
|
|
import type Block from 'vault/components/wizard/namespaces/step-2';
|
|
import type FlashMessageService from 'vault/services/flash-messages';
|
|
import type NamespaceService from 'vault/services/namespace';
|
|
import type RouterService from '@ember/routing/router-service';
|
|
import type WizardService from 'vault/services/wizard';
|
|
|
|
const DEFAULT_STEPS = [
|
|
{ title: 'Select setup', component: 'wizard/namespaces/step-1' },
|
|
{ title: 'Map out namespaces', component: 'wizard/namespaces/step-2' },
|
|
{ title: 'Apply changes', component: 'wizard/namespaces/step-3' },
|
|
];
|
|
|
|
interface Args {
|
|
isIntroModal: boolean;
|
|
onRefresh: CallableFunction;
|
|
onFlexiblePolicyComplete: CallableFunction;
|
|
}
|
|
|
|
interface WizardState {
|
|
securityPolicyChoice: SecurityPolicy | null;
|
|
namespacePaths: string[] | null;
|
|
namespaceBlocks: Block[] | null;
|
|
creationMethod: CreationMethod | null;
|
|
codeSnippet: string | null;
|
|
}
|
|
|
|
export default class WizardNamespacesWizardComponent extends Component<Args> {
|
|
@service declare readonly api: ApiService;
|
|
@service declare readonly router: RouterService;
|
|
@service declare readonly flashMessages: FlashMessageService;
|
|
@service declare readonly wizard: WizardService;
|
|
@service declare namespace: NamespaceService;
|
|
|
|
@tracked currentStep = 0;
|
|
@tracked steps = DEFAULT_STEPS;
|
|
@tracked wizardState: WizardState = {
|
|
securityPolicyChoice: null,
|
|
namespacePaths: null,
|
|
namespaceBlocks: null,
|
|
creationMethod: null,
|
|
codeSnippet: null,
|
|
};
|
|
|
|
methods = CreationMethod;
|
|
policy = SecurityPolicy;
|
|
|
|
wizardId = WIZARD_ID_MAP.namespace;
|
|
|
|
// Whether the current step requirements have been met to proceed to the next step
|
|
get canProceed() {
|
|
switch (this.currentStep) {
|
|
case 0: // Step 1 - requires security policy choice
|
|
return Boolean(this.wizardState.securityPolicyChoice);
|
|
case 1: // Step 2 - requires valid namespace inputs
|
|
return Boolean(this.wizardState.namespacePaths);
|
|
case 2: // Step 3 - no validation is needed
|
|
return true;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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.isFinalStep && this.wizardState.securityPolicyChoice === SecurityPolicy.STRICT
|
|
? 'Done & Exit'
|
|
: 'Exit';
|
|
}
|
|
|
|
updateSteps() {
|
|
if (this.wizardState.securityPolicyChoice === SecurityPolicy.FLEXIBLE) {
|
|
this.steps = [
|
|
{ title: 'Select setup', component: 'wizard/namespaces/step-1' },
|
|
{ title: 'Apply changes', component: 'wizard/namespaces/step-3' },
|
|
];
|
|
} else {
|
|
this.steps = DEFAULT_STEPS;
|
|
}
|
|
}
|
|
|
|
@action
|
|
onStepChange(step: number) {
|
|
this.currentStep = step;
|
|
// if user policy selection changes which steps we show, update upon page navigation
|
|
// instead of flashing the changes when toggling
|
|
this.updateSteps();
|
|
}
|
|
|
|
@action
|
|
updateWizardState(key: string, value: unknown) {
|
|
this.wizardState = {
|
|
...this.wizardState,
|
|
[key]: value,
|
|
};
|
|
}
|
|
|
|
@action
|
|
async onDone() {
|
|
await this.onDismiss();
|
|
this.args.onFlexiblePolicyComplete();
|
|
this.flashMessages.success(`Your current setup is 1 namespace.`, { title: 'Guided start complete' });
|
|
}
|
|
|
|
@action
|
|
async onDismiss() {
|
|
this.wizard.dismiss(this.wizardId);
|
|
await this.args.onRefresh();
|
|
}
|
|
|
|
@action
|
|
async onSubmit() {
|
|
switch (this.wizardState.creationMethod) {
|
|
case CreationMethod.UI:
|
|
await this.createNamespacesFromWizard();
|
|
break;
|
|
default:
|
|
// The other creation methods require the user to execute the commands on their own
|
|
// In these cases, there is no submit button
|
|
break;
|
|
}
|
|
}
|
|
|
|
@action
|
|
onIntroChange(visible: boolean) {
|
|
this.wizard.setIntroVisible(this.wizardId, visible);
|
|
}
|
|
|
|
@action
|
|
async createNamespacesFromWizard() {
|
|
try {
|
|
const { namespacePaths } = this.wizardState;
|
|
if (!namespacePaths) return;
|
|
|
|
for (const nsPath of namespacePaths) {
|
|
const parts = nsPath.split('/');
|
|
const namespaceName = parts[parts.length - 1] as string;
|
|
const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : undefined;
|
|
// this provides the full nested path for the header
|
|
const fullPath = parentPath ? this.namespace.path + '/' + parentPath : undefined;
|
|
await this.createNamespace(namespaceName, fullPath);
|
|
}
|
|
|
|
this.flashMessages.success('Your new configuration has been applied.', { title: 'Namespaces created' });
|
|
} catch (error) {
|
|
const { message } = await this.api.parseError(error);
|
|
this.flashMessages.danger(`Error creating namespaces: ${message}`);
|
|
} finally {
|
|
this.onDismiss();
|
|
}
|
|
}
|
|
|
|
@action
|
|
switchNamespace(targetNamespace: string) {
|
|
this.router.transitionTo('vault.cluster.dashboard', {
|
|
queryParams: { namespace: targetNamespace },
|
|
});
|
|
}
|
|
|
|
async createNamespace(path: string, header?: string) {
|
|
const headers = header ? this.api.buildHeaders({ namespace: header }) : undefined;
|
|
await this.api.sys.systemWriteNamespacesPath(path, {}, headers);
|
|
}
|
|
}
|