mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-20 00:13:53 -05:00
* Move components and routes over to new PR * Move components to secrets-engine folder * Use native FormData * Update params that are passed in * Add loading state * Add comments * Update jsdoc description * Remove unused action * Remove debugger * Fix linting errors * Add version card component and fix merge conflict issues Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
parent
9d27d4f837
commit
ec0ca21dec
13 changed files with 228 additions and 67 deletions
|
|
@ -2,7 +2,7 @@
|
|||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12">
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12" ...attributes>
|
||||
<Hds::Text::Display @size="300">Metadata</Hds::Text::Display>
|
||||
|
||||
<div class="flex gap-16 is-flex-column has-top-padding-s">
|
||||
|
|
@ -12,14 +12,12 @@
|
|||
<F.Control>
|
||||
<div class="flex column-gap-8">
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{@model.path}}
|
||||
@value={{@model.secretsEngine.path}}
|
||||
autocomplete="off"
|
||||
disabled={{@disabled}}
|
||||
disabled
|
||||
class="path-input-text"
|
||||
name="Path"
|
||||
data-test-input="path"
|
||||
{{! TODO: Update onchange handler, maybe match form-field.hbs }}
|
||||
{{on "input" this.updatePath}}
|
||||
/>
|
||||
<Hds::Copy::Button @text="Copy" @isIconOnly={{true}} @targetToCopy=".path-input-text" />
|
||||
</div>
|
||||
|
|
@ -32,23 +30,21 @@
|
|||
<F.Control>
|
||||
<div class="flex column-gap-8">
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{@model.accessor}}
|
||||
@value={{@model.secretsEngine.accessor}}
|
||||
autocomplete="off"
|
||||
disabled={{@disabled}}
|
||||
disabled
|
||||
name="Accessor"
|
||||
class="accessor-input-text"
|
||||
data-test-input="accessor"
|
||||
{{! TODO: update onchange accordingly }}
|
||||
{{on "input" this.updateAccessor}}
|
||||
/>
|
||||
<Hds::Copy::Button @text="Copy" @isIconOnly={{true}} @targetToCopy=".accessor-input-text" />
|
||||
</div>
|
||||
</F.Control>
|
||||
</Hds::Form::Fieldset>
|
||||
|
||||
<Hds::Form::Textarea::Field name="demo-description" @value={{@model.description}} as |F|>
|
||||
<Hds::Form::Textarea::Field name="description" @value={{@model.secretsEngine.description}} as |F|>
|
||||
<F.Label>Description</F.Label>
|
||||
<F.HelperText>A short description of the secrets engine’s purpose.</F.HelperText>
|
||||
<F.HelperText>A short description of the secrets engine's purpose.</F.HelperText>
|
||||
{{! TODO: Confirm with Design - Is there a length limit for descriptions? Design has it around 765 characters, but not sure theres a backend limit? }}
|
||||
<F.CharacterCount @maxLength={{500}} />
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import SecretsEngineResource from 'vault/resources/secrets/engine';
|
||||
interface Args {
|
||||
model: SecretsEngineResource;
|
||||
}
|
||||
|
||||
export default class Metadata extends Component<Args> {
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
}
|
||||
|
||||
updatePath() {
|
||||
// This method can be used to update the path of the secrets engine.
|
||||
}
|
||||
|
||||
updateAccessor() {
|
||||
// This method can be used to update the accessor of the secrets engine.
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,17 @@
|
|||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12">
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12" ...attributes>
|
||||
<Hds::Form::Toggle::Group as |G|>
|
||||
<G.Legend>
|
||||
<Hds::Text::Display @size="300">Security</Hds::Text::Display>
|
||||
</G.Legend>
|
||||
|
||||
{{! TODO: Toggle fields here are currently hardcoded, will be replaced with actual data from model once wired into parent component }}
|
||||
<G.ToggleField name="local" {{on "click" this.toggleLocal}} as |F|>
|
||||
<G.ToggleField name="local" checked={{@model.secretsEngine.local}} as |F|>
|
||||
<F.Label>Local</F.Label>
|
||||
<F.HelperText>Secrets stay in one cluster and are not replicated.</F.HelperText>
|
||||
</G.ToggleField>
|
||||
<G.ToggleField name="seal-wrap" {{on "click" this.toggleSealWrap}} as |F|>
|
||||
<G.ToggleField name="seal-wrap" checked={{@model.secretsEngine.seal_wrap}} as |F|>
|
||||
<F.Label>Seal wrap</F.Label>
|
||||
<F.HelperText>Wrap secrets with an additional encryption layer using a seal.</F.HelperText>
|
||||
</G.ToggleField>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12">
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-s has-top-bottom-margin-12" ...attributes>
|
||||
<Hds::Text::Display @size="300">Version</Hds::Text::Display>
|
||||
|
||||
{{! TODO: Having this set up with flex for now, grid might be better? }}
|
||||
|
|
@ -14,9 +14,12 @@
|
|||
</div>
|
||||
|
||||
<div class="flex is-flex-column is-flex-align-start row-gap-12">
|
||||
<Hds::Text::Body @tag="p" class="hds-border-strong side-padding-4 border-radius-4">{{@model.type}}</Hds::Text::Body>
|
||||
<Hds::Text::Body
|
||||
@tag="p"
|
||||
class="hds-border-strong side-padding-4 border-radius-4"
|
||||
>{{@model.secretsEngine.type}}</Hds::Text::Body>
|
||||
{{! TODO: Verify if we want to display the full version or chop down ie. v0.17.1 vs v0.17.1-0.230942309423094... }}
|
||||
<Hds::Text::Body @tag="p">{{@model.running_plugin_version}}</Hds::Text::Body>
|
||||
<Hds::Text::Body @tag="p">{{@model.secretsEngine.running_plugin_version}}</Hds::Text::Body>
|
||||
{{! TODO: leaving as is for now to match design, but we might be removing this if we cant get latest version from some source }}
|
||||
<Hds::Text::Body @tag="p">v.12.46</Hds::Text::Body>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import SecretsEngineResource from 'vault/resources/secrets/engine';
|
||||
interface Args {
|
||||
model: SecretsEngineResource;
|
||||
}
|
||||
|
||||
export default class Version extends Component<Args> {
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
}
|
||||
}
|
||||
38
ui/app/components/secret-engine/page-header.hbs
Normal file
38
ui/app/components/secret-engine/page-header.hbs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#let (engines-display-data @model.secretsEngine.type) as |engineData|}}
|
||||
<Hds::PageHeader class="page-header" as |PH|>
|
||||
<PH.Title>{{@model.secretsEngine.id}} configuration</PH.Title>
|
||||
<PH.Description>{{get engineData "displayName"}}</PH.Description>
|
||||
<PH.Breadcrumb>
|
||||
<Hds::Breadcrumb>
|
||||
<Hds::Breadcrumb::Item @text="Secrets" />
|
||||
<Hds::Breadcrumb::Item
|
||||
@text={{@model.secretsEngine.id}}
|
||||
@route="vault.cluster.secrets.backend.list-root"
|
||||
@model={{@model.secretsEngine.id}}
|
||||
/>
|
||||
<Hds::Breadcrumb::Item @text="Configuration" />
|
||||
</Hds::Breadcrumb>
|
||||
</PH.Breadcrumb>
|
||||
<PH.IconTile @icon={{get engineData "glyph"}} />
|
||||
<PH.Subtitle>{{get engineData "typeDisplay"}}</PH.Subtitle>
|
||||
</Hds::PageHeader>
|
||||
|
||||
<div class="has-top-margin-l">
|
||||
<div class="tabs-container box is-marginless is-fullwidth is-paddingless">
|
||||
<nav class="tabs" aria-label={{@model.secretsEngine.id}}>
|
||||
<ul>
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.secrets.backend.configuration.general-settings" @model={{@model.secretsEngine.id}}>
|
||||
General settings
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
65
ui/app/components/secret-engine/page/general-settings.hbs
Normal file
65
ui/app/components/secret-engine/page/general-settings.hbs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<SecretEngine::PageHeader @model={{@model}} />
|
||||
|
||||
{{#if this.saveGeneralSettings.isRunning}}
|
||||
{{! TODO: Fix loading state styles }}
|
||||
<Hds::Layout::Flex @gap="24" @direction="column" @justify="center" @align="center">
|
||||
<div class="one-fourth-width">
|
||||
<Hds::Icon @name="loading-static" @size="24" @stretched={{true}} />
|
||||
</div>
|
||||
<Hds::Text::Display @tag="h2">Saving configuration...</Hds::Text::Display>
|
||||
</Hds::Layout::Flex>
|
||||
{{else}}
|
||||
<form method="POST" {{on "submit" (perform this.saveGeneralSettings)}} aria-label="general settings form">
|
||||
<Hds::Text::Body class="has-top-bottom-margin-xxs">
|
||||
Mount parameters that you can tune to fit required engine behavior.
|
||||
</Hds::Text::Body>
|
||||
|
||||
<Hds::Layout::Flex @gap="24">
|
||||
<SecretEngine::Card::Version @model={{@model}} class="is-fullwidth" />
|
||||
{{! TODO: Lease duration component }}
|
||||
</Hds::Layout::Flex>
|
||||
|
||||
<Hds::Layout::Flex @gap="24">
|
||||
<SecretEngine::Card::Metadata @model={{@model}} class="is-fullwidth" />
|
||||
<SecretEngine::Card::Security @model={{@model}} class="is-fullwidth" />
|
||||
</Hds::Layout::Flex>
|
||||
|
||||
<div class="field is-grouped has-top-bottom-margin-12">
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Save changes" type="submit" disabled={{this.saveGeneralSettings.isRunning}} data-test-submit />
|
||||
<Hds::Button
|
||||
@text="Discard"
|
||||
@color="secondary"
|
||||
data-test-cancel
|
||||
{{on "click" (fn (mut this.showUnsavedChangesModal))}}
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showUnsavedChangesModal}}
|
||||
<Hds::Modal id="unsavedChangesModal" @onClose={{(fn (mut this.showUnsavedChangesModal))}} as |M|>
|
||||
<M.Header>
|
||||
Unsaved changes
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="hds-typography-body-300 hds-foreground-primary">You've made changes to the following
|
||||
<Hds::Text::Display>{{@model.secretsEngine.id}}</Hds::Text::Display>
|
||||
settings:</p>
|
||||
{{! TODO: display what fields were changed }}
|
||||
<br />
|
||||
Would you like to apply them?
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button type="button" @text="Save changes" {{on "click" (action (perform this.saveGeneralSettings))}} />
|
||||
{{! TODO: confirm with design where we want to transition to if Discard changes is clicked }}
|
||||
<Hds::Button type="button" @text="Discard changes" @color="secondary" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
60
ui/app/components/secret-engine/page/general-settings.ts
Normal file
60
ui/app/components/secret-engine/page/general-settings.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
import type Router from '@ember/routing/router';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type ApiService from 'vault/services/api';
|
||||
import type SecretsEngineResource from 'vault/resources/secrets/engine';
|
||||
|
||||
/**
|
||||
* @module GeneralSettingsComponent is used to configure the SSH secret engine.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <Secrets:Page:GeneralSettings
|
||||
* @model={{this.model}}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @param {string} secretsEngine - secrets engine resource
|
||||
*/
|
||||
|
||||
interface Args {
|
||||
model: {
|
||||
secretsEngine: SecretsEngineResource;
|
||||
};
|
||||
}
|
||||
|
||||
export default class GeneralSettingsComponent extends Component<Args> {
|
||||
@service declare readonly router: Router;
|
||||
@service declare readonly api: ApiService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@tracked errorMessage: string | null = null;
|
||||
@tracked invalidFormAlert: string | null = null;
|
||||
@tracked showUnsavedChangesModal = false;
|
||||
|
||||
saveGeneralSettings = task(async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
const fd = new FormData(event.target as HTMLFormElement);
|
||||
await this.api.sys.mountsTuneConfigurationParameters(this.args.model.secretsEngine.id, {
|
||||
// TODO: add other params when other card components are made
|
||||
description: fd.get('description') as string,
|
||||
});
|
||||
this.flashMessages.success('Engine settings successfully updated.');
|
||||
} catch (e) {
|
||||
// handle error state
|
||||
const { message } = await this.api.parseError(e);
|
||||
this.flashMessages.danger(`Try again or check your network connection. ${message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -183,6 +183,8 @@ Router.map(function () {
|
|||
this.mount('pki');
|
||||
this.route('index', { path: '/' });
|
||||
this.route('configuration', function () {
|
||||
this.route('index', { path: '/' });
|
||||
this.route('general-settings');
|
||||
// only CONFIGURABLE_SECRET_ENGINES can be configured and access the edit route
|
||||
this.route('edit');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
import type SecretsEngineResource from 'vault/resources/secrets/engine';
|
||||
|
||||
export default class SecretsBackendConfigurationGeneralSettingsRoute extends Route {
|
||||
async model() {
|
||||
const secretsEngine = this.modelFor('vault.cluster.secrets.backend') as SecretsEngineResource;
|
||||
// TODO: get list of versions using the sys/plugins/catalog endpoint.
|
||||
return { secretsEngine };
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,23 @@
|
|||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import engineDisplayData from 'vault/helpers/engines-display-data';
|
||||
|
||||
export default class SecretsBackendConfigurationIndexRoute extends Route {
|
||||
@service router;
|
||||
|
||||
beforeModel() {
|
||||
const {
|
||||
secretsEngine: { type },
|
||||
} = this.modelFor('vault.cluster.secrets.backend.configuration');
|
||||
const engine = engineDisplayData(type);
|
||||
|
||||
if (!engine?.isOldEngine) {
|
||||
return this.router.replaceWith('vault.cluster.secrets.backend.configuration.general-settings');
|
||||
}
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const engine = engineDisplayData(resolvedModel.secretsEngine.type);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<SecretEngine::Page::GeneralSettings @model={{this.model}} />
|
||||
|
|
@ -52,15 +52,17 @@
|
|||
/>
|
||||
</li>
|
||||
{{/each}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.configuration"
|
||||
@model={{@model.id}}
|
||||
data-test-configuration-tab={{true}}
|
||||
>
|
||||
Configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if options.isOldEngine}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.configuration"
|
||||
@model={{@model.id}}
|
||||
data-test-configuration-tab={{true}}
|
||||
>
|
||||
Configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue