mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
UI: HDS adoption replace <Modal> (#23382)
* UI: Part 1 - hds adoption replace <Modal> (#23363) * replace policy-form modal * replace clients/attribution modal * clients/config modal * scope form odal * remove button type * include toolbar to match other example templates * rotate credentials modal * add toolbar button class for hds buttons * transformation-edit modal * add back test selector * add route arg to button! * update link status * fix link-status tests * remove prevent default * update db tests * update tests * use page alert for hcp link status banner * fix scopy button selector * fix sidebar test * change to neutral banner * UI: Part 2 - hds adoption replace <Modal> (#23398) * upgrade HDS library (adds support for snippet containers * cleanup flight icons * replace transit key action modals * re-add deps as devDeps * remove line * address transit tests * UI: Part 3 - hds adoption replace <Modal> (#23415) * cleanup css * cleanup extra type attr * masked input download modal * use Hds::Button in download button" * fix size of modal * tiny icon fix * refactor download button to always render download icon * update tests * UI: Part 3.5 - hds adoption replace <Modal> (#23448) * replication-promote modal * replication component modals * replication add secondary modal * move update text for diff * UI: Part 4 - hds adoption replace <Modal> (#23451) * k8 configure modal * kv delete modal * ldap modals * pki modals * add trash icon * move deps * UI: Part 5 - hds adoption replace <Modal> (#23471) * replace confirmation modals --------- * UI: Part 6 - hds adoption replace <Modal> (#23484) * search select with modal * policy search select modal * replace date dropdown for client dashboard * change padding to top * update policy example args * lolllll test typo wow * update dropdown tests * shamir flow modals! * add one more container * update test selectors * UI: Final hds adoption replace <Modal> cleanup PR (#23522) * search select with modal * policy search select modal * replace date dropdown for client dashboard * change padding to top * update policy example args * lolllll test typo wow * update dropdown tests * shamir flow modals! * add one more container * update test selectors * remove wormhole and modal component * fix selectors * uninstall wormhole * remove shamir-modal-flow class * fix confirm modal test * fix pki and kv test * fix toolbar selector kv * client and download button test * fix-confirmation-modal-padding * fix replication modal tests so relevant modal opens (#23540) * more confirmation modal tests * adds changelog
This commit is contained in:
3
changelog/23382.txt
Normal file
3
changelog/23382.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Makes modals accessible by implementing Helios Design System modal component
|
||||
```
|
||||
@@ -73,15 +73,15 @@ export default class DatabaseConnectionEdit extends Component {
|
||||
}
|
||||
|
||||
@action
|
||||
continueWithoutRotate(evt) {
|
||||
evt.preventDefault();
|
||||
continueWithoutRotate() {
|
||||
this.showSaveModal = false;
|
||||
const { name } = this.args.model;
|
||||
this.transitionToRoute(SHOW_ROUTE, name);
|
||||
}
|
||||
|
||||
@action
|
||||
continueWithRotate(evt) {
|
||||
evt.preventDefault();
|
||||
continueWithRotate() {
|
||||
this.showSaveModal = false;
|
||||
const { backend, name } = this.args.model;
|
||||
this.rotateCredentials(backend, name)
|
||||
.then(() => {
|
||||
|
||||
@@ -15,10 +15,9 @@ import timestamp from 'core/utils/timestamp';
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <DateDropdown @handleSubmit={{this.actionFromParent}} @name="startTime" @submitText="Save" @handleCancel={{this.onCancel}}/>
|
||||
* <DateDropdown @handleSubmit={{this.actionFromParent}} @name="startTime" @submitText="Save" />
|
||||
* ```
|
||||
* @param {function} handleSubmit - callback function from parent that the date picker triggers on submit
|
||||
* @param {function} [handleCancel] - optional callback for cancel action, if exists then buttons appear modal style with a light gray background
|
||||
* @param {string} [dateType] - optional argument to give the selected month/year a type
|
||||
* @param {string} [submitText] - optional argument to change submit button text
|
||||
* @param {function} [validateDate] - parent function to validate date selection, receives date object and returns an error message that's passed to the inline alert
|
||||
@@ -69,12 +68,6 @@ export default class DateDropdown extends Component {
|
||||
this.resetDropdown();
|
||||
}
|
||||
|
||||
@action
|
||||
handleCancel() {
|
||||
this.args.handleCancel();
|
||||
this.resetDropdown();
|
||||
}
|
||||
|
||||
resetDropdown() {
|
||||
this.maxMonthIdx = 11;
|
||||
this.disabledYear = null;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</nav>
|
||||
{{/if}}
|
||||
{{#if this.showExamplePolicy}}
|
||||
<PolicyExample @policyType={{this.policy.policyType}} />
|
||||
<PolicyExample @policyType={{this.policy.policyType}} @container="#search-select-modal" />
|
||||
{{else}}
|
||||
<Select
|
||||
@name="policyType"
|
||||
|
||||
@@ -23,11 +23,24 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field">
|
||||
{{#if @model.isNew}}
|
||||
<Toolbar>
|
||||
<label class="has-text-weight-bold">Policy</label>
|
||||
<ToolbarActions>
|
||||
<div class="toolbar-separator"></div>
|
||||
<Toolbar>
|
||||
<label class="has-text-weight-bold has-right-margin-xxs">Policy</label>
|
||||
{{#if @renderPolicyExampleModal}}
|
||||
{{! only true in policy create and edit routes }}
|
||||
<ToolbarFilters>
|
||||
<Hds::Button
|
||||
@text="How to write a policy"
|
||||
@icon="bulb"
|
||||
@size="small"
|
||||
@color="tertiary"
|
||||
{{on "click" (fn (mut this.showTemplateModal))}}
|
||||
data-test-policy-example-button
|
||||
/>
|
||||
</ToolbarFilters>
|
||||
{{/if}}
|
||||
<ToolbarActions>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{#if @model.isNew}}
|
||||
<div class="control is-flex">
|
||||
<Input
|
||||
id="fileUploadToggle"
|
||||
@@ -40,25 +53,24 @@
|
||||
/>
|
||||
<label for="fileUploadToggle">Upload file</label>
|
||||
</div>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{#if this.showFileUpload}}
|
||||
<TextFile @uploadOnly={{true}} @onChange={{this.setPolicyFromFile}} />
|
||||
{{else}}
|
||||
<JsonEditor
|
||||
@title="Policy"
|
||||
@showToolbar={{false}}
|
||||
@value={{@model.policy}}
|
||||
@valueUpdated={{action (mut @model.policy)}}
|
||||
@mode="ruby"
|
||||
@extraKeys={{hash Shift-Enter=(perform this.save)}}
|
||||
data-test-policy-editor
|
||||
/>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{! EDITING - no file upload toggle}}
|
||||
<Hds::Copy::Button
|
||||
@text="Copy"
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{@model.policy}}
|
||||
class="transparent"
|
||||
data-test-copy-button
|
||||
/>
|
||||
{{/if}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{#if this.showFileUpload}}
|
||||
<TextFile @uploadOnly={{true}} @onChange={{this.setPolicyFromFile}} />
|
||||
{{else}}
|
||||
{{! EDITING - no file upload toggle}}
|
||||
<JsonEditor
|
||||
@title="Policy"
|
||||
@showToolbar={{false}}
|
||||
@value={{@model.policy}}
|
||||
@valueUpdated={{action (mut @model.policy)}}
|
||||
@mode="ruby"
|
||||
@@ -70,36 +82,6 @@
|
||||
<span class="is-size-9 has-text-grey has-bottom-margin-l">
|
||||
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field.
|
||||
</span>
|
||||
{{! Only renders button (and modal) if not already in the "create policy" modal }}
|
||||
{{#if @renderPolicyExampleModal}}
|
||||
<span class="is-size-9 has-text-grey has-bottom-margin-l">
|
||||
See
|
||||
<button
|
||||
type="button"
|
||||
class="text-button has-text-info"
|
||||
{{on "click" (fn (mut this.showTemplateModal))}}
|
||||
data-test-policy-example-button
|
||||
>
|
||||
example template
|
||||
</button>.
|
||||
</span>
|
||||
{{! Only renders more information if already in the "create policy" modal }}
|
||||
{{else}}
|
||||
<p class="has-top-margin-l">
|
||||
More information about
|
||||
{{uppercase @model.policyType}}
|
||||
policies can be found
|
||||
<DocLink
|
||||
@path={{if
|
||||
(eq @model.policyType "acl")
|
||||
"/vault/docs/concepts/policies#capabilities"
|
||||
"/vault/tutorials/policies/sentinel#role-governing-policies-rgps"
|
||||
}}
|
||||
>
|
||||
here.
|
||||
</DocLink>
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{#each @model.additionalAttrs as |attr|}}
|
||||
@@ -128,26 +110,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{! SAMPLE POLICY MODAL. Only renders modal if not already in create policy modal }}
|
||||
{{#if @renderPolicyExampleModal}}
|
||||
<Modal
|
||||
@title="Example {{uppercase @model.policyType}} Policy"
|
||||
|
||||
{{! SAMPLE POLICY MODAL. Only renders in policy create and edit routes }}
|
||||
{{#if this.showTemplateModal}}
|
||||
<Hds::Modal
|
||||
id="policy-example-modal"
|
||||
@size="large"
|
||||
@onClose={{fn (mut this.showTemplateModal) false}}
|
||||
@isActive={{this.showTemplateModal}}
|
||||
@showCloseButton={{true}}
|
||||
data-test-policy-example-modal
|
||||
as |M|
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
{{! code-mirror modifier does not render value initially until focus event fires }}
|
||||
{{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }}
|
||||
{{#if this.showTemplateModal}}
|
||||
<PolicyExample @policyType={{@model.policyType}} />
|
||||
{{/if}}
|
||||
</section>
|
||||
<div class="modal-card-head has-border-top-light">
|
||||
<button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
<M.Header data-test-modal-title>
|
||||
Example
|
||||
{{uppercase @model.policyType}}
|
||||
Policy
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<PolicyExample @policyType={{@model.policyType}} @container="#policy-example-modal" />
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} data-test-modal-close-button />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -46,7 +46,6 @@
|
||||
</Frame.Sidebar>
|
||||
<Frame.Main id="app-main-content" class={{if this.console.isOpen "main--console-open"}}>
|
||||
{{! outlet for app content }}
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
|
||||
{{yield}}
|
||||
<div data-test-console-panel class={{if this.console.isOpen "panel-open"}}>
|
||||
|
||||
@@ -48,25 +48,19 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.button.masked-input-toggle,
|
||||
.button.download-button {
|
||||
.button.masked-input-toggle {
|
||||
min-width: $spacing-xl;
|
||||
border-left: 0;
|
||||
color: $grey;
|
||||
box-shadow: 0 3px 1px 0px rgba(10, 10, 10, 0.12);
|
||||
}
|
||||
|
||||
.button.download-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.button.masked-input-toggle {
|
||||
border-radius: 0 $radius $radius 0;
|
||||
}
|
||||
|
||||
.display-only {
|
||||
.button.masked-input-toggle,
|
||||
.button.download-button {
|
||||
.button.masked-input-toggle {
|
||||
background: transparent;
|
||||
height: auto;
|
||||
line-height: 1rem;
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
.modal {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
|
||||
&.is-active {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-background {
|
||||
background: $ui-gray-100;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.9;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
box-shadow: $box-shadow-highest;
|
||||
border: 1px solid $grey-light;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 70px);
|
||||
margin-top: 60px;
|
||||
min-width: calc(100vw * 0.3);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&-head {
|
||||
border-radius: 0;
|
||||
background-color: $grey-lightest;
|
||||
border-bottom: 1px solid $grey-light;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&-foot {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
background-color: $white;
|
||||
padding: 20px;
|
||||
|
||||
.button:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title.title {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.copy-text {
|
||||
background-color: $grey-lightest;
|
||||
padding: $spacing-s;
|
||||
margin-bottom: $spacing-s;
|
||||
|
||||
code {
|
||||
overflow: scroll;
|
||||
max-width: calc(100% - 36px);
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-close {
|
||||
margin-top: $spacing-s;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-card-title.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-card-body {
|
||||
background-color: $white;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.is-highlight {
|
||||
.modal-card-head {
|
||||
background: $yellow-010;
|
||||
border: 1px solid $yellow-100;
|
||||
}
|
||||
.modal-card-title {
|
||||
color: $yellow-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.is-danger {
|
||||
.modal-card-head {
|
||||
background: $red-010;
|
||||
border: 1px solid $red-100;
|
||||
}
|
||||
.modal-card-title {
|
||||
color: $red-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-confirm-section {
|
||||
margin: $spacing-xl 0 $spacing-m;
|
||||
}
|
||||
|
||||
.modal-card-foot-outlined {
|
||||
background: $ui-gray-050;
|
||||
border-top: $base-border;
|
||||
}
|
||||
|
||||
// customize spacing (.modal-card-body is restricted to padding: 20px)
|
||||
.modal-card-custom {
|
||||
background-color: white;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: auto;
|
||||
|
||||
&.has-padding-m {
|
||||
padding: $spacing-m;
|
||||
}
|
||||
|
||||
&.has-padding-btm-left {
|
||||
padding-bottom: $spacing-m;
|
||||
padding-left: $spacing-m;
|
||||
}
|
||||
}
|
||||
|
||||
// responsive css
|
||||
@media screen and (min-width: 769px), print {
|
||||
.modal-card,
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
max-height: calc(100vh - 40px);
|
||||
width: 640px;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
.field-title {
|
||||
font-weight: 700;
|
||||
font-size: $size-7;
|
||||
}
|
||||
@@ -24,29 +24,3 @@
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.link-status {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: $size-7;
|
||||
font-weight: $font-weight-semibold;
|
||||
|
||||
&.connected {
|
||||
background-color: var(--token-color-surface-action);
|
||||
color: var(--token-color-foreground-action-active);
|
||||
|
||||
a {
|
||||
color: var(--token-color-foreground-action-active);
|
||||
}
|
||||
}
|
||||
&.warning {
|
||||
background-color: var(--token-color-surface-warning);
|
||||
color: var(--token-color-palette-amber-300);
|
||||
|
||||
a {
|
||||
color: var(--token-color-palette-amber-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
&:has(.hds-modal) {
|
||||
// toolbar buttons that open/close a modal pass attrs to the modal content
|
||||
white-space: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-filters + .toolbar-actions {
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
@import './components/loader';
|
||||
@import './components/login-form';
|
||||
@import './components/masked-input';
|
||||
@import './components/modal-component.scss';
|
||||
@import './components/namespace-picker';
|
||||
@import './components/namespace-reminder';
|
||||
@import './components/navigate-input';
|
||||
@@ -104,7 +103,6 @@
|
||||
@import './components/secrets-engines-card';
|
||||
// action-block extends selectable-card
|
||||
@import './components/action-block';
|
||||
@import './components/shamir-modal-flow';
|
||||
@import './components/shamir-progress';
|
||||
@import './components/sidebar';
|
||||
@import './components/splash-page';
|
||||
|
||||
@@ -91,12 +91,6 @@
|
||||
color: $red-500;
|
||||
}
|
||||
|
||||
&.is-warning-outlined {
|
||||
background-color: $yellow-010;
|
||||
border: 1px solid $yellow-700;
|
||||
color: $yellow-700;
|
||||
}
|
||||
|
||||
&.is-flat {
|
||||
min-width: auto;
|
||||
border: none;
|
||||
@@ -332,11 +326,11 @@ a.button.disabled {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO HDS adoption cleanup: audit styles with design and see what to keep/remove once buttons are fully HDS
|
||||
// Existing class on <Hds::Copy::Button> component, modifying to match existing UI Structure buttons
|
||||
.hds-copy-button {
|
||||
font-weight: $font-weight-semibold;
|
||||
box-shadow: $box-shadow-low;
|
||||
border-radius: $radius;
|
||||
font-weight: $font-weight-semibold; // TODO delete
|
||||
box-shadow: $box-shadow-low; // TODO delete
|
||||
|
||||
&.white-icon svg {
|
||||
color: $white;
|
||||
@@ -350,15 +344,14 @@ a.button.disabled {
|
||||
color: $ui-gray-500;
|
||||
}
|
||||
|
||||
&.icon-only {
|
||||
margin-right: $spacing-xxs;
|
||||
margin-left: $spacing-xxs;
|
||||
}
|
||||
|
||||
&.transparent {
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
&:hover {
|
||||
border: 1px solid $grey-light;
|
||||
border-color: var(--token-color-border-strong);
|
||||
}
|
||||
}
|
||||
|
||||
&.primary {
|
||||
@@ -386,3 +379,20 @@ a.button.disabled {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Existing class on <Hds::Button> component, modifying to match existing UI Structure buttons
|
||||
.hds-button {
|
||||
font-weight: $font-weight-semibold; // TODO consult design on font weight after button class audit
|
||||
// for toolbar-button must pass arg @color="secondary"
|
||||
&.toolbar-button {
|
||||
color: $black;
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
&:hover:not(.disabled) {
|
||||
background-color: $ui-gray-100;
|
||||
border: 0;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,3 +170,27 @@ form {
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// HDS modifications and overrides
|
||||
// * ONLY for universal changes (i.e. to address component functionality)
|
||||
|
||||
// <Hds::Modal>
|
||||
.hds-modal {
|
||||
&:has(.hds-dropdown) {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
.hds-modal__body {
|
||||
&:has(.hds-dropdown) {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
// <Hds::Dropdown>
|
||||
.hds-dropdown-list-item {
|
||||
> button:disabled {
|
||||
color: $black;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
{{#if this.hasCsvData}}
|
||||
<button
|
||||
<Hds::Button
|
||||
data-test-attribution-export-button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
@text="Export attribution data"
|
||||
@color="secondary"
|
||||
{{on "click" (fn (mut this.showCSVDownloadModal) true)}}
|
||||
>
|
||||
Export attribution data
|
||||
</button>
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,41 +89,41 @@
|
||||
</div>
|
||||
|
||||
{{! MODAL FOR CSV DOWNLOAD }}
|
||||
<Modal
|
||||
@title="Export attribution data"
|
||||
@type="info"
|
||||
@isActive={{this.showCSVDownloadModal}}
|
||||
@showCloseButton={{true}}
|
||||
@onClose={{action (mut this.showCSVDownloadModal) false}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<p class="has-bottom-margin-s">
|
||||
This export will include the namespace path, authentication method path, and the associated total, entity, and
|
||||
non-entity clients for the below
|
||||
{{if this.formattedEndDate "date range" "month"}}.
|
||||
</p>
|
||||
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
|
||||
<p class="has-bottom-margin-s">{{this.formattedStartDate}} {{if this.formattedEndDate "-"}} {{this.formattedEndDate}}</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}}>
|
||||
Export
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" onclick={{action (mut this.showCSVDownloadModal) false}}>
|
||||
Cancel
|
||||
</button>
|
||||
{{#if @upgradeExplanation}}
|
||||
<div class="has-text-grey is-size-8">
|
||||
<AlertInline @type="warning" @isMarginless={{true}}>
|
||||
Your data contains an upgrade.
|
||||
<DocLink
|
||||
@path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
|
||||
>
|
||||
Learn more here.
|
||||
</DocLink>
|
||||
</AlertInline>
|
||||
<p class="has-left-padding-l">{{@upgradeExplanation}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.showCSVDownloadModal}}
|
||||
<Hds::Modal id="attribution-csv-download-modal" @onClose={{fn (mut this.showCSVDownloadModal) false}} as |M|>
|
||||
<M.Header @icon="info" data-test-export-modal-title>
|
||||
Export attribution data
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="has-bottom-margin-s">
|
||||
This export will include the namespace path, authentication method path, and the associated total, entity, and
|
||||
non-entity clients for the below
|
||||
{{if this.formattedEndDate "date range" "month"}}.
|
||||
</p>
|
||||
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
|
||||
<p class="has-bottom-margin-s" data-test-export-date-range>
|
||||
{{this.formattedStartDate}}
|
||||
{{if this.formattedEndDate "-"}}
|
||||
{{this.formattedEndDate}}</p>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Export" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}} />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
{{#if @upgradeExplanation}}
|
||||
<div class="has-text-grey is-size-8 has-top-padding-m">
|
||||
<AlertInline @type="warning">
|
||||
Your data contains an upgrade.
|
||||
<DocLink
|
||||
@path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
|
||||
>
|
||||
Learn more here.
|
||||
</DocLink>
|
||||
</AlertInline>
|
||||
<p class="has-left-padding-l">{{@upgradeExplanation}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -47,47 +47,34 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Modal
|
||||
@title={{this.modalTitle}}
|
||||
@onClose={{action (mut this.modalOpen) false}}
|
||||
@isActive={{this.modalOpen}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
{{#if (eq @model.enabled "On")}}
|
||||
<p class="has-bottom-margin-s" data-test-clients-config-modal="on">
|
||||
Vault will start tracking data starting from today’s date,
|
||||
{{date-format (now) "MMMM d, yyyy"}}. If you’ve previously enabled usage tracking, that historical data will still
|
||||
be available to you.
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="has-bottom-margin-s" data-test-clients-config-modal="off">
|
||||
Turning usage tracking off means that all data for the current month will be deleted. You will still be able to
|
||||
query previous months.
|
||||
</p>
|
||||
<p>Are you sure?</p>
|
||||
{{/if}}
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
data-test-clients-config-modal="continue"
|
||||
{{on "click" (perform this.save)}}
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
{{on "click" (fn (mut this.modalOpen) false)}}
|
||||
data-test-clients-config-modal="cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.modalOpen}}
|
||||
<Hds::Modal id="clients-config-modal" @color="warning" @onClose={{fn (mut this.modalOpen) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle" data-test-clients-config-modal="title">
|
||||
{{this.modalTitle}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
{{#if (eq @model.enabled "On")}}
|
||||
<p class="has-bottom-margin-s" data-test-clients-config-modal="on">
|
||||
Vault will start tracking data starting from today’s date,
|
||||
{{date-format (now) "MMMM d, yyyy"}}. If you’ve previously enabled usage tracking, that historical data will
|
||||
still be available to you.
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="has-bottom-margin-s" data-test-clients-config-modal="off">
|
||||
Turning usage tracking off means that all data for the current month will be deleted. You will still be able to
|
||||
query previous months.
|
||||
</p>
|
||||
<p>Are you sure?</p>
|
||||
{{/if}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Continue" {{on "click" (perform this.save)}} data-test-clients-config-modal="continue" />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-clients-config-modal="cancel" />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless" data-test-clients-config-table>
|
||||
{{#each this.infoRows as |item|}}
|
||||
|
||||
@@ -175,26 +175,28 @@
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{! BILLING START DATE MODAL }}
|
||||
|
||||
<Modal
|
||||
@title="Edit start month"
|
||||
@onClose={{action (mut this.showBillingStartModal) false}}
|
||||
@isActive={{this.showBillingStartModal}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-custom has-padding-m">
|
||||
{{! BILLING START DATE MODAL }}
|
||||
{{#if this.showBillingStartModal}}
|
||||
<Hds::Modal id="clients-edit-date-modal" @onClose={{fn (mut this.showBillingStartModal) false}} as |M|>
|
||||
<M.Header>
|
||||
Edit start month
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="has-bottom-margin-s">
|
||||
{{this.versionText.description}}
|
||||
</p>
|
||||
<p><strong>{{this.versionText.label}}</strong></p>
|
||||
</section>
|
||||
<DateDropdown
|
||||
@handleSubmit={{this.handleClientActivityQuery}}
|
||||
@dateType="startDate"
|
||||
@submitText="Save"
|
||||
@handleCancel={{fn this.handleClientActivityQuery (hash dateType="cancel")}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<DateDropdown
|
||||
class="has-top-padding-s"
|
||||
@handleSubmit={{this.handleClientActivityQuery}}
|
||||
@dateType="startDate"
|
||||
@submitText="Save"
|
||||
/>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button data-test-date-dropdown-cancel @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -352,35 +352,32 @@
|
||||
{{/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>
|
||||
{{#if this.showSaveModal}}
|
||||
<Hds::Modal id="rotate-credentials-modal" @onClose={{this.continueWithoutRotate}} as |M|>
|
||||
<M.Header @icon="info" data-test-db-connection-modal-title>
|
||||
Rotate your root credentials?
|
||||
</M.Header>
|
||||
<M.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>
|
||||
</M.Body>
|
||||
<M.Footer>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Rotate and enable" {{on "click" this.continueWithRotate}} data-test-enable-rotate-connection />
|
||||
<Hds::Button
|
||||
@text="Enable without rotating"
|
||||
@color="secondary"
|
||||
{{on "click" this.continueWithoutRotate}}
|
||||
data-test-enable-connection
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
|
||||
<ConfirmationModal
|
||||
@title="Delete connection?"
|
||||
|
||||
@@ -3,89 +3,36 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<div class="modal-card-custom has-padding-btm-left">
|
||||
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
|
||||
<D.Trigger
|
||||
data-test-popup-menu-trigger="month"
|
||||
class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||
@htmlTag="button"
|
||||
>
|
||||
{{or this.selectedMonth.name "Month"}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="popup-menu-content is-wide">
|
||||
<nav class="box menu scroll" aria-label="months">
|
||||
<ul data-test-month-list class="menu-list">
|
||||
{{#each this.dropdownMonths as |month|}}
|
||||
<button
|
||||
data-test-dropdown-month={{month.name}}
|
||||
type="button"
|
||||
class="button link"
|
||||
disabled={{if (gt month.index this.maxMonthIdx) true false}}
|
||||
{{on "click" (fn this.selectMonth month D.actions)}}
|
||||
>
|
||||
{{month.name}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
|
||||
<D.Trigger
|
||||
data-test-popup-menu-trigger="year"
|
||||
class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||
@htmlTag="button"
|
||||
>
|
||||
{{or this.selectedYear "Year"}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="popup-menu-content is-wide">
|
||||
<nav class="box menu" aria-label="years">
|
||||
<ul data-test-year-list class="menu-list">
|
||||
{{#each this.dropdownYears as |year|}}
|
||||
<button
|
||||
data-test-dropdown-year={{year}}
|
||||
type="button"
|
||||
class="button link"
|
||||
disabled={{if (eq year this.disabledYear) true false}}
|
||||
{{on "click" (fn this.selectYear year D.actions)}}
|
||||
>
|
||||
{{year}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
{{#unless @handleCancel}}
|
||||
<button
|
||||
data-test-date-dropdown-submit
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
disabled={{if (and this.selectedMonth this.selectedYear) false true}}
|
||||
{{on "click" this.handleSubmit}}
|
||||
>
|
||||
{{or @submitText "Submit"}}
|
||||
</button>
|
||||
{{/unless}}
|
||||
{{#if this.invalidDate}}
|
||||
<AlertInline @type="danger" @message={{this.invalidDate}} @paddingTop={{true}} @mimicRefresh={{true}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if @handleCancel}}
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
data-test-date-dropdown-submit
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
disabled={{if (and this.selectedMonth this.selectedYear) false true}}
|
||||
{{on "click" this.handleSubmit}}
|
||||
>
|
||||
{{or @submitText "Submit"}}
|
||||
</button>
|
||||
<button data-test-date-dropdown-cancel type="button" class="button is-secondary" {{on "click" this.handleCancel}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
<Hds::SegmentedGroup ...attributes as |S|>
|
||||
<S.Dropdown @height="200px" as |dd|>
|
||||
<dd.ToggleButton data-test-toggle-month @text={{or this.selectedMonth.name "Month"}} @color="secondary" />
|
||||
{{#each this.dropdownMonths as |month|}}
|
||||
<dd.Interactive
|
||||
data-test-dropdown-month={{month.name}}
|
||||
disabled={{if (gt month.index this.maxMonthIdx) true false}}
|
||||
{{on "click" (fn this.selectMonth month dd)}}
|
||||
@text={{month.name}}
|
||||
/>
|
||||
{{/each}}
|
||||
</S.Dropdown>
|
||||
<S.Dropdown data-test-year-list @height="200px" as |dd|>
|
||||
<dd.ToggleButton data-test-toggle-year @text={{or this.selectedYear "Year"}} @color="secondary" />
|
||||
{{#each this.dropdownYears as |year|}}
|
||||
<dd.Interactive
|
||||
data-test-dropdown-year={{year}}
|
||||
disabled={{if (eq year this.disabledYear) true false}}
|
||||
{{on "click" (fn this.selectYear year dd)}}
|
||||
@text={{year}}
|
||||
/>
|
||||
{{/each}}
|
||||
</S.Dropdown>
|
||||
<S.Button
|
||||
data-test-date-dropdown-submit
|
||||
disabled={{if (and this.selectedMonth this.selectedYear) false true}}
|
||||
{{on "click" this.handleSubmit}}
|
||||
@text={{or @submitText "Submit"}}
|
||||
/>
|
||||
</Hds::SegmentedGroup>
|
||||
{{#if this.invalidDate}}
|
||||
<AlertInline @type="danger" @message={{this.invalidDate}} @paddingTop={{true}} @mimicRefresh={{true}} />
|
||||
{{/if}}
|
||||
@@ -4,9 +4,8 @@
|
||||
~}}
|
||||
|
||||
{{#if (and this.state this.version.isEnterprise)}}
|
||||
<div class="link-status {{if (eq this.state 'connected') 'connected' 'warning'}}">
|
||||
<Icon @name="info" />
|
||||
<p data-test-link-status>
|
||||
<Hds::Alert @type="page" @color={{if (eq this.state "connected") "neutral" "warning"}} as |A|>
|
||||
<A.Title data-test-link-status>
|
||||
{{#if (eq this.state "connected")}}
|
||||
This self-managed Vault is linked to
|
||||
<ExternalLink @href="https://portal.cloud.hashicorp.com/sign-in">
|
||||
@@ -19,49 +18,41 @@
|
||||
</button>
|
||||
for more information.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</A.Title>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
|
||||
<Modal
|
||||
@title="HCP Link error"
|
||||
@onClose={{fn (mut this.showModal) false}}
|
||||
@isActive={{this.showModal}}
|
||||
@type="info"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Timestamp</p>
|
||||
<p data-test-link-status-timestamp>
|
||||
{{or this.timestamp "Not available"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="has-top-bottom-margin">
|
||||
<p class="has-text-weight-bold">Error</p>
|
||||
{{#if this.error}}
|
||||
<code class="tag has-text-danger" data-test-link-status-error>
|
||||
{{this.error}}
|
||||
</code>
|
||||
{{else}}
|
||||
<p data-test-link-status-error>
|
||||
Not available
|
||||
{{#if this.showModal}}
|
||||
<Hds::Modal id="hcp-link-error-modal" @color="warning" @size="small" @onClose={{fn (mut this.showModal) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
HCP Link error
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Timestamp</p>
|
||||
<p data-test-link-status-timestamp>
|
||||
{{or this.timestamp "Not available"}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Additional information</p>
|
||||
<p>Check the logs for more information</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (fn (mut this.showModal) false)}}
|
||||
data-test-link-status-close
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</div>
|
||||
<div class="has-top-bottom-margin">
|
||||
<p class="has-text-weight-bold">Error</p>
|
||||
{{#if this.error}}
|
||||
<code class="tag has-text-danger" data-test-link-status-error>
|
||||
{{this.error}}
|
||||
</code>
|
||||
{{else}}
|
||||
<p data-test-link-status-error>
|
||||
Not available
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Additional information</p>
|
||||
<p>Check the logs for more information</p>
|
||||
</div>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} data-test-link-status-close />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -23,6 +23,7 @@
|
||||
@id="entities"
|
||||
@label="Entities"
|
||||
@placeholder="Search"
|
||||
@renderInPlace={{true}}
|
||||
@models={{array "identity/entity"}}
|
||||
@inputValue={{@model.entityIds}}
|
||||
@shouldRenderName={{true}}
|
||||
@@ -35,6 +36,7 @@
|
||||
@id="groups"
|
||||
@label="Groups"
|
||||
@placeholder="Search"
|
||||
@renderInPlace={{true}}
|
||||
@models={{array "identity/group"}}
|
||||
@inputValue={{@model.groupIds}}
|
||||
@shouldRenderName={{true}}
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
Scope
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
<p.levelRight>
|
||||
<Hds::Button
|
||||
@text="How to write JSON template for scopes"
|
||||
@icon="bulb"
|
||||
@color="tertiary"
|
||||
{{on "click" (fn (mut this.showTemplateModal))}}
|
||||
data-test-oidc-scope-example
|
||||
/>
|
||||
</p.levelRight>
|
||||
</PageHeader>
|
||||
|
||||
<form {{on "submit" (perform this.save)}}>
|
||||
@@ -41,15 +50,8 @@
|
||||
<FormField @attr={{field}} @model={{@model}} @modelValidations={{this.modelValidations}} />
|
||||
{{/each}}
|
||||
<p class="is-size-9 has-text-grey has-bottom-margin-l">
|
||||
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. See
|
||||
<button
|
||||
type="button"
|
||||
class="text-button has-text-info"
|
||||
{{on "click" (fn (mut this.showTemplateModal))}}
|
||||
data-test-oidc-scope-example
|
||||
>
|
||||
example template
|
||||
</button>.
|
||||
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. Click 'How to write JSON
|
||||
template for scopes' to view an example.
|
||||
</p>
|
||||
</div>
|
||||
<div class="has-top-margin-l has-bottom-margin-l">
|
||||
@@ -78,40 +80,25 @@
|
||||
{{/if}}
|
||||
</form>
|
||||
|
||||
<Modal
|
||||
@title="Scope template"
|
||||
@onClose={{fn (mut this.showTemplateModal) false}}
|
||||
@isActive={{this.showTemplateModal}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<div class="is-flex-between is-flex-center has-bottom-margin-s">
|
||||
<p data-test-modal-text>
|
||||
{{#if this.showTemplateModal}}
|
||||
<Hds::Modal id="scope-template-modal" @size="large" @onClose={{fn (mut this.showTemplateModal) false}} as |M|>
|
||||
<M.Header data-test-scope-modal="title">
|
||||
Scope template
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p data-test-scope-modal="text">
|
||||
Example of a JSON template for scopes:
|
||||
</p>
|
||||
<Hds::Copy::Button
|
||||
@text="Copy"
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{this.exampleTemplate}}
|
||||
class="transparent"
|
||||
data-test-copy-button
|
||||
/>
|
||||
</div>
|
||||
{{! code-mirror modifier does not render value initially in wormhole until focus event fires }}
|
||||
{{! wait until the Modal is rendered and then show the JsonEditor }}
|
||||
{{#if this.showTemplateModal}}
|
||||
<JsonEditor @value={{this.exampleTemplate}} @mode="ruby" @readOnly={{true}} @showToolbar={{false}} />
|
||||
{{/if}}
|
||||
<p class="has-top-margin-m">
|
||||
The full list of template parameters can be found
|
||||
<DocLink @path="/vault/docs/concepts/oidc-provider#scopes">
|
||||
here.
|
||||
</DocLink>
|
||||
</p>
|
||||
</section>
|
||||
<div class="modal-card-head has-border-top-light">
|
||||
<button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
<JsonEditor @value={{this.exampleTemplate}} @mode="ruby" @readOnly={{true}} @container=".hds-modal" />
|
||||
<p class="has-top-margin-m">
|
||||
The full list of template parameters can be found
|
||||
<DocLink @path="/vault/docs/concepts/oidc-provider#scopes">
|
||||
here.
|
||||
</DocLink>
|
||||
</p>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} data-test-close-modal />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -53,14 +53,13 @@
|
||||
{{/if}}
|
||||
{{#if this.capabilities.canUpdate}}
|
||||
{{#if (gt this.model.allowed_roles.length 0)}}
|
||||
<button
|
||||
class="toolbar-link"
|
||||
onclick={{action (mut this.isEditModalActive) true}}
|
||||
type="button"
|
||||
<Hds::Button
|
||||
@text="Edit transformation"
|
||||
@color="secondary"
|
||||
class="toolbar-button"
|
||||
{{on "click" (fn (mut this.isEditModalActive) true)}}
|
||||
data-test-edit-link
|
||||
>
|
||||
Edit transformation
|
||||
</button>
|
||||
/>
|
||||
{{else}}
|
||||
<ToolbarSecretLink @secret={{this.model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}>
|
||||
Edit transformation
|
||||
@@ -96,30 +95,27 @@
|
||||
<MessageError @model={{this.model}} @errorMessage={{this.error}} />
|
||||
</ConfirmationModal>
|
||||
|
||||
<Modal
|
||||
@title="Edit transformation"
|
||||
@onClose={{action (mut this.isEditModalActive) false}}
|
||||
@isActive={{this.isEditModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<p>
|
||||
You’re editing a transformation that is in use by at least one role. Editing it may mean that encode and decode
|
||||
operations stop working. Are you sure?
|
||||
</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.edit"
|
||||
@model={{this.model.id}}
|
||||
class="button is-primary"
|
||||
data-test-edit-confirm-button={{true}}
|
||||
>
|
||||
Confirm
|
||||
</LinkTo>
|
||||
<button type="button" class="button is-secondary" onclick={{action (mut this.isEditModalActive) false}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.isEditModalActive}}
|
||||
<Hds::Modal id="transformation-edit-modal" @color="warning" @onClose={{fn (mut this.isEditModalActive) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Edit transformation
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
You’re editing a transformation that is in use by at least one role. Editing it may mean that encode and decode
|
||||
operations stop working. Are you sure?
|
||||
</p>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
@text="Confirm"
|
||||
@route="vault.cluster.secrets.backend.edit"
|
||||
@model={{this.model.id}}
|
||||
data-test-edit-confirm-button
|
||||
/>
|
||||
<Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -74,19 +74,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal @title="Copy your generated key" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-datakey-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Copy your generated key
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
{{#if (eq @param "plaintext")}}
|
||||
<h2 class="has-text-weight-semibold is-6">Plaintext</h2>
|
||||
<p class="sub-text">Plaintext is base64 encoded</p>
|
||||
<Hds::Copy::Snippet class="has-bottom-margin-m" @textToCopy={{@plaintext}} @color="secondary" />
|
||||
<Hds::Copy::Snippet
|
||||
class="has-bottom-margin-m"
|
||||
@textToCopy={{@plaintext}}
|
||||
@color="secondary"
|
||||
@container="#transit-datakey-modal"
|
||||
/>
|
||||
{{/if}}
|
||||
<h2 class="has-text-weight-semibold is-6">Ciphertext</h2>
|
||||
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-datakey-modal" />
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -56,16 +56,22 @@
|
||||
</div>
|
||||
</form>
|
||||
{{#if @isModalActive}}
|
||||
<Modal @title="Copy your unwrapped data" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
<h2 class="has-text-weight-semibold is-6">Plaintext</h2>
|
||||
<p class="sub-text">Plaintext is base64 encoded</p>
|
||||
<Hds::Copy::Snippet @textToCopy={{@plaintext}} @color="secondary" data-test-encrypted-value="plaintext" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Modal id="transit-decrypt-modal" @onClose={{fn (mut @isModalActive) false}} data-test-decrypt-modal as |M|>
|
||||
<M.Header>
|
||||
Copy your unwrapped data
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="has-text-weight-semibold is-6">Plaintext</h2>
|
||||
<p class="sub-text">Plaintext is base64 encoded</p>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@plaintext}}
|
||||
@color="secondary"
|
||||
@container="#transit-decrypt-modal"
|
||||
data-test-encrypted-value="plaintext"
|
||||
/>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -69,19 +69,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal
|
||||
@title="Copy your token"
|
||||
@onClose={{action (mut @isModalActive) false}}
|
||||
@isActive={{@isModalActive}}
|
||||
data-test-encrypt-modal
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-encrypt-modal" @onClose={{fn (mut @isModalActive) false}} data-test-encrypt-modal as |M|>
|
||||
<M.Header>
|
||||
Copy your token
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="title is-6">Ciphertext</h2>
|
||||
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" data-test-encrypted-value="ciphertext" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@ciphertext}}
|
||||
@color="secondary"
|
||||
@container="#transit-encrypt-modal"
|
||||
data-test-encrypted-value="ciphertext"
|
||||
/>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -65,15 +65,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal @title="Copy your wrapped key" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
<h2 class="title is-6">Wrapped Key</h2>
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-export-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Copy your wrapped key
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="title is-6">Wrapped key</h2>
|
||||
{{#if this.wrapTTL}}
|
||||
<Hds::Copy::Snippet
|
||||
class="has-bottom-margin-m"
|
||||
@textToCopy={{@wrappedToken}}
|
||||
@color="secondary"
|
||||
@container="#transit-export-modal"
|
||||
data-test-encrypted-value="export"
|
||||
/>
|
||||
{{else}}
|
||||
@@ -85,13 +89,14 @@
|
||||
@text="Copy"
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{stringify @keys}}
|
||||
@container="#transit-export-modal"
|
||||
class="transparent top-right-absolute"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -51,14 +51,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal @title="Copy your unwrapped data" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-hmac-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Copy your unwrapped data
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="title is-6">HMAC</h2>
|
||||
<Hds::Copy::Snippet @textToCopy={{@hmac}} @color="secondary" data-test-encrypted-value="hmac" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@hmac}}
|
||||
@color="secondary"
|
||||
@container="#transit-hmac-modal"
|
||||
data-test-encrypted-value="hmac"
|
||||
/>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -63,14 +63,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal @title="Copy your token" @onClose={{action (mut @isModalActive)}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-rewrap-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Copy your token
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="title is-6">Ciphertext</h2>
|
||||
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-rewrap-modal" />
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -120,19 +120,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal
|
||||
@title="Copy your signature"
|
||||
@onClose={{action (mut @isModalActive) false}}
|
||||
@isActive={{@isModalActive}}
|
||||
data-test-sign-modal
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<div class="box is-shadowless is-fullwidth is-sideless">
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-sign-modal" @onClose={{fn (mut @isModalActive) false}} data-test-sign-modal as |M|>
|
||||
<M.Header>
|
||||
Copy your signature
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h2 class="title is-6">Signature</h2>
|
||||
<Hds::Copy::Snippet @textToCopy={{@signature}} @color="secondary" data-test-encrypted-value="signature" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@signature}}
|
||||
@color="secondary"
|
||||
@container="#transit-sign-modal"
|
||||
data-test-encrypted-value="signature"
|
||||
/>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -192,16 +192,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal @title="Results" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Alert @type="inline" @color={{if @valid "success" "critical"}} as |A|>
|
||||
<A.Title>{{if @valid "Valid" "Not Valid"}}</A.Title>
|
||||
<A.Description>
|
||||
The input is
|
||||
{{if @valid "valid" "not valid"}}
|
||||
for the given
|
||||
{{if @signature "signature." "HMAC."}}
|
||||
</A.Description>
|
||||
</Hds::Alert>
|
||||
</section>
|
||||
</Modal>
|
||||
{{#if @isModalActive}}
|
||||
<Hds::Modal id="transit-verify-modal" @size="small" @onClose={{fn (mut @isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Results
|
||||
<Hds::Badge
|
||||
@text={{if @valid "Valid" "Not Valid"}}
|
||||
@size="large"
|
||||
@color={{if @valid "success" "critical"}}
|
||||
@icon={{if @valid "check-circle" "x-circle"}}
|
||||
/>
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
The input is
|
||||
{{if @valid "valid" "not valid"}}
|
||||
for the given
|
||||
{{if @signature "signature." "HMAC."}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -95,15 +95,13 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
<DownloadButton
|
||||
class="button is-ghost"
|
||||
@color="tertiary"
|
||||
@filename={{this.keyFilename}}
|
||||
@data={{this.keyData}}
|
||||
@extension="json"
|
||||
@stringify={{true}}
|
||||
>
|
||||
<Icon @name="download" />
|
||||
Download keys
|
||||
</DownloadButton>
|
||||
@text="Download keys"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Page.content>
|
||||
|
||||
@@ -29,14 +29,13 @@
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<DownloadButton
|
||||
class="toolbar-link"
|
||||
class="toolbar-button"
|
||||
@color="secondary"
|
||||
@filename={{this.model.name}}
|
||||
@data={{this.model.policy}}
|
||||
@extension={{if (eq this.policyType "acl") this.model.format "sentinel"}}
|
||||
>
|
||||
Download policy
|
||||
<Chevron @isButton={{true}} />
|
||||
</DownloadButton>
|
||||
@text="Download policy"
|
||||
/>
|
||||
{{#if (and (not-eq this.model.id "root") (or this.capabilities.canUpdate this.capabilities.canDelete))}}
|
||||
<ToolbarLink @route="vault.cluster.policy.edit" @model={{this.model.id}} data-test-policy-edit-toggle>
|
||||
Edit policy
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<h4 class="field-title has-bottom-padding-m is-fullwidth">
|
||||
<h4 class="has-text-weight-bold is-size-7 has-bottom-padding-m is-fullwidth">
|
||||
{{concat "PGP Key " this.pgpKeyFile.filename}}
|
||||
</h4>
|
||||
<Hds::Copy::Snippet
|
||||
@@ -27,6 +27,7 @@
|
||||
@textToCopy={{this.pgpKey}}
|
||||
@color="secondary"
|
||||
data-test-pgp-key-copy
|
||||
@container="#shamir-flow-modal"
|
||||
/>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
@text="Copy"
|
||||
@textToCopy={{or @clipboardCode @codeBlock}}
|
||||
@isIconOnly={{@isIconOnly}}
|
||||
@container={{@container}}
|
||||
/>
|
||||
</div>
|
||||
@@ -3,40 +3,45 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<Modal @title={{@title}} @onClose={{@onClose}} @isActive={{@isActive}} @type={{this.type}} @showCloseButton={{true}}>
|
||||
<section class="modal-card-body">
|
||||
{{yield}}
|
||||
<div class="modal-confirm-section">
|
||||
<p class="has-text-weight-semibold is-size-6">
|
||||
Confirm
|
||||
</p>
|
||||
<p class="sub-text has-top-bottom-margin-xxs">Type
|
||||
<strong>{{this.confirmText}}</strong>
|
||||
to confirm
|
||||
{{this.toConfirmMsg}}</p>
|
||||
<Input
|
||||
@type="text"
|
||||
@value={{this.confirmationInput}}
|
||||
name="confirmationInput"
|
||||
class="input has-margin-top"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-test-confirmation-modal-input={{or @title true}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button {{this.buttonClass}}"
|
||||
disabled={{not-eq this.confirmationInput this.confirmText}}
|
||||
{{on "click" @onConfirm}}
|
||||
data-test-confirm-button={{or @title true}}
|
||||
>
|
||||
{{this.buttonText}}
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" {{on "click" @onClose}} data-test-cancel-button>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if @isActive}}
|
||||
<Hds::Modal id="confirmation-modal" @onClose={{@onClose}} @color="critical" as |M|>
|
||||
<M.Header data-test-confirmation-modal-title @icon="alert-triangle">
|
||||
{{@title}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
{{yield}}
|
||||
<div class="has-top-padding-m">
|
||||
<p class="has-text-weight-semibold is-size-6">
|
||||
Confirm
|
||||
</p>
|
||||
<p class="sub-text has-top-bottom-margin-xxs">Type
|
||||
<strong>{{this.confirmText}}</strong>
|
||||
to confirm
|
||||
{{@toConfirmMsg}}
|
||||
</p>
|
||||
<Input
|
||||
@type="text"
|
||||
@value={{this.confirmationInput}}
|
||||
name="confirmationInput"
|
||||
class="input has-margin-top"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-test-confirmation-modal-input={{@title}}
|
||||
/>
|
||||
</div>
|
||||
</M.Body>
|
||||
<M.Footer>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
@color="critical"
|
||||
type="button"
|
||||
disabled={{not-eq this.confirmationInput this.confirmText}}
|
||||
{{on "click" @onConfirm}}
|
||||
data-test-confirm-button={{@title}}
|
||||
@text={{or @buttonText "Confirm"}}
|
||||
/>
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" @onClose}} data-test-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -2,11 +2,11 @@
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
/**
|
||||
* @module ConfirmationModal
|
||||
* ConfirmationModal components are used to provide an alternative to ConfirmationButton that automatically prompts the user to fill in confirmation text before they can continue with a potentially destructive action. It is built off the Modal component
|
||||
* ConfirmationModal components wrap the <Hds::Modal> component to present a critical (red) type-to-confirm modal.
|
||||
* They are used for extremely destructive actions that require extra consideration before confirming.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
@@ -15,6 +15,7 @@ import Component from '@glimmer/component';
|
||||
* @title="Do Dangerous Thing?"
|
||||
* @isActive={{isModalActive}}
|
||||
* @onClose={{action (mut isModalActive) false}}
|
||||
* @confirmText="yes"
|
||||
* @onConfirmMsg="deleting this thing to delete."
|
||||
* />
|
||||
* ```
|
||||
@@ -23,30 +24,12 @@ import Component from '@glimmer/component';
|
||||
* @param {boolean} isActive - Controls whether the modal is "active" eg. visible or not.
|
||||
* @param {string} title - Title of the modal
|
||||
* @param {string} [confirmText=Yes] - The confirmation text that the user must type before continuing
|
||||
* @param {string} [toConfirmMsg=''] - Finishes the sentence "Type <confirmText> to confirm <toConfirmMsg>", default is an empty string (ex. 'secret deletion')
|
||||
* @param {string} [toConfirmMsg] - Finishes the sentence "Type <confirmText> to confirm <toConfirmMsg>", default is an empty string (ex. 'secret deletion')
|
||||
* @param {string} [buttonText=Confirm] - Button text on the confirm button
|
||||
* @param {string} [buttonClass=is-danger] - extra class to add to confirm button (eg. "is-danger")
|
||||
* @param {string} [type=warning] - The header styling based on type, passed into the message-types helper (in the Modal component).
|
||||
*/
|
||||
|
||||
export default class ConfirmationModal extends Component {
|
||||
get buttonClass() {
|
||||
return this.args.buttonClass || 'is-danger';
|
||||
}
|
||||
|
||||
get buttonText() {
|
||||
return this.args.buttonText || 'Confirm';
|
||||
}
|
||||
|
||||
get confirmText() {
|
||||
return this.args.confirmText || 'Yes';
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.args.type || 'warning';
|
||||
}
|
||||
|
||||
get toConfirmMsg() {
|
||||
return this.args.toConfirmMsg || '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<button data-test-download-button type="button" {{on "click" this.handleDownload}} ...attributes>
|
||||
{{yield}}
|
||||
</button>
|
||||
<Hds::Button
|
||||
data-test-download-button
|
||||
@icon={{if @hideIcon "" "download"}}
|
||||
@text={{or @text "Download"}}
|
||||
@color={{@color}}
|
||||
@iconPosition={{or @iconPosition "leading"}}
|
||||
@isIconOnly={{@isIconOnly}}
|
||||
{{on "click" this.handleDownload}}
|
||||
...attributes
|
||||
/>
|
||||
@@ -12,22 +12,20 @@ import { tracked } from '@glimmer/tracking';
|
||||
import { assert } from '@ember/debug';
|
||||
/**
|
||||
* @module DownloadButton
|
||||
* DownloadButton components are an action button used to download data. Both the action text and icon are yielded.
|
||||
* DownloadButton wraps an <Hds::Button> to perform a download action.
|
||||
* * NOTE: when using in an engine, remember to add the 'download' service to its dependencies (in /engine.js) and map to it in /app.js
|
||||
* [ember-docs](https://ember-engines.com/docs/services)
|
||||
* @example
|
||||
* ```js
|
||||
* <DownloadButton
|
||||
* class="button"
|
||||
* @text="Download this stuff"
|
||||
* @color="secondary"
|
||||
* @data={{this.data}}
|
||||
* @filename={{this.filename}}
|
||||
* @mime={{this.mime}}
|
||||
* @extension={{this.extension}}
|
||||
* @stringify={{true}}
|
||||
* >
|
||||
* <Icon @name="download" />
|
||||
* Download
|
||||
* </DownloadButton>
|
||||
* />
|
||||
* ```
|
||||
* @param {string} [filename] - name of file that prefixes the ISO timestamp generated at download
|
||||
* @param {string} [data] - data to download
|
||||
@@ -35,6 +33,12 @@ import { assert } from '@ember/debug';
|
||||
* @param {string} [extension='txt'] - file extension, the download service uses this to determine the mimetype
|
||||
* @param {boolean} [stringify=false] - argument to stringify the data before passing to the File constructor
|
||||
* @param {callback} [onSuccess] - callback from parent to invoke if download is successful
|
||||
* @param {boolean} [hideIcon=false] - renders the 'download' icon by default, pass true to hide (ex: when download button appears in a dropdown)
|
||||
* * HDS ARGS https://helios.hashicorp.design/components/button?tab=code
|
||||
* @param {string} [text="Download"] - button text, defaults to 'Download'
|
||||
* @param {string} [color] - HDS default is primary, but there are four color options: primary, secondary, tertiary, and critical.
|
||||
* @param {string} [iconPosition="leading"] - icon position, 'leading' (HDS default) or 'trailing'
|
||||
* @param {boolean} [isIconOnly] - button only renders an icon, no text
|
||||
*/
|
||||
|
||||
export default class DownloadButton extends Component {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
@text="Copy"
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{@value}}
|
||||
class="transparent icon-only is-paddingless"
|
||||
class="transparent has-padding-xxs"
|
||||
data-test-copy-button
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
@@ -39,14 +39,20 @@
|
||||
@text="Copy"
|
||||
@isIconOnly={{true}}
|
||||
@textToCopy={{@value}}
|
||||
class="transparent icon-only is-paddingless"
|
||||
class="transparent has-padding-xxs"
|
||||
data-test-copy-button
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @allowDownload}}
|
||||
<button type="button" class="button download-button" {{on "click" (fn (mut this.modalOpen) true)}}>
|
||||
<Icon data-test-download-icon @name="download" />
|
||||
</button>
|
||||
<Hds::Button
|
||||
@text="Download secret value"
|
||||
@icon="download"
|
||||
@isIconOnly={{true}}
|
||||
@color="tertiary"
|
||||
class="has-padding-xxs"
|
||||
data-test-download-icon
|
||||
{{on "click" (fn (mut this.modalOpen) true)}}
|
||||
/>
|
||||
{{/if}}
|
||||
<button
|
||||
onclick={{this.toggleMask}}
|
||||
@@ -61,31 +67,25 @@
|
||||
</div>
|
||||
|
||||
{{! CONFIRM DOWNLOAD MODAL }}
|
||||
{{#if @allowDownload}}
|
||||
<Modal
|
||||
@title="Download secret value?"
|
||||
@onClose={{action (mut this.modalOpen) false}}
|
||||
@isActive={{this.modalOpen}}
|
||||
@type="warning"
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
{{#if this.modalOpen}}
|
||||
<Hds::Modal @color="warning" id="confirm-download-modal" @onClose={{fn (mut this.modalOpen) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Download secret value?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
This download is
|
||||
<strong>unencrypted</strong>. Are you sure you want to download this secret data as plaintext?
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<DownloadButton
|
||||
class="button is-primary"
|
||||
@filename={{or @name "secret-value"}}
|
||||
@data={{@value}}
|
||||
@stringify={{true}}
|
||||
aria-label="Download secret value"
|
||||
@onSuccess={{fn (mut this.modalOpen) false}}
|
||||
>
|
||||
Download
|
||||
</DownloadButton>
|
||||
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<DownloadButton
|
||||
@filename={{or @name "secret-value"}}
|
||||
@data={{@value}}
|
||||
@stringify={{true}}
|
||||
@onSuccess={{fn (mut this.modalOpen) false}}
|
||||
/>
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -1,37 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<EmberWormhole @to="modal-wormhole">
|
||||
<div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" data-test-modal-div ...attributes>
|
||||
<div class="modal-background" role="button" {{on "click" @onClose}} data-test-modal-background={{@title}}></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<h2 class="modal-card-title title is-5" data-test-modal-title>
|
||||
{{#if this.glyph}}
|
||||
<Icon
|
||||
class={{this.glyph.glyphClass}}
|
||||
aria-hidden="true"
|
||||
@name={{this.glyph.glyph}}
|
||||
data-test-modal-glyph={{this.glyph.glyph}}
|
||||
/>
|
||||
{{/if}}
|
||||
<span class={{if this.glyph "has-left-padding-xs"}}>{{capitalize @title}}</span>
|
||||
</h2>
|
||||
{{#if this.showCloseButton}}
|
||||
<button
|
||||
type="button"
|
||||
class="icon-button has-text-grey"
|
||||
aria-label="close"
|
||||
{{on "click" @onClose}}
|
||||
data-test-modal-close-button
|
||||
>
|
||||
<Icon @name="x-circle" class="close-icon" />
|
||||
</button>
|
||||
{{/if}}
|
||||
</header>
|
||||
{{yield}}
|
||||
</div>
|
||||
</div>
|
||||
</EmberWormhole>
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { messageTypes } from 'core/helpers/message-types';
|
||||
/**
|
||||
* @module Modal
|
||||
* Modal components are used to overlay content on top of the page. Has a darkened background,
|
||||
* a title, and in order to close it you must pass an onClose function.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <Modal
|
||||
* @title="Export attribution data"
|
||||
* @type="info"
|
||||
* @isActive={{this.showModal}}
|
||||
* @showCloseButton={{true}}
|
||||
* @onClose={{action (mut this.showModal) false}}
|
||||
* >
|
||||
* Whatever content pops up when the modal isActive!
|
||||
* </Modal>
|
||||
* ```
|
||||
* @callback onClose
|
||||
* @param {onClose} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown).
|
||||
* @param {boolean} isActive=false - whether or not modal displays
|
||||
* @param {string} [title] - This text shows up in the header section of the modal. Only the first word should be capitalized.
|
||||
* @param {boolean} [showCloseButton=false] - controls whether the close button in the top right corner shows.
|
||||
* @param {string} [type=null] - The header styling based on type passed into the message-types helper.
|
||||
*/
|
||||
|
||||
export default class ModalComponent extends Component {
|
||||
get isActive() {
|
||||
return this.args.isActive || false;
|
||||
}
|
||||
|
||||
get showCloseButton() {
|
||||
return this.args.showCloseButton || false;
|
||||
}
|
||||
|
||||
get glyph() {
|
||||
if (!this.args.type) {
|
||||
return null;
|
||||
}
|
||||
return messageTypes([this.args.type]);
|
||||
}
|
||||
|
||||
get modalClass() {
|
||||
if (!this.args.type) {
|
||||
return 'modal';
|
||||
}
|
||||
return 'modal ' + messageTypes([this.args.type]).class;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,14 @@
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<JsonEditor @value={{get this.policyTemplates @policyType}} @mode="ruby" @readOnly={{true}} @showToolbar={{true}} />
|
||||
<JsonEditor
|
||||
@value={{get this.policyTemplates @policyType}}
|
||||
@mode="ruby"
|
||||
@readOnly={{true}}
|
||||
@showToolbar={{true}}
|
||||
{{! Passed to copy button }}
|
||||
@container={{@container}}
|
||||
/>
|
||||
<div class="has-bottom-margin-m has-top-padding-s">
|
||||
<p>
|
||||
More information about
|
||||
|
||||
@@ -14,28 +14,11 @@ import Component from '@glimmer/component';
|
||||
* @example
|
||||
* <PolicyExample
|
||||
* @policyType={{@model.policyType}}
|
||||
* @container="#search-select-modal"
|
||||
* />
|
||||
*
|
||||
* @example (in modal)
|
||||
* <Modal
|
||||
* @onClose={{fn (mut this.showTemplateModal) false}}
|
||||
* @isActive={{this.showTemplateModal}}
|
||||
* >
|
||||
* <section class="modal-card-body">
|
||||
* {{! code-mirror modifier does not render value initially until focus event fires }}
|
||||
* {{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }}
|
||||
* {{#if this.showTemplateModal}}
|
||||
* <PolicyExample @policyType={{@model.policyType}}/>
|
||||
* {{/if}}
|
||||
* </section>
|
||||
* <div class="modal-card-head has-border-top-light">
|
||||
* <button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
|
||||
* Close
|
||||
* </button>
|
||||
* </div>
|
||||
* </Modal>
|
||||
* ```
|
||||
* @param {string} policyType - policy type to decide which template to render; can either be "acl" or "rgp"
|
||||
* @param {string} container - selector for the container the example renders inside, passed to the copy button in JsonEditor
|
||||
*/
|
||||
|
||||
export default class PolicyExampleComponent extends Component {
|
||||
|
||||
@@ -83,32 +83,24 @@
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{! wait until user has selected 'create a new item' before rendering modal}}
|
||||
{{#if this.nameInput}}
|
||||
<Modal
|
||||
@title="Create new {{singularize @id}}"
|
||||
@onClose={{action (mut this.showModal) false}}
|
||||
@isActive={{this.showModal}}
|
||||
@type="info"
|
||||
@showCloseButton={{false}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
{{#if this.showModal}}
|
||||
<Hds::Modal id="search-select-modal" @onClose={{fn (mut this.showModal) false}} as |M|>
|
||||
<M.Header data-test-modal-title>
|
||||
Create new
|
||||
{{singularize @id}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
{{#if @modalSubtext}}
|
||||
<p class="has-bottom-margin-s" data-test-modal-subtext>
|
||||
{{@modalSubtext}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{#if (has-block)}}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
{{! dynamically render template from modal-form/ folder}}
|
||||
{{! form must receive an @onSave and @onCancel arg that executes the callback}}
|
||||
{{component @modalFormTemplate nameInput=this.nameInput onSave=this.resetModal onCancel=this.resetModal}}
|
||||
{{/if}}
|
||||
</section>
|
||||
</Modal>
|
||||
{{! dynamically render template from modal-form/ folder}}
|
||||
{{! form must receive an @onSave and @onCancel arg that executes the callback}}
|
||||
{{component @modalFormTemplate nameInput=this.nameInput onSave=this.resetModal onCancel=this.resetModal}}
|
||||
</M.Body>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -43,6 +43,7 @@
|
||||
@options={{this.dropdownOptions}}
|
||||
@onChange={{this.selectOrCreate}}
|
||||
@placeholderComponent={{component "search-select-placeholder"}}
|
||||
@renderInPlace={{@renderInPlace}}
|
||||
@verticalPosition="below"
|
||||
@disabled={{@disabled}}
|
||||
as |option|
|
||||
|
||||
@@ -53,6 +53,7 @@ import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-ut
|
||||
* @param {string} [wildcardLabel] - string (singular) for rendering label tag beside a wildcard selection (i.e. 'role*'), for the number of items it includes, e.g. @wildcardLabel="role" -> "includes 4 roles"
|
||||
* @param {string} [placeholder] - text you wish to replace the default "search" with
|
||||
* @param {boolean} [displayInherit=false] - if you need the search select component to display inherit instead of box.
|
||||
* @param {boolean} [renderInPlace] - pass `true` when power select renders in a modal
|
||||
* @param {function} [renderInfoTooltip] - receives each inputValue string and list of dropdownOptions as args, so parent can determine when to render a tooltip beside a selectedOption and the tooltip text. see 'oidc/provider-form.js'
|
||||
* @param {boolean} [disabled] - if true sets the disabled property on the ember-power-select component and makes it unusable.
|
||||
*
|
||||
|
||||
@@ -2,130 +2,131 @@
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<section class="modal-card-body">
|
||||
{{#if this.encodedToken}}
|
||||
<p class="has-bottom-margin-l" data-test-dr-token-flow-step="show-token">
|
||||
Below is the process and the values necessary to generate your operation token. Read the instructions carefully!
|
||||
</p>
|
||||
<div class="has-bottom-margin-m">
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="field-title">
|
||||
Encoded operation token
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
This is a one-time token that will be used to generate the operation token. Please save it.
|
||||
</p>
|
||||
<Hds::Copy::Snippet @textToCopy={{this.encodedToken}} data-test-shamir-encoded-token />
|
||||
</div>
|
||||
{{#if this.otp}}
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="field-title">
|
||||
One time password (OTP)
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
This OTP will be used to decode the generated operation token. Please save it.
|
||||
</p>
|
||||
<Hds::Copy::Snippet @textToCopy={{this.otp}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="field-title">
|
||||
DR operation token command
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
{{#if this.otp}}
|
||||
This command contains both the encoded token and the OTP. It should be executed on the secondary cluster in order
|
||||
to generate the operation token.
|
||||
{{else}}
|
||||
This command requires the OTP saved earlier. It should be executed on the secondary cluster in order to generate
|
||||
the operation token.
|
||||
{{/if}}
|
||||
</p>
|
||||
{{! template-lint-disable quotes }}
|
||||
{{#let
|
||||
(if
|
||||
this.otp
|
||||
(concat 'vault operator generate-root -dr-token -otp="' this.otp '" -decode="' this.encodedToken '"')
|
||||
(concat 'vault operator generate-root -dr-token -otp="<enter your otp here>" -decode="' this.encodedToken '"')
|
||||
)
|
||||
as |cmd|
|
||||
}}
|
||||
<CodeSnippet @codeBlock={{cmd}} />
|
||||
{{/let}}
|
||||
{{! template-lint-enable quotes }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="button is-primary" {{on "click" this.onCancelClose}}>
|
||||
Clear & Close
|
||||
</button>
|
||||
</div>
|
||||
{{else if this.started}}
|
||||
<Shamir::Form
|
||||
@action={{@action}}
|
||||
@progress={{this.progress}}
|
||||
@threshold={{this.threshold}}
|
||||
@errors={{this.errors}}
|
||||
@inputLabel="Root key portion"
|
||||
@buttonText="Generate token"
|
||||
@onSubmit={{this.onSubmitKey}}
|
||||
@otp={{this.otp}}
|
||||
@alwaysShowProgress={{true}}
|
||||
data-test-dr-token-flow-step="shamir"
|
||||
>
|
||||
<p>Generate an operation token by entering a portion of the
|
||||
<strong>primary's root key</strong>. Once all portions are entered, the generated token may be used to manage your
|
||||
secondary Disaster Recovery cluster.
|
||||
{{! THIS COMPONENT RENDERS INSIDE A MODAL (must pass @container to copy buttons) }}
|
||||
{{#if this.encodedToken}}
|
||||
<p class="has-bottom-margin-l" data-test-dr-token-flow-step="show-token">
|
||||
Below is the process and the values necessary to generate your operation token. Read the instructions carefully!
|
||||
</p>
|
||||
<div class="has-bottom-margin-m">
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="has-text-weight-bold is-size-7">
|
||||
Encoded operation token
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
This is a one-time token that will be used to generate the operation token. Please save it.
|
||||
</p>
|
||||
</Shamir::Form>
|
||||
{{else if this.generateWithPGP}}
|
||||
<ChoosePgpKeyForm
|
||||
@onCancel={{fn (mut this.generateWithPGP) false}}
|
||||
@onSubmit={{this.usePgpKey}}
|
||||
@formText={{this.pgpText.form}}
|
||||
@confirmText={{this.pgpText.confirm}}
|
||||
@buttonText="Generate operation token"
|
||||
data-test-dr-token-flow-step="choose-pgp"
|
||||
/>
|
||||
{{else}}
|
||||
{{! Generate token flow not started }}
|
||||
<form
|
||||
{{on "submit" this.startGenerate}}
|
||||
id="shamir"
|
||||
aria-label="shamir generate form"
|
||||
data-test-dr-token-flow-step="begin"
|
||||
>
|
||||
<MessageError @errors={{this.errors}} />
|
||||
<div class="has-bottom-margin-m" data-test-shamir-modal-body>
|
||||
<p>
|
||||
Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If
|
||||
you'd like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin
|
||||
generation of the operation token.
|
||||
<Hds::Copy::Snippet @textToCopy={{this.encodedToken}} @container="#shamir-flow-modal" data-test-shamir-encoded-token />
|
||||
</div>
|
||||
{{#if this.otp}}
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="has-text-weight-bold is-size-7">
|
||||
One time password (OTP)
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
This OTP will be used to decode the generated operation token. Please save it.
|
||||
</p>
|
||||
<Hds::Copy::Snippet @textToCopy={{this.otp}} @container="#shamir-flow-modal" />
|
||||
</div>
|
||||
<div class="field is-grouped is-flex-center">
|
||||
<div class="control is-flex-row">
|
||||
<button type="button" class="link" {{on "click" (fn (mut this.generateWithPGP) true)}} data-test-use-pgp-key-cta>
|
||||
Provide PGP Key
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<span class="has-side-padding-s">
|
||||
or
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary" data-test-generate-token-cta>
|
||||
Generate operation token
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="has-bottom-margin-xl">
|
||||
<h4 class="has-text-weight-bold is-size-7">
|
||||
DR operation token command
|
||||
</h4>
|
||||
<p class="help has-text-grey has-bottom-margin-xs">
|
||||
{{#if this.otp}}
|
||||
This command contains both the encoded token and the OTP. It should be executed on the secondary cluster in order
|
||||
to generate the operation token.
|
||||
{{else}}
|
||||
This command requires the OTP saved earlier. It should be executed on the secondary cluster in order to generate
|
||||
the operation token.
|
||||
{{/if}}
|
||||
</p>
|
||||
{{! template-lint-disable quotes }}
|
||||
{{#let
|
||||
(if
|
||||
this.otp
|
||||
(concat 'vault operator generate-root -dr-token -otp="' this.otp '" -decode="' this.encodedToken '"')
|
||||
(concat 'vault operator generate-root -dr-token -otp="<enter your otp here>" -decode="' this.encodedToken '"')
|
||||
)
|
||||
as |cmd|
|
||||
}}
|
||||
<CodeSnippet @codeBlock={{cmd}} @container="#shamir-flow-modal" />
|
||||
{{/let}}
|
||||
{{! template-lint-enable quotes }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Hds::Button {{on "click" this.onCancelClose}} @text="Clear & Close" />
|
||||
</div>
|
||||
{{else if this.started}}
|
||||
<Shamir::Form
|
||||
@action={{@action}}
|
||||
@progress={{this.progress}}
|
||||
@threshold={{this.threshold}}
|
||||
@errors={{this.errors}}
|
||||
@inputLabel="Root key portion"
|
||||
@buttonText="Generate token"
|
||||
@onSubmit={{this.onSubmitKey}}
|
||||
@otp={{this.otp}}
|
||||
@alwaysShowProgress={{true}}
|
||||
data-test-dr-token-flow-step="shamir"
|
||||
>
|
||||
<p>Generate an operation token by entering a portion of the
|
||||
<strong>primary's root key</strong>. Once all portions are entered, the generated token may be used to manage your
|
||||
secondary Disaster Recovery cluster.
|
||||
</p>
|
||||
</Shamir::Form>
|
||||
{{else if this.generateWithPGP}}
|
||||
<ChoosePgpKeyForm
|
||||
@onCancel={{fn (mut this.generateWithPGP) false}}
|
||||
@onSubmit={{this.usePgpKey}}
|
||||
@formText={{this.pgpText.form}}
|
||||
@confirmText={{this.pgpText.confirm}}
|
||||
@buttonText="Generate operation token"
|
||||
data-test-dr-token-flow-step="choose-pgp"
|
||||
/>
|
||||
{{else}}
|
||||
{{! Generate token flow not started }}
|
||||
<form
|
||||
{{on "submit" this.startGenerate}}
|
||||
id="shamir"
|
||||
aria-label="shamir generate form"
|
||||
data-test-dr-token-flow-step="begin"
|
||||
>
|
||||
<MessageError @errors={{this.errors}} />
|
||||
<div class="has-bottom-margin-m">
|
||||
<p>
|
||||
Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If you'd
|
||||
like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin generation
|
||||
of the operation token.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-grouped is-flex-center">
|
||||
<div class="control is-flex-row">
|
||||
<Hds::Button
|
||||
@color="tertiary"
|
||||
@icon="key"
|
||||
{{on "click" (fn (mut this.generateWithPGP) true)}}
|
||||
data-test-use-pgp-key-cta
|
||||
@text="Provide PGP Key"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button type="button" class="button is-secondary" {{on "click" this.onCancelClose}} data-test-shamir-modal-cancel-button>
|
||||
{{if this.encodedToken "Close" "Cancel"}}
|
||||
</button>
|
||||
</footer>
|
||||
<div class="control">
|
||||
<span class="has-side-padding-s">
|
||||
or
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<Hds::Button type="submit" data-test-generate-token-cta @text="Generate operation token" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
<hr class="has-background-gray-100" />
|
||||
|
||||
<Hds::Button
|
||||
@color="secondary"
|
||||
{{on "click" this.onCancelClose}}
|
||||
data-test-shamir-modal-cancel-button
|
||||
@text={{if this.encodedToken "Close" "Cancel"}}
|
||||
/>
|
||||
@@ -21,7 +21,7 @@
|
||||
<h4 class="hds-alert__title hds-font-weight-semibold">
|
||||
One Time Password (otp)
|
||||
</h4>
|
||||
<Hds::Copy::Snippet data-test-otp @textToCopy={{@otp}} @color="secondary" />
|
||||
<Hds::Copy::Snippet data-test-otp @textToCopy={{@otp}} @color="secondary" @container="#shamir-flow-modal" />
|
||||
</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
|
||||
@@ -37,11 +37,6 @@ export const MESSAGE_TYPES = {
|
||||
glyph: 'loading',
|
||||
text: 'Loading',
|
||||
},
|
||||
rotation: {
|
||||
class: 'is-info',
|
||||
glyphClass: 'has-text-grey',
|
||||
glyph: 'rotate-cw',
|
||||
},
|
||||
};
|
||||
|
||||
export function messageTypes([type]) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-replication-action-trigger
|
||||
data-test-replication-action-trigger="demote"
|
||||
>
|
||||
Demote
|
||||
</button>
|
||||
@@ -30,7 +30,6 @@
|
||||
@title="Demote to secondary?"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@buttonClass="is-primary"
|
||||
@confirmText={{this.model.replicationModeForDisplay}}
|
||||
@toConfirmMsg="demoting this cluster"
|
||||
@onConfirm={{action "onSubmit" "demote" this.model.replicationAttrs.modeForUrl}}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
type="button"
|
||||
class="button is-danger"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-replication-action-trigger
|
||||
data-test-replication-action-trigger="disable"
|
||||
>
|
||||
Disable Replication
|
||||
</button>
|
||||
|
||||
@@ -25,16 +25,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
@title="Generate operation token"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
{{#if this.isModalActive}}
|
||||
{{! Wrapped in if statement so the Shamir constructor fires on modal open }}
|
||||
<Shamir::DrTokenFlow @action="generate-dr-operation-token" @onCancel={{action (mut this.isModalActive) false}} />
|
||||
{{#if this.isModalActive}}
|
||||
<Hds::Modal id="shamir-flow-modal" @color="warning" @onClose={{action (mut this.isModalActive) false}} as |M|>
|
||||
<M.Header>
|
||||
Generate operation token
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<Shamir::DrTokenFlow @action="generate-dr-operation-token" @onCancel={{action (mut this.isModalActive) false}} />
|
||||
</M.Body>
|
||||
{{! Section & Footer is in child component since the form must do side effects on cancel }}
|
||||
{{/if}}
|
||||
</Modal>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -20,107 +20,106 @@
|
||||
type="button"
|
||||
class="button is-tertiary"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-replication-action-trigger
|
||||
data-test-replication-action-trigger="promote"
|
||||
>
|
||||
Promote
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
@title="Promote cluster?"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
{{#if (eq this.replicationMode "dr")}}
|
||||
<p class="has-bottom-margin-m">
|
||||
To promote this DR Replication Secondary to a primary, enter the DR Operation token.
|
||||
</p>
|
||||
{{/if}}
|
||||
<p class="has-bottom-margin-m">
|
||||
Vault Replication is not designed for active-active usage. Enabling two primaries should never be done, as it can lead
|
||||
to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote it
|
||||
before promoting a secondary.
|
||||
</p>
|
||||
|
||||
<div data-test-promote-dr-inputs>
|
||||
{{#if this.isModalActive}}
|
||||
<Hds::Modal id="replication-promote-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Promote cluster?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
{{#if (eq this.replicationMode "dr")}}
|
||||
<div class="field is-borderless">
|
||||
<label for="dr_operation_token_promote" class="is-label is-size-6">
|
||||
DR Operation Token
|
||||
<p class="has-bottom-margin-m">
|
||||
To promote this DR Replication Secondary to a primary, enter the DR Operation token.
|
||||
</p>
|
||||
{{/if}}
|
||||
<p class="has-bottom-margin-m">
|
||||
Vault Replication is not designed for active-active usage. Enabling two primaries should never be done, as it can
|
||||
lead to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote
|
||||
it before promoting a secondary.
|
||||
</p>
|
||||
|
||||
<div data-test-promote-dr-inputs>
|
||||
{{#if (eq this.replicationMode "dr")}}
|
||||
<div class="field is-borderless">
|
||||
<label for="dr_operation_token_promote" class="is-label is-size-6">
|
||||
DR Operation Token
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
class="input"
|
||||
id="dr_operation_token_promote"
|
||||
name="dr_operation_token_promote"
|
||||
@value={{this.dr_operation_token_promote}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field">
|
||||
<label for="primary_cluster_addr" class="is-label is-size-6">
|
||||
Primary cluster address
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
class="input"
|
||||
id="dr_operation_token_promote"
|
||||
name="dr_operation_token_promote"
|
||||
@value={{this.dr_operation_token_promote}}
|
||||
id="primary_cluster_addr"
|
||||
name="primary_cluster_addr"
|
||||
@value={{this.primary_cluster_addr}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field">
|
||||
<label for="primary_cluster_addr" class="is-label is-size-6">
|
||||
Primary cluster address
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input class="input" id="primary_cluster_addr" name="primary_cluster_addr" @value={{this.primary_cluster_addr}} />
|
||||
</div>
|
||||
<p class="help">
|
||||
Overrides the cluster address that the primary gives to secondary nodes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="b-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="forcePromote"
|
||||
class="styled"
|
||||
checked={{this.force}}
|
||||
onchange={{action (mut this.force) value="target.checked"}}
|
||||
/>
|
||||
<label for="forcePromote" class="is-label is-size-6">
|
||||
Force promotion of this cluster
|
||||
</label>
|
||||
<p>
|
||||
Promote the cluster even if certain safety checks fail. This could result in data loss of data isn't fully
|
||||
replicated
|
||||
<p class="help">
|
||||
Overrides the cluster address that the primary gives to secondary nodes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="b-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="forcePromote"
|
||||
class="styled"
|
||||
checked={{this.force}}
|
||||
onchange={{action (mut this.force) value="target.checked"}}
|
||||
/>
|
||||
<label for="forcePromote" class="is-label is-size-6">
|
||||
Force promotion of this cluster
|
||||
</label>
|
||||
<p>
|
||||
Promote the cluster even if certain safety checks fail. This could result in data loss of data isn't fully
|
||||
replicated
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}}
|
||||
onclick={{action
|
||||
"onSubmit"
|
||||
"promote"
|
||||
this.model.replicationAttrs.modeForUrl
|
||||
(hash
|
||||
dr_operation_token_promote=this.dr_operation_token_promote
|
||||
primary_cluster_addr=this.primary_cluster_addr
|
||||
force=this.force
|
||||
)
|
||||
}}
|
||||
data-test-promote-confirm-button
|
||||
>
|
||||
Promote
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) false}}
|
||||
data-test-promote-cancel-button
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}}
|
||||
{{on
|
||||
"click"
|
||||
(fn
|
||||
this.onSubmit
|
||||
"promote"
|
||||
this.model.replicationAttrs.modeForUrl
|
||||
(hash
|
||||
dr_operation_token_promote=this.dr_operation_token_promote
|
||||
primary_cluster_addr=this.primary_cluster_addr
|
||||
force=this.force
|
||||
)
|
||||
)
|
||||
}}
|
||||
data-test-confirm-button
|
||||
@text="Promote"
|
||||
/>
|
||||
<Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} data-test-promote-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -18,36 +18,28 @@
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-replication-action-trigger
|
||||
data-test-replication-action-trigger="recover"
|
||||
>
|
||||
Recover
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
@title="Begin recovery?"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<p>
|
||||
If replication is in an adverse state, we can begin recovery. This will attempt to recover to continue syncing.
|
||||
</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button type="button" class="button is-primary" onclick={{action "onSubmit" "recover"}} data-test-recover-confirm-button>
|
||||
Recover
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) false}}
|
||||
data-test-recover-cancel-button
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.isModalActive}}
|
||||
<Hds::Modal id="replication-recover-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Begin recovery?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
If replication is in an adverse state, we can begin recovery. This will attempt to recover to continue syncing.
|
||||
</p>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Recover" {{on "click" (fn this.onSubmit "recover")}} data-test-confirm-button />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-recover-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -17,41 +17,33 @@
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-replication-action-trigger
|
||||
data-test-replication-action-trigger="reindex"
|
||||
>
|
||||
Reindex
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
@title="Begin reindex?"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<p class="has-bottom-margin-m">
|
||||
Reindexing can cause a very long delay depending on the number and size of objects in the data store.
|
||||
{{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}}
|
||||
</p>
|
||||
<p>
|
||||
Progress will be shown, and you will
|
||||
{{if this.model.replicationAttrs.isPrimary "not"}}
|
||||
be able to use Vault during this time.
|
||||
</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button type="button" class="button is-primary" onclick={{action "onSubmit" "reindex"}} data-test-reindex-confirm-button>
|
||||
Reindex
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) false}}
|
||||
data-test-reindex-cancel-button
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.isModalActive}}
|
||||
<Hds::Modal id="replication-reindex-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Begin reindex?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="has-bottom-margin-m">
|
||||
Reindexing can cause a very long delay depending on the number and size of objects in the data store.
|
||||
{{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}}
|
||||
</p>
|
||||
<p>
|
||||
Progress will be shown, and you will
|
||||
{{if this.model.replicationAttrs.isPrimary "not"}}
|
||||
be able to use Vault during this time.
|
||||
</p>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Reindex" {{on "click" (fn this.onSubmit "reindex")}} data-test-confirm-button />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-reindex-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -18,117 +18,111 @@
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) true}}
|
||||
data-test-update-primary-action-trigger
|
||||
data-test-replication-action-trigger="update-primary"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
@title="Update primary"
|
||||
@onClose={{action (mut this.isModalActive) false}}
|
||||
@isActive={{this.isModalActive}}
|
||||
@type="warning"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<p class="has-bottom-margin-m">
|
||||
Use a secondary activation token to change this secondary’s assigned primary. This does not wipe all data in the
|
||||
cluster.
|
||||
</p>
|
||||
{{#if this.isModalActive}}
|
||||
<Hds::Modal id="replication-update-primary-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Update primary
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="has-bottom-margin-m">
|
||||
Use a secondary activation token to change this secondary’s assigned primary. This does not wipe all data in the
|
||||
cluster.
|
||||
</p>
|
||||
|
||||
<div data-test-update-primary-inputs>
|
||||
{{#if (eq this.replicationMode "dr")}}
|
||||
<div data-test-update-primary-inputs>
|
||||
{{#if (eq this.replicationMode "dr")}}
|
||||
<div class="field">
|
||||
<label for="dr_operation_token_primary" class="is-label">
|
||||
DR operation token
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
class="input"
|
||||
id="dr_operation_token_primary"
|
||||
name="dr_operation_token_primary"
|
||||
@value={{this.dr_operation_token_primary}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field">
|
||||
<label for="dr_operation_token_primary" class="is-label">
|
||||
DR operation token
|
||||
<label for="secondary-token" class="is-label">
|
||||
Secondary activation token
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
class="input"
|
||||
id="dr_operation_token_primary"
|
||||
name="dr_operation_token_primary"
|
||||
@value={{this.dr_operation_token_primary}}
|
||||
/>
|
||||
<Textarea @value={{this.token}} id="secondary-token" name="secondary-token" class="textarea" />
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field">
|
||||
<label for="secondary-token" class="is-label">
|
||||
Secondary activation token
|
||||
</label>
|
||||
<div class="control">
|
||||
<Textarea @value={{this.token}} id="secondary-token" name="secondary-token" class="textarea" />
|
||||
<div class="field">
|
||||
<label for="primary_api_addr" class="is-label">
|
||||
Primary API address
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input class="input" @value={{this.primary_api_addr}} id="primary_api_addr" name="primary_api_addr" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Set this to the API address (normal Vault address) to override the value embedded in the token.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="ca_file" class="is-label">
|
||||
CA file
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input @value={{this.ca_file}} id="ca_file" name="ca_file" class="input" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Specifies the path to a CA root file (PEM format) that the secondary can use when unwrapping the token from the
|
||||
primary.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="ca_path" class="is-label">
|
||||
CA path
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input @value={{this.ca_path}} id="ca_path" name="ca_file" class="input" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Specifies the path to a CA root directory containing PEM-format files that the secondary can use when unwrapping
|
||||
the token from the primary.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="primary_api_addr" class="is-label">
|
||||
Primary API address
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input class="input" @value={{this.primary_api_addr}} id="primary_api_addr" name="primary_api_addr" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Set this to the API address (normal Vault address) to override the value embedded in the token.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="ca_file" class="is-label">
|
||||
CA file
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input @value={{this.ca_file}} id="ca_file" name="ca_file" class="input" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Specifies the path to a CA root file (PEM format) that the secondary can use when unwrapping the token from the
|
||||
primary.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="ca_path" class="is-label">
|
||||
CA path
|
||||
<em class="is-optional">(optional)</em>
|
||||
</label>
|
||||
<div class="control">
|
||||
<Input @value={{this.ca_path}} id="ca_path" name="ca_file" class="input" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Specifies the path to a CA root directory containing PEM-format files that the secondary can use when unwrapping
|
||||
the token from the primary.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
onclick={{action
|
||||
"onSubmit"
|
||||
"update-primary"
|
||||
this.model.replicationAttrs.modeForUrl
|
||||
(hash
|
||||
token=this.token
|
||||
dr_operation_token_primary=this.dr_operation_token_primary
|
||||
primary_api_addr=this.primary_api_addr
|
||||
ca_path=this.ca_path
|
||||
ca_file=this.ca_file
|
||||
)
|
||||
}}
|
||||
data-test-confirm-action-trigger
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
onclick={{action (mut this.isModalActive) false}}
|
||||
data-test-update-primary-cancel-button
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
{{on
|
||||
"click"
|
||||
(fn
|
||||
this.onSubmit
|
||||
"update-primary"
|
||||
this.model.replicationAttrs.modeForUrl
|
||||
(hash
|
||||
token=this.token
|
||||
dr_operation_token_primary=this.dr_operation_token_primary
|
||||
primary_api_addr=this.primary_api_addr
|
||||
ca_path=this.ca_path
|
||||
ca_file=this.ca_file
|
||||
)
|
||||
)
|
||||
}}
|
||||
data-test-confirm-button
|
||||
@text="Update"
|
||||
/>
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-update-primary-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'core/components/modal';
|
||||
@@ -24,7 +24,6 @@
|
||||
"ember-router-helpers": "*",
|
||||
"ember-svg-jar": "*",
|
||||
"ember-truth-helpers": "*",
|
||||
"ember-wormhole": "*",
|
||||
"escape-string-regexp": "*",
|
||||
"@hashicorp/ember-flight-icons": "*",
|
||||
"@hashicorp/flight-icons": "*",
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
<ToolbarActions>
|
||||
{{#if this.model}}
|
||||
<DownloadButton
|
||||
class="toolbar-link"
|
||||
class="toolbar-button"
|
||||
@color="secondary"
|
||||
@filename={{concat this.model.ca.id "-ca"}}
|
||||
@data={{this.model.ca.caPem}}
|
||||
@extension="pem"
|
||||
>
|
||||
Download CA cert
|
||||
<Chevron @isButton={{true}} />
|
||||
</DownloadButton>
|
||||
@text="Download CA cert"
|
||||
/>
|
||||
{{/if}}
|
||||
<ToolbarLink @route="configure" data-test-kmip-link-configure>
|
||||
Configure
|
||||
|
||||
@@ -103,26 +103,21 @@
|
||||
</div>
|
||||
|
||||
{{#if this.showConfirm}}
|
||||
<Modal
|
||||
@title="Edit configuration"
|
||||
@type="warning"
|
||||
@isActive={{this.showConfirm}}
|
||||
@showCloseButton={{true}}
|
||||
@onClose={{fn (mut this.showConfirm) false}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Modal id="kubernetes-edit-config-modal" @onClose={{fn (mut this.showConfirm) false}} @color="warning" as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Edit configuration
|
||||
</M.Header>
|
||||
<M.Body data-test-edit-config-body>
|
||||
<p>
|
||||
Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are
|
||||
you sure?
|
||||
</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button data-test-config-confirm type="button" class="button is-primary" {{on "click" (perform this.save)}}>
|
||||
Confirm
|
||||
</button>
|
||||
<button type="button" class="button" onclick={{fn (mut this.showConfirm) false}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button data-test-config-confirm {{on "click" (perform this.save)}} @text="Confirm" />
|
||||
<Hds::Button {{on "click" F.close}} @color="secondary" @text="Cancel" />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -1,16 +1,23 @@
|
||||
<button type="button" class="toolbar-link" {{on "click" (fn (mut this.modalOpen) true)}} data-test-kv-delete={{@mode}}>
|
||||
{{yield}}
|
||||
</button>
|
||||
<Hds::Button
|
||||
@text={{or @text (capitalize @mode)}}
|
||||
@color="secondary"
|
||||
class="toolbar-button"
|
||||
{{on "click" (fn (mut this.modalOpen) true)}}
|
||||
data-test-kv-delete={{@mode}}
|
||||
/>
|
||||
|
||||
{{#if this.modalOpen}}
|
||||
<Modal
|
||||
@title={{this.modalDisplay.title}}
|
||||
<Hds::Modal
|
||||
id="kv-delete-modal-{{@mode}}"
|
||||
@color={{this.modalDisplay.color}}
|
||||
@onClose={{fn (mut this.modalOpen) false}}
|
||||
@isActive={{this.modalOpen}}
|
||||
@type={{this.modalDisplay.type}}
|
||||
@showCloseButton={{true}}
|
||||
data-test-delete-modal
|
||||
as |M|
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<M.Header data-test-modal-title @icon="trash">
|
||||
{{this.modalDisplay.title}}
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p class="has-bottom-margin-s">
|
||||
{{this.modalDisplay.intro}}
|
||||
</p>
|
||||
@@ -45,19 +52,19 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button {{if (eq this.modalDisplay.type 'danger') 'is-danger-outlined' 'is-warning-outlined'}}"
|
||||
{{on "click" this.onDelete}}
|
||||
data-test-delete-modal-confirm
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
@text="Confirm"
|
||||
{{on "click" this.onDelete}}
|
||||
@color={{if (eq this.modalDisplay.color "critical") this.modalDisplay.color}}
|
||||
data-test-delete-modal-confirm
|
||||
/>
|
||||
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -21,6 +21,7 @@ import { assert } from '@ember/debug';
|
||||
* @param {string} mode - delete, delete-metadata, or destroy.
|
||||
* @param {object} secret - The kv/data model.
|
||||
* @param {object} [metadata] - The kv/metadata model. It is only required when mode is "delete" or "metadata-delete".
|
||||
* @param {string} [text] - Button text that renders in KV v2 toolbar, defaults to capitalize @mode
|
||||
* @param {callback} onDelete - callback function fired to handle delete event.
|
||||
*/
|
||||
|
||||
@@ -34,20 +35,20 @@ export default class KvDeleteModal extends Component {
|
||||
case 'delete':
|
||||
return {
|
||||
title: 'Delete version?',
|
||||
type: 'warning',
|
||||
color: 'warning',
|
||||
intro:
|
||||
'There are two ways to delete a version of a secret. Both delete actions can be undeleted later. How would you like to proceed?',
|
||||
};
|
||||
case 'destroy':
|
||||
return {
|
||||
title: 'Destroy version?',
|
||||
type: 'danger',
|
||||
color: 'critical',
|
||||
intro: `This action will permanently destroy Version ${this.args.version} of the secret, and the secret data cannot be read or recovered later.`,
|
||||
};
|
||||
case 'delete-metadata':
|
||||
return {
|
||||
title: 'Delete metadata and secret data?',
|
||||
type: 'danger',
|
||||
color: 'critical',
|
||||
intro:
|
||||
'This will permanently delete the metadata and versions of the secret. All version history will be removed. This cannot be undone.',
|
||||
};
|
||||
|
||||
@@ -34,14 +34,10 @@
|
||||
@metadata={{@metadata}}
|
||||
@onDelete={{this.handleDestruction}}
|
||||
@version={{this.version}}
|
||||
>
|
||||
Delete
|
||||
</KvDeleteModal>
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.showDestroy}}
|
||||
<KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}}>
|
||||
Destroy
|
||||
</KvDeleteModal>
|
||||
<KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}} />
|
||||
{{/if}}
|
||||
{{#if (or @secret.canReadData @secret.canReadMetadata @secret.canEditData)}}
|
||||
<div class="toolbar-separator"></div>
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
|
||||
<:toolbarActions>
|
||||
{{#if @secret.canDeleteMetadata}}
|
||||
<KvDeleteModal @mode="delete-metadata" @metadata={{@metadata}} @onDelete={{this.onDelete}}>
|
||||
Permanently delete
|
||||
</KvDeleteModal>
|
||||
<KvDeleteModal
|
||||
@mode="delete-metadata"
|
||||
@metadata={{@metadata}}
|
||||
@onDelete={{this.onDelete}}
|
||||
@text="Permanently delete"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @secret.canUpdateMetadata}}
|
||||
<ToolbarLink @route="secret.metadata.edit" data-test-edit-metadata>Edit metadata</ToolbarLink>
|
||||
|
||||
@@ -39,37 +39,35 @@
|
||||
</OverviewCard>
|
||||
|
||||
{{#if this.selectedStatus}}
|
||||
<Modal
|
||||
@title="Account Check-in"
|
||||
@isActive={{this.selectedStatus}}
|
||||
@showCloseButton={{true}}
|
||||
@onClose={{fn (mut this.selectedStatus) undefined}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Modal id="account-check-in-modal" @onClose={{fn (mut this.selectedStatus) undefined}} as |M|>
|
||||
<M.Header>
|
||||
Account Check-in
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
This action will check-in account
|
||||
{{this.selectedStatus.account}}
|
||||
back to the library. Do you want to proceed?
|
||||
</p>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
|
||||
disabled={{this.checkIn.isRunning}}
|
||||
data-test-check-in-confirm
|
||||
{{on "click" (perform this.checkIn)}}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button"
|
||||
disabled={{this.checkIn.isRunning}}
|
||||
{{on "click" (fn (mut this.selectedStatus) "")}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
@icon={{if this.save.isRunning "is-loading"}}
|
||||
disabled={{this.checkIn.isRunning}}
|
||||
data-test-check-in-confirm
|
||||
{{on "click" (perform this.checkIn)}}
|
||||
@text="Confirm"
|
||||
/>
|
||||
<Hds::Button
|
||||
@icon={{if this.save.isRunning "is-loading"}}
|
||||
@color="secondary"
|
||||
disabled={{this.checkIn.isRunning}}
|
||||
{{on "click" (fn (mut this.selectedStatus) "")}}
|
||||
@text="Cancel"
|
||||
/>
|
||||
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -78,14 +78,11 @@
|
||||
</form>
|
||||
|
||||
{{#if this.showRotatePrompt}}
|
||||
<Modal
|
||||
@title="Rotate your root password?"
|
||||
@type="info"
|
||||
@isActive={{this.showRotatePrompt}}
|
||||
@showCloseButton={{true}}
|
||||
@onClose={{fn (mut this.showRotatePrompt) false}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Modal id="ldap-rotate-password-modal" @onClose={{fn (mut this.showRotatePrompt) false}} as |M|>
|
||||
<M.Header @icon="info">
|
||||
Rotate your root password?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
It’s best practice to rotate the administrator (root) password immediately after the initial configuration of the
|
||||
LDAP engine. The rotation will update the password both in Vault and your directory server. Once rotated,
|
||||
@@ -95,19 +92,17 @@
|
||||
<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
|
||||
data-test-save-with-rotate
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (fn (perform this.save) null true)}}
|
||||
>
|
||||
Save and rotate
|
||||
</button>
|
||||
<button data-test-save-without-rotate type="button" class="button" {{on "click" (fn (perform this.save) null false)}}>
|
||||
Save without rotating
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button data-test-save-with-rotate @text="Save and rotate" {{on "click" (fn (perform this.save) null true)}} />
|
||||
<Hds::Button
|
||||
data-test-save-without-rotate
|
||||
@text="Save without rotating"
|
||||
@color="secondary"
|
||||
{{on "click" (fn (perform this.save) null false)}}
|
||||
/>
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -54,32 +54,23 @@
|
||||
</div>
|
||||
|
||||
{{#if this.showCheckOutPrompt}}
|
||||
<Modal
|
||||
@title="Account Check-out"
|
||||
@isActive={{this.showCheckOutPrompt}}
|
||||
@showCloseButton={{true}}
|
||||
@onClose={{fn (mut this.showCheckOutPrompt) false}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Modal id="account-check-out-modal" @onClose={{fn (mut this.showCheckOutPrompt) false}} as |M|>
|
||||
<M.Header>
|
||||
Account Check-out
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
Current generated credential’s time-to-live is set at
|
||||
{{format-duration @library.ttl}}. You can set a different limit if you’d like:
|
||||
</p>
|
||||
<br />
|
||||
<TtlPicker @label="TTL" @hideToggle={{true}} @initialValue={{@library.ttl}} @onChange={{this.setTtl}} />
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button data-test-check-out="save" type="button" class="button is-primary" {{on "click" this.checkOut}}>
|
||||
Check-out
|
||||
</button>
|
||||
<button
|
||||
data-test-check-out="cancel"
|
||||
type="button"
|
||||
class="button"
|
||||
{{on "click" (fn (mut this.showCheckOutPrompt) false)}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button data-test-check-out="save" @text="Check-out" {{on "click" this.checkOut}} />
|
||||
<Hds::Button data-test-check-out="cancel" @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -111,19 +111,18 @@
|
||||
|
||||
{{#if this.showDeleteAllIssuers}}
|
||||
<ConfirmationModal
|
||||
@title="Delete All Issuers?"
|
||||
@title="Delete all issuers?"
|
||||
@toConfirmMsg="deleting all issuers and keys."
|
||||
@buttonText="Confirm"
|
||||
@confirmText="delete-all"
|
||||
@isActive={{this.showDeleteAllIssuers}}
|
||||
@onClose={{action (mut this.showDeleteAllIssuers) false}}
|
||||
@onConfirm={{this.deleteAllIssuers}}
|
||||
>
|
||||
<section class="modal-card-custom">
|
||||
<p>
|
||||
This endpoint deletes
|
||||
<strong>all</strong>
|
||||
issuers and keys within the mount. It is highly recommended to use the individual delete operations instead. This mount
|
||||
will be unusable until new issuers and keys are provisioned.
|
||||
</section>
|
||||
</p>
|
||||
</ConfirmationModal>
|
||||
{{/if}}
|
||||
@@ -48,9 +48,9 @@
|
||||
@data={{@pem}}
|
||||
@extension="pem"
|
||||
data-test-issuer-download-type="pem"
|
||||
>
|
||||
PEM format
|
||||
</DownloadButton>
|
||||
@text="PEM format"
|
||||
@hideIcon={{true}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @der}}
|
||||
@@ -62,9 +62,9 @@
|
||||
@data={{@der}}
|
||||
@extension="der"
|
||||
data-test-issuer-download-type="der"
|
||||
>
|
||||
DER format
|
||||
</DownloadButton>
|
||||
@text="DER format"
|
||||
@hideIcon={{true}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
@@ -148,45 +148,37 @@
|
||||
{{/if}}
|
||||
|
||||
{{! ROOT ROTATION MODAL }}
|
||||
<Modal
|
||||
@type="rotation"
|
||||
@title="Rotate this root"
|
||||
@onClose={{fn (mut this.showRotationModal) false}}
|
||||
@isActive={{this.showRotationModal}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<h3 class="title is-5">Root rotation</h3>
|
||||
<p class="has-text-grey has-bottom-padding-s">
|
||||
Root rotation is an impactful process. Please be ready to ensure that the new root is properly distributed to
|
||||
end-users’ trust stores. You can also do this manually by
|
||||
<DocLink @path="/vault/docs/secrets/pki/rotation-primitives#suggested-root-rotation-procedure">
|
||||
following our documentation.
|
||||
</DocLink>
|
||||
</p>
|
||||
<h3 class="title is-5 has-top-bottom-margin">How root rotation will work</h3>
|
||||
<p class="has-text-grey">
|
||||
<ol class="has-left-margin-m has-bottom-margin-s">
|
||||
<li>The new root will be generated using defaults from the old one that you can customize.</li>
|
||||
<li>You will identify intermediates, which Vault will then cross-sign.</li>
|
||||
</ol>
|
||||
Then, you can begin re-issuing leaf certs and phase out the old root.
|
||||
</p>
|
||||
<div class="has-top-margin-l has-tall-padding">
|
||||
<img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.issuers.issuer.rotate-root")}}
|
||||
data-test-root-rotate-step-one
|
||||
>
|
||||
Generate new root
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.showRotationModal) false)}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.showRotationModal}}
|
||||
<Hds::Modal id="pki-rotate-root-modal" @size="large" @onClose={{fn (mut this.showRotationModal) false}} as |M|>
|
||||
<M.Header @icon="rotate-cw">
|
||||
Rotate this root
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h3 class="title is-5">Root rotation</h3>
|
||||
<p class="has-text-grey has-bottom-padding-s">
|
||||
Root rotation is an impactful process. Please be ready to ensure that the new root is properly distributed to
|
||||
end-users’ trust stores. You can also do this manually by
|
||||
<DocLink @path="/vault/docs/secrets/pki/rotation-primitives#suggested-root-rotation-procedure">
|
||||
following our documentation.
|
||||
</DocLink>
|
||||
</p>
|
||||
<h3 class="title is-5 has-top-bottom-margin">How root rotation will work</h3>
|
||||
<p class="has-text-grey">
|
||||
<ol class="has-left-margin-m has-bottom-margin-s">
|
||||
<li>The new root will be generated using defaults from the old one that you can customize.</li>
|
||||
<li>You will identify intermediates, which Vault will then cross-sign.</li>
|
||||
</ol>
|
||||
Then, you can begin re-issuing leaf certs and phase out the old root.
|
||||
</p>
|
||||
<div class="has-top-margin-l has-tall-padding">
|
||||
<img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" />
|
||||
</div>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Generate new root" @route="issuers.issuer.rotate-root" data-test-root-rotate-step-one />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -53,9 +53,9 @@
|
||||
@extension="pem"
|
||||
@fetchData={{fn this.fetchDataForDownload "pem"}}
|
||||
data-test-issuer-download-type="pem"
|
||||
>
|
||||
PEM format
|
||||
</DownloadButton>
|
||||
@text="PEM format"
|
||||
@hideIcon={{true}}
|
||||
/>
|
||||
</li>
|
||||
<li class="action">
|
||||
<DownloadButton
|
||||
@@ -64,9 +64,9 @@
|
||||
@extension="der"
|
||||
@fetchData={{fn this.fetchDataForDownload "der"}}
|
||||
data-test-issuer-download-type="der"
|
||||
>
|
||||
DER format
|
||||
</DownloadButton>
|
||||
@text="DER format"
|
||||
@hideIcon={{true}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -19,14 +19,13 @@
|
||||
{{/if}}
|
||||
{{#if @key.privateKey}}
|
||||
<DownloadButton
|
||||
class="toolbar-link"
|
||||
class="toolbar-button"
|
||||
@color="secondary"
|
||||
@filename="{{@key.backend}}-{{or @key.keyName 'private-key'}}"
|
||||
@data={{@key.privateKey}}
|
||||
@extension="pem"
|
||||
>
|
||||
Download private key
|
||||
<Chevron @isButton={{true}} />
|
||||
</DownloadButton>
|
||||
@text="Download private key"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @canEdit}}
|
||||
<ToolbarLink @route="keys.key.edit" @model={{@key.keyId}} data-test-pki-key-edit>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PkiPaginatedList @listRoute="keys.index" @list={{@keyModels}} @hasConfig={{@hasConfig}}>
|
||||
<:actions>
|
||||
{{#if @canImportKey}}
|
||||
<ToolbarLink @route="keys.import" @type="download" data-test-pki-key-import>
|
||||
<ToolbarLink @route="keys.import" @type="upload" data-test-pki-key-import>
|
||||
Import
|
||||
</ToolbarLink>
|
||||
{{/if}}
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
Perform manual tidy
|
||||
</ToolbarLink>
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="toolbar-link"
|
||||
<Hds::Button
|
||||
class="toolbar-button"
|
||||
@color="secondary"
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
{{on "click" (fn (mut this.tidyOptionsModal) true)}}
|
||||
data-test-pki-tidy-options-modal
|
||||
>
|
||||
Tidy
|
||||
<Icon @name="chevron-right" />
|
||||
</button>
|
||||
@text="Tidy"
|
||||
/>
|
||||
{{/if}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
@@ -101,104 +101,75 @@
|
||||
@title="Tidy status unavailable"
|
||||
@message="After the next tidy operation has been performed, information about the current or most recent tidy operation will display here."
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="link"
|
||||
<Hds::Button
|
||||
@color="tertiary"
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
{{on "click" (fn (mut this.tidyOptionsModal) true)}}
|
||||
data-test-tidy-empty-state-configure
|
||||
>
|
||||
Tidy
|
||||
</button>
|
||||
@text="Tidy"
|
||||
/>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
|
||||
{{! TIDY OPTIONS MODAL }}
|
||||
<Modal
|
||||
@title="Tidy this mount"
|
||||
@onClose={{fn (mut this.tidyOptionsModal) false}}
|
||||
@isActive={{this.tidyOptionsModal}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section aria-label="tidy-options-modal-content" class="modal-card-body">
|
||||
<h3 class="title is-5">How tidying will work</h3>
|
||||
<p class="has-text-grey has-bottom-padding-s">
|
||||
Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain
|
||||
buffer period beyond their expiration time.
|
||||
<DocLink @path="/vault/docs/secrets/pki/considerations#automate-crl-building-and-tidying">
|
||||
Documentation.
|
||||
</DocLink>
|
||||
</p>
|
||||
<p class="has-text-grey">
|
||||
<ol class="has-left-margin-m has-bottom-margin-s">
|
||||
<li>Select a tidy operation:</li>
|
||||
<ul class="bullet has-bottom-margin-xs">
|
||||
<li><strong>Automatic tidy</strong>
|
||||
periodically runs a tidy operation with saved configuration settings after waiting the specified interval
|
||||
duration between tidies
|
||||
</li>
|
||||
<li><strong>Manual tidy</strong> runs a tidy operation once</li>
|
||||
</ul>
|
||||
<li>Configure the parameters that determine how to tidy and run the operation.</li>
|
||||
</ol>
|
||||
</p>
|
||||
<div class="has-top-margin-l has-padding">
|
||||
<img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" />
|
||||
</div>
|
||||
</section>
|
||||
<footer aria-label="tidy-option-buttons" class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.tidy.auto.configure")}}
|
||||
data-test-tidy-modal-auto-button
|
||||
>
|
||||
Automatic tidy
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.tidy.manual")}}
|
||||
data-test-tidy-modal-manual-button
|
||||
>
|
||||
Manual tidy
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-secondary"
|
||||
{{on "click" (fn (mut this.tidyOptionsModal) false)}}
|
||||
data-test-tidy-modal-cancel-button
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
{{#if this.tidyOptionsModal}}
|
||||
<Hds::Modal id="pki-tidy-modal" @size="large" @onClose={{fn (mut this.tidyOptionsModal) false}} as |M|>
|
||||
<M.Header>
|
||||
Tidy this mount
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<h3 class="title is-5">How tidying will work</h3>
|
||||
<p class="has-text-grey has-bottom-padding-s">
|
||||
Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain
|
||||
buffer period beyond their expiration time.
|
||||
<DocLink @path="/vault/docs/secrets/pki/considerations#automate-crl-building-and-tidying">
|
||||
Documentation.
|
||||
</DocLink>
|
||||
</p>
|
||||
<p class="has-text-grey">
|
||||
<ol class="has-left-margin-m has-bottom-margin-s">
|
||||
<li>Select a tidy operation:</li>
|
||||
<ul class="bullet has-bottom-margin-xs">
|
||||
<li><strong>Automatic tidy</strong>
|
||||
periodically runs a tidy operation with saved configuration settings after waiting the specified interval
|
||||
duration between tidies
|
||||
</li>
|
||||
<li><strong>Manual tidy</strong> runs a tidy operation once</li>
|
||||
</ul>
|
||||
<li>Configure the parameters that determine how to tidy and run the operation.</li>
|
||||
</ol>
|
||||
</p>
|
||||
<div class="has-top-margin-l has-padding">
|
||||
<img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" />
|
||||
</div>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Automatic tidy" @route="tidy.auto.configure" data-test-tidy-modal-auto-button />
|
||||
<Hds::Button @text="Manual tidy" @route="tidy.manual" data-test-tidy-modal-manual-button />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-tidy-modal-cancel-button />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
|
||||
{{! CANCEL TIDY CONFIRMATION MODAL }}
|
||||
{{#if this.confirmCancelTidy}}
|
||||
<Modal
|
||||
@type="warning"
|
||||
@title="Cancel tidy?"
|
||||
@onClose={{fn (mut this.confirmCancelTidy) false}}
|
||||
@isActive={{this.confirmCancelTidy}}
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section aria-label="confirm-cancel-modal-content" class="modal-card-body">
|
||||
<Hds::Modal id="pki-cancel-tidy-modal" @color="warning" @onClose={{fn (mut this.confirmCancelTidy) false}} as |M|>
|
||||
<M.Header @icon="alert-triangle">
|
||||
Cancel tidy?
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
This will cancel the tidy at the next available checkpoint, which may process additional certificates between when the
|
||||
operation was marked as cancelled and when the operation stopped.
|
||||
<p class="has-top-margin-s">Click “Confirm” to cancel the running tidy operation.</p>
|
||||
</section>
|
||||
<footer aria-label="confirm-cancel-buttons" class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (perform this.cancelTidy)}}
|
||||
data-test-tidy-modal-cancel-button
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.confirmCancelTidy) false)}}>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Confirm" {{on "click" (perform this.cancelTidy)}} data-test-tidy-modal-cancel-button />
|
||||
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -26,11 +26,11 @@ const DEFAULTS = {
|
||||
|
||||
export default Controller.extend(copy(DEFAULTS, true), {
|
||||
isModalActive: false,
|
||||
isTokenCopied: false,
|
||||
expirationDate: null,
|
||||
store: service(),
|
||||
rm: service('replication-mode'),
|
||||
replicationMode: alias('rm.mode'),
|
||||
flashMessages: service(),
|
||||
|
||||
submitError(e) {
|
||||
if (e.errors) {
|
||||
@@ -121,25 +121,13 @@ export default Controller.extend(copy(DEFAULTS, true), {
|
||||
onSubmit(/*action, mode, data, event*/) {
|
||||
return this.submitHandler(...arguments);
|
||||
},
|
||||
copyClose(successMessage) {
|
||||
// separate action for copy & close button so it does not try and use execCommand to copy token to clipboard
|
||||
if (!!successMessage && typeof successMessage === 'string') {
|
||||
this.flashMessages.success(successMessage);
|
||||
}
|
||||
closeTokenModal() {
|
||||
this.toggleProperty('isModalActive');
|
||||
this.transitionToRoute('mode.secondaries');
|
||||
this.set('isTokenCopied', false);
|
||||
},
|
||||
toggleModal(successMessage) {
|
||||
if (!!successMessage && typeof successMessage === 'string') {
|
||||
this.flashMessages.success(successMessage);
|
||||
}
|
||||
// use copy browser extension to copy token if you close the modal by clicking outside of it.
|
||||
const htmlSelectedToken = document.querySelector('#token-textarea');
|
||||
htmlSelectedToken.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
this.toggleProperty('isModalActive');
|
||||
this.transitionToRoute('mode.secondaries');
|
||||
onCopy() {
|
||||
this.set('isTokenCopied', true);
|
||||
},
|
||||
clear() {
|
||||
this.reset();
|
||||
|
||||
@@ -60,8 +60,16 @@
|
||||
</form>
|
||||
|
||||
{{#if this.isModalActive}}
|
||||
<Modal @title="Copy your token" @onClose={{action "toggleModal" "Token copied!"}} @isActive={{this.isModalActive}}>
|
||||
<section class="modal-card-body">
|
||||
<Hds::Modal
|
||||
id="replication-copy-token-modal"
|
||||
@onClose={{action "closeTokenModal"}}
|
||||
@isDismissDisabled={{not this.isTokenCopied}}
|
||||
as |M|
|
||||
>
|
||||
<M.Header>
|
||||
Copy your token
|
||||
</M.Header>
|
||||
<M.Body>
|
||||
<p>
|
||||
This token can be used to enable
|
||||
{{this.model.replicationModeForDisplay}}
|
||||
@@ -79,14 +87,28 @@
|
||||
<InfoTableRow @label="Expires" @value={{date-format this.expirationDate "MMM dd, yyyy hh:mm:ss a"}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<Hds::Copy::Button
|
||||
@text="Copy & Close"
|
||||
@textToCopy={{this.token}}
|
||||
class="primary"
|
||||
{{on "click" (action "copyClose" "Token copied!")}}
|
||||
/>
|
||||
</footer>
|
||||
</Modal>
|
||||
</M.Body>
|
||||
<M.Footer>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Copy::Button
|
||||
data-test-modal-copy
|
||||
@text="Copy token"
|
||||
@textToCopy={{this.token}}
|
||||
class="primary"
|
||||
@container=".hds-modal"
|
||||
{{on "click" (action "onCopy")}}
|
||||
/>
|
||||
<Hds::Button
|
||||
data-test-modal-close
|
||||
disabled={{not this.isTokenCopied}}
|
||||
@text="Close"
|
||||
@color="secondary"
|
||||
{{on "click" (action "closeTokenModal")}}
|
||||
/>
|
||||
{{#unless this.isTokenCopied}}
|
||||
<AlertInline @type="warning" @message="Copy token to dismiss modal" />
|
||||
{{/unless}}
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
@@ -65,7 +65,6 @@
|
||||
"@ember/test-waiters": "^3.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"@hashicorp/ember-flight-icons": "^3.0.9",
|
||||
"@hashicorp/structure-icons": "^1.3.0",
|
||||
"@icholy/duration": "^5.1.0",
|
||||
"@tsconfig/ember": "^1.0.1",
|
||||
@@ -166,7 +165,6 @@
|
||||
"ember-test-selectors": "6.0.0",
|
||||
"ember-tether": "^2.0.1",
|
||||
"ember-truth-helpers": "3.0.0",
|
||||
"ember-wormhole": "0.6.0",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
@@ -248,7 +246,8 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@hashicorp/design-system-components": "^2.9.0",
|
||||
"@hashicorp/design-system-components": "^2.12.2",
|
||||
"@hashicorp/ember-flight-icons": "^3.1.3",
|
||||
"handlebars": "4.7.7",
|
||||
"highlight.js": "^10.4.1",
|
||||
"node-notifier": "^8.0.1",
|
||||
|
||||
@@ -390,9 +390,9 @@ module('Acceptance | client counts dashboard tab', function (hooks) {
|
||||
.dom(SELECTORS.emptyStateTitle)
|
||||
.includesText('start date found', 'Empty state shows no billing start date');
|
||||
await click(SELECTORS.monthDropdown);
|
||||
await click(this.element.querySelector('[data-test-month-list] button:not([disabled])'));
|
||||
await click(this.element.querySelector('[data-test-dropdown-month]:not([disabled])'));
|
||||
await click(SELECTORS.yearDropdown);
|
||||
await click(this.element.querySelector('[data-test-year-list] button:not([disabled])'));
|
||||
await click(this.element.querySelector('[data-test-dropdown-year]:not([disabled])'));
|
||||
await click(SELECTORS.dateDropdownSubmit);
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateTitle)
|
||||
|
||||
@@ -250,11 +250,16 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
const modalDefaultTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
|
||||
|
||||
// checks on secondary token modal
|
||||
assert.dom('#modal-wormhole').exists();
|
||||
assert.dom('.hds-modal#replication-copy-token-modal').exists();
|
||||
assert.dom('[data-test-inline-error-message]').hasText('Copy token to dismiss modal');
|
||||
assert.strictEqual(modalDefaultTtl, '1800s', 'shows the correct TTL of 1800s');
|
||||
// click off the modal to make sure you don't just have to click on the copy-close button to copy the token
|
||||
await click('[data-test-modal-background="Copy your token"]');
|
||||
assert.dom('[data-test-modal-close]').isDisabled('cancel is disabled');
|
||||
await click('[data-test-modal-copy]');
|
||||
assert.dom('[data-test-modal-close]').isEnabled('cancel is enabled after token is copied');
|
||||
await click('[data-test-modal-close]');
|
||||
|
||||
// add another secondary not using the default ttl
|
||||
await click('[data-test-secondary-add]');
|
||||
@@ -269,7 +274,8 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
||||
await settled();
|
||||
const modalTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
|
||||
assert.strictEqual(modalTtl, '180s', 'shows the correct TTL of 180s');
|
||||
await click('[data-test-modal-background="Copy your token"]');
|
||||
await click('[data-test-modal-copy]');
|
||||
await click('[data-test-modal-close]');
|
||||
|
||||
// confirm you were redirected to the secondaries page
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -432,7 +432,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
||||
.dom(SELECTORS.issuerDetails.configure)
|
||||
.hasAttribute('href', `/ui/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/edit`);
|
||||
await click(SELECTORS.issuerDetails.rotateRoot);
|
||||
assert.dom(find(SELECTORS.issuerDetails.rotateModal).parentElement).hasClass('is-active');
|
||||
assert.dom(SELECTORS.issuerDetails.rotateModal).exists('rotate root modal opens');
|
||||
await click(SELECTORS.issuerDetails.rotateModalGenerate);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
|
||||
@@ -163,7 +163,6 @@ module('Acceptance | pki tidy', function (hooks) {
|
||||
.exists('Configure tidy modal options button exists');
|
||||
await click(SELECTORS.tidyConfigureModal.tidyOptionsModal);
|
||||
assert.dom(SELECTORS.tidyConfigureModal.configureTidyModal).exists('Configure tidy modal exists');
|
||||
await click(SELECTORS.tidyConfigureModal.tidyOptionsModal);
|
||||
await click(SELECTORS.tidyConfigureModal.tidyModalAutoButton);
|
||||
await click(SELECTORS.tidyForm.toggleLabel('Automatic tidy disabled'));
|
||||
await click(SELECTORS.tidyForm.inputByAttr('tidyCertStore'));
|
||||
|
||||
@@ -281,7 +281,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
||||
await connectionPage.save();
|
||||
await settled();
|
||||
assert
|
||||
.dom('.modal.is-active .title')
|
||||
.dom('[data-test-db-connection-modal-title]')
|
||||
.hasText('Rotate your root credentials?', 'Modal appears asking to rotate root credentials');
|
||||
assert.dom('[data-test-enable-connection]').exists('Enable button exists');
|
||||
await click('[data-test-enable-connection]');
|
||||
@@ -397,7 +397,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
||||
await connectionPage.save();
|
||||
await settled();
|
||||
assert
|
||||
.dom('.modal.is-active .title')
|
||||
.dom('[data-test-db-connection-modal-title]')
|
||||
.hasText('Rotate your root credentials?', 'Modal appears asking to ');
|
||||
await connectionPage.enable();
|
||||
assert.strictEqual(
|
||||
@@ -418,7 +418,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
||||
});
|
||||
await connectionPage.delete();
|
||||
assert
|
||||
.dom('.modal.is-active .title')
|
||||
.dom('[data-test-confirmation-modal-title]')
|
||||
.hasText('Delete connection?', 'Modal appears asking to confirm delete action');
|
||||
await fillIn('[data-test-confirmation-modal-input="Delete connection?"]', connectionDetails.id);
|
||||
await click('[data-test-confirm-button]');
|
||||
|
||||
@@ -37,14 +37,14 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
encodePlaintext: false,
|
||||
encodeContext: false,
|
||||
assertAfterEncrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.ok(
|
||||
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
|
||||
`${key}: ciphertext shows a vault-prefixed ciphertext`
|
||||
);
|
||||
},
|
||||
assertBeforeDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert
|
||||
.dom('[data-test-transit-input="context"]')
|
||||
.hasValue(
|
||||
@@ -52,9 +52,8 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
`${key}: the ui shows the base64-encoded context`
|
||||
);
|
||||
},
|
||||
|
||||
assertAfterDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.strictEqual(
|
||||
find('[data-test-encrypted-value="plaintext"]').innerText,
|
||||
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
|
||||
@@ -69,20 +68,20 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
encodePlaintext: false,
|
||||
encodeContext: false,
|
||||
assertAfterEncrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.ok(
|
||||
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
|
||||
`${key}: ciphertext shows a vault-prefixed ciphertext`
|
||||
);
|
||||
},
|
||||
assertBeforeDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert
|
||||
.dom('[data-test-transit-input="context"]')
|
||||
.hasValue(encodeString('context'), `${key}: the ui shows the input context`);
|
||||
},
|
||||
assertAfterDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.strictEqual(
|
||||
find('[data-test-encrypted-value="plaintext"]').innerText,
|
||||
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
|
||||
@@ -97,20 +96,20 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
encodePlaintext: false,
|
||||
encodeContext: false,
|
||||
assertAfterEncrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.ok(
|
||||
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
|
||||
`${key}: ciphertext shows a vault-prefixed ciphertext`
|
||||
);
|
||||
},
|
||||
assertBeforeDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert
|
||||
.dom('[data-test-transit-input="context"]')
|
||||
.hasValue(encodeString('context'), `${key}: the ui shows the input context`);
|
||||
},
|
||||
assertAfterDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.strictEqual(
|
||||
find('[data-test-encrypted-value="plaintext"]').innerText,
|
||||
encodeString('This is the secret'),
|
||||
@@ -125,20 +124,20 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
encodePlaintext: true,
|
||||
encodeContext: true,
|
||||
assertAfterEncrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
|
||||
assert.ok(
|
||||
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
|
||||
`${key}: ciphertext shows a vault-prefixed ciphertext`
|
||||
);
|
||||
},
|
||||
assertBeforeDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
|
||||
assert
|
||||
.dom('[data-test-transit-input="context"]')
|
||||
.hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`);
|
||||
},
|
||||
assertAfterDecrypt: (key) => {
|
||||
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
|
||||
assert.strictEqual(
|
||||
find('[data-test-encrypted-value="plaintext"]').innerText,
|
||||
encodeString('There are many secrets 🤐'),
|
||||
@@ -161,7 +160,7 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
if (testCase.encodeContext) {
|
||||
await click('[data-test-transit-b64-toggle="context"]');
|
||||
}
|
||||
assert.dom('.modal.is-active').doesNotExist(`${name}: is not open before encrypt`);
|
||||
assert.dom('[data-test-encrypt-modal]').doesNotExist(`${name}: is not open before encrypt`);
|
||||
await click('[data-test-button-encrypt]');
|
||||
|
||||
if (testCase.assertAfterEncrypt) {
|
||||
@@ -170,9 +169,9 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
}
|
||||
// store ciphertext for decryption step
|
||||
const copiedCiphertext = find('[data-test-encrypted-value="ciphertext"]').innerText;
|
||||
await click('.modal.is-active [data-test-modal-background]');
|
||||
await click('dialog button');
|
||||
|
||||
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
assert.dom('dialog.hds-modal').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
await click('[data-test-transit-action-link="decrypt"]');
|
||||
|
||||
if (testCase.assertBeforeDecrypt) {
|
||||
@@ -187,9 +186,9 @@ const testConvergentEncryption = async function (assert, keyName) {
|
||||
testCase.assertAfterDecrypt(keyName);
|
||||
}
|
||||
|
||||
await click('.modal.is-active [data-test-modal-background]');
|
||||
await click('dialog button');
|
||||
|
||||
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
assert.dom('dialog.hds-modal').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ export const SELECTORS = {
|
||||
attributionBlock: '[data-test-clients-attribution]',
|
||||
filterBar: '[data-test-clients-filter-bar]',
|
||||
rangeDropdown: '[data-test-calendar-widget-trigger]',
|
||||
monthDropdown: '[data-test-popup-menu-trigger="month"]',
|
||||
yearDropdown: '[data-test-popup-menu-trigger="year"]',
|
||||
monthDropdown: '[data-test-toggle-month]',
|
||||
yearDropdown: '[data-test-toggle-year]',
|
||||
dateDropdownSubmit: '[data-test-date-dropdown-submit]',
|
||||
runningTotalMonthStats: '[data-test-running-total="single-month-stats"]',
|
||||
runningTotalMonthlyCharts: '[data-test-running-total="monthly-charts"]',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const PAGE = {
|
||||
message: '[data-test-page-error] p',
|
||||
},
|
||||
toolbar: 'nav.toolbar',
|
||||
toolbarAction: 'nav.toolbar-actions .toolbar-link',
|
||||
toolbarAction: 'nav.toolbar-actions .toolbar-link, nav.toolbar-actions .toolbar-button',
|
||||
secretRow: '[data-test-component="info-table-row"]', // replace with infoRow
|
||||
// specific page selectors
|
||||
backends: {
|
||||
|
||||
@@ -12,12 +12,12 @@ export const SELECTORS = {
|
||||
hdsAlertButtonText: '[data-test-cancel-tidy-action] .hds-button__text',
|
||||
timeStartedRow: '[data-test-value-div="Time started"]',
|
||||
timeFinishedRow: '[data-test-value-div="Time finished"]',
|
||||
cancelTidyModalBackground: '[data-test-modal-background="Cancel tidy?"]',
|
||||
cancelTidyModalBackground: '.hds-modal#pki-cancel-tidy-modal',
|
||||
tidyEmptyStateConfigure: '[data-test-tidy-empty-state-configure]',
|
||||
manualTidyToolbar: '[data-test-pki-manual-tidy-config]',
|
||||
autoTidyToolbar: '[data-test-pki-auto-tidy-config]',
|
||||
tidyConfigureModal: {
|
||||
configureTidyModal: '[data-test-modal-background="Tidy this mount"]',
|
||||
configureTidyModal: '.hds-modal#pki-tidy-modal',
|
||||
tidyModalAutoButton: '[data-test-tidy-modal-auto-button]',
|
||||
tidyModalManualButton: '[data-test-tidy-modal-manual-button]',
|
||||
tidyModalCancelButton: '[data-test-tidy-modal-cancel-button]',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
export const SELECTORS = {
|
||||
issuerLink: '[data-test-delete-all-issuers-link]',
|
||||
deleteAllIssuerModal: '[data-test-modal-background="Delete All Issuers?"]',
|
||||
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete All Issuers?"]',
|
||||
deleteAllIssuerButton: '[data-test-confirm-button="Delete All Issuers?"]',
|
||||
deleteAllIssuerModal: '.hds-modal#confirmation-modal',
|
||||
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete all issuers?"]',
|
||||
deleteAllIssuerButton: '[data-test-confirm-button="Delete all issuers?"]',
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ export const SELECTORS = {
|
||||
download: '[data-test-issuer-download]',
|
||||
groupTitle: '[data-test-group-title]',
|
||||
parsingAlertBanner: '[data-test-parsing-error-alert-banner]',
|
||||
rotateModal: '[data-test-modal-background="Rotate this root"]',
|
||||
rotateModal: '.hds-modal#pki-rotate-root-modal',
|
||||
rotateModalGenerate: '[data-test-root-rotate-step-one]',
|
||||
rotateRoot: '[data-test-pki-issuer-rotate-root]',
|
||||
row: '[data-test-component="info-table-row"]',
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<div id="qunit-fixture">
|
||||
<div id="ember-testing-container">
|
||||
<div id="ember-testing"></div>
|
||||
<div id="modal-wormhole"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
|
||||
test('it renders empty state with no data', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution @chartLegend={{this.chartLegend}} />
|
||||
`);
|
||||
|
||||
@@ -59,7 +58,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
|
||||
test('it renders with data for namespaces', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.totalClientAttribution}}
|
||||
@@ -93,7 +91,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
this.start = formatRFC3339(subMonths(this.mockNow, 1));
|
||||
this.end = formatRFC3339(subMonths(endOfMonth(this.mockNow), 1));
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.totalClientAttribution}}
|
||||
@@ -147,7 +144,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
|
||||
test('it renders single chart for current month', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.totalClientAttribution}}
|
||||
@@ -169,7 +165,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
|
||||
test('it renders single chart and correct text for for date range', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.totalClientAttribution}}
|
||||
@@ -193,7 +188,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
test('it renders with data for selected namespace auth methods for a date range', async function (assert) {
|
||||
this.set('selectedNamespace', 'second');
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.namespaceMountsData}}
|
||||
@@ -225,7 +219,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
|
||||
test('it renders modal', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Attribution
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@totalClientAttribution={{this.namespaceMountsData}}
|
||||
@@ -235,7 +228,9 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
/>
|
||||
`);
|
||||
await click('[data-test-attribution-export-button]');
|
||||
assert.dom('.modal.is-active .title').hasText('Export attribution data', 'modal appears to export csv');
|
||||
assert.dom('.modal.is-active').includesText('June 2022 - December 2022');
|
||||
assert
|
||||
.dom('[data-test-export-modal-title]')
|
||||
.hasText('Export attribution data', 'modal appears to export csv');
|
||||
assert.dom('[ data-test-export-date-range]').includesText('June 2022 - December 2022');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
});
|
||||
|
||||
test('it should function in edit mode when reporting is disabled', async function (assert) {
|
||||
assert.expect(13);
|
||||
assert.expect(12);
|
||||
|
||||
this.server.put('/sys/internal/counters/config', (schema, req) => {
|
||||
const { enabled, retention_months } = JSON.parse(req.requestBody);
|
||||
@@ -64,7 +64,6 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
this.createModel('disable');
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Config @model={{this.model}} @mode="edit" />
|
||||
`);
|
||||
|
||||
@@ -86,9 +85,8 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
|
||||
await fillIn('[data-test-input="retentionMonths"]', 24);
|
||||
await click('[data-test-clients-config-save]');
|
||||
assert.dom('.modal.is-active').exists('Modal renders');
|
||||
assert
|
||||
.dom('[data-test-modal-title] span')
|
||||
.dom('[data-test-clients-config-modal="title"]')
|
||||
.hasText('Turn usage tracking on?', 'Correct modal title renders');
|
||||
assert.dom('[data-test-clients-config-modal="on"]').exists('Correct modal description block renders');
|
||||
|
||||
@@ -100,14 +98,14 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
|
||||
await click('[data-test-input="enabled"]');
|
||||
await click('[data-test-clients-config-save]');
|
||||
assert.dom('.modal.is-active').exists('Modal renders');
|
||||
assert.dom('[data-test-clients-config-modal]').exists('Modal renders');
|
||||
assert
|
||||
.dom('[data-test-modal-title] span')
|
||||
.dom('[data-test-clients-config-modal="title"]')
|
||||
.hasText('Turn usage tracking off?', 'Correct modal title renders');
|
||||
assert.dom('[data-test-clients-config-modal="off"]').exists('Correct modal description block renders');
|
||||
|
||||
await click('[data-test-clients-config-modal="cancel"]');
|
||||
assert.dom('.modal.is-active').doesNotExist('Modal is hidden on cancel');
|
||||
assert.dom('[data-test-clients-config-modal]').doesNotExist('Modal is hidden on cancel');
|
||||
});
|
||||
|
||||
test('it should function in edit mode when reporting is enabled', async function (assert) {
|
||||
@@ -123,7 +121,6 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
this.createModel('enable', true, 24);
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Config @model={{this.model}} @mode="edit" />
|
||||
`);
|
||||
|
||||
@@ -162,7 +159,6 @@ module('Integration | Component | client count config', function (hooks) {
|
||||
this.createModel();
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::Config @model={{this.model}} @mode="edit" />
|
||||
`);
|
||||
await fillIn('[data-test-input="retentionMonths"]', 24);
|
||||
|
||||
@@ -1433,8 +1433,7 @@ module('Integration | Component | clients/monthly-usage', function (hooks) {
|
||||
|
||||
test('it renders empty state with no data', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::MonthlyUsage @chartLegend={{this.chartLegend}} @responseTimestamp={{this.timestamp}}/>
|
||||
<Clients::MonthlyUsage @chartLegend={{this.chartLegend}} @responseTimestamp={{this.timestamp}}/>
|
||||
`);
|
||||
assert.dom('[data-test-monthly-usage]').exists('monthly usage component renders');
|
||||
assert.dom('[data-test-component="empty-state"]').exists();
|
||||
@@ -1455,8 +1454,7 @@ module('Integration | Component | clients/monthly-usage', function (hooks) {
|
||||
),
|
||||
]);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::MonthlyUsage
|
||||
<Clients::MonthlyUsage
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@verticalBarChartData={{this.byMonthActivityData}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
|
||||
@@ -1444,8 +1444,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
||||
const expectedNewNonEntity = formatNumber([calculateAverage(NEW_ACTIVITY, 'non_entity_clients')]);
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::RunningTotal
|
||||
<Clients::RunningTotal
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@selectedAuthMethod={{this.selectedAuthMethod}}
|
||||
@byMonthActivityData={{this.byMonthActivityData}}
|
||||
@@ -1515,8 +1514,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
||||
const expectedTotalNonEntity = formatNumber([TOTAL_USAGE_COUNTS.non_entity_clients]);
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::RunningTotal
|
||||
<Clients::RunningTotal
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@selectedAuthMethod={{this.selectedAuthMethod}}
|
||||
@byMonthActivityData={{this.byMonthActivityData}}
|
||||
@@ -1558,8 +1556,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
||||
const expectedNewNonEntity = formatNumber([singleMonthNew.non_entity_clients]);
|
||||
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Clients::RunningTotal
|
||||
<Clients::RunningTotal
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@selectedAuthMethod={{this.selectedAuthMethod}}
|
||||
@byMonthActivityData={{this.singleMonth}}
|
||||
|
||||
@@ -18,8 +18,7 @@ module('Integration | Component | confirmation-modal', function (hooks) {
|
||||
this.set('onConfirm', confirmAction);
|
||||
this.set('onClose', closeAction);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
@title="Confirmation Modal"
|
||||
@isActive={{true}}
|
||||
@onConfirm={{this.onConfirm}}
|
||||
@@ -30,11 +29,11 @@ module('Integration | Component | confirmation-modal', function (hooks) {
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-confirm-button]').isDisabled();
|
||||
assert.dom('[data-test-modal-div]').hasAttribute('class', 'modal is-highlight is-active');
|
||||
assert.dom('.hds-modal#confirmation-modal').exists('modal is active');
|
||||
assert.dom('[data-test-confirm-button]').hasText('Plz Continue', 'Confirm button has specified value');
|
||||
assert
|
||||
.dom('[data-test-modal-title]')
|
||||
.hasStyle({ color: 'rgb(160, 125, 2)' }, 'title exists with warning header');
|
||||
.dom('[data-test-confirmation-modal-title] [data-test-icon="alert-triangle"]')
|
||||
.exists('title has with warning icon');
|
||||
await fillIn('[data-test-confirmation-modal-input="Confirmation Modal"]', 'Destructive Thing');
|
||||
assert.dom('[data-test-confirm-button="Confirmation Modal"]').isNotDisabled();
|
||||
|
||||
|
||||
@@ -12,14 +12,12 @@ import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
|
||||
import timestamp from 'core/utils/timestamp';
|
||||
|
||||
const SELECTORS = {
|
||||
monthDropdown: '[data-test-popup-menu-trigger="month"]',
|
||||
monthDropdown: '[data-test-toggle-month]',
|
||||
specificMonth: (m) => `[data-test-dropdown-month="${m}"]`,
|
||||
yearDropdown: '[data-test-popup-menu-trigger="year"]',
|
||||
yearDropdown: '[data-test-toggle-year]',
|
||||
specificYear: (y) => `[data-test-dropdown-year="${y}"]`,
|
||||
|
||||
submitButton: '[data-test-date-dropdown-submit]',
|
||||
cancelButton: '[data-test-date-dropdown-cancel]',
|
||||
monthOptions: '[data-test-month-list] button',
|
||||
monthOptions: '[data-test-dropdown-month]',
|
||||
};
|
||||
|
||||
module('Integration | Component | date-dropdown', function (hooks) {
|
||||
@@ -34,27 +32,11 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
|
||||
test('it renders dropdown', async function (assert) {
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown/>
|
||||
</div>
|
||||
`);
|
||||
assert.dom(SELECTORS.submitButton).hasText('Submit', 'button renders default text');
|
||||
assert.dom(SELECTORS.cancelButton).doesNotExist('it does not render cancel button by default');
|
||||
});
|
||||
|
||||
test('it fires off cancel callback', async function (assert) {
|
||||
assert.expect(2);
|
||||
const onCancel = () => {
|
||||
assert.ok('fires onCancel callback');
|
||||
};
|
||||
this.set('onCancel', onCancel);
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<DateDropdown @handleCancel={{this.onCancel}} @submitText="Save"/>
|
||||
</div>
|
||||
`);
|
||||
assert.dom(SELECTORS.submitButton).hasText('Save', 'button renders passed in text');
|
||||
await click(SELECTORS.cancelButton);
|
||||
});
|
||||
|
||||
test('it renders dropdown and selects month and year', async function (assert) {
|
||||
@@ -74,7 +56,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
this.set('parentAction', parentAction);
|
||||
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown
|
||||
@handleSubmit={{this.parentAction}}
|
||||
@dateType="start"
|
||||
@@ -112,7 +94,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
test('selecting month first: current year enabled when current month selected', async function (assert) {
|
||||
assert.expect(5);
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown/>
|
||||
</div>
|
||||
`);
|
||||
@@ -129,7 +111,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
test('selecting month first: it disables current year when future months selected', async function (assert) {
|
||||
assert.expect(5);
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown/>
|
||||
</div>
|
||||
`);
|
||||
@@ -149,7 +131,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
test('selecting year first: it disables future months when current year selected', async function (assert) {
|
||||
assert.expect(12);
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown/>
|
||||
</div>
|
||||
`);
|
||||
@@ -170,7 +152,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
|
||||
test('selecting year first: it enables all months when past year is selected', async function (assert) {
|
||||
assert.expect(12);
|
||||
await render(hbs`
|
||||
<div class="is-flex-align-baseline">
|
||||
<div class="has-padding-l">
|
||||
<DateDropdown/>
|
||||
</div>
|
||||
`);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { click, render, resetOnerror, setupOnerror } from '@ember/test-helpers';
|
||||
import { isPresent } from 'ember-cli-page-object';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import sinon from 'sinon';
|
||||
|
||||
@@ -28,25 +27,23 @@ module('Integration | Component | download button', function (hooks) {
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`
|
||||
<DownloadButton class="button">
|
||||
<Icon @name="download" />
|
||||
Download
|
||||
</DownloadButton>
|
||||
`);
|
||||
assert.dom(SELECTORS.button).hasClass('button');
|
||||
assert.ok(isPresent(SELECTORS.icon), 'renders yielded icon');
|
||||
assert.dom(SELECTORS.button).hasTextContaining('Download', 'renders yielded text');
|
||||
<DownloadButton /> `);
|
||||
assert.dom(SELECTORS.icon).exists('renders download icon');
|
||||
assert.dom(SELECTORS.button).hasText('Download', 'renders default text');
|
||||
});
|
||||
|
||||
test('it renders passed args', async function (assert) {
|
||||
await render(hbs`
|
||||
<DownloadButton @text="I do something" @hideIcon={{true}} /> `);
|
||||
assert.dom(SELECTORS.icon).doesNotExist('hides icon');
|
||||
assert.dom(SELECTORS.button).hasText('I do something', 'renders passed text');
|
||||
});
|
||||
|
||||
test('it downloads with defaults when only passed @data arg', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await render(hbs`
|
||||
<DownloadButton class="button"
|
||||
@data={{this.data}}
|
||||
>
|
||||
Download
|
||||
</DownloadButton>
|
||||
<DownloadButton @data={{this.data}} />
|
||||
`);
|
||||
await click(SELECTORS.button);
|
||||
const [filename, content, extension] = this.downloadSpy.getCall(0).args;
|
||||
@@ -59,16 +56,13 @@ module('Integration | Component | download button', function (hooks) {
|
||||
assert.expect(3);
|
||||
|
||||
await render(hbs`
|
||||
<DownloadButton class="button"
|
||||
<DownloadButton
|
||||
@data={{this.data}}
|
||||
@filename={{this.filename}}
|
||||
@mime={{this.mime}}
|
||||
@extension={{this.extension}}
|
||||
>
|
||||
Download
|
||||
</DownloadButton>
|
||||
/>
|
||||
`);
|
||||
|
||||
await click(SELECTORS.button);
|
||||
const [filename, content, extension] = this.downloadSpy.getCall(0).args;
|
||||
assert.ok(filename.includes(`${this.filename}-`), 'filename added to ISO string');
|
||||
@@ -80,9 +74,7 @@ module('Integration | Component | download button', function (hooks) {
|
||||
assert.expect(3);
|
||||
this.fetchData = () => 'this is fetched data from a parent function';
|
||||
await render(hbs`
|
||||
<DownloadButton class="button" @fetchData={{this.fetchData}} >
|
||||
Download
|
||||
</DownloadButton>
|
||||
<DownloadButton @fetchData={{this.fetchData}} />
|
||||
`);
|
||||
|
||||
await click(SELECTORS.button);
|
||||
@@ -103,7 +95,7 @@ module('Integration | Component | download button', function (hooks) {
|
||||
});
|
||||
this.fetchData = () => 'this is fetched data from a parent function';
|
||||
await render(hbs`
|
||||
<DownloadButton class="button" @data={{this.data}} @fetchData={{this.fetchData}} />
|
||||
<DownloadButton @data={{this.data}} @fetchData={{this.fetchData}} />
|
||||
`);
|
||||
resetOnerror();
|
||||
});
|
||||
|
||||
@@ -45,9 +45,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
|
||||
// TODO: Add capabilities tests
|
||||
test('it renders show view as default', async function (assert) {
|
||||
assert.expect(8);
|
||||
await render(
|
||||
hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} /><div id="modal-wormhole" />`
|
||||
);
|
||||
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} />`);
|
||||
assert.dom('[data-test-secret-header]').hasText('Unicorns', 'Shows key name');
|
||||
assert.dom('[data-test-keymgmt-key-toolbar]').exists('Subnav toolbar exists');
|
||||
assert.dom('[data-test-tab="Details"]').exists('Details tab exists');
|
||||
@@ -71,9 +69,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
|
||||
this.set('mode', 'edit');
|
||||
this.set('model', model);
|
||||
|
||||
await render(
|
||||
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
|
||||
);
|
||||
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
|
||||
assert.dom('[data-test-secret-header]').hasText('Edit Key', 'Shows edit header');
|
||||
assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist');
|
||||
assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist');
|
||||
@@ -86,9 +82,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
|
||||
this.set('mode', 'create');
|
||||
this.set('model', model);
|
||||
|
||||
await render(
|
||||
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
|
||||
);
|
||||
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
|
||||
assert.dom('[data-test-secret-header]').hasText('Create Key', 'Shows edit header');
|
||||
assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist');
|
||||
assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist');
|
||||
@@ -100,9 +94,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
this.model = store.createRecord('keymgmt/key');
|
||||
this.set('mode', 'create');
|
||||
await render(
|
||||
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
|
||||
);
|
||||
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
|
||||
assert.dom('[data-test-input="type"]').hasValue('rsa-2048', 'Has type rsa-2048 by default');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -199,14 +199,13 @@ module('Integration | Component | kubernetes | Page::Configure', function (hooks
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<Page::Configure @model={{this.editModel}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
<Page::Configure @model={{this.editModel}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
await click('[data-test-config-save]');
|
||||
assert
|
||||
.dom('.modal-card-body')
|
||||
.dom('[data-test-edit-config-body]')
|
||||
.hasText(
|
||||
'Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are you sure?',
|
||||
'Confirm modal renders'
|
||||
|
||||
@@ -44,8 +44,7 @@ module('Integration | Component | ldap | AccountsCheckedOut', function (hooks) {
|
||||
this.renderComponent = () => {
|
||||
return render(
|
||||
hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<AccountsCheckedOut
|
||||
<AccountsCheckedOut
|
||||
@libraries={{array this.library}}
|
||||
@statuses={{this.statuses}}
|
||||
@showLibraryColumn={{this.showLibraryColumn}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user