mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 10:12:35 +00:00
UI: move selectable card to add-on (#23739)
* remove title-number class and consolidate border radius * move selectable card to core addon * add top padding to db cards * update transform icon color * new selectable card component * fix db test * use selectable card in mount backend form * fix query param for overview card * update tests * fix replication card styling * make card accessible; * update tabindex * change to standalone for error handling * update test selector * update tests * go back to number only css class * fix on click tests * add changelog * update class name in template file * delete box radio
This commit is contained in:
3
changelog/14998.txt
Normal file
3
changelog/14998.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Update mount backend form to use selectable cards
|
||||
```
|
||||
@@ -3,13 +3,12 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<form {{on "submit" this.transitionToCredential}} class="selectable-card is-rounded no-flex">
|
||||
<div class="is-flex-between is-fullwidth card-details">
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-l" ...attributes>
|
||||
<form {{on "submit" this.transitionToCredential}}>
|
||||
<div class="is-flex-between is-fullwidth">
|
||||
<h3 class="title is-5">{{@title}}</h3>
|
||||
</div>
|
||||
<div class="has-top-bottom-margin">
|
||||
<p class="is-label search-label">{{@searchLabel}}</p>
|
||||
</div>
|
||||
<p class="has-top-margin-s has-bottom-margin-xs has-text-weight-semibold">{{@searchLabel}}</p>
|
||||
<SearchSelect
|
||||
@id="search-input-role"
|
||||
@models={{@models}}
|
||||
@@ -20,7 +19,12 @@
|
||||
@placeholder={{@placeholder}}
|
||||
data-test-search-roles
|
||||
/>
|
||||
<button type="submit" class="button is-secondary" disabled={{not this.role}} data-test-get-credentials>
|
||||
Get credentials
|
||||
</button>
|
||||
</form>
|
||||
<Hds::Button
|
||||
@text="Get credentials"
|
||||
type="submit"
|
||||
@color="secondary"
|
||||
disabled={{not this.role}}
|
||||
data-test-get-credentials
|
||||
/>
|
||||
</form>
|
||||
</Hds::Card::Container>
|
||||
@@ -4,39 +4,30 @@
|
||||
~}}
|
||||
|
||||
{{#each (array "generic" "cloud" "infra") as |category|}}
|
||||
<h3 class="title box-radio-header">
|
||||
<Hds::Text::Display class="has-top-padding-m has-bottom-padding-s" @tag="h2" size="400">
|
||||
{{capitalize category}}
|
||||
</h3>
|
||||
<div class="box-radio-container">
|
||||
</Hds::Text::Display>
|
||||
<div class="flex row-wrap row-gap-8 column-gap-16 has-bottom-padding-m">
|
||||
{{#each (filter-by "category" category this.mountTypes) as |type|}}
|
||||
<BoxRadio
|
||||
@displayName={{type.displayName}}
|
||||
@type={{type.type}}
|
||||
@glyph={{or type.glyph type.type}}
|
||||
@groupValue={{this.selection}}
|
||||
@groupName="mount-type"
|
||||
@onRadioChange={{mut this.selection}}
|
||||
<SelectableCard
|
||||
id={{type.type}}
|
||||
class="has-top-padding-m has-text-centered small-card"
|
||||
@onClick={{fn @setMountType type.type}}
|
||||
@disabled={{if type.requiredFeature (not (has-feature type.requiredFeature)) false}}
|
||||
@tooltipMessage={{if
|
||||
(or (eq type.type "transform") (eq type.type "kmip") (eq type.type "keymgmt"))
|
||||
(concat
|
||||
type.displayName
|
||||
" is part of the Advanced Data Protection module, which is not included in your enterprise license."
|
||||
)
|
||||
"This secret engine is not included in your license."
|
||||
}}
|
||||
/>
|
||||
data-test-mount-type={{type.type}}
|
||||
>
|
||||
<Icon @name={{or type.glyph type.type}} @size="24" class="has-bottom-margin-xs" />
|
||||
<Hds::Text::Body @tag="h3" @size="300">
|
||||
{{type.displayName}}
|
||||
</Hds::Text::Body>
|
||||
{{#if (and type.requiredFeature (not (has-feature type.requiredFeature)))}}
|
||||
<Hds::Badge @text="Enterprise" @icon="enterprise" @size="small" />
|
||||
{{/if}}
|
||||
</SelectableCard>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<button
|
||||
data-test-mount-next
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (fn @setMountType this.selection)}}
|
||||
disabled={{not this.selection}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<Hds::Button @text="Cancel" @color="secondary" @route="vault.cluster.secrets.backends" />
|
||||
</div>
|
||||
@@ -7,7 +7,6 @@ import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { allMethods, methods } from 'vault/helpers/mountable-auth-methods';
|
||||
import { allEngines, mountableEngines } from 'vault/helpers/mountable-secret-engines';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -19,13 +18,12 @@ import { tracked } from '@glimmer/tracking';
|
||||
* ```js
|
||||
* <MountBackend::TypeForm @setMountType={{this.setMountType}} @mountType="secret" />
|
||||
* ```
|
||||
* @param {CallableFunction} setMountType - function will recieve the mount type string. Should update the model type value
|
||||
* @param {CallableFunction} setMountType - function will receive the mount type string. Should update the model type value
|
||||
* @param {string} [mountType=auth] - mount type can be `auth` or `secret`
|
||||
*/
|
||||
|
||||
export default class MountBackendTypeForm extends Component {
|
||||
@service version;
|
||||
@tracked selection;
|
||||
|
||||
get secretEngines() {
|
||||
return this.version.isEnterprise ? allEngines() : mountableEngines();
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* @module SelectableCard
|
||||
* SelectableCard components are card-like components that display a title, total, subtotal, and anything after the yield.
|
||||
* They are designed to be used in containers that act as flexbox or css grid containers.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <SelectableCard @cardTitle="Tokens" @total={{totalHttpRequests}} @subText="Total"/>
|
||||
* ```
|
||||
* @param {string} [cardTitle] - cardTitle displays the card title.
|
||||
* @param {number} [total = 0] - the number displayed as the largest text in the component.
|
||||
* @param {string} [subText] - subText describes the total.
|
||||
* @param {string} [actionText] - action text link.
|
||||
* @param {string} [actionTo] - route where link will take you.
|
||||
* @param {string} [queryParam] - tab for the route the link will take you.
|
||||
* @param {string} [type] - type used in the link type.
|
||||
*/
|
||||
|
||||
export default class SelectableCard extends Component {
|
||||
get gridContainer() {
|
||||
return this.args.gridContainer || false;
|
||||
}
|
||||
get total() {
|
||||
return this.args.total || 0;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ const ENTERPRISE_AUTH_METHODS = [
|
||||
value: 'saml',
|
||||
type: 'saml',
|
||||
category: 'generic',
|
||||
glyph: 'saml-color',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -20,6 +21,7 @@ const MOUNTABLE_AUTH_METHODS = [
|
||||
value: 'alicloud',
|
||||
type: 'alicloud',
|
||||
category: 'cloud',
|
||||
glyph: 'alibaba-color',
|
||||
},
|
||||
{
|
||||
displayName: 'AppRole',
|
||||
@@ -108,6 +110,7 @@ const MOUNTABLE_AUTH_METHODS = [
|
||||
value: 'userpass',
|
||||
type: 'userpass',
|
||||
category: 'generic',
|
||||
glyph: 'users',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const ENTERPRISE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'KMIP',
|
||||
type: 'kmip',
|
||||
glyph: 'lock',
|
||||
engineRoute: 'kmip.scopes.index',
|
||||
category: 'generic',
|
||||
requiredFeature: 'KMIP',
|
||||
@@ -33,6 +34,7 @@ const MOUNTABLE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'AliCloud',
|
||||
type: 'alicloud',
|
||||
glyph: 'alibaba-color',
|
||||
category: 'cloud',
|
||||
},
|
||||
{
|
||||
@@ -50,6 +52,7 @@ const MOUNTABLE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'Consul',
|
||||
type: 'consul',
|
||||
glyph: 'consul-color',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
@@ -72,17 +75,20 @@ const MOUNTABLE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'KV',
|
||||
type: 'kv',
|
||||
glyph: 'key-values',
|
||||
engineRoute: 'kv.list',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Nomad',
|
||||
type: 'nomad',
|
||||
glyph: 'nomad-color',
|
||||
category: 'infra',
|
||||
},
|
||||
{
|
||||
displayName: 'PKI Certificates',
|
||||
type: 'pki',
|
||||
glyph: 'certificate',
|
||||
engineRoute: 'pki.overview',
|
||||
category: 'generic',
|
||||
},
|
||||
@@ -94,16 +100,19 @@ const MOUNTABLE_SECRET_ENGINES = [
|
||||
{
|
||||
displayName: 'SSH',
|
||||
type: 'ssh',
|
||||
glyph: 'terminal-screen',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'Transit',
|
||||
type: 'transit',
|
||||
glyph: 'swap-horizontal',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
displayName: 'TOTP',
|
||||
type: 'totp',
|
||||
glyph: 'history',
|
||||
category: 'generic',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
.action-block {
|
||||
@extend .selectable-card;
|
||||
box-shadow: 0 0 0 1px rgba($grey-dark, 0.3);
|
||||
grid-template-columns: 2fr 1fr;
|
||||
display: grid;
|
||||
padding: $spacing-m $spacing-l;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
.box-radio-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.title.box-radio-header {
|
||||
font-size: $size-6;
|
||||
color: $grey;
|
||||
margin: $size-7 0 0 0;
|
||||
}
|
||||
.box-radio-spacing {
|
||||
margin: $size-6 $size-3 $size-6 0;
|
||||
}
|
||||
.box-radio {
|
||||
box-sizing: border-box;
|
||||
flex-basis: 7rem;
|
||||
width: 7rem;
|
||||
min-height: 7.5rem;
|
||||
padding: $size-10 $size-6 $size-10;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border-radius: $radius;
|
||||
box-shadow: $box-shadow;
|
||||
text-align: center;
|
||||
color: $grey;
|
||||
font-weight: $font-weight-semibold;
|
||||
line-height: 1;
|
||||
margin: $size-6 $size-3 $size-6 0;
|
||||
font-size: 12px;
|
||||
transition: box-shadow ease-in-out $speed;
|
||||
will-change: box-shadow;
|
||||
|
||||
&.is-selected {
|
||||
box-shadow: 0 0 0 1px $grey-light, $box-shadow-middle;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
input[type='radio'].radio + label {
|
||||
border: 1px solid $grey-light;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin: 1rem auto 0;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
input[type='radio'].radio:checked + label {
|
||||
background: $blue;
|
||||
border: 1px solid $blue;
|
||||
box-shadow: inset 0 0 0 0.15rem $white;
|
||||
}
|
||||
input[type='radio'].radio:focus + label {
|
||||
box-shadow: 0 0 10px 1px rgba($blue, 0.4), inset 0 0 0 0.15rem $white;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
.selectable-card.secondaries {
|
||||
.known-secondaries-card {
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/3;
|
||||
|
||||
|
||||
@@ -6,12 +6,4 @@
|
||||
.overview-card {
|
||||
padding: $spacing-l;
|
||||
line-height: initial;
|
||||
|
||||
.title-number {
|
||||
color: $black;
|
||||
padding-top: $spacing-s;
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.replication-dashboard {
|
||||
box-shadow: none;
|
||||
|
||||
.selectable-card {
|
||||
.replication-summary-card {
|
||||
line-height: normal;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
.replication {
|
||||
.selectable-card {
|
||||
display: initial;
|
||||
line-height: normal;
|
||||
padding: $spacing-l;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px rgba($grey-dark, 0.3);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,6 @@
|
||||
@include until($mobile) {
|
||||
grid-template-columns: 2fr;
|
||||
}
|
||||
|
||||
.selectable-card.is-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 0.5fr;
|
||||
grid-template-rows: 1fr 2fr 0.5fr;
|
||||
padding: $spacing-l 0 14px $spacing-l; // modify bottom spacing to better align with other cards
|
||||
}
|
||||
}
|
||||
|
||||
.selectable-card-container.has-grid.has-three-col-grid {
|
||||
|
||||
@@ -4,106 +4,20 @@
|
||||
*/
|
||||
|
||||
.selectable-card {
|
||||
box-shadow: 0 0 0 1px rgba($grey-dark, 0.3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: $spacing-l 0 $spacing-l $spacing-l;
|
||||
line-height: 0;
|
||||
|
||||
&.no-flex {
|
||||
padding: $spacing-l;
|
||||
display: initial;
|
||||
line-height: initial;
|
||||
|
||||
.title-number {
|
||||
padding-top: $spacing-s;
|
||||
&:hover:not(.disabled),
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
cursor: pointer;
|
||||
box-shadow: var(--token-focus-ring-action-box-shadow);
|
||||
}
|
||||
|
||||
.search-label {
|
||||
margin-bottom: -$spacing-xs;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px $grey-light, $box-shadow-middle;
|
||||
}
|
||||
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
&:disabled {
|
||||
border-color: $ui-gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.card-details {
|
||||
grid-column-start: 2;
|
||||
grid-row-start: 3;
|
||||
align-self: center;
|
||||
justify-self: right;
|
||||
padding-right: $spacing-l;
|
||||
}
|
||||
|
||||
.change-metric {
|
||||
justify-self: right;
|
||||
padding-right: $spacing-l;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
||||
.hs-icon {
|
||||
color: $grey-light;
|
||||
align-self: center;
|
||||
justify-self: right;
|
||||
}
|
||||
|
||||
.amount-change {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
.item-c {
|
||||
grid-column: 1 / span 2;
|
||||
align-self: start;
|
||||
justify-self: end;
|
||||
|
||||
font-weight: $font-weight-semibold;
|
||||
white-space: nowrap;
|
||||
|
||||
@include until($mobile) {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-number {
|
||||
color: $black;
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.vlt-table {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
&.small-card {
|
||||
width: 7rem;
|
||||
min-height: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.selectable-card.is-rounded {
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.selectable-card.has-error-border {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.change-metric-icon.is-decrease {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.change-metric-icon.is-increase {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
@import './components/autocomplete-input';
|
||||
@import './components/b64-toggle';
|
||||
@import './components/box-label';
|
||||
@import './components/box-radio';
|
||||
@import './components/calendar-widget';
|
||||
@import './components/cluster-banners';
|
||||
@import './components/codemirror';
|
||||
@@ -93,14 +92,12 @@
|
||||
@import './components/replication-dashboard';
|
||||
@import './components/replication-mode-summary';
|
||||
@import './components/replication-page';
|
||||
@import './components/replication-primary-card';
|
||||
@import './components/replication-summary';
|
||||
@import './components/role-item';
|
||||
@import './components/search-select';
|
||||
@import './components/selectable-card';
|
||||
@import './components/selectable-card-container';
|
||||
@import './components/secrets-engines-card';
|
||||
// action-block extends selectable-card
|
||||
@import './components/action-block';
|
||||
@import './components/shamir-progress';
|
||||
@import './components/sidebar';
|
||||
|
||||
@@ -104,6 +104,28 @@
|
||||
flex: 50%;
|
||||
}
|
||||
|
||||
// moving away from !important, fresh flex styles below
|
||||
.flex {
|
||||
display: flex;
|
||||
// direction
|
||||
&.row-wrap {
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
// alignment
|
||||
&.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.row-gap-8 {
|
||||
row-gap: $spacing-xs;
|
||||
}
|
||||
|
||||
&.column-gap-16 {
|
||||
column-gap: $spacing-m;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flex Responsive */
|
||||
@media screen and (min-width: 769px), print {
|
||||
.is-flex-v-centered-tablet {
|
||||
|
||||
@@ -114,10 +114,6 @@
|
||||
}
|
||||
|
||||
// border-radius
|
||||
.border-radius-2 {
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.border-radius-4 {
|
||||
border-radius: $radius-large;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<div class="box is-sideless is-bottomless is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="enable" @noun={{if (eq @mountType "secret") "Secret Engine" "Auth Method"}} />
|
||||
<MessageError @errorMessage={{this.errorMessage}} />
|
||||
{{#if @mountModel.type}}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
{{! conditional to check if SelectableCard is apart of a CSS Grid, if yes return grid item class }}
|
||||
{{#if this.gridContainer}}
|
||||
<div class="selectable-card is-rounded is-grid-container">
|
||||
<div class="selectable-card-title">
|
||||
<h2 class="title-number">{{format-number this.total}}</h2>
|
||||
<h3 class="title is-5" data-test-selectable-card-title={{@cardTitle}}>{{@cardTitle}}</h3>
|
||||
<p class="has-text-grey is-size-8">{{@subText}}</p>
|
||||
</div>
|
||||
{{yield}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="selectable-card is-rounded no-flex">
|
||||
<div class="is-flex-between is-fullwidth card-details" data-test-selectable-card={{@cardTitle}}>
|
||||
<h3 class="title is-5">{{@cardTitle}}</h3>
|
||||
<LinkTo
|
||||
@route={{@actionTo}}
|
||||
class="has-icon-right is-ghost is-no-underline has-text-weight-semibold"
|
||||
@query={{hash itemType=@queryParam}}
|
||||
data-test-action-text={{@actionText}}
|
||||
>
|
||||
{{@actionText}}
|
||||
{{#if @actionText}}
|
||||
<Icon @name="chevron-right" />
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
</div>
|
||||
<p class="has-text-grey is-size-8">{{@subText}}</p>
|
||||
<h2 class="title-number">{{format-number this.total}}</h2>
|
||||
{{yield}}
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -32,26 +32,30 @@
|
||||
<div class="box is-fullwidth is-shadowless has-tall-padding">
|
||||
<div class="selectable-card-container {{if (and (eq this.model.connections 403) (eq this.model.roles 403)) 'one-card'}}">
|
||||
{{#if this.model.connectionCapabilities.canList}}
|
||||
<SelectableCard
|
||||
<OverviewCard
|
||||
@cardTitle="Connections"
|
||||
@total={{if (eq this.model.connections 404) 0 this.model.connections.length}}
|
||||
@subText="The total number of connections to external databases that you have access to."
|
||||
@actionText="Configure new"
|
||||
@actionTo="vault.cluster.secrets.backend.create-root"
|
||||
@queryParam={{"connection"}}
|
||||
@type="role"
|
||||
/>
|
||||
@actionQuery={{hash itemType="connection"}}
|
||||
>
|
||||
<Hds::Text::Display class="has-top-padding-m" @tag="h2" @size="500">
|
||||
{{format-number (if (eq this.model.connections 404) 0 this.model.connections.length)}}
|
||||
</Hds::Text::Display>
|
||||
</OverviewCard>
|
||||
{{/if}}
|
||||
{{#if (or this.model.roleCapabilities.canList this.model.staticRoleCapabilities.canList)}}
|
||||
<SelectableCard
|
||||
<OverviewCard
|
||||
@cardTitle="Roles"
|
||||
@total={{if (eq this.model.roles 404) 0 this.model.roles.length}}
|
||||
{{! TODO: Messaging needs massaging }}
|
||||
@subText="The total number of roles that have been set up that you can list."
|
||||
@subText="The total number of roles configured that you have permissions to list."
|
||||
@actionText="Create new"
|
||||
@actionTo="vault.cluster.secrets.backend.create-root"
|
||||
@queryParam={{"role"}}
|
||||
/>
|
||||
@actionQuery={{hash itemType="role"}}
|
||||
>
|
||||
<Hds::Text::Display class="has-top-padding-m" @tag="h2" @size="500">
|
||||
{{format-number (if (eq this.model.roles 404) 0 this.model.roles.length)}}
|
||||
</Hds::Text::Display>
|
||||
</OverviewCard>
|
||||
{{/if}}
|
||||
<GetCredentialsCard
|
||||
@title="Get Credentials"
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
{{#if @disabled}}
|
||||
<div class="box-radio-spacing">
|
||||
<ToolTip @verticalPosition="above" @horizontalPosition="center" as |T|>
|
||||
<T.Trigger @tabindex="-1">
|
||||
<label
|
||||
for={{@type}}
|
||||
class="box-radio is-disabled is-marginless"
|
||||
data-test-mount-type-radio
|
||||
data-test-mount-type={{@type}}
|
||||
>
|
||||
<Icon @name={{@glyph}} @size="24" class="has-text-grey-light" />
|
||||
{{@displayName}}
|
||||
<RadioButton
|
||||
id={{@type}}
|
||||
name={{@groupName}}
|
||||
class="radio"
|
||||
@disabled={{@disabled}}
|
||||
@value={{@type}}
|
||||
@groupValue={{@groupValue}}
|
||||
@onChange={{@onRadioChange}}
|
||||
/>
|
||||
</label>
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box">
|
||||
{{@tooltipMessage}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="box-radio-spacing">
|
||||
<label
|
||||
for={{@type}}
|
||||
class="box-radio is-marginless {{if (eq @groupValue @type) ' is-selected'}}"
|
||||
data-test-mount-type-radio
|
||||
data-test-mount-type={{@type}}
|
||||
>
|
||||
<Icon @name={{@glyph}} @size="24" class="has-text-grey-light" />
|
||||
{{@displayName}}
|
||||
<RadioButton
|
||||
id={{@type}}
|
||||
name={{@groupName}}
|
||||
class="radio"
|
||||
@disabled={{@disabled}}
|
||||
@value={{@type}}
|
||||
@groupValue={{@mountType}}
|
||||
@onChange={{@onRadioChange}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -1,30 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module BoxRadio
|
||||
* BoxRadio components are used to display options for a radio selection.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <BoxRadio @displayName="Catahoula Leopard" @type="catahoula" @glyph="dog" @groupValue="labrador" @groupName="my-favorite-dog" @onRadioChange={{handleRadioChange}} />
|
||||
* ```
|
||||
* @param {string} displayName - This is the string that will show on the box radio option.
|
||||
* @param {string} type - type is the key that the radio input will be identified by. Please use a value without spaces.
|
||||
* @param {string} glyph - glyph is the name of the icon that will be used in the box
|
||||
* @param {string} groupValue - The key of the radio option that is currently selected for this radio group
|
||||
* @param {string} groupName - The name (key) of the group that this radio option belongs to
|
||||
* @param {function} onRadioChange - This callback will trigger when the radio option is selected (if enabled)
|
||||
* @param {boolean} [disabled=false] - This parameter controls whether the radio option is selectable. If not, it will be grayed out and show a tooltip.
|
||||
* @param {string} [tooltipMessage=default] - The message that shows in the tooltip if the radio option is disabled
|
||||
*/
|
||||
|
||||
export default class BoxRadio extends Component {
|
||||
get tooltipMessage() {
|
||||
return this.args.tooltipMessage || 'This option is not available to you at this time.';
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,6 @@ export default class Icon extends Component {
|
||||
}
|
||||
|
||||
get hsIconClass() {
|
||||
return this.size === '24' ? 'hs-icon-xl' : 'hs-icon-l';
|
||||
return this.size === '24' ? 'hs-icon-xlm' : 'hs-icon-l';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,22 +6,21 @@
|
||||
<Hds::Card::Container
|
||||
@level="mid"
|
||||
@hasBorder={{true}}
|
||||
class="overview-card border-radius-2"
|
||||
class="overview-card"
|
||||
data-test-overview-card-container={{@cardTitle}}
|
||||
...attributes
|
||||
>
|
||||
<div class="is-flex-between" data-test-overview-card={{@cardTitle}}>
|
||||
<h3 class="title is-5">{{@cardTitle}}</h3>
|
||||
<div class="flex row-wrap space-between has-bottom-margin-m" data-test-overview-card={{@cardTitle}}>
|
||||
<h3 class="has-text-weight-bold is-size-5">{{@cardTitle}}</h3>
|
||||
{{#if @actionText}}
|
||||
<LinkTo
|
||||
class="has-icon-right is-ghost is-no-underline has-text-weight-semibold"
|
||||
<Hds::Link::Standalone
|
||||
@icon={{or @actionIcon "chevron-right"}}
|
||||
@iconPosition="trailing"
|
||||
@text={{@actionText}}
|
||||
@route={{@actionTo}}
|
||||
@query={{hash @actionQuery}}
|
||||
@query={{@actionQuery}}
|
||||
data-test-action-text={{@actionText}}
|
||||
>
|
||||
{{@actionText}}
|
||||
<Icon @name="chevron-right" />
|
||||
</LinkTo>
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
<p class="has-text-grey is-size-8 {{unless @actionText 'has-top-margin-s'}}">{{@subText}}</p>
|
||||
|
||||
16
ui/lib/core/addon/components/selectable-card.hbs
Normal file
16
ui/lib/core/addon/components/selectable-card.hbs
Normal file
@@ -0,0 +1,16 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<Hds::Card::Container
|
||||
tabindex={{unless @disabled "0"}}
|
||||
class="selectable-card {{if @disabled 'disabled'}}"
|
||||
@level={{if @disabled "base" "mid"}}
|
||||
@hasBorder={{true}}
|
||||
{{on "click" @onClick}}
|
||||
{{on "keypress" @onClick}}
|
||||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
</Hds::Card::Container>
|
||||
@@ -3,8 +3,9 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<div
|
||||
class="selectable-card is-rounded card-container {{if this.hasErrorClass 'has-error-border'}}"
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="has-padding-m card-container {{if this.hasErrorClass 'has-error-border'}}"
|
||||
data-test-replication-secondary-card
|
||||
>
|
||||
{{! Check if Status or Primary Cluster Card }}
|
||||
@@ -119,4 +120,4 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</Hds::Card::Container>
|
||||
@@ -3,8 +3,9 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<div
|
||||
class="selectable-card is-rounded card-container summary {{if this.hasErrorClass 'has-error-border'}}"
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="replication-summary-card card-container summary {{if this.hasErrorClass 'has-error-border'}}"
|
||||
data-test-replication-summary-card
|
||||
>
|
||||
{{! Check if DR or Performance Card }}
|
||||
@@ -59,4 +60,4 @@
|
||||
<div><code class="is-word-break has-text-black">{{this.merkleRootPerformance}}</code></div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</Hds::Card::Container>
|
||||
@@ -3,4 +3,4 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'core/components/box-radio';
|
||||
export { default } from 'core/components/selectable-card';
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="is-grid has-top-margin-l grid-2-columns grid-gap-2">
|
||||
<div>
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-l is-flex-half border-radius-2">
|
||||
<Hds::Card::Container @level="mid" @hasBorder={{true}} class="has-padding-l is-flex-half">
|
||||
<div class="is-flex-between">
|
||||
<h3 class="is-size-5 has-text-weight-semibold">All accounts</h3>
|
||||
{{#if @library.canCheckOut}}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
@actionText="View issuers"
|
||||
@actionTo="issuers"
|
||||
>
|
||||
<h2 class="title-number">{{format-number (if (eq @issuers 404) 0 @issuers.length)}}</h2>
|
||||
<Hds::Text::Display @tag="h2" @size="500">
|
||||
{{format-number (if (eq @issuers 404) 0 @issuers.length)}}
|
||||
</Hds::Text::Display>
|
||||
</OverviewCard>
|
||||
{{#if (not-eq @roles 403)}}
|
||||
<OverviewCard
|
||||
@@ -19,7 +21,9 @@
|
||||
@actionText="View roles"
|
||||
@actionTo="roles"
|
||||
>
|
||||
<h2 class="title-number">{{format-number (if (eq @roles 404) 0 @roles.length)}}</h2>
|
||||
<Hds::Text::Display @tag="h2" @size="500">
|
||||
{{format-number (if (eq @roles 404) 0 @roles.length)}}
|
||||
</Hds::Text::Display>
|
||||
</OverviewCard>
|
||||
{{/if}}
|
||||
<OverviewCard @cardTitle="Issue certificate" @subText="Begin issuing a certificate by choosing a role.">
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<div class="selectable-card is-rounded secondaries">
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="has-padding-m known-secondaries-card {{if this.hasErrorClass 'has-error-border'}}"
|
||||
>
|
||||
<div class="level">
|
||||
<h3 class="card-title title is-5">{{this.replicationAttrs.secondaries.length}} Known secondaries</h3>
|
||||
<ToolbarLink @route="mode.secondaries" @model={{this.cluster.replicationMode}} data-test-manage-link>
|
||||
@@ -25,4 +28,4 @@
|
||||
Add secondary
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</Hds::Card::Container>
|
||||
@@ -3,7 +3,7 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<div class="selectable-card is-rounded {{dasherize this.title}} {{if this.hasError 'has-error-border'}}">
|
||||
<Hds::Card::Container @hasBorder={{true}} class="has-padding-m {{if this.hasErrorClass 'has-error-border'}}">
|
||||
<h3 class="card-title title is-5">{{this.title}}</h3>
|
||||
<div>
|
||||
{{#if this.hasError}}
|
||||
@@ -23,4 +23,4 @@
|
||||
{{/if}}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</Hds::Card::Container>
|
||||
@@ -1,16 +1,16 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="10" height="10" rx="1" fill="white" stroke="#BAC1CC" stroke-width="2"/>
|
||||
<rect x="13" y="13" width="10" height="10" rx="1" fill="white" stroke="#BAC1CC" stroke-width="2"/>
|
||||
<rect x="19" y="6" width="2" height="2" fill="#BAC1CC"/>
|
||||
<rect x="19" y="9" width="2" height="2" fill="#BAC1CC"/>
|
||||
<rect x="11" y="21" width="2" height="2" transform="rotate(-180 11 21)" fill="#BAC1CC"/>
|
||||
<rect x="8" y="21" width="2" height="2" transform="rotate(-180 8 21)" fill="#BAC1CC"/>
|
||||
<rect x="5" y="21" width="2" height="2" transform="rotate(-180 5 21)" fill="#BAC1CC"/>
|
||||
<rect x="5" y="18" width="2" height="2" transform="rotate(-180 5 18)" fill="#BAC1CC"/>
|
||||
<rect x="5" y="15" width="2" height="2" transform="rotate(-180 5 15)" fill="#BAC1CC"/>
|
||||
<path d="M7.02393 9H8.78174L6.98438 2.65869H5.01123L3.21826 9H4.81787L5.12109 7.60693H6.72949L7.02393 9ZM5.89453 4.08691H5.97803L6.479 6.44678H5.37598L5.89453 4.08691Z" fill="#BAC1CC"/>
|
||||
<path d="M15.4248 17.3833L16.9761 18.1743L15.4248 18.9653L16.0269 20.0068L17.4902 19.062L17.3979 20.8022H18.6021L18.5098 19.0576L19.9731 20.0112L20.5752 18.9653L19.0195 18.1743L20.5752 17.3833L19.9731 16.3418L18.5142 17.291L18.6021 15.5464H17.3979L17.4858 17.291L16.0269 16.3374L15.4248 17.3833Z" fill="#BAC1CC"/>
|
||||
<rect x="13" y="3" width="2" height="2" fill="#BAC1CC"/>
|
||||
<rect x="16" y="3" width="2" height="2" fill="#BAC1CC"/>
|
||||
<rect x="19" y="3" width="2" height="2" fill="#BAC1CC"/>
|
||||
<rect x="1" y="1" width="10" height="10" rx="1" fill="white" stroke="#000000" stroke-width="2"/>
|
||||
<rect x="13" y="13" width="10" height="10" rx="1" fill="white" stroke="#000000" stroke-width="2"/>
|
||||
<rect x="19" y="6" width="2" height="2" fill="#000000"/>
|
||||
<rect x="19" y="9" width="2" height="2" fill="#000000"/>
|
||||
<rect x="11" y="21" width="2" height="2" transform="rotate(-180 11 21)" fill="#000000"/>
|
||||
<rect x="8" y="21" width="2" height="2" transform="rotate(-180 8 21)" fill="#000000"/>
|
||||
<rect x="5" y="21" width="2" height="2" transform="rotate(-180 5 21)" fill="#000000"/>
|
||||
<rect x="5" y="18" width="2" height="2" transform="rotate(-180 5 18)" fill="#000000"/>
|
||||
<rect x="5" y="15" width="2" height="2" transform="rotate(-180 5 15)" fill="#000000"/>
|
||||
<path d="M7.02393 9H8.78174L6.98438 2.65869H5.01123L3.21826 9H4.81787L5.12109 7.60693H6.72949L7.02393 9ZM5.89453 4.08691H5.97803L6.479 6.44678H5.37598L5.89453 4.08691Z" fill="#000000"/>
|
||||
<path d="M15.4248 17.3833L16.9761 18.1743L15.4248 18.9653L16.0269 20.0068L17.4902 19.062L17.3979 20.8022H18.6021L18.5098 19.0576L19.9731 20.0112L20.5752 18.9653L19.0195 18.1743L20.5752 17.3833L19.9731 16.3418L18.5142 17.291L18.6021 15.5464H17.3979L17.4858 17.291L16.0269 16.3374L15.4248 17.3833Z" fill="#000000"/>
|
||||
<rect x="13" y="3" width="2" height="2" fill="#000000"/>
|
||||
<rect x="16" y="3" width="2" height="2" fill="#000000"/>
|
||||
<rect x="19" y="3" width="2" height="2" fill="#000000"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -99,7 +99,7 @@ module('Acceptance | Enterprise | KMIP secrets', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type).submit();
|
||||
await mountSecrets.path(engine.type).submit();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
`vault.cluster.secrets.backend.${engine.engineRoute}`,
|
||||
|
||||
@@ -28,7 +28,7 @@ module('Acceptance | Enterprise | keymgmt', function (hooks) {
|
||||
await runCmd([`delete sys/mounts/${engine.type}`]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type);
|
||||
await mountSecrets.path(engine.type);
|
||||
await mountSecrets.submit();
|
||||
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -75,7 +75,7 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) {
|
||||
await runCmd([`delete sys/mounts/${engine.type}`]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type);
|
||||
await mountSecrets.path(engine.type);
|
||||
await mountSecrets.submit();
|
||||
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -26,7 +26,7 @@ module('Acceptance | alicloud/enable', function (hooks) {
|
||||
await settled();
|
||||
await mountSecrets.selectType('alicloud');
|
||||
await settled();
|
||||
await mountSecrets.next().path(enginePath).submit();
|
||||
await mountSecrets.path(enginePath).submit();
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -478,11 +478,11 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
||||
assert.dom('[data-test-secret-create]').doesNotExist('Add role button does not show due to permissions');
|
||||
assert.dom('[data-test-edit-link]').doesNotExist('Edit button does not show due to permissions');
|
||||
await visit(`/vault/secrets/${backend}/overview`);
|
||||
assert.dom('[data-test-selectable-card="Connections"]').exists('Connections card exists on overview');
|
||||
assert.dom('[data-test-overview-card="Connections"]').exists('Connections card exists on overview');
|
||||
assert
|
||||
.dom('[data-test-selectable-card="Roles"]')
|
||||
.dom('[data-test-overview-card="Roles"]')
|
||||
.doesNotExist('Roles card does not exist on overview w/ policy');
|
||||
assert.dom('.title-number').hasText('1', 'Lists the correct number of connections');
|
||||
assert.dom('.overview-card h2').hasText('1', 'Lists the correct number of connections');
|
||||
// confirm get credentials card is an option to select. Regression bug.
|
||||
await typeIn('.ember-text-field', 'blah');
|
||||
assert.dom('[data-test-get-credentials]').isEnabled();
|
||||
@@ -570,9 +570,8 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
||||
.dom('[data-test-secret-list-tab="Roles"]')
|
||||
.doesNotExist(`does not show the roles tab because it does not have permissions`);
|
||||
assert
|
||||
.dom('[data-test-selectable-card="Connections"]')
|
||||
.dom('[data-test-overview-card="Connections"]')
|
||||
.exists({ count: 1 }, 'renders only the connection card');
|
||||
|
||||
await click('[data-test-action-text="Configure new"]');
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create?itemType=connection`);
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ module('Acceptance | gcpkms/enable', function (hooks) {
|
||||
await mountSecrets.visit();
|
||||
await settled();
|
||||
await mountSecrets.selectType('gcpkms');
|
||||
await mountSecrets.next().path(enginePath).submit();
|
||||
await mountSecrets.path(enginePath).submit();
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
|
||||
@@ -51,8 +51,6 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
||||
await mountSecrets.visit();
|
||||
await click('[data-test-mount-type="kv"]');
|
||||
|
||||
await click('[data-test-mount-next]');
|
||||
|
||||
await fillIn('[data-test-input="path"]', enginePath);
|
||||
await fillIn('[data-test-input="maxVersions"]', maxVersion);
|
||||
await click('[data-test-input="casRequired"]');
|
||||
@@ -141,7 +139,7 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
||||
// mount version 1 engine
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType('kv');
|
||||
await mountSecrets.next().path(this.backend).toggleOptions().version(1).submit();
|
||||
await mountSecrets.path(this.backend).toggleOptions().version(1).submit();
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await consoleComponent.runCommands([`delete sys/mounts/${this.backend}`]);
|
||||
|
||||
@@ -33,7 +33,6 @@ module('Acceptance | ldap | overview', function (hooks) {
|
||||
await visit('/vault/secrets');
|
||||
await click('[data-test-enable-engine]');
|
||||
await click('[data-test-mount-type="ldap"]');
|
||||
await click('[data-test-mount-next]');
|
||||
await fillIn('[data-test-input="path"]', 'ldap-test');
|
||||
await click('[data-test-mount-submit]');
|
||||
assert.true(isURL('overview'), 'Transitions to ldap overview route on mount success');
|
||||
|
||||
@@ -31,7 +31,6 @@ module('Acceptance | settings', function (hooks) {
|
||||
|
||||
await mountSecrets.selectType(type);
|
||||
await mountSecrets
|
||||
.next()
|
||||
.path(path)
|
||||
.toggleOptions()
|
||||
.enableDefaultTtl()
|
||||
|
||||
@@ -45,7 +45,6 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
|
||||
await page.selectType('kv');
|
||||
await page
|
||||
.next()
|
||||
.path(path)
|
||||
.toggleOptions()
|
||||
.enableDefaultTtl()
|
||||
@@ -70,7 +69,6 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
|
||||
await page.selectType('kv');
|
||||
await page
|
||||
.next()
|
||||
.path(path)
|
||||
.toggleOptions()
|
||||
.enableDefaultTtl()
|
||||
@@ -91,7 +89,6 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
await page.visit();
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
|
||||
await page.selectType('pki');
|
||||
await page.next();
|
||||
assert.dom('[data-test-input="maxLeaseTtl"]').exists();
|
||||
assert
|
||||
.dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-toggle]')
|
||||
@@ -102,7 +99,6 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
// Go back and choose a different type
|
||||
await page.back();
|
||||
await page.selectType('database');
|
||||
await page.next();
|
||||
assert.dom('[data-test-input="maxLeaseTtl"]').exists('3650');
|
||||
assert
|
||||
.dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-toggle]')
|
||||
@@ -124,12 +120,12 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
|
||||
await page.selectType('kv');
|
||||
await page.next().path(path).submit();
|
||||
await page.path(path).submit();
|
||||
await page.secretList();
|
||||
await settled();
|
||||
await page.enableEngine();
|
||||
await page.selectType('kv');
|
||||
await page.next().path(path).submit();
|
||||
await page.path(path).submit();
|
||||
assert.dom('[data-test-message-error-description]').containsText(`path is already in use at ${path}`);
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
|
||||
|
||||
@@ -174,7 +170,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
// create the engine
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType('kv');
|
||||
await mountSecrets.next().path(enginePath).setMaxVersion(101).submit();
|
||||
await mountSecrets.path(enginePath).setMaxVersion(101).submit();
|
||||
await settled();
|
||||
assert
|
||||
.dom('[data-test-flash-message]')
|
||||
@@ -202,7 +198,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type).submit();
|
||||
await mountSecrets.path(engine.type).submit();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
`vault.cluster.secrets.backend.${engine.engineRoute}`,
|
||||
@@ -229,7 +225,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type);
|
||||
await mountSecrets.path(engine.type);
|
||||
if (engine.type === 'kv') {
|
||||
await mountSecrets.toggleOptions().version(1);
|
||||
}
|
||||
@@ -258,7 +254,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType(engine.type);
|
||||
await mountSecrets.next().path(engine.type).submit();
|
||||
await mountSecrets.path(engine.type).submit();
|
||||
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
@@ -277,7 +273,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType('kv');
|
||||
await mountSecrets.next().path(v2).submit();
|
||||
await mountSecrets.path(v2).submit();
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${v2}/kv/list`, `${v2} navigates to list url`);
|
||||
assert.strictEqual(
|
||||
@@ -293,7 +289,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
||||
]);
|
||||
await mountSecrets.visit();
|
||||
await mountSecrets.selectType('kv');
|
||||
await mountSecrets.next().path(v1).toggleOptions().version(1).submit();
|
||||
await mountSecrets.path(v1).toggleOptions().version(1).submit();
|
||||
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${v1}/list`, `${v1} navigates to list url`);
|
||||
assert.strictEqual(
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
export const SELECTORS = {
|
||||
rolesCardTitle: '[data-test-overview-card="Roles"] .title',
|
||||
rolesCardTitle: '[data-test-overview-card="Roles"] h3',
|
||||
rolesCardSubTitle: '[data-test-overview-card-container="Roles"] p',
|
||||
rolesCardLink: '[data-test-overview-card="Roles"] a',
|
||||
rolesCardNumRoles: '[data-test-roles-card-overview-num]',
|
||||
generateCredentialsCardTitle: '[data-test-overview-card="Generate credentials"] .title',
|
||||
generateCredentialsCardTitle: '[data-test-overview-card="Generate credentials"] h3',
|
||||
generateCredentialsCardSubTitle: '[data-test-overview-card-container="Generate credentials"] p',
|
||||
generateCredentialsCardButton: '[data-test-generate-credential-button]',
|
||||
emptyStateTitle: '.empty-state .empty-state-title',
|
||||
|
||||
@@ -7,11 +7,11 @@ export const SELECTORS = {
|
||||
issuersCardTitle: '[data-test-overview-card-container="Issuers"] h3',
|
||||
issuersCardSubtitle: '[data-test-overview-card-container="Issuers"] p',
|
||||
issuersCardLink: '[data-test-overview-card-container="Issuers"] a',
|
||||
issuersCardOverviewNum: '[data-test-overview-card-container="Issuers"] .title-number',
|
||||
issuersCardOverviewNum: '[data-test-overview-card-container="Issuers"] h2',
|
||||
rolesCardTitle: '[data-test-overview-card-container="Roles"] h3',
|
||||
rolesCardSubtitle: '[data-test-overview-card-container="Roles"] p',
|
||||
rolesCardLink: '[data-test-overview-card-container="Roles"] a',
|
||||
rolesCardOverviewNum: '[data-test-overview-card-container="Roles"] .title-number',
|
||||
rolesCardOverviewNum: '[data-test-overview-card-container="Roles"] h2',
|
||||
issueCertificate: '[data-test-overview-card-container="Issue certificate"] h3',
|
||||
issueCertificateInput: '[data-test-issue-certificate-input]',
|
||||
issueCertificatePowerSearch: '[data-test-issue-certificate-input] span',
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import sinon from 'sinon';
|
||||
import { render, click } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | box-radio', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set('type', 'aws');
|
||||
this.set('displayName', 'An Option');
|
||||
this.set('mountType', '');
|
||||
this.set('disabled', false);
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const spy = sinon.spy();
|
||||
this.set('onRadioChange', spy);
|
||||
await render(hbs`<BoxRadio
|
||||
@type={{this.type}}
|
||||
@glyph={{this.type}}
|
||||
@displayName={{this.displayName}}
|
||||
@onRadioChange={{this.onRadioChange}}
|
||||
@disabled={{this.disabled}}
|
||||
/>`);
|
||||
|
||||
assert.dom(this.element).hasText('An Option', 'shows the display name of the option');
|
||||
assert.dom('.tooltip').doesNotExist('tooltip does not exist when disabled is false');
|
||||
await click('[data-test-mount-type="aws"]');
|
||||
assert.ok(spy.calledOnce, 'calls the radio change function when option clicked');
|
||||
});
|
||||
|
||||
test('it renders correctly when disabled', async function (assert) {
|
||||
const spy = sinon.spy();
|
||||
this.set('onRadioChange', spy);
|
||||
await render(hbs`<BoxRadio
|
||||
@type={{this.type}}
|
||||
@glyph={{this.type}}
|
||||
@displayName={{this.displayName}}
|
||||
@onRadioChange={{this.onRadioChange}}
|
||||
@disabled={{true}}
|
||||
/>`);
|
||||
|
||||
assert.dom(this.element).hasText('An Option', 'shows the display name of the option');
|
||||
assert.dom('.ember-basic-dropdown-trigger').exists('tooltip exists');
|
||||
await click('[data-test-mount-type="aws"]');
|
||||
assert.ok(spy.notCalled, 'does not call the radio change function when option is clicked');
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ module('Integration | Component | icon', function (hooks) {
|
||||
assert.dom('.al').hasAttribute('aria-label', 'Testing', 'renders aria-label');
|
||||
|
||||
await render(hbs`<Icon @name="vault-logo" @size="24"/>`);
|
||||
assert.dom('.hs-icon').hasClass('hs-icon-xl', 'adds the larger size class');
|
||||
assert.dom('.hs-icon').hasClass('hs-icon-xlm', 'adds the larger size class');
|
||||
|
||||
const promise = waitForError();
|
||||
render(hbs`<Icon @name="vault-logo" @size="12"/>`);
|
||||
|
||||
@@ -60,11 +60,9 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
|
||||
);
|
||||
await component.selectType('aws');
|
||||
await component.next();
|
||||
assert.strictEqual(component.pathValue, 'aws', 'sets the value of the type');
|
||||
await component.back();
|
||||
await component.selectType('approle');
|
||||
await component.next();
|
||||
assert.strictEqual(component.pathValue, 'approle', 'updates the value of the type');
|
||||
});
|
||||
|
||||
@@ -73,7 +71,6 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
|
||||
);
|
||||
await component.selectType('approle');
|
||||
await component.next();
|
||||
assert.strictEqual(this.model.type, 'approle', 'Updates type on model');
|
||||
assert.strictEqual(component.pathValue, 'approle', 'defaults to approle (first in the list)');
|
||||
await component.path('newpath');
|
||||
@@ -82,7 +79,6 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
assert.strictEqual(this.model.type, '', 'Clears type on back');
|
||||
assert.strictEqual(this.model.path, 'newpath', 'Path is still newPath');
|
||||
await component.selectType('aws');
|
||||
await component.next();
|
||||
assert.strictEqual(this.model.type, 'aws', 'Updates type on model');
|
||||
assert.strictEqual(component.pathValue, 'newpath', 'keeps custom path value');
|
||||
});
|
||||
@@ -92,7 +88,6 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
|
||||
);
|
||||
await component.selectType('github');
|
||||
await component.next();
|
||||
await component.toggleOptions();
|
||||
assert
|
||||
.dom('[data-test-input="config.tokenType"]')
|
||||
@@ -148,11 +143,9 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
|
||||
);
|
||||
await component.selectType('azure');
|
||||
await component.next();
|
||||
assert.strictEqual(component.pathValue, 'azure', 'sets the value of the type');
|
||||
await component.back();
|
||||
await component.selectType('nomad');
|
||||
await component.next();
|
||||
assert.strictEqual(component.pathValue, 'nomad', 'updates the value of the type');
|
||||
});
|
||||
|
||||
@@ -161,7 +154,6 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
|
||||
);
|
||||
await component.selectType('kv');
|
||||
await component.next();
|
||||
assert.strictEqual(this.model.type, 'kv', 'Updates type on model');
|
||||
assert.strictEqual(component.pathValue, 'kv', 'path matches mount type');
|
||||
await component.path('newpath');
|
||||
@@ -170,7 +162,6 @@ module('Integration | Component | mount backend form', function (hooks) {
|
||||
assert.strictEqual(this.model.type, '', 'Clears type on back');
|
||||
assert.strictEqual(this.model.path, 'newpath', 'path is still newpath');
|
||||
await component.selectType('ssh');
|
||||
await component.next();
|
||||
assert.strictEqual(this.model.type, 'ssh', 'Updates type on model');
|
||||
assert.strictEqual(component.pathValue, 'newpath', 'path stays the same');
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
|
||||
this.setType = sinon.spy();
|
||||
});
|
||||
|
||||
test('it calls secrets setMountType only on next click', async function (assert) {
|
||||
test('it calls secrets setMountType when type is selected', async function (assert) {
|
||||
const spy = sinon.spy();
|
||||
this.set('setType', spy);
|
||||
await render(hbs`<MountBackend::TypeForm @mountType="secret" @setMountType={{this.setType}} />`);
|
||||
@@ -31,17 +31,11 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
|
||||
assert
|
||||
.dom('[data-test-mount-type]')
|
||||
.exists({ count: secretTypes.length }, 'Renders all mountable engines');
|
||||
await click(`[data-test-mount-type="nomad"]`);
|
||||
assert.dom(`[data-test-mount-type="nomad"] input`).isChecked(`ssh is checked`);
|
||||
assert.ok(spy.notCalled, 'callback not called');
|
||||
await click(`[data-test-mount-type="ssh"]`);
|
||||
assert.dom(`[data-test-mount-type="ssh"] input`).isChecked(`ssh is checked`);
|
||||
assert.ok(spy.notCalled, 'callback not called');
|
||||
await click('[data-test-mount-next]');
|
||||
assert.ok(spy.calledOnceWith('ssh'));
|
||||
});
|
||||
|
||||
test('it calls auth setMountType only on next click', async function (assert) {
|
||||
test('it calls auth setMountType when type is selected', async function (assert) {
|
||||
const spy = sinon.spy();
|
||||
this.set('setType', spy);
|
||||
await render(hbs`<MountBackend::TypeForm @setMountType={{this.setType}} />`);
|
||||
@@ -50,13 +44,7 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
|
||||
.dom('[data-test-mount-type]')
|
||||
.exists({ count: authTypes.length }, 'Renders all mountable auth methods');
|
||||
await click(`[data-test-mount-type="okta"]`);
|
||||
assert.dom(`[data-test-mount-type="okta"] input`).isChecked(`ssh is checked`);
|
||||
assert.ok(spy.notCalled, 'callback not called');
|
||||
await click(`[data-test-mount-type="github"]`);
|
||||
assert.dom(`[data-test-mount-type="github"] input`).isChecked(`ssh is checked`);
|
||||
assert.ok(spy.notCalled, 'callback not called');
|
||||
await click('[data-test-mount-next]');
|
||||
assert.ok(spy.calledOnceWith('github'));
|
||||
assert.ok(spy.calledOnceWith('okta'));
|
||||
});
|
||||
|
||||
module('Enterprise', function (hooks) {
|
||||
|
||||
@@ -23,7 +23,7 @@ module('Integration | Component overview-card', function (hooks) {
|
||||
|
||||
test('it returns card title, ', async function (assert) {
|
||||
await render(hbs`<OverviewCard @cardTitle={{this.cardTitle}}/>`);
|
||||
const titleText = this.element.querySelector('.title').innerText;
|
||||
const titleText = this.element.querySelector('h3').innerText;
|
||||
assert.strictEqual(titleText, 'Card title');
|
||||
});
|
||||
test('it returns card subtext, ', async function (assert) {
|
||||
@@ -32,8 +32,10 @@ module('Integration | Component overview-card', function (hooks) {
|
||||
assert.strictEqual(titleText, 'This is subtext for card');
|
||||
});
|
||||
test('it returns card action text', async function (assert) {
|
||||
await render(hbs`<OverviewCard @cardTitle={{this.cardTitle}} @actionText={{this.actionText}}/>`);
|
||||
await render(
|
||||
hbs`<OverviewCard @cardTitle={{this.cardTitle}} @actionText={{this.actionText}} @actionTo="route"/>`
|
||||
);
|
||||
const titleText = this.element.querySelector('a').innerText;
|
||||
assert.strictEqual(titleText, 'View card ');
|
||||
assert.strictEqual(titleText, 'View card');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ module('Integration | Component | replication-primary-card', function (hooks) {
|
||||
|
||||
assert.dom('[data-test-hasError]').doesNotExist('shows no error for non-State cards');
|
||||
|
||||
assert.dom('.last-wal').includesText(title);
|
||||
assert.dom('.card-title').includesText(title);
|
||||
assert.dom('[data-test-description]').includesText(description);
|
||||
assert.dom('[data-test-metric]').includesText(metric);
|
||||
});
|
||||
|
||||
@@ -5,30 +5,25 @@
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
const TOTAL = 15;
|
||||
const CARD_TITLE = 'Connections';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component selectable-card', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set('total', TOTAL);
|
||||
this.set('cardTitle', CARD_TITLE);
|
||||
this.onClick = sinon.spy();
|
||||
});
|
||||
|
||||
test('it shows the card total', async function (assert) {
|
||||
await render(hbs`<SelectableCard @total={{this.total}} @cardTitle={{this.cardTitle}}/>`);
|
||||
const titleNumber = this.element.querySelector('.title-number').innerText;
|
||||
|
||||
assert.strictEqual(titleNumber, '15');
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<SelectableCard @onClick={{this.onClick}}/>`);
|
||||
await click('.selectable-card');
|
||||
assert.ok(this.onClick.calledOnce, 'calls on click');
|
||||
});
|
||||
|
||||
test('it returns card title, ', async function (assert) {
|
||||
await render(hbs`<SelectableCard @total={{1}} @cardTitle={{this.cardTitle}}/>`);
|
||||
const titleText = this.element.querySelector('.title').innerText;
|
||||
assert.strictEqual(titleText, 'Connections');
|
||||
test('it renders block content', async function (assert) {
|
||||
await render(hbs`<SelectableCard @onClick={{this.onClick}}>hello</SelectableCard>`);
|
||||
assert.dom('.selectable-card').hasText('hello');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,12 +10,11 @@ export default {
|
||||
...fields,
|
||||
header: text('[data-test-mount-form-header]'),
|
||||
submit: clickable('[data-test-mount-submit]'),
|
||||
next: clickable('[data-test-mount-next]'),
|
||||
back: clickable('[data-test-mount-back]'),
|
||||
path: fillable('[data-test-input="path"]'),
|
||||
toggleOptions: clickable('[data-test-toggle-group="Method Options"]'),
|
||||
pathValue: value('[data-test-input="path"]'),
|
||||
types: collection('[data-test-mount-type-radio] input', {
|
||||
types: collection('[data-test-mount-type]', {
|
||||
select: clickable(),
|
||||
id: attribute('id'),
|
||||
}),
|
||||
@@ -26,9 +25,9 @@ export default {
|
||||
async mount(type, path) {
|
||||
await this.selectType(type);
|
||||
if (path) {
|
||||
await this.next().path(path).submit();
|
||||
await this.path(path).submit();
|
||||
} else {
|
||||
await this.next().submit();
|
||||
await this.submit();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user