390 lines
15 KiB
Handlebars
390 lines
15 KiB
Handlebars
<PageHeader as |p|>
|
||
<p.top>
|
||
<KeyValueHeader @path="vault.cluster.secrets.backend.show" @mode={{this.mode}} @root={{@root}} @showCurrent={{true}} />
|
||
</p.top>
|
||
<p.levelLeft>
|
||
<h1 class="title is-3" data-test-secret-header="true">
|
||
{{#if (eq @mode "create")}}
|
||
Create Connection
|
||
{{else if (eq @mode "edit")}}
|
||
Edit Connection
|
||
{{else}}
|
||
{{@model.id}}
|
||
{{/if}}
|
||
</h1>
|
||
</p.levelLeft>
|
||
</PageHeader>
|
||
|
||
{{#if @model.isAvailablePlugin}}
|
||
{{#if (eq @mode "show")}}
|
||
<Toolbar>
|
||
<ToolbarActions>
|
||
{{#if @model.canDelete}}
|
||
<button
|
||
type="button"
|
||
class="toolbar-link"
|
||
onclick={{action (mut this.isDeleteModalActive) true}}
|
||
data-test-database-connection-delete
|
||
>
|
||
Delete connection
|
||
</button>
|
||
{{/if}}
|
||
{{#if @model.canReset}}
|
||
<ConfirmAction
|
||
@buttonClasses="toolbar-link"
|
||
@onConfirmAction={{action "reset"}}
|
||
@confirmTitle="Reset connection?"
|
||
@confirmMessage="This will close the connection and its underlying plugin and restart it with the configuration stored in the barrier."
|
||
@confirmButtonText="Reset"
|
||
data-test-database-connection-reset
|
||
>
|
||
Reset connection
|
||
</ConfirmAction>
|
||
{{/if}}
|
||
{{#if (or @model.canReset @model.canDelete)}}
|
||
<div class="toolbar-separator"></div>
|
||
{{/if}}
|
||
{{#if @model.canRotateRoot}}
|
||
{{! template-lint-disable quotes }}
|
||
<ConfirmAction
|
||
@buttonClasses="toolbar-link"
|
||
@onConfirmAction={{this.rotate}}
|
||
@confirmTitle="Rotate credentials?"
|
||
@confirmMessage='This will rotate the "root" user credentials stored for the database connection. The password will not be accessible once rotated.'
|
||
@confirmButtonText="Rotate"
|
||
data-test-database-connection-rotate
|
||
>
|
||
Rotate root credentials
|
||
</ConfirmAction>
|
||
{{! template-lint-enable }}
|
||
{{/if}}
|
||
{{#if @model.canAddRole}}
|
||
<ToolbarSecretLink
|
||
@secret=""
|
||
@mode="create"
|
||
@type="add"
|
||
@queryParams={{query-params initialKey=@model.name itemType="role"}}
|
||
data-test-secret-create={{true}}
|
||
>
|
||
Add role
|
||
</ToolbarSecretLink>
|
||
{{/if}}
|
||
{{#if @model.canEdit}}
|
||
<ToolbarSecretLink @secret={{@model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}>
|
||
Edit configuration
|
||
</ToolbarSecretLink>
|
||
{{/if}}
|
||
</ToolbarActions>
|
||
</Toolbar>
|
||
{{/if}}
|
||
{{/if}}
|
||
|
||
{{#if (eq @mode "create")}}
|
||
|
||
{{#if (eq @model.plugin_name "vault-plugin-database-oracle")}}
|
||
<AlertBanner @type="warning">
|
||
Please ensure that your Oracle plugin has the default name of
|
||
<b>vault-plugin-database-oracle</b>. Custom naming is not supported in the UI at this time. If the plugin is already
|
||
named vault-plugin-database-oracle, disregard this warning.
|
||
</AlertBanner>
|
||
{{/if}}
|
||
|
||
<form {{on "submit" this.handleCreateConnection}}>
|
||
{{#each @model.fieldAttrs as |attr|}}
|
||
{{#if (not-eq attr.options.readOnly true)}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/if}}
|
||
{{/each}}
|
||
|
||
{{! Plugin Config Section }}
|
||
<div class="form-section box is-shadowless is-fullwidth">
|
||
<fieldset class="form-fieldset">
|
||
<legend class="title is-5">Plugin config</legend>
|
||
{{#if @model.pluginFieldGroups}}
|
||
{{#each @model.pluginFieldGroups as |fieldGroup|}}
|
||
{{#each-in fieldGroup as |group fields|}}
|
||
{{#if (eq group "default")}}
|
||
<div class="columns is-desktop is-multiline">
|
||
{{#each fields as |attr|}}
|
||
{{#if
|
||
(contains attr.name (array "max_open_connections" "max_idle_connections" "max_connection_lifetime"))
|
||
}}
|
||
<div class="column is-one-third">
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
</div>
|
||
{{else}}
|
||
<div class="column is-full">
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
</div>
|
||
{{/if}}
|
||
{{/each}}
|
||
</div>
|
||
{{else}}
|
||
{{#let (camelize (concat "show" group)) as |prop|}}
|
||
<ToggleButton
|
||
@isOpen={{get this prop}}
|
||
@openLabel={{concat "Hide " group}}
|
||
@closedLabel={{group}}
|
||
@onClick={{fn (mut (get this prop))}}
|
||
class="is-block"
|
||
data-test-toggle-group={{group}}
|
||
/>
|
||
{{#if (get this prop)}}
|
||
<div class="box is-marginless">
|
||
{{#each fields as |attr|}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/each}}
|
||
</div>
|
||
{{/if}}
|
||
{{/let}}
|
||
{{/if}}
|
||
{{/each-in}}
|
||
{{/each}}
|
||
{{else}}
|
||
<EmptyState @title="No plugin selected" @message="Select a plugin type to be able to configure it." />
|
||
{{/if}}
|
||
</fieldset>
|
||
</div>
|
||
|
||
{{! Statements Section }}
|
||
{{! template-lint-configure simple-unless "warn" }}
|
||
{{#unless (and @model.plugin_name (not @model.statementFields))}}
|
||
<div class="form-section box is-shadowless is-fullwidth">
|
||
<h3 class="title is-5">Statements</h3>
|
||
{{#if (eq @model.statementFields null)}}
|
||
<EmptyState @title="No plugin selected" @message="Select a plugin type to be able to configure it." />
|
||
{{else}}
|
||
{{#each @model.statementFields as |attr|}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/each}}
|
||
{{/if}}
|
||
</div>
|
||
{{/unless}}
|
||
|
||
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
|
||
<div class="field is-grouped">
|
||
<div class="control">
|
||
<button data-test-secret-save type="submit" disabled={{this.buttonDisabled}} class="button is-primary">
|
||
Create database
|
||
</button>
|
||
</div>
|
||
<div class="control">
|
||
<SecretLink @mode="list" class="button">
|
||
Cancel
|
||
</SecretLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
{{else if (and (eq @mode "edit") @model.isAvailablePlugin)}}
|
||
<form {{on "submit" this.handleUpdateConnection}}>
|
||
{{#each @model.fieldAttrs as |attr|}}
|
||
{{#if (or (eq attr.name "name") (eq attr.name "plugin_name"))}}
|
||
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
|
||
{{else if (not-eq attr.options.readOnly true)}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/if}}
|
||
{{/each}}
|
||
|
||
{{! Plugin Config Edit }}
|
||
<div class="form-section box is-shadowless is-fullwidth">
|
||
<fieldset class="form-fieldset">
|
||
<legend class="title is-5">Plugin config</legend>
|
||
{{#each @model.pluginFieldGroups as |fieldGroup|}}
|
||
{{#each-in fieldGroup as |group fields|}}
|
||
{{#if (eq group "default")}}
|
||
<div class="columns is-desktop is-multiline">
|
||
{{#each fields as |attr|}}
|
||
{{#if
|
||
(contains attr.name (array "max_open_connections" "max_idle_connections" "max_connection_lifetime"))
|
||
}}
|
||
<div class="column is-one-third">
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
</div>
|
||
{{else if (eq attr.name "password")}}
|
||
<div class="column is-full">
|
||
<label for={{attr.name}} class="is-label">
|
||
{{capitalize (or attr.options.label attr.name)}}
|
||
</label>
|
||
<div class="field">
|
||
<Toggle
|
||
@name="show-{{attr.name}}"
|
||
@status="success"
|
||
@size="small"
|
||
@onChange={{fn this.updateShowPassword (not this.showPasswordField)}}
|
||
@checked={{this.showPasswordField}}
|
||
data-test-toggle={{attr.name}}
|
||
>
|
||
<span class="ttl-picker-label has-text-grey">Update password</span><br />
|
||
<div class="description has-text-grey">
|
||
<span>
|
||
{{if
|
||
this.showPasswordField
|
||
"The new password that will be used when connecting to the database"
|
||
"Vault will use the existing password"
|
||
}}
|
||
</span>
|
||
</div>
|
||
{{#if this.showPasswordField}}
|
||
<Input
|
||
{{on "change" (fn this.updatePassword attr.name)}}
|
||
@type="password"
|
||
@value={{get @model attr.name}}
|
||
name={{attr.name}}
|
||
class="input"
|
||
{{! Prevents browsers from auto-filling }}
|
||
autocomplete="new-password"
|
||
spellcheck="false"
|
||
/>
|
||
{{/if}}
|
||
</Toggle>
|
||
</div>
|
||
</div>
|
||
{{else}}
|
||
<div class="column is-full">
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
</div>
|
||
{{/if}}
|
||
{{/each}}
|
||
</div>
|
||
{{else}}
|
||
{{#let (camelize (concat "show" group)) as |prop|}}
|
||
<ToggleButton
|
||
@isOpen={{get this prop}}
|
||
@openLabel={{concat "Hide " group}}
|
||
@closedLabel={{group}}
|
||
@onClick={{fn (mut (get this prop))}}
|
||
class="is-block"
|
||
data-test-toggle-group={{group}}
|
||
/>
|
||
{{#if (get this prop)}}
|
||
<div class="box is-marginless">
|
||
{{#each fields as |attr|}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/each}}
|
||
</div>
|
||
{{/if}}
|
||
{{/let}}
|
||
{{/if}}
|
||
{{/each-in}}
|
||
{{/each}}
|
||
</fieldset>
|
||
</div>
|
||
|
||
{{! Statements Edit Section }}
|
||
{{#unless (and @model.plugin_name (not @model.statementFields))}}
|
||
<div class="form-section box is-shadowless is-fullwidth">
|
||
<fieldset class="form-fieldset">
|
||
<legend class="title is-5">Statements</legend>
|
||
{{#each @model.statementFields as |attr|}}
|
||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} />
|
||
{{/each}}
|
||
</fieldset>
|
||
</div>
|
||
{{/unless}}
|
||
|
||
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
|
||
<div class="field is-grouped">
|
||
<div class="control">
|
||
<button data-test-secret-save type="submit" disabled={{this.buttonDisabled}} class="button is-primary">
|
||
Save
|
||
</button>
|
||
</div>
|
||
<div class="control">
|
||
<SecretLink @mode="list" class="button">
|
||
Cancel
|
||
</SecretLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
{{else if (eq @model.isAvailablePlugin false)}}
|
||
<EmptyState
|
||
@title="Database type unavailable"
|
||
@subTitle="Not supported in the UI"
|
||
@icon="skip"
|
||
@message="This database type cannot be viewed in the UI. You will have to use the API or CLI to perform actions here."
|
||
@bottomBorder={{true}}
|
||
>
|
||
<LinkTo @route="vault.cluster.secrets.backend.list-root" class="link">
|
||
<Chevron @direction="left" />
|
||
Go back
|
||
</LinkTo>
|
||
<a href="https://www.vaultproject.io/api/secret/databases" target="_blank" rel="noreferrer noopener">Documentation</a>
|
||
</EmptyState>
|
||
{{else}}
|
||
{{#each @model.showAttrs as |attr|}}
|
||
{{#let attr.options.defaultShown as |defaultDisplay|}}
|
||
{{#if (eq attr.type "object")}}
|
||
<InfoTableRow
|
||
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
|
||
@defaultShown={{defaultDisplay}}
|
||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||
@value={{stringify (get @model attr.name)}}
|
||
/>
|
||
{{else if (eq attr.type "array")}}
|
||
<InfoTableRow
|
||
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
|
||
@defaultShown={{defaultDisplay}}
|
||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||
@value={{get @model attr.name}}
|
||
@isLink={{true}}
|
||
@queryParam="role"
|
||
@type={{attr.type}}
|
||
/>
|
||
{{else}}
|
||
<InfoTableRow
|
||
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
|
||
@defaultShown={{defaultDisplay}}
|
||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||
@value={{get @model attr.name}}
|
||
/>
|
||
{{/if}}
|
||
{{/let}}
|
||
{{/each}}
|
||
{{/if}}
|
||
|
||
<Modal
|
||
@title="Rotate your root credentials?"
|
||
@onClose={{this.continueWithoutRotate}}
|
||
@isActive={{this.showSaveModal}}
|
||
@type="info"
|
||
@showCloseButton={{false}}
|
||
>
|
||
<section class="modal-card-body">
|
||
<p class="has-bottom-margin-s">
|
||
It’s best practice to rotate the root credential immediately after the initial configuration of each database. Once
|
||
rotated,
|
||
<strong>only Vault knows the new root password</strong>.
|
||
</p>
|
||
<p>Would you like to rotate your new credentials? You can also do this later.</p>
|
||
</section>
|
||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||
<button
|
||
type="button"
|
||
class="button is-primary"
|
||
{{on "click" this.continueWithRotate}}
|
||
data-test-enable-rotate-connection
|
||
>
|
||
Rotate and enable
|
||
</button>
|
||
<button type="button" class="button is-secondary" {{on "click" this.continueWithoutRotate}} data-test-enable-connection>
|
||
Enable without rotating
|
||
</button>
|
||
</footer>
|
||
</Modal>
|
||
|
||
<ConfirmationModal
|
||
@title="Delete connection?"
|
||
@onClose={{action (mut this.isDeleteModalActive) false}}
|
||
@isActive={{this.isDeleteModalActive}}
|
||
@confirmText={{@model.name}}
|
||
@toConfirmMsg="deleting the connection"
|
||
@onConfirm={{action "delete"}}
|
||
>
|
||
<p>
|
||
Deleting the connection means that any associated roles won't be able to generate credentials until the connection is
|
||
reconfigured.
|
||
</p>
|
||
<MessageError @model={{this.model}} @errorMessage={{this.error}} />
|
||
</ConfirmationModal> |