mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI: Hds::Dropdown replace PopupMenu (#25321)
This commit is contained in:
3
changelog/25321.txt
Normal file
3
changelog/25321.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Use Hds::Dropdown component to replace list view popup menus
|
||||
```
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import { assert } from '@ember/debug';
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
flashMessages: service(),
|
||||
params: null,
|
||||
successMessage() {
|
||||
return 'Save was successful';
|
||||
},
|
||||
errorMessage() {
|
||||
return 'There was an error saving';
|
||||
},
|
||||
onError(model) {
|
||||
if (model && model.rollbackAttributes) {
|
||||
model.rollbackAttributes();
|
||||
}
|
||||
},
|
||||
onSuccess() {},
|
||||
// override and return a promise
|
||||
transaction() {
|
||||
assert('override transaction call in an extension of popup-base', false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
performTransaction() {
|
||||
const args = [...arguments];
|
||||
const messageArgs = this.messageArgs(...args);
|
||||
return this.transaction(...args)
|
||||
.then(() => {
|
||||
this.onSuccess();
|
||||
this.flashMessages.success(this.successMessage(...messageArgs));
|
||||
})
|
||||
.catch((e) => {
|
||||
this.onError(...messageArgs);
|
||||
this.flashMessages.success(this.errorMessage(e, ...messageArgs));
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -3,25 +3,38 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Base from './_popup-base';
|
||||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
export default Base.extend({
|
||||
messageArgs(model) {
|
||||
const type = model.get('identityType');
|
||||
const id = model.id;
|
||||
return [type, id];
|
||||
},
|
||||
export default class IdentityPopupAlias extends Component {
|
||||
@service flashMessages;
|
||||
@tracked showConfirmModal = false;
|
||||
|
||||
successMessage(type, id) {
|
||||
return `Successfully deleted ${type}: ${id}`;
|
||||
},
|
||||
onSuccess(type, id) {
|
||||
if (this.args.onSuccess) {
|
||||
this.args.onSuccess();
|
||||
}
|
||||
this.flashMessages.success(`Successfully deleted ${type}: ${id}`);
|
||||
}
|
||||
onError(err, type, id) {
|
||||
if (this.args.onError) {
|
||||
this.args.onError();
|
||||
}
|
||||
const error = errorMessage(err);
|
||||
this.flashMessages.danger(`There was a problem deleting ${type}: ${id} - ${error}`);
|
||||
}
|
||||
|
||||
errorMessage(e, type, id) {
|
||||
const error = e.errors ? e.errors.join(' ') : e.message;
|
||||
return `There was a problem deleting ${type}: ${id} - ${error}`;
|
||||
},
|
||||
|
||||
transaction(model) {
|
||||
return model.destroyRecord();
|
||||
},
|
||||
});
|
||||
@action
|
||||
async deleteAlias() {
|
||||
const { identityType, id } = this.args.item;
|
||||
try {
|
||||
await this.args.item.destroyRecord();
|
||||
this.onSuccess(identityType, id);
|
||||
} catch (e) {
|
||||
this.onError(e, identityType, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,37 +3,44 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
import Base from './_popup-base';
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
export default Base.extend({
|
||||
model: alias('params.firstObject'),
|
||||
export default class IdentityPopupMembers extends Component {
|
||||
@service flashMessages;
|
||||
@tracked showConfirmModal = false;
|
||||
|
||||
groupArray: computed('params', function () {
|
||||
return this.params.objectAt(1);
|
||||
}),
|
||||
onSuccess(memberId) {
|
||||
if (this.args.onSuccess) {
|
||||
this.args.onSuccess();
|
||||
}
|
||||
this.flashMessages.success(`Successfully removed '${memberId}' from the group`);
|
||||
}
|
||||
onError(err, memberId) {
|
||||
if (this.args.onError) {
|
||||
this.args.onError();
|
||||
}
|
||||
const error = errorMessage(err);
|
||||
this.flashMessages.danger(`There was a problem removing '${memberId}' from the group - ${error}`);
|
||||
}
|
||||
|
||||
memberId: computed('params', function () {
|
||||
return this.params.objectAt(2);
|
||||
}),
|
||||
transaction() {
|
||||
const members = this.args.model[this.args.groupArray];
|
||||
this.args.model[this.args.groupArray] = members.without(this.args.memberId);
|
||||
return this.args.model.save();
|
||||
}
|
||||
|
||||
messageArgs(/*model, groupArray, memberId*/) {
|
||||
return [...arguments];
|
||||
},
|
||||
|
||||
successMessage(model, groupArray, memberId) {
|
||||
return `Successfully removed '${memberId}' from the group`;
|
||||
},
|
||||
|
||||
errorMessage(e, model, groupArray, memberId) {
|
||||
const error = e.errors ? e.errors.join(' ') : e.message;
|
||||
return `There was a problem removing '${memberId}' from the group - ${error}`;
|
||||
},
|
||||
|
||||
transaction(model, groupArray, memberId) {
|
||||
const members = model.get(groupArray);
|
||||
model.set(groupArray, members.without(memberId));
|
||||
return model.save();
|
||||
},
|
||||
});
|
||||
@action
|
||||
async removeGroup() {
|
||||
const memberId = this.args.memberId;
|
||||
try {
|
||||
await this.transaction();
|
||||
this.onSuccess(memberId);
|
||||
} catch (e) {
|
||||
this.onError(e, memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,45 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Base from './_popup-base';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
export default Base.extend({
|
||||
model: alias('params.firstObject'),
|
||||
key: computed('params', function () {
|
||||
return this.params.objectAt(1);
|
||||
}),
|
||||
export default class IdentityPopupMetadata extends Component {
|
||||
@service flashMessages;
|
||||
@tracked showConfirmModal = false;
|
||||
|
||||
messageArgs(model, key) {
|
||||
return [model, key];
|
||||
},
|
||||
onSuccess(key) {
|
||||
if (this.args.onSuccess) {
|
||||
this.args.onSuccess();
|
||||
}
|
||||
this.flashMessages.success(`Successfully removed '${key}' from metadata`);
|
||||
}
|
||||
onError(err, key) {
|
||||
if (this.args.onError) {
|
||||
this.args.onError();
|
||||
}
|
||||
const error = errorMessage(err);
|
||||
this.flashMessages.danger(`There was a problem removing '${key}' from the metadata - ${error}`);
|
||||
}
|
||||
|
||||
successMessage(model, key) {
|
||||
return `Successfully removed '${key}' from metadata`;
|
||||
},
|
||||
errorMessage(e, model, key) {
|
||||
const error = e.errors ? e.errors.join(' ') : e.message;
|
||||
return `There was a problem removing '${key}' from the metadata - ${error}`;
|
||||
},
|
||||
transaction() {
|
||||
const metadata = this.args.model.metadata;
|
||||
delete metadata[this.args.key];
|
||||
this.args.model.metadata = { ...metadata };
|
||||
return this.args.model.save();
|
||||
}
|
||||
|
||||
transaction(model, key) {
|
||||
const metadata = model.metadata;
|
||||
delete metadata[key];
|
||||
model.set('metadata', { ...metadata });
|
||||
return model.save();
|
||||
},
|
||||
});
|
||||
@action
|
||||
async removeMetadata() {
|
||||
const key = this.args.key;
|
||||
try {
|
||||
await this.transaction();
|
||||
this.onSuccess(key);
|
||||
} catch (e) {
|
||||
this.onError(e, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,47 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
import Base from './_popup-base';
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default Base.extend({
|
||||
model: alias('params.firstObject'),
|
||||
policyName: computed('params', function () {
|
||||
return this.params.objectAt(1);
|
||||
}),
|
||||
export default class IdentityPopupPolicy extends Component {
|
||||
@service flashMessages;
|
||||
@tracked showConfirmModal = false;
|
||||
|
||||
messageArgs(model, policyName) {
|
||||
return [model, policyName];
|
||||
},
|
||||
onSuccess(policyName, modelId) {
|
||||
if (this.args.onSuccess) {
|
||||
this.args.onSuccess();
|
||||
}
|
||||
this.flashMessages.success(`Successfully removed '${policyName}' policy from ${modelId}`);
|
||||
}
|
||||
onError(err, policyName) {
|
||||
if (this.args.onError) {
|
||||
this.args.onError();
|
||||
}
|
||||
const error = errorMessage(err);
|
||||
this.flashMessages.danger(`There was a problem removing '${policyName}' policy - ${error}`);
|
||||
}
|
||||
|
||||
successMessage(model, policyName) {
|
||||
return `Successfully removed '${policyName}' policy from ${model.id} `;
|
||||
},
|
||||
transaction() {
|
||||
const policies = this.args.model.policies;
|
||||
this.args.model.policies = policies.without(this.args.policyName);
|
||||
return this.args.model.save();
|
||||
}
|
||||
|
||||
errorMessage(e, model, policyName) {
|
||||
const error = e.errors ? e.errors.join(' ') : e.message;
|
||||
return `There was a problem removing '${policyName}' policy - ${error}`;
|
||||
},
|
||||
|
||||
transaction(model, policyName) {
|
||||
const policies = model.get('policies');
|
||||
model.set('policies', policies.without(policyName));
|
||||
return model.save();
|
||||
},
|
||||
});
|
||||
@action
|
||||
async removePolicy() {
|
||||
const {
|
||||
policyName,
|
||||
model: { id },
|
||||
} = this.args;
|
||||
try {
|
||||
await this.transaction();
|
||||
this.onSuccess(policyName, id);
|
||||
} catch (e) {
|
||||
this.onError(e, policyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
ui/app/components/secret-list/aws-role-item.js
Normal file
11
ui/app/components/secret-list/aws-role-item.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class SecretListAwsRoleItemComponent extends Component {
|
||||
@tracked showConfirmModal = false;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import { action } from '@ember/object';
|
||||
|
||||
export default class DatabaseListItem extends Component {
|
||||
@tracked roleType = '';
|
||||
@tracked actionRunning = null;
|
||||
@service store;
|
||||
@service flashMessages;
|
||||
|
||||
@@ -41,6 +42,7 @@ export default class DatabaseListItem extends Component {
|
||||
resetConnection(id) {
|
||||
const { backend } = this.args.item;
|
||||
const adapter = this.store.adapterFor('database/connection');
|
||||
this.actionRunning = 'reset';
|
||||
adapter
|
||||
.resetConnection(backend, id)
|
||||
.then(() => {
|
||||
@@ -48,12 +50,14 @@ export default class DatabaseListItem extends Component {
|
||||
})
|
||||
.catch((e) => {
|
||||
this.flashMessages.danger(e.errors);
|
||||
});
|
||||
})
|
||||
.finally(() => (this.actionRunning = null));
|
||||
}
|
||||
@action
|
||||
rotateRootCred(id) {
|
||||
const { backend } = this.args.item;
|
||||
const adapter = this.store.adapterFor('database/connection');
|
||||
this.actionRunning = 'rotateRoot';
|
||||
adapter
|
||||
.rotateRootCredentials(backend, id)
|
||||
.then(() => {
|
||||
@@ -61,12 +65,14 @@ export default class DatabaseListItem extends Component {
|
||||
})
|
||||
.catch((e) => {
|
||||
this.flashMessages.danger(e.errors);
|
||||
});
|
||||
})
|
||||
.finally(() => (this.actionRunning = null));
|
||||
}
|
||||
@action
|
||||
rotateRoleCred(id) {
|
||||
const { backend } = this.args.item;
|
||||
const adapter = this.store.adapterFor('database/credential');
|
||||
this.actionRunning = 'rotateRole';
|
||||
adapter
|
||||
.rotateRoleCredentials(backend, id)
|
||||
.then(() => {
|
||||
@@ -74,6 +80,7 @@ export default class DatabaseListItem extends Component {
|
||||
})
|
||||
.catch((e) => {
|
||||
this.flashMessages.danger(e.errors);
|
||||
});
|
||||
})
|
||||
.finally(() => (this.actionRunning = null));
|
||||
}
|
||||
}
|
||||
|
||||
11
ui/app/components/secret-list/item.js
Normal file
11
ui/app/components/secret-list/item.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class SecretListItemComponent extends Component {
|
||||
@tracked showConfirmModal = false;
|
||||
}
|
||||
11
ui/app/components/secret-list/ssh-role-item.js
Normal file
11
ui/app/components/secret-list/ssh-role-item.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class SecretListSshRoleItemComponent extends Component {
|
||||
@tracked showConfirmModal = false;
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import ListController from 'core/mixins/list-controller';
|
||||
export default Controller.extend(ListController, {
|
||||
flashMessages: service(),
|
||||
|
||||
entityToDisable: null,
|
||||
itemToDelete: null,
|
||||
|
||||
// callback from HDS pagination to set the queryParams page
|
||||
get paginationQueryParams() {
|
||||
return (page) => {
|
||||
@@ -33,7 +36,8 @@ export default Controller.extend(ListController, {
|
||||
this.flashMessages.success(
|
||||
`There was a problem deleting ${type}: ${id} - ${e.errors.join(' ') || e.message}`
|
||||
);
|
||||
});
|
||||
})
|
||||
.finally(() => this.set('itemToDelete', null));
|
||||
},
|
||||
|
||||
toggleDisabled(model) {
|
||||
@@ -51,7 +55,8 @@ export default Controller.extend(ListController, {
|
||||
this.flashMessages.success(
|
||||
`There was a problem ${action[1]} ${type}: ${id} - ${e.errors.join(' ') || e.message}`
|
||||
);
|
||||
});
|
||||
})
|
||||
.finally(() => this.set('entityToDisable', null));
|
||||
},
|
||||
reloadRecord(model) {
|
||||
model.reload();
|
||||
|
||||
@@ -21,8 +21,8 @@ export default Controller.extend({
|
||||
|
||||
filterFocused: false,
|
||||
|
||||
// set via the route `loading` action
|
||||
isLoading: false,
|
||||
isLoading: false, // set via the route `loading` action
|
||||
policyToDelete: null, // set when clicking 'Delete' from popup menu
|
||||
|
||||
// callback from HDS pagination to set the queryParams page
|
||||
get paginationQueryParams() {
|
||||
@@ -77,7 +77,8 @@ export default Controller.extend({
|
||||
flash.danger(
|
||||
`There was an error deleting the ${policyType.toUpperCase()} policy "${name}": ${errors}.`
|
||||
);
|
||||
});
|
||||
})
|
||||
.finally(() => this.set('policyToDelete', null));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export default class VaultClusterSecretsBackendController extends Controller {
|
||||
@tracked secretEngineOptions = [];
|
||||
@tracked selectedEngineType = null;
|
||||
@tracked selectedEngineName = null;
|
||||
@tracked engineToDisable = null;
|
||||
|
||||
get sortedDisplayableBackends() {
|
||||
// show supported secret engines first and then organize those by id.
|
||||
@@ -80,6 +81,8 @@ export default class VaultClusterSecretsBackendController extends Controller {
|
||||
this.flashMessages.danger(
|
||||
`There was an error disabling the ${engineType} Secrets Engine at ${path}: ${err.errors.join(' ')}.`
|
||||
);
|
||||
} finally {
|
||||
this.engineToDisable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +84,5 @@ export default IdentityModel.extend({
|
||||
canEdit: alias('updatePath.canUpdate'),
|
||||
|
||||
aliasPath: lazyCapabilities(apiPath`identity/group-alias`),
|
||||
canAddAlias: computed('aliasPath.canCreate', 'type', 'alias', function () {
|
||||
const type = this.type;
|
||||
const alias = this.alias;
|
||||
// internal groups can't have aliases, and external groups can only have one
|
||||
if (type === 'internal' || alias) {
|
||||
return false;
|
||||
}
|
||||
return this.aliasPath.canCreate;
|
||||
}),
|
||||
canAddAlias: alias('aliasPath.canCreate'),
|
||||
});
|
||||
|
||||
@@ -101,12 +101,12 @@ export default class OidcClientModel extends Model {
|
||||
// CAPABILITIES //
|
||||
@lazyCapabilities(apiPath`identity/oidc/client/${'name'}`, 'name') clientPath;
|
||||
get canRead() {
|
||||
return this.clientPath.get('canRead');
|
||||
return this.clientPath.get('canRead') !== false;
|
||||
}
|
||||
get canEdit() {
|
||||
return this.clientPath.get('canUpdate');
|
||||
return this.clientPath.get('canUpdate') !== false;
|
||||
}
|
||||
get canDelete() {
|
||||
return this.clientPath.get('canDelete');
|
||||
return this.clientPath.get('canDelete') !== false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ export default class OidcProviderModel extends Model {
|
||||
|
||||
@lazyCapabilities(apiPath`identity/oidc/provider/${'name'}`, 'name') providerPath;
|
||||
get canRead() {
|
||||
return this.providerPath.get('canRead');
|
||||
return this.providerPath.get('canRead') !== false;
|
||||
}
|
||||
get canEdit() {
|
||||
return this.providerPath.get('canUpdate');
|
||||
return this.providerPath.get('canUpdate') !== false;
|
||||
}
|
||||
get canDelete() {
|
||||
return this.providerPath.get('canDelete');
|
||||
return this.providerPath.get('canDelete') !== false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<Identity::PopupAlias @params={{array item}} />
|
||||
<Identity::PopupAlias @item={{item}} />
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if @model.canEdit}}
|
||||
<Identity::PopupMembers @params={{array @model "memberGroupIds" gid}} />
|
||||
<Identity::PopupMembers @model={{@model}} @groupArray="memberGroupIds" @memberId={{gid}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if @model.canEdit}}
|
||||
<Identity::PopupMembers @params={{array @model "memberEntityIds" gid}} />
|
||||
<Identity::PopupMembers @model={{@model}} @groupArray="memberEntityIds" @memberId={{gid}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if @model.canEdit}}
|
||||
<Identity::PopupMetadata @params={{array @model key}} />
|
||||
<Identity::PopupMetadata @key={{key}} @model={{@model}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if @model.canEdit}}
|
||||
<Identity::PopupPolicy @params={{array @model policyName}} />
|
||||
<Identity::PopupPolicy @model={{@model}} @policyName={{policyName}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,43 +3,38 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<PopupMenu @name="alias-menu">
|
||||
{{#let (get this.params "0") as |item|}}
|
||||
<nav class="menu" aria-label="navigation for managing aliases">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.identity.aliases.show"
|
||||
@models={{array (pluralize item.parentType) item.id "details"}}
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if item.updatePath.isPending}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if item.canEdit}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.identity.aliases.edit"
|
||||
@models={{array (pluralize item.parentType) item.id}}
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.canDelete}}
|
||||
<ConfirmAction
|
||||
@buttonText="Delete"
|
||||
@isInDropdown={{true}}
|
||||
@onConfirmAction={{action "performTransaction" item}}
|
||||
data-test-item-delete
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
{{/let}}
|
||||
</PopupMenu>
|
||||
<div class="has-text-right">
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Alias management options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.identity.aliases.show"
|
||||
@models={{array (pluralize @item.parentType) @item.id "details"}}
|
||||
/>
|
||||
{{#if @item.updatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if @item.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.identity.aliases.edit"
|
||||
@models={{array (pluralize @item.parentType) @item.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<dd.Interactive @text="Remove" @color="critical" {{on "click" (fn (mut this.showConfirmModal) true)}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal @color="critical" @onClose={{fn (mut this.showConfirmModal) false}} @onConfirm={{this.deleteAlias}} />
|
||||
{{/if}}
|
||||
@@ -3,18 +3,24 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<PopupMenu @name="member-edit-menu">
|
||||
<nav class="menu" aria-label="navigation for managing identity members">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<ConfirmAction
|
||||
@buttonText="Remove"
|
||||
@confirmTitle="Remove this group?"
|
||||
@isInDropdown={{true}}
|
||||
@confirmMessage="This may affect permissions for this group."
|
||||
@onConfirmAction={{action "performTransaction" this.model this.groupArray this.memberId}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<div class="has-text-right">
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Identity member options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive @text="Remove" @color="critical" {{on "click" (fn (mut this.showConfirmModal) true)}} />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@onClose={{fn (mut this.showConfirmModal) false}}
|
||||
@onConfirm={{this.removeGroup}}
|
||||
@confirmTitle="Remove this group?"
|
||||
@confirmMessage="This may affect permissions for this group."
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -3,18 +3,19 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<PopupMenu @name="metadata-edit-menu">
|
||||
<nav class="menu" aria-label="navigation for managing identity metadata">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<ConfirmAction
|
||||
@buttonText="Remove"
|
||||
@confirmTitle="Remove metadata?"
|
||||
@isInDropdown={{true}}
|
||||
@confirmMessage="This data may be used outside of Vault."
|
||||
@onConfirmAction={{action "performTransaction" this.model this.key}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<div class="has-text-right">
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon @icon="more-horizontal" @text="Metadata options" @hasChevron={{false}} data-test-popup-menu-trigger />
|
||||
<dd.Interactive @text="Remove" @color="critical" {{on "click" (fn (mut this.showConfirmModal) true)}} />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@onClose={{fn (mut this.showConfirmModal) false}}
|
||||
@onConfirm={{this.removeMetadata}}
|
||||
@confirmTitle="Remove metadata?"
|
||||
@confirmMessage="This data may be used outside of Vault."
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -3,28 +3,30 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<PopupMenu @name="policy-menu">
|
||||
<nav class="menu" aria-label="navigation for managing identity policies">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.policy.show" @models={{array "acl" this.policyName}}>
|
||||
View Policy
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.policy.edit" @models={{array "acl" this.policyName}}>
|
||||
Edit Policy
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="action">
|
||||
<ConfirmAction
|
||||
@buttonText="Remove from {{this.model.identityType}}"
|
||||
@confirmTitle="Remove this policy?"
|
||||
@isInDropdown={{true}}
|
||||
@confirmMessage="This policy may affect permissions to access Vault data."
|
||||
@onConfirmAction={{action "performTransaction" this.model this.policyName}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<div class="has-text-right">
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Identity policy management options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive @text="View policy" @route="vault.cluster.policy.show" @models={{array "acl" @policyName}} />
|
||||
<dd.Interactive @text="Edit policy" @route="vault.cluster.policy.edit" @models={{array "acl" @policyName}} />
|
||||
<dd.Interactive
|
||||
@text="Remove from {{@model.identityType}}"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.showConfirmModal) true)}}
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@onClose={{fn (mut this.showConfirmModal) false}}
|
||||
@onConfirm={{this.removePolicy}}
|
||||
@confirmTitle="Remove this policy?"
|
||||
@confirmMessage="This policy may affect permissions to access Vault data."
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -19,30 +19,26 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="navigation for managing login enforcements">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.mfa.enforcements.enforcement"
|
||||
@model={{@model.name}}
|
||||
data-test-list-item-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.mfa.enforcements.enforcement.edit"
|
||||
@model={{@model.name}}
|
||||
data-test-list-item-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage M-F-A enforcement"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.mfa.enforcements.enforcement"
|
||||
@model={{@model.name}}
|
||||
data-test-list-item-link="details"
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.mfa.enforcements.enforcement.edit"
|
||||
@model={{@model.name}}
|
||||
data-test-list-item-link="edit"
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,30 +30,26 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="navigation for managing MFA methods">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.mfa.methods.method"
|
||||
@model={{@model.id}}
|
||||
data-test-mfa-method-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.mfa.methods.method.edit"
|
||||
@model={{@model.id}}
|
||||
data-test-mfa-method-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage M-F-A method"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.mfa.methods.method"
|
||||
@model={{@model.id}}
|
||||
data-test-mfa-method-menu-link="details"
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.mfa.methods.method.edit"
|
||||
@model={{@model.id}}
|
||||
data-test-mfa-method-menu-link="edit"
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,32 +24,32 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="navigation for managing OIDC client {{client.name}}">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.clients.client.details"
|
||||
@model={{client.name}}
|
||||
@disabled={{eq client.canRead false}}
|
||||
data-test-oidc-client-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.clients.client.edit"
|
||||
@model={{client.name}}
|
||||
@disabled={{eq client.canEdit false}}
|
||||
data-test-oidc-client-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{#if (or client.canRead client.canEdit)}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Application nav options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if client.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.oidc.clients.client.details"
|
||||
@model={{client.name}}
|
||||
data-test-oidc-client-menu-link="details"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if client.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.oidc.clients.client.edit"
|
||||
@model={{client.name}}
|
||||
data-test-oidc-client-menu-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,32 +24,32 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="navigation for managing provider {{provider.name}}">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.providers.provider.details"
|
||||
@model={{provider.name}}
|
||||
@disabled={{eq provider.canRead false}}
|
||||
data-test-oidc-provider-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.providers.provider.edit"
|
||||
@model={{provider.name}}
|
||||
@disabled={{not provider.canEdit}}
|
||||
data-test-oidc-provider-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{#if (or provider.canRead provider.canEdit)}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Provider nav options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if provider.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.oidc.providers.provider.details"
|
||||
@model={{provider.name}}
|
||||
data-test-oidc-provider-menu-link="details"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if provider.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.oidc.providers.provider.edit"
|
||||
@model={{provider.name}}
|
||||
data-test-oidc-provider-menu-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,58 +23,60 @@
|
||||
</LinkTo>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu @name="role-aws-nav" @contentClass="is-wide">
|
||||
<nav class="menu" aria-label="navigation for managing A-W-S role {{@item.id}}">
|
||||
<ul class="menu-list">
|
||||
{{#if @item.generatePath.isPending}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else if @item.canGenerate}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
data-test-role-aws-link="generate"
|
||||
>
|
||||
Generate credentials
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.isPending}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canEdit}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<ConfirmAction
|
||||
@buttonText="Delete"
|
||||
@isInDropdown={{true}}
|
||||
@onConfirmAction={{@delete}}
|
||||
data-test-aws-role-delete={{@item.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="manage A-W-S role {{@item.id}}"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @item.generatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else if @item.canGenerate}}
|
||||
<dd.Interactive
|
||||
@text="Generate credentials"
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
data-test-role-aws-link="generate"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.secrets.backend.show"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="show"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.secrets.backend.edit"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<dd.Interactive
|
||||
@text="Delete"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.showConfirmModal) true)}}
|
||||
data-test-aws-role-delete={{@item.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
</LinkedBlock>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal @color="critical" @onClose={{fn (mut this.showConfirmModal) false}} @onConfirm={{@delete}} />
|
||||
{{/if}}
|
||||
@@ -26,77 +26,56 @@
|
||||
</LinkTo>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu" aria-label="navigation for managing database {{@item.id}}">
|
||||
<ul class="menu-list">
|
||||
{{#if @item.canEdit}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="edit" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
|
||||
Edit connection
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canEditRole}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="edit" @secret={{concat "role/" @item.id}} class="has-text-black has-text-weight-semibold">
|
||||
Edit Role
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canReset}}
|
||||
<li class="action">
|
||||
<Hds::Button
|
||||
@text="Reset connection"
|
||||
@color="secondary"
|
||||
class="link"
|
||||
{{on "click" (fn this.resetConnection @item.id)}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (and (eq @item.type "dynamic") @item.canGenerateCredentials)}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
@query={{hash roleType=this.keyTypeValue}}
|
||||
>
|
||||
Generate credentials
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{else if (and (eq @item.type "static") @item.canGetCredentials)}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
@query={{hash roleType=this.keyTypeValue}}
|
||||
>
|
||||
Get credentials
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (and @item.canRotateRoleCredentials (eq this.keyTypeValue "static"))}}
|
||||
<li class="action">
|
||||
<Hds::Button
|
||||
@text="Rotate credentials"
|
||||
@color="secondary"
|
||||
class="link"
|
||||
{{on "click" (fn this.rotateRoleCred @item.id)}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canRotateRoot}}
|
||||
<li class="action">
|
||||
<Hds::Button
|
||||
@text="Rotate root credentials"
|
||||
@color="secondary"
|
||||
class="link"
|
||||
{{on "click" (fn this.rotateRootCred @item.id)}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage database {{@item.id}}"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @item.canEdit}}
|
||||
<dd.Interactive @text="Edit connection" @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} />
|
||||
{{/if}}
|
||||
{{#if @item.canEditRole}}
|
||||
<dd.Interactive @text="Edit Role" @route="vault.cluster.secrets.backend.edit" @model={{concat "role/" @item.id}} />
|
||||
{{/if}}
|
||||
{{#if @item.canReset}}
|
||||
<dd.Interactive
|
||||
@text="Reset connection"
|
||||
@icon={{if (eq this.actionRunning "reset") "loading"}}
|
||||
{{on "click" (fn this.resetConnection @item.id)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (eq @item.type "dynamic") @item.canGenerateCredentials)}}
|
||||
<dd.Interactive
|
||||
@text="Generate credentials"
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
@query={{hash roleType=this.keyTypeValue}}
|
||||
/>
|
||||
{{else if (and (eq @item.type "static") @item.canGetCredentials)}}
|
||||
<dd.Interactive
|
||||
@text="Get credentials"
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
@query={{hash roleType=this.keyTypeValue}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and @item.canRotateRoleCredentials (eq this.keyTypeValue "static"))}}
|
||||
<dd.Interactive
|
||||
@text="Rotate credentials"
|
||||
@icon={{if (eq this.actionRunning "rotateRole") "loading"}}
|
||||
{{on "click" (fn this.rotateRoleCred @item.id)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canRotateRoot}}
|
||||
<dd.Interactive
|
||||
@text="Rotate root credentials"
|
||||
@icon={{if (eq this.actionRunning "rotateRoot") "loading"}}
|
||||
{{on "click" (fn this.rotateRootCred @item.id)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
@@ -30,56 +30,57 @@
|
||||
</SecretLink>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu" aria-label="navigation for managing {{@item.id}}">
|
||||
<ul class="menu-list">
|
||||
{{#if @item.isFolder}}
|
||||
<SecretLink @mode="list" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
|
||||
Contents
|
||||
</SecretLink>
|
||||
{{else}}
|
||||
{{#if (or @item.versionPath.isLoading @item.secretPath.isLoading)}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{@item.id}}
|
||||
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
>
|
||||
Details
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canEdit}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="edit"
|
||||
@secret={{@item.id}}
|
||||
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
>
|
||||
Edit
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@buttonText="Delete"
|
||||
@confirmMessage="This will permanently delete this secret."
|
||||
@onConfirmAction={{@delete}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage database {{@item.id}}"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @item.isFolder}}
|
||||
<dd.Interactive @text="Contents" @route="vault.cluster.secrets.backend.list" @model={{@item.id}} />
|
||||
{{else}}
|
||||
{{#if (or @item.versionPath.isLoading @item.secretPath.isLoading)}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.secrets.backend.show"
|
||||
@model={{@item.id}}
|
||||
@query={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
|
||||
/>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{#if @item.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.secrets.backend.edit"
|
||||
@model={{@item.id}}
|
||||
@query={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<dd.Interactive
|
||||
@text="Delete"
|
||||
@color="critical"
|
||||
data-test-confirm-action-trigger
|
||||
{{on "click" (fn (mut this.showConfirmModal) true)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
</LinkedBlock>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@onClose={{fn (mut this.showConfirmModal) false}}
|
||||
@confirmMessage="This will permanently delete this secret."
|
||||
@onConfirm={{@delete}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -36,89 +36,86 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (eq @backendType "ssh")}}
|
||||
<PopupMenu @name="role-ssh-nav">
|
||||
<nav class="menu" aria-label="navigation for managing SSH role {{@item.id}}">
|
||||
<ul class="menu-list">
|
||||
{{#if (eq @item.keyType "otp")}}
|
||||
{{#if @item.generatePath.isPending}}
|
||||
<li class="action">
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
{{else if @item.canGenerate}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="generate"
|
||||
>
|
||||
Generate Credentials
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{else if (eq @item.keyType "ca")}}
|
||||
{{#if @item.signPath.isPending}}
|
||||
<li class="action">
|
||||
<li class="action">
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
</li>
|
||||
{{else if @item.canGenerate}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.secrets.backend.sign"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="generate"
|
||||
>
|
||||
Sign Keys
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if @item.canEditZeroAddress}}
|
||||
<li class="action">
|
||||
<Hds::Button
|
||||
disabled={{@loadingToggleZeroAddress}}
|
||||
class="link"
|
||||
@icon={{if @loadingToggleZeroAddress "loading"}}
|
||||
@isIconOnly={{@loadingToggleZeroAddress}}
|
||||
{{on "click" @toggleZeroAddress}}
|
||||
@text={{if @item.zeroAddress "Disable Zero Address" "Enable Zero Address"}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.isPending}}
|
||||
<li class="action">
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canEdit}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<ConfirmAction
|
||||
@buttonText="Delete"
|
||||
@isInDropdown={{true}}
|
||||
@onConfirmAction={{@delete}}
|
||||
data-test-ssh-role-delete
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage SSH role {{@item.id}}"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if (eq @item.keyType "otp")}}
|
||||
{{#if @item.generatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else if @item.canGenerate}}
|
||||
<dd.Interactive
|
||||
@text="Generate credentials"
|
||||
@route="vault.cluster.secrets.backend.credentials"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="generate"
|
||||
/>
|
||||
{{/if}}
|
||||
{{else if (eq @item.keyType "ca")}}
|
||||
{{#if @item.signPath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else if @item.canGenerate}}
|
||||
<dd.Interactive
|
||||
@text="Sign Keys"
|
||||
@route="vault.cluster.secrets.backend.sign"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="generate"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if @loadingToggleZeroAddress}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else if @item.canEditZeroAddress}}
|
||||
<dd.Interactive
|
||||
@text={{if @item.zeroAddress "Disable Zero Address" "Enable Zero Address"}}
|
||||
{{on "click" @toggleZeroAddress}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if @item.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.secrets.backend.show"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="show"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.secrets.backend.edit"
|
||||
@model={{@item.id}}
|
||||
data-test-role-ssh-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @item.canDelete}}
|
||||
<dd.Interactive
|
||||
@text="Delete"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.showConfirmModal) true)}}
|
||||
data-test-ssh-role-delete
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
</LinkedBlock>
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<ConfirmModal @color="critical" @onClose={{fn (mut this.showConfirmModal) false}} @onConfirm={{@delete}} />
|
||||
{{/if}}
|
||||
@@ -25,26 +25,20 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu" aria-label="navigation for managing transformation item {{@itemPath}}">
|
||||
<ul class="menu-list">
|
||||
{{#if @item.updatePath.canRead}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="show" @secret={{@itemPath}} class="has-text-black has-text-weight-semibold">
|
||||
Details
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.canUpdate}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="edit" @secret={{@itemPath}} class="has-text-black has-text-weight-semibold">
|
||||
Edit
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage transform {{@itemType}}"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @item.updatePath.canRead}}
|
||||
<dd.Interactive @text="Details" @route="vault.cluster.secrets.backend.show" @model={{@itemPath}} />
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.canUpdate}}
|
||||
<dd.Interactive @text="Edit" @route="vault.cluster.secrets.backend.edit" @model={{@itemPath}} />
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,17 +49,13 @@
|
||||
<div class="column is-12 has-text-grey has-text-weight-semibold">
|
||||
<Icon @name="file" class="has-text-grey-light" />
|
||||
{{#if this.isBuiltin}}
|
||||
<ToolTip @verticalPosition="above" @horizontalPosition="left" as |T|>
|
||||
<T.Trigger @tabindex={{false}}>
|
||||
{{@item.id}}
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box">
|
||||
This is a built-in HashiCorp
|
||||
{{@itemType}}. It can't be viewed or edited.
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
<Hds::TooltipButton
|
||||
@text="This is a built-in HashiCorp {{@itemType}}. It can't be viewed or edited."
|
||||
@placement="top-start"
|
||||
aria-label="Why this item cannot be viewed or edited"
|
||||
>
|
||||
{{@item.id}}
|
||||
</Hds::TooltipButton>
|
||||
{{else}}
|
||||
{{@item.id}}
|
||||
{{/if}}
|
||||
|
||||
@@ -3,66 +3,42 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
{{! CBS TODO do not let click if !canRead }}
|
||||
{{#if (eq @options.item "transformation")}}
|
||||
<LinkedBlock
|
||||
@params={{array "vault.cluster.secrets.backend.show" @item.id}}
|
||||
class="list-item-row"
|
||||
data-test-secret-link={{@item.id}}
|
||||
@encode={{true}}
|
||||
@queryParams={{secret-query-params @backendModel.type}}
|
||||
>
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-10">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{@item.id}}
|
||||
@queryParams={{if (eq @backendModel.type "transform") (hash tab="actions") ""}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
>
|
||||
<Icon @name="file" class="has-text-grey-light" />
|
||||
{{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}
|
||||
</SecretLink>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
|
||||
<PopupMenu name="secret-menu" aria-label={{concat "navigation for managing transformation " @item.id}}>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (or @item.versionPath.isLoading @item.secretPath.isLoading)}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if @item.updatePath.canRead}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="show" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
|
||||
Details
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.canUpdate}}
|
||||
<li class="action">
|
||||
<SecretLink @mode="edit" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
|
||||
Edit
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
{{else}}
|
||||
<div class="list-item-row">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12 has-text-grey has-text-weight-semibold">
|
||||
<LinkedBlock
|
||||
@params={{array "vault.cluster.secrets.backend.show" @item.id}}
|
||||
class="list-item-row"
|
||||
data-test-secret-link={{@item.id}}
|
||||
@encode={{true}}
|
||||
@queryParams={{secret-query-params @backendModel.type}}
|
||||
>
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-10">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{@item.id}}
|
||||
@queryParams={{if (eq @backendModel.type "transform") (hash tab="actions") ""}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
>
|
||||
<Icon @name="file" class="has-text-grey-light" />
|
||||
{{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}
|
||||
</div>
|
||||
</SecretLink>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage transformation"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @item.updatePath.canRead}}
|
||||
<dd.Interactive @text="Details" @route="vault.cluster.secrets.backend.show" @model={{@item.id}} />
|
||||
{{/if}}
|
||||
{{#if @item.updatePath.canUpdate}}
|
||||
<dd.Interactive @text="Edit" @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} />
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</LinkedBlock>
|
||||
@@ -151,21 +151,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-1 is-flex-end">
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu" aria-label="copy public key">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<Hds::Copy::Button
|
||||
@text="Copy Public Key"
|
||||
@textToCopy={{meta.public_key}}
|
||||
@isFullWidth={{true}}
|
||||
class="in-dropdown link is-flex-start"
|
||||
{{on "click" (action (set-flash-message "Public key copied!"))}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Public key options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.CopyItem @text={{meta.public_key}} @copyItemTitle="Copy Public Key" />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,15 +5,10 @@
|
||||
|
||||
<div class="wizard-header">
|
||||
{{#unless this.hidePopup}}
|
||||
<PopupMenu @class="wizard-dismiss-menu">
|
||||
<nav class="menu" aria-label="navigation for wizard content">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<Hds::Button @text="Dismiss" @color="secondary" class="link" {{on "click" (action "dismissWizard")}} />
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon @icon="more-horizontal" @text="Wizard dismiss menu" @hasChevron={{false}} class="wizard-dismiss-menu" />
|
||||
<dd.Interactive @text="Dismiss" {{on "click" (action "dismissWizard")}} />
|
||||
</Hds::Dropdown>
|
||||
{{/unless}}
|
||||
<h1 class="title is-5">
|
||||
<Icon @name={{this.glyph}} />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<Identity::PopupAlias @params={{array item}} @onSuccess={{action "onDelete"}} />
|
||||
<Identity::PopupAlias @item={{item}} @onSuccess={{action "onDelete"}} />
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<LinkedBlock
|
||||
@params={{array "vault.cluster.access.identity.show" item.id "details"}}
|
||||
class="list-item-row"
|
||||
data-test-identity-row
|
||||
data-test-identity-row={{item.name}}
|
||||
>
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-7-tablet is-10-mobile">
|
||||
@@ -32,63 +32,53 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu @name="identity-item" @onOpen={{action "reloadRecord" item}}>
|
||||
<nav class="menu" aria-label="navigation for managing identity">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.access.identity.show" @models={{array item.id "details"}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if (or item.isReloading item.updatePath.isPending item.aliasPath.isPending)}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if item.canAddAlias}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.identity.aliases.add"
|
||||
@models={{array (pluralize this.identityType) item.id}}
|
||||
>
|
||||
Create alias
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.canEdit}}
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.access.identity.edit" @model={{item.id}}>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="action">
|
||||
{{#if item.disabled}}
|
||||
<Hds::Button @text="Enable" {{on "click" (action "toggleDisabled" item)}} class="link" />
|
||||
{{else if (eq this.identityType "entity")}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@buttonText="Disable"
|
||||
@confirmMessage="Associated tokens will not be revoked, but cannot be used"
|
||||
@confirmTitle="Disable this entity?"
|
||||
@onConfirmAction={{action "toggleDisabled" item}}
|
||||
@modalColor="warning"
|
||||
/>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.canDelete}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@buttonText="Delete"
|
||||
@onConfirmAction={{action "delete" item}}
|
||||
@confirmTitle="Delete this {{this.identityType}}?"
|
||||
data-test-item-delete
|
||||
/>
|
||||
{{/if}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Identity management options"
|
||||
@hasChevron={{false}}
|
||||
{{on "click" (action "reloadRecord" item)}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.identity.show"
|
||||
@models={{array item.id "details"}}
|
||||
/>
|
||||
{{#if (or item.isReloading item.updatePath.isPending item.aliasPath.isPending)}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if item.canAddAlias}}
|
||||
{{! entities can always add aliases, internal groups cannot have any and external groups can only have one }}
|
||||
{{#if (or (eq this.identityType "entity") (and (eq item.type "external") (not item.alias)))}}
|
||||
<dd.Interactive
|
||||
data-test-popup-menu="create alias"
|
||||
@text="Create alias"
|
||||
@route="vault.cluster.access.identity.aliases.add"
|
||||
@models={{array (pluralize this.identityType) item.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{/if}}
|
||||
{{#if item.canEdit}}
|
||||
<dd.Interactive @text="Edit" @route="vault.cluster.access.identity.edit" @model={{item.id}} />
|
||||
{{#if item.disabled}}
|
||||
<dd.Interactive @text="Enable" {{on "click" (action "toggleDisabled" item)}} />
|
||||
{{else if (eq this.identityType "entity")}}
|
||||
<dd.Interactive @text="Disable" @color="critical" {{on "click" (fn (mut this.entityToDisable) item)}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if item.canDelete}}
|
||||
<dd.Interactive
|
||||
@text="Delete"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.itemToDelete) item)}}
|
||||
data-test-popup-menu="delete"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
@@ -117,4 +107,22 @@
|
||||
@iconPosition="trailing"
|
||||
/>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.entityToDisable}}
|
||||
<ConfirmModal
|
||||
@confirmMessage="Associated tokens will not be revoked, but cannot be used."
|
||||
@confirmTitle="Disable this entity?"
|
||||
@onConfirm={{action "toggleDisabled" this.entityToDisable}}
|
||||
@onClose={{fn (mut this.entityToDisable) null}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.itemToDelete}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmTitle="Delete this {{this.identityType}}?"
|
||||
@onConfirm={{action "delete" this.itemToDelete}}
|
||||
@onClose={{fn (mut this.itemToDelete) null}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -83,17 +83,20 @@
|
||||
{{#if target.link}}
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="Enforcement target more menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo @route={{target.link}} @models={{target.linkModels}} data-test-target-link={{target.title}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage enforcement target"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route={{target.link}}
|
||||
@models={{target.linkModels}}
|
||||
data-test-target-link={{target.title}}
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -38,32 +38,28 @@
|
||||
{{#if (not-eq model.name "allow_all")}}
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.assignments.assignment.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-assignment-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.assignments.assignment.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-assignment-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Assignment nav options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.oidc.assignments.assignment.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-assignment-menu-link="details"
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.oidc.assignments.assignment.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-assignment-menu-link="edit"
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -28,32 +28,28 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.keys.key.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-key-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.keys.key.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-key-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Key nav options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.oidc.keys.key.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-key-menu-link="details"
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.oidc.keys.key.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-key-menu-link="edit"
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,32 +29,28 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.scopes.scope.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-scope-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="vault.cluster.access.oidc.scopes.scope.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-scope-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Assignment nav options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.access.oidc.scopes.scope.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
data-test-oidc-scope-menu-link="details"
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.access.oidc.scopes.scope.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
data-test-oidc-scope-menu-link="edit"
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,52 +91,44 @@
|
||||
</LinkTo>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu name="policy-nav">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if item.updatePath.isPending}}
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
<li class="action">
|
||||
<LoadingDropdownOption />
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if item.canRead}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.policy.show"
|
||||
@models={{array this.policyType item.id}}
|
||||
data-test-policy-link="show"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.canEdit}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="vault.cluster.policy.edit"
|
||||
@models={{array this.policyType item.id}}
|
||||
data-test-policy-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.canDelete}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@buttonText="Delete"
|
||||
@confirmTitle="Delete this policy?"
|
||||
@confirmMessage="This will permanently delete this policy and may affect access to some data"
|
||||
@onConfirmAction={{action "deletePolicy" item}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Policy nav menu"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if item.updatePath.isPending}}
|
||||
<dd.Generic class="has-text-center">
|
||||
<LoadingDropdownOption />
|
||||
</dd.Generic>
|
||||
{{else}}
|
||||
{{#if item.canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="vault.cluster.policy.show"
|
||||
@models={{array this.policyType item.id}}
|
||||
data-test-policy-link="show"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if item.canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="vault.cluster.policy.edit"
|
||||
@models={{array this.policyType item.id}}
|
||||
data-test-policy-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and item.canDelete (not-eq item.name "default"))}}
|
||||
<dd.Interactive
|
||||
@text="Delete"
|
||||
@color="critical"
|
||||
data-test-confirm-action-trigger
|
||||
{{on "click" (fn (mut this.policyToDelete) item)}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
@@ -180,4 +172,14 @@
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<UpgradePage @title="Sentinel" @minimumEdition="Vault Enterprise Premium" />
|
||||
{{/if}}
|
||||
|
||||
{{#if this.policyToDelete}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmTitle="Delete this policy?"
|
||||
@confirmMessage="This will permanently delete this policy and may affect access to some data."
|
||||
@onClose={{fn (mut this.policyToDelete) null}}
|
||||
@onConfirm={{action "deletePolicy" this.policyToDelete}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -86,28 +86,39 @@
|
||||
</ReadMore>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{! meatball sandwich menu }}
|
||||
<div class="linked-block-popup-menu">
|
||||
<PopupMenu @name="engine-menu">
|
||||
<nav class="menu" aria-label="{{if backend.isSupportedBackend 'supported' 'unsupported'}} secrets engine menu">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.configuration" @model={{backend.id}} data-test-engine-config>
|
||||
View configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if (not-eq backend.type "cubbyhole")}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@confirmMessage="Any data in this engine will be permanently deleted."
|
||||
@confirmTitle="Disable engine?"
|
||||
@buttonText="Disable"
|
||||
@onConfirmAction={{perform this.disableEngine backend}}
|
||||
/>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="{{if backend.isSupportedBackend 'supported' 'unsupported'}} secrets engine menu"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="View configuration"
|
||||
@route="vault.cluster.secrets.backend.configuration"
|
||||
@model={{backend.id}}
|
||||
data-test-engine-config
|
||||
/>
|
||||
{{#if (not-eq backend.type "cubbyhole")}}
|
||||
<dd.Interactive
|
||||
@text="Disable"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.engineToDisable) backend)}}
|
||||
data-test-confirm-action-trigger
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
{{#if this.engineToDisable}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmMessage="Any data in this engine will be permanently deleted."
|
||||
@confirmTitle="Disable engine?"
|
||||
@onClose={{fn (mut this.engineToDisable) null}}
|
||||
@onConfirm={{perform this.disableEngine this.engineToDisable}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -56,28 +56,22 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu @name="engine-menu">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if message.canEditCustomMessages}}
|
||||
<li class="action">
|
||||
<LinkTo @route="messages.message.edit" @model={{message.id}}>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if message.canDeleteCustomMessages}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@buttonText="Delete"
|
||||
@confirmTitle="Are you sure?"
|
||||
@confirmMessage="This will delete this message permanently. You cannot undo this action."
|
||||
@onConfirmAction={{perform this.deleteMessage message}}
|
||||
/>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{#if (or message.canEditCustomMessages message.canDeleteCustomMessages)}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Message popup menu"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if message.canEditCustomMessages}}
|
||||
<dd.Interactive @text="Edit" @route="messages.message.edit" @model={{message.id}} />
|
||||
{{/if}}
|
||||
{{#if message.canDeleteCustomMessages}}
|
||||
<dd.Interactive @text="Disable" @color="critical" {{on "click" (fn (mut this.messageToDelete) message)}} />
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,4 +111,13 @@
|
||||
<Hds::Button @text="Close" {{on "click" F.close}} data-test-modal-button="maximum-message-modal" />
|
||||
</M.Footer>
|
||||
</Hds::Modal>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.messageToDelete}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmMessage="This will delete this message permanently. You cannot undo this action."
|
||||
@onClose={{fn (mut this.messageToDelete) null}}
|
||||
@onConfirm={{perform this.deleteMessage this.messageToDelete}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -30,6 +30,7 @@ export default class MessagesList extends Component {
|
||||
@service customMessages;
|
||||
|
||||
@tracked showMaxMessageModal = false;
|
||||
@tracked messageToDelete = null;
|
||||
|
||||
// This follows the pattern in sync/addon/components/secrets/page/destinations for FilterInput.
|
||||
// Currently, FilterInput doesn't do a full page refresh causing it to lose focus.
|
||||
@@ -110,6 +111,8 @@ export default class MessagesList extends Component {
|
||||
} catch (e) {
|
||||
const message = errorMessage(e);
|
||||
this.flashMessages.danger(message);
|
||||
} finally {
|
||||
this.messageToDelete = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,44 +25,13 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showConfirmModal}}
|
||||
<Hds::Modal
|
||||
id="confirm-action-modal"
|
||||
class="has-text-left"
|
||||
<ConfirmModal
|
||||
@color={{this.modalColor}}
|
||||
@size="small"
|
||||
@onClose={{fn (mut this.showConfirmModal) false}}
|
||||
as |M|
|
||||
>
|
||||
{{#if @disabledMessage}}
|
||||
<M.Header data-test-confirm-action-title @icon="x-circle">
|
||||
Not allowed
|
||||
</M.Header>
|
||||
<M.Body data-test-confirm-action-message>
|
||||
{{@disabledMessage}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button data-test-confirm-cancel-button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
{{else}}
|
||||
<M.Header data-test-confirm-action-title @icon="alert-circle">
|
||||
{{or @confirmTitle "Are you sure?"}}
|
||||
</M.Header>
|
||||
<M.Body data-test-confirm-action-message>
|
||||
{{this.confirmMessage}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
data-test-confirm-button
|
||||
disabled={{@isRunning}}
|
||||
@icon={{if @isRunning "loading"}}
|
||||
@color={{if (eq this.modalColor "critical") "critical" "primary"}}
|
||||
@text="Confirm"
|
||||
{{on "click" this.onConfirm}}
|
||||
/>
|
||||
<Hds::Button data-test-confirm-cancel-button @color="secondary" @text="Cancel" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
{{/if}}
|
||||
</Hds::Modal>
|
||||
@onConfirm={{this.onConfirm}}
|
||||
@confirmTitle={{@confirmTitle}}
|
||||
@confirmMessage={{this.confirmMessage}}
|
||||
@disabledMessage={{@disabledMessage}}
|
||||
@isRunning={{@isRunning}}
|
||||
/>
|
||||
{{/if}}
|
||||
51
ui/lib/core/addon/components/confirm-modal.hbs
Normal file
51
ui/lib/core/addon/components/confirm-modal.hbs
Normal file
@@ -0,0 +1,51 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
{{! Replaces ConfirmAction in dropdowns, instead use dd.Interactive + this modal }}
|
||||
{{! Destructive action confirmation modal that asks "Are you sure?" or similar @confirmTitle }}
|
||||
{{! If a tracked property is used to pass the list item to the destructive action, }}
|
||||
{{! remember to reset item to null via the @onClose action }}
|
||||
|
||||
<Hds::Modal
|
||||
id="confirm-action-modal"
|
||||
class="has-text-left"
|
||||
@color={{or @color "warning"}}
|
||||
@size="small"
|
||||
@onClose={{@onClose}}
|
||||
data-test-confirm-modal
|
||||
as |M|
|
||||
>
|
||||
{{#if @disabledMessage}}
|
||||
<M.Header data-test-confirm-action-title @icon="x-circle">
|
||||
Not allowed
|
||||
</M.Header>
|
||||
<M.Body data-test-confirm-action-message>
|
||||
{{@disabledMessage}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::Button data-test-confirm-cancel-button @text="Close" {{on "click" F.close}} />
|
||||
</M.Footer>
|
||||
{{else}}
|
||||
<M.Header data-test-confirm-action-title @icon="alert-circle">
|
||||
{{or @confirmTitle "Are you sure?"}}
|
||||
</M.Header>
|
||||
<M.Body data-test-confirm-action-message>
|
||||
{{or @confirmMessage "You will not be able to recover it later."}}
|
||||
</M.Body>
|
||||
<M.Footer as |F|>
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
data-test-confirm-button
|
||||
disabled={{@isRunning}}
|
||||
@icon={{if @isRunning "loading"}}
|
||||
@color={{if (eq @color "critical") "critical" "primary"}}
|
||||
@text="Confirm"
|
||||
{{on "click" @onConfirm}}
|
||||
/>
|
||||
<Hds::Button data-test-confirm-cancel-button @color="secondary" @text="Cancel" {{on "click" F.close}} />
|
||||
</Hds::ButtonSet>
|
||||
</M.Footer>
|
||||
{{/if}}
|
||||
</Hds::Modal>
|
||||
@@ -5,7 +5,8 @@
|
||||
import Component from '@glimmer/component';
|
||||
/**
|
||||
* @module ConfirmationModal
|
||||
* ConfirmationModal components wrap the <Hds::Modal> component to present a critical (red) type-to-confirm modal.
|
||||
* ConfirmationModal components wrap the <Hds::Modal> component to present a critical (red) type-to-confirm modal
|
||||
* which require the user to type something to confirm the action.
|
||||
* They are used for extremely destructive actions that require extra consideration before confirming.
|
||||
*
|
||||
* @example
|
||||
|
||||
6
ui/lib/core/app/components/confirm-modal.js
Normal file
6
ui/lib/core/app/components/confirm-modal.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'core/components/confirm-modal';
|
||||
@@ -71,57 +71,55 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="menu items for managing {{metadata.path}}">
|
||||
<ul class="menu-list">
|
||||
{{#if metadata.pathIsDirectory}}
|
||||
<li>
|
||||
<LinkTo @route="list-directory" @model={{metadata.fullSecretPath}}>
|
||||
Content
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{else}}
|
||||
<li>
|
||||
<LinkTo @route="secret.details" @model={{metadata.fullSecretPath}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if metadata.canReadMetadata}}
|
||||
<li>
|
||||
<LinkTo @route="secret.metadata.versions" @model={{metadata.fullSecretPath}}>
|
||||
View version history
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if metadata.canCreateVersionData}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="secret.details.edit"
|
||||
@model={{metadata.fullSecretPath}}
|
||||
data-test-popup-create-new-version
|
||||
>
|
||||
Create new version
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if metadata.canDeleteMetadata}}
|
||||
<ConfirmAction
|
||||
@buttonText="Permanently delete"
|
||||
@isInDropdown={{true}}
|
||||
@onConfirmAction={{fn this.onDelete metadata}}
|
||||
@confirmMessage="This will permanently delete this secret and all its versions."
|
||||
data-test-popup-metadata-delete
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage secret"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if metadata.pathIsDirectory}}
|
||||
<dd.Interactive @text="Content" @route="list-directory" @model={{metadata.fullSecretPath}} />
|
||||
{{else}}
|
||||
<dd.Interactive @text="Details" @route="secret.details" @model={{metadata.fullSecretPath}} />
|
||||
{{#if metadata.canReadMetadata}}
|
||||
<dd.Interactive
|
||||
@text="View version history"
|
||||
@route="secret.metadata.versions"
|
||||
@model={{metadata.fullSecretPath}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if metadata.canCreateVersionData}}
|
||||
<dd.Interactive
|
||||
@text="Create new version"
|
||||
@route="secret.details.edit"
|
||||
@model={{metadata.fullSecretPath}}
|
||||
data-test-popup-create-new-version
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if metadata.canDeleteMetadata}}
|
||||
<dd.Interactive
|
||||
@text="Permanently delete"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.metadataToDelete) metadata)}}
|
||||
data-test-popup-metadata-delete
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
{{/each}}
|
||||
{{#if this.metadataToDelete}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@onClose={{fn (mut this.metadataToDelete) null}}
|
||||
@onConfirm={{fn this.onDelete this.metadataToDelete}}
|
||||
@confirmMessage="This will permanently delete this secret and all its versions."
|
||||
/>
|
||||
{{/if}}
|
||||
{{! Pagination }}
|
||||
<Hds::Pagination::Numbered
|
||||
@currentPage={{@secrets.meta.currentPage}}
|
||||
|
||||
@@ -30,6 +30,7 @@ export default class KvListPageComponent extends Component {
|
||||
@service store;
|
||||
|
||||
@tracked secretPath;
|
||||
@tracked metadataToDelete = null; // set to the metadata intended to delete
|
||||
|
||||
get mountPoint() {
|
||||
// mountPoint tells transition where to start. In this case, mountPoint will always be vault.cluster.secrets.backend.kv.
|
||||
@@ -71,6 +72,8 @@ export default class KvListPageComponent extends Component {
|
||||
} catch (error) {
|
||||
const message = errorMessage(error, 'Error deleting secret. Please try again or contact support.');
|
||||
this.flashMessages.danger(message);
|
||||
} finally {
|
||||
this.metadataToDelete = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,31 +70,27 @@
|
||||
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<PopupMenu @name="version-{{versionData.version}}">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo @route="secret.details" @query={{hash version=versionData.version}}>
|
||||
View version
|
||||
{{versionData.version}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if @metadata.canCreateVersionData}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="secret.details.edit"
|
||||
@query={{hash version=versionData.version}}
|
||||
data-test-create-new-version-from={{versionData.version}}
|
||||
@disabled={{or versionData.destroyed versionData.isSecretDeleted}}
|
||||
>
|
||||
Create new version from
|
||||
{{versionData.version}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage version"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="View version {{versionData.version}}"
|
||||
@route="secret.details"
|
||||
@query={{hash version=versionData.version}}
|
||||
/>
|
||||
{{#if (and @metadata.canCreateVersionData (not versionData.destroyed) (not versionData.isSecretDeleted))}}
|
||||
<dd.Interactive
|
||||
@text="Create new version from {{versionData.version}}"
|
||||
@route="secret.details.edit"
|
||||
@query={{hash version=versionData.version}}
|
||||
data-test-create-new-version-from={{versionData.version}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,22 +84,21 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu" aria-label="issuer config options">
|
||||
<ul class="menu-list">
|
||||
<li data-test-popup-menu-details>
|
||||
<LinkTo @route="issuers.issuer.details" @model={{pkiIssuer.id}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo @route="issuers.issuer.edit" @model={{pkiIssuer.id}}>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage issuer"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="issuers.issuer.details"
|
||||
@model={{pkiIssuer.id}}
|
||||
data-test-popup-menu-details
|
||||
/>
|
||||
<dd.Interactive @text="Edit" @route="issuers.issuer.edit" @model={{pkiIssuer.id}} />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,32 +40,32 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="keys.key.details"
|
||||
@model={{pkiKey.keyId}}
|
||||
@disabled={{not @canRead}}
|
||||
data-test-key-menu-link="details"
|
||||
>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="keys.key.edit"
|
||||
@model={{pkiKey.keyId}}
|
||||
@disabled={{not @canEdit}}
|
||||
data-test-key-menu-link="edit"
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{#if (or @canRead @canEdit)}}
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage key"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if @canRead}}
|
||||
<dd.Interactive
|
||||
@text="Details"
|
||||
@route="keys.key.details"
|
||||
@model={{pkiKey.keyId}}
|
||||
data-test-key-menu-link="details"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @canEdit}}
|
||||
<dd.Interactive
|
||||
@text="Edit"
|
||||
@route="keys.key.edit"
|
||||
@model={{pkiKey.keyId}}
|
||||
data-test-key-menu-link="edit"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,17 +26,15 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo @route="certificates.certificate.details" @model={{pkiCertificate.id}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage certificate"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive @text="Details" @route="certificates.certificate.details" @model={{pkiCertificate.id}} />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,22 +27,16 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu>
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo @route="roles.role.details" @model={{pkiRole.id}}>
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo @route="roles.role.edit" @model={{pkiRole.id}}>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Manage role"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive @text="Details" @route="roles.role.details" @model={{pkiRole.id}} />
|
||||
<dd.Interactive @text="Edit" @route="roles.role.edit" @model={{pkiRole.id}} />
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@ export default Controller.extend(copy(DEFAULTS, true), {
|
||||
store: service(),
|
||||
rm: service('replication-mode'),
|
||||
replicationMode: alias('rm.mode'),
|
||||
secondaryToRevoke: null,
|
||||
|
||||
submitError(e) {
|
||||
if (e.errors) {
|
||||
@@ -114,7 +115,8 @@ export default Controller.extend(copy(DEFAULTS, true), {
|
||||
});
|
||||
},
|
||||
(...args) => this.submitError(...args)
|
||||
);
|
||||
)
|
||||
.finally(() => this.set('secondaryToRevoke', null));
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
@@ -29,34 +29,29 @@
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (or (eq this.replicationMode "performance") this.model.canRevokeSecondary)}}
|
||||
<PopupMenu @name="secondary-details">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (eq this.replicationMode "performance")}}
|
||||
<li class="action">
|
||||
<LinkTo
|
||||
@route="mode.secondaries.config-show"
|
||||
@models={{array this.replicationMode secondary}}
|
||||
data-test-replication-path-filter-link={{true}}
|
||||
>
|
||||
Path filter config
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if this.model.canRevokeSecondary}}
|
||||
<li class="action">
|
||||
<ConfirmAction
|
||||
@buttonText="Revoke"
|
||||
@isInDropdown={{true}}
|
||||
@confirmTitle="Revoke token?"
|
||||
@confirmMessage="This will revoke this secondary token."
|
||||
@onConfirmAction={{action "onSubmit" "revoke-secondary" "primary" (hash id=secondary)}}
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Secondary popup nav menu"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
{{#if (eq this.replicationMode "performance")}}
|
||||
<dd.Interactive
|
||||
@text="Path filter config"
|
||||
@route="mode.secondaries.config-show"
|
||||
@models={{array this.replicationMode secondary}}
|
||||
data-test-replication-path-filter-link={{true}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.model.canRevokeSecondary}}
|
||||
<dd.Interactive
|
||||
@text="Revoke"
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.secondaryToRevoke) secondary)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,4 +71,14 @@
|
||||
/>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.secondaryToRevoke}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmTitle="Revoke token?"
|
||||
@confirmMessage="This will revoke this secondary token."
|
||||
@onClose={{fn (mut this.secondaryToRevoke) null}}
|
||||
@onConfirm={{action "onSubmit" "revoke-secondary" "primary" (hash id=this.secondaryToRevoke)}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -90,7 +90,7 @@
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td @align="right">
|
||||
<Hds::Dropdown @isInline={{true}} as |dd|>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Actions"
|
||||
|
||||
@@ -8,7 +8,11 @@ import { selectChoose, clickTrigger } from 'ember-power-select/test-support/help
|
||||
import page from 'vault/tests/pages/access/identity/create';
|
||||
import showPage from 'vault/tests/pages/access/identity/show';
|
||||
import indexPage from 'vault/tests/pages/access/identity/index';
|
||||
|
||||
const SELECTORS = {
|
||||
identityRow: (name) => `[data-test-identity-row="${name}"]`,
|
||||
popupMenu: '[data-test-popup-menu-trigger]',
|
||||
menuDelete: '[data-test-popup-menu="delete"]',
|
||||
};
|
||||
export const testCRUD = async (name, itemType, assert) => {
|
||||
await page.visit({ item_type: itemType });
|
||||
await settled();
|
||||
@@ -24,7 +28,6 @@ export const testCRUD = async (name, itemType, assert) => {
|
||||
`${itemType}: navigates to show on create`
|
||||
);
|
||||
assert.ok(showPage.nameContains(name), `${itemType}: renders the name on the show page`);
|
||||
|
||||
await indexPage.visit({ item_type: itemType });
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
@@ -32,10 +35,10 @@ export const testCRUD = async (name, itemType, assert) => {
|
||||
1,
|
||||
`${itemType}: lists the entity in the entity list`
|
||||
);
|
||||
await indexPage.items.filterBy('name', name)[0].menu();
|
||||
await waitUntil(() => find('[data-test-item-delete]'));
|
||||
await indexPage.delete();
|
||||
await settled();
|
||||
|
||||
await click(`${SELECTORS.identityRow(name)} ${SELECTORS.popupMenu}`);
|
||||
await waitUntil(() => find(SELECTORS.menuDelete));
|
||||
await click(SELECTORS.menuDelete);
|
||||
await indexPage.confirmDelete();
|
||||
await settled();
|
||||
assert.ok(
|
||||
|
||||
@@ -3,12 +3,22 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { currentRouteName } from '@ember/test-helpers';
|
||||
import { fillIn, click, currentRouteName, currentURL, visit } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import page from 'vault/tests/pages/access/identity/index';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import { SELECTORS as GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const SELECTORS = {
|
||||
listItem: (name) => `[data-test-identity-row="${name}"]`,
|
||||
menu: `[data-test-popup-menu-trigger]`,
|
||||
menuItem: (element) => `[data-test-popup-menu="${element}"]`,
|
||||
submit: '[data-test-identity-submit]',
|
||||
confirm: '[data-test-confirm-button]',
|
||||
};
|
||||
module('Acceptance | /access/identity/entities', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
@@ -33,4 +43,62 @@ module('Acceptance | /access/identity/entities', function (hooks) {
|
||||
'navigates to the correct route'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders popup menu for entities', async function (assert) {
|
||||
const name = `entity-${uuidv4()}`;
|
||||
await runCmd(`vault write identity/entity name="${name}" policies="default"`);
|
||||
await visit('/vault/access/identity/entities');
|
||||
assert.strictEqual(currentURL(), '/vault/access/identity/entities', 'navigates to entities tab');
|
||||
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menu}`);
|
||||
assert
|
||||
.dom('.hds-dropdown ul')
|
||||
.hasText('Details Create alias Edit Disable Delete', 'all actions render for entities');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menuItem('delete')}`);
|
||||
await click(SELECTORS.confirm);
|
||||
});
|
||||
|
||||
test('it renders popup menu for external groups', async function (assert) {
|
||||
const name = `external-${uuidv4()}`;
|
||||
await runCmd(`vault write identity/group name="${name}" policies="default" type="external"`);
|
||||
await visit('/vault/access/identity/groups');
|
||||
assert.strictEqual(currentURL(), '/vault/access/identity/groups', 'navigates to the groups tab');
|
||||
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menu}`);
|
||||
assert
|
||||
.dom('.hds-dropdown ul')
|
||||
.hasText('Details Create alias Edit Delete', 'all actions render for external groups');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menuItem('delete')}`);
|
||||
await click(SELECTORS.confirm);
|
||||
});
|
||||
|
||||
test('it renders popup menu for external groups with alias', async function (assert) {
|
||||
const name = `external-hasalias-${uuidv4()}`;
|
||||
await runCmd(`vault write identity/group name="${name}" policies="default" type="external"`);
|
||||
await visit('/vault/access/identity/groups');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menu}`);
|
||||
await click(SELECTORS.menuItem('create alias'));
|
||||
await fillIn(GENERAL.inputByAttr('name'), 'alias-test');
|
||||
await click(SELECTORS.submit);
|
||||
|
||||
await visit('/vault/access/identity/groups');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menu}`);
|
||||
assert
|
||||
.dom('.hds-dropdown ul')
|
||||
.hasText('Details Edit Delete', 'no "Create alias" option for external groups with an alias');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menuItem('delete')}`);
|
||||
await click(SELECTORS.confirm);
|
||||
});
|
||||
|
||||
test('it renders popup menu for internal groups', async function (assert) {
|
||||
const name = `internal-${uuidv4()}`;
|
||||
await runCmd(`vault write identity/group name="${name}" policies="default" type="internal"`);
|
||||
await visit('/vault/access/identity/groups');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menu}`);
|
||||
assert
|
||||
.dom('.hds-dropdown ul')
|
||||
.hasText('Details Edit Delete', 'no "Create alias" option for internal groups');
|
||||
await click(`${SELECTORS.listItem(name)} ${SELECTORS.menuItem('delete')}`);
|
||||
await click(SELECTORS.confirm);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -255,7 +255,7 @@ module('Acceptance | mfa-method', function (hooks) {
|
||||
await visit('/vault/access/mfa/methods');
|
||||
const id = this.element.querySelector('[data-test-mfa-method-list-item] .tag').textContent.trim();
|
||||
const model = this.store.peekRecord('mfa-method', id);
|
||||
await click('[data-test-mfa-method-list-item] .ember-basic-dropdown-trigger');
|
||||
await click('[data-test-mfa-method-list-item] [data-test-popup-menu-trigger]');
|
||||
await click('[data-test-mfa-method-menu-link="edit"]');
|
||||
|
||||
const keys = ['issuer', 'period', 'key_size', 'qr_size', 'algorithm', 'digits', 'skew'];
|
||||
|
||||
@@ -319,7 +319,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
||||
);
|
||||
});
|
||||
|
||||
test('it hide corrects actions for user with read policy', async function (assert) {
|
||||
test('it hides correct actions for user with read policy', async function (assert) {
|
||||
await authPage.login(this.pkiKeyReader);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await click(SELECTORS.keysTab);
|
||||
@@ -330,7 +330,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'One key is in list');
|
||||
const keyId = find(SELECTORS.keyPages.keyId).innerText;
|
||||
await click(SELECTORS.keyPages.popupMenuTrigger);
|
||||
assert.dom(SELECTORS.keyPages.popupMenuEdit).hasClass('disabled', 'popup menu edit link is disabled');
|
||||
assert.dom(SELECTORS.keyPages.popupMenuEdit).doesNotExist('popup menu edit link is not shown');
|
||||
await click(SELECTORS.keyPages.popupMenuDetails);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/keys/${keyId}/details`);
|
||||
assert.dom(SELECTORS.keyPages.keyDeleteButton).doesNotExist('Delete key button is not shown');
|
||||
|
||||
@@ -117,7 +117,7 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) {
|
||||
assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
|
||||
assert.dom(PAGE.secretTab('Version History')).hasText('Version History');
|
||||
assert.dom(PAGE.secretTab('Version History')).doesNotHaveClass('active');
|
||||
assert.dom(PAGE.toolbarAction).exists({ count: 5 }, 'toolbar renders all actions');
|
||||
assert.dom(PAGE.toolbarAction).exists({ count: 4 }, 'toolbar renders all actions');
|
||||
});
|
||||
|
||||
test('it navigates back to engine index route via breadcrumbs from secret details', async function (assert) {
|
||||
|
||||
@@ -59,7 +59,7 @@ module('Acceptance | secrets/ssh', function (hooks) {
|
||||
assert.strictEqual(listPage.secrets.length, 1, 'shows role in the list');
|
||||
const secret = listPage.secrets.objectAt(0);
|
||||
await secret.menuToggle();
|
||||
assert.ok(listPage.menuItems.length > 0, 'shows links in the menu');
|
||||
assert.dom('.hds-dropdown li').exists({ count: 5 }, 'Renders 5 popup menu items');
|
||||
});
|
||||
|
||||
test('it deletes a role', async function (assert) {
|
||||
|
||||
@@ -244,7 +244,7 @@ module('Acceptance | transit (flaky)', function (hooks) {
|
||||
assert.dom(SELECTORS.infoRow('Convergent encryption')).hasText('Yes');
|
||||
await click(SELECTORS.rootCrumb(this.path));
|
||||
await click(SELECTORS.popupMenu);
|
||||
const actions = findAll('.ember-basic-dropdown-content li');
|
||||
const actions = findAll('.hds-dropdown__list li');
|
||||
assert.strictEqual(actions.length, 2, 'shows 2 items in popup menu');
|
||||
|
||||
await click(SELECTORS.secretLink);
|
||||
|
||||
@@ -57,7 +57,7 @@ export const SELECTORS = {
|
||||
generateIssuerRoot: '[data-test-generate-issuer="root"]',
|
||||
generateIssuerIntermediate: '[data-test-generate-issuer="intermediate"]',
|
||||
issuerPopupMenu: '[data-test-popup-menu-trigger]',
|
||||
issuerPopupDetails: '[data-test-popup-menu-details] a',
|
||||
issuerPopupDetails: '[data-test-popup-menu-details]',
|
||||
issuerDetails: {
|
||||
title: '[data-test-pki-issuer-page-title]',
|
||||
...ISSUERDETAILS,
|
||||
|
||||
@@ -49,10 +49,10 @@ module('Integration | Component | auth-config-form options', function (hooks) {
|
||||
});
|
||||
sinon.spy(model.config, 'serialize');
|
||||
this.set('model', model);
|
||||
await render(hbs`{{auth-config-form/options model=this.model}}`);
|
||||
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
||||
component.save();
|
||||
return settled().then(() => {
|
||||
assert.ok(model.config.serialize.calledOnce);
|
||||
assert.strictEqual(model.config.serialize.callCount, 1, 'config serialize was called once');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
37
ui/tests/integration/components/confirm-modal-test.js
Normal file
37
ui/tests/integration/components/confirm-modal-test.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | confirm-modal', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.onConfirm = sinon.spy();
|
||||
this.onClose = sinon.spy();
|
||||
});
|
||||
|
||||
test('it renders a reasonable default', async function (assert) {
|
||||
await render(hbs`<ConfirmModal @onConfirm={{this.onConfirm}} @onClose={{this.onClose}} />`);
|
||||
assert
|
||||
.dom('[data-test-confirm-modal]')
|
||||
.hasClass('hds-modal--color-warning', 'renders warning modal color');
|
||||
assert
|
||||
.dom('[data-test-confirm-button]')
|
||||
.hasClass('hds-button--color-primary', 'renders primary confirm button');
|
||||
assert.dom('[data-test-confirm-action-title]').hasText('Are you sure?', 'renders default title');
|
||||
assert
|
||||
.dom('[data-test-confirm-action-message]')
|
||||
.hasText('You will not be able to recover it later.', 'renders default body text');
|
||||
await click('[data-test-confirm-cancel-button]');
|
||||
assert.ok(this.onClose.called, 'calls the onClose action when Cancel is clicked');
|
||||
await click('[data-test-confirm-button]');
|
||||
assert.ok(this.onConfirm.called, 'calls the onConfirm action when Confirm is clicked');
|
||||
});
|
||||
});
|
||||
@@ -90,7 +90,7 @@ module('Integration | Component | kv | Page::List', function (hooks) {
|
||||
|
||||
const popupSelector = `${PAGE.list.item('my-secret-0')} ${PAGE.popup}`;
|
||||
await click(popupSelector);
|
||||
await click('[data-test-confirm-action-trigger]');
|
||||
await click('[data-test-popup-metadata-delete]');
|
||||
await click('[data-test-confirm-button]');
|
||||
assert.dom(PAGE.list.item('my-secret-0')).doesNotExist('deleted the first record from the list');
|
||||
});
|
||||
|
||||
@@ -87,10 +87,10 @@ module('Integration | Component | kv | Page::Secret::Metadata::Version-History',
|
||||
{ owner: this.engine }
|
||||
);
|
||||
// because the popup menu is nested in a linked block we must combine the two selectors
|
||||
const popupSelector = `${PAGE.versions.linkedBlock(2)} ${PAGE.popup}`;
|
||||
const popupSelector = `${PAGE.versions.linkedBlock(1)} ${PAGE.popup}`;
|
||||
await click(popupSelector);
|
||||
assert
|
||||
.dom('[data-test-create-new-version-from="2"]')
|
||||
.dom('[data-test-create-new-version-from="1"]')
|
||||
.exists('Shows the option to create a new version from that secret.');
|
||||
});
|
||||
});
|
||||
|
||||
54
ui/tests/integration/components/oidc/client-list-test.js
Normal file
54
ui/tests/integration/components/oidc/client-list-test.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { overrideCapabilities } from 'vault/tests/helpers/oidc-config';
|
||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
|
||||
module('Integration | Component | oidc/client-list', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.store.createRecord('oidc/client', { name: 'first-client' });
|
||||
this.store.createRecord('oidc/client', { name: 'second-client' });
|
||||
this.model = this.store.peekAll('oidc/client');
|
||||
});
|
||||
|
||||
test('it renders list of clients', async function (assert) {
|
||||
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub(['read', 'update']));
|
||||
await render(hbs`<Oidc::ClientList @model={{this.model}} />`);
|
||||
|
||||
assert.dom('[data-test-oidc-client-linked-block]').exists({ count: 2 }, 'Two clients are rendered');
|
||||
assert.dom('[data-test-oidc-client-linked-block="first-client"]').exists('First client is rendered');
|
||||
assert.dom('[data-test-oidc-client-linked-block="second-client"]').exists('Second client is rendered');
|
||||
|
||||
await click('[data-test-oidc-client-linked-block="first-client"] [data-test-popup-menu-trigger]');
|
||||
assert.dom('[data-test-oidc-client-menu-link="details"]').exists('Details link is rendered');
|
||||
assert.dom('[data-test-oidc-client-menu-link="edit"]').exists('Edit link is rendered');
|
||||
});
|
||||
|
||||
test('it renders popup menu based on permissions', async function (assert) {
|
||||
this.server.post('/sys/capabilities-self', (schema, req) => {
|
||||
const { paths } = JSON.parse(req.requestBody);
|
||||
if (paths[0] === 'identity/oidc/client/first-client') {
|
||||
return overrideCapabilities('identity/oidc/client/first-client', ['read']);
|
||||
} else {
|
||||
return overrideCapabilities('identity/oidc/client/second-client', ['deny']);
|
||||
}
|
||||
});
|
||||
await render(hbs`<Oidc::ClientList @model={{this.model}} />`);
|
||||
|
||||
assert.dom('[data-test-popup-menu-trigger]').exists({ count: 1 }, 'Only one popup menu is rendered');
|
||||
await click('[data-test-popup-menu-trigger]');
|
||||
assert.dom('[data-test-oidc-client-menu-link="details"]').exists('Details link is rendered');
|
||||
assert.dom('[data-test-oidc-client-menu-link="edit"]').doesNotExist('Edit link is not rendered');
|
||||
});
|
||||
});
|
||||
55
ui/tests/integration/components/oidc/provider-list-test.js
Normal file
55
ui/tests/integration/components/oidc/provider-list-test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { overrideCapabilities } from 'vault/tests/helpers/oidc-config';
|
||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
|
||||
module('Integration | Component | oidc/provider-list', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.store.createRecord('oidc/provider', { name: 'first-provider', issuer: 'foobar' });
|
||||
this.store.createRecord('oidc/provider', { name: 'second-provider', issuer: 'foobar' });
|
||||
this.model = this.store.peekAll('oidc/provider');
|
||||
});
|
||||
|
||||
test('it renders list of providers', async function (assert) {
|
||||
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub(['read', 'update']));
|
||||
await render(hbs`<Oidc::ProviderList @model={{this.model}} />`);
|
||||
|
||||
assert.dom('[data-test-oidc-provider-linked-block]').exists({ count: 2 }, 'Two clients are rendered');
|
||||
assert.dom('[data-test-oidc-provider-linked-block="first-provider"]').exists('First client is rendered');
|
||||
assert
|
||||
.dom('[data-test-oidc-provider-linked-block="second-provider"]')
|
||||
.exists('Second client is rendered');
|
||||
|
||||
await click('[data-test-oidc-provider-linked-block="first-provider"] [data-test-popup-menu-trigger]');
|
||||
assert.dom('[data-test-oidc-provider-menu-link="details"]').exists('Details link is rendered');
|
||||
assert.dom('[data-test-oidc-provider-menu-link="edit"]').exists('Edit link is rendered');
|
||||
});
|
||||
|
||||
test('it renders popup menu based on permissions', async function (assert) {
|
||||
this.server.post('/sys/capabilities-self', (schema, req) => {
|
||||
const { paths } = JSON.parse(req.requestBody);
|
||||
if (paths[0] === 'identity/oidc/provider/first-provider') {
|
||||
return overrideCapabilities('identity/oidc/provider/first-provider', ['read']);
|
||||
} else {
|
||||
return overrideCapabilities('identity/oidc/provider/second-provider', ['deny']);
|
||||
}
|
||||
});
|
||||
await render(hbs`<Oidc::ProviderList @model={{this.model}} />`);
|
||||
assert.dom('[data-test-popup-menu-trigger]').exists({ count: 1 }, 'Only one popup menu is rendered');
|
||||
await click('[data-test-popup-menu-trigger]');
|
||||
assert.dom('[data-test-oidc-provider-menu-link="details"]').exists('Details link is rendered');
|
||||
assert.dom('[data-test-oidc-provider-menu-link="edit"]').doesNotExist('Edit link is not rendered');
|
||||
});
|
||||
});
|
||||
@@ -91,8 +91,8 @@ module('Integration | Component | pki key list page', function (hooks) {
|
||||
assert.dom(SELECTORS.popupMenuEdit).exists('edit link exists');
|
||||
});
|
||||
|
||||
test('it hides or disables actions when permission denied', async function (assert) {
|
||||
assert.expect(4);
|
||||
test('it hides actions when permission denied', async function (assert) {
|
||||
assert.expect(3);
|
||||
await render(
|
||||
hbs`
|
||||
<Page::PkiKeyList
|
||||
@@ -108,8 +108,6 @@ module('Integration | Component | pki key list page', function (hooks) {
|
||||
);
|
||||
assert.dom(SELECTORS.importKey).doesNotExist('renders import action');
|
||||
assert.dom(SELECTORS.generateKey).doesNotExist('renders generate action');
|
||||
await click(SELECTORS.popupMenuTrigger);
|
||||
assert.dom(SELECTORS.popupMenuDetails).hasClass('disabled', 'details link enabled');
|
||||
assert.dom(SELECTORS.popupMenuEdit).hasClass('disabled', 'edit link enabled');
|
||||
assert.dom(SELECTORS.popupMenuTrigger).doesNotExist('does not render popup menu when no permission');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,8 +53,8 @@ module('Integration | Component | transform-list-item', function (hooks) {
|
||||
/>`);
|
||||
|
||||
assert.dom('[data-test-secret-link="template/foo"]').exists('shows clickable list item');
|
||||
await click('button.popup-menu-trigger');
|
||||
assert.dom('.popup-menu-content li').exists({ count: 1 }, 'has one option');
|
||||
await click('[data-test-popup-menu-trigger]');
|
||||
assert.dom('.hds-dropdown li').exists({ count: 1 }, 'has one option');
|
||||
});
|
||||
|
||||
test('it has details and edit menu item if read & edit capabilities', async function (assert) {
|
||||
@@ -76,8 +76,8 @@ module('Integration | Component | transform-list-item', function (hooks) {
|
||||
/>`);
|
||||
|
||||
assert.dom('[data-test-secret-link="alphabet/foo"]').exists('shows clickable list item');
|
||||
await click('button.popup-menu-trigger');
|
||||
assert.dom('.popup-menu-content li').exists({ count: 2 }, 'has both options');
|
||||
await click('[data-test-popup-menu-trigger]');
|
||||
assert.dom('.hds-dropdown li').exists({ count: 2 }, 'has both options');
|
||||
});
|
||||
|
||||
test('it is not clickable if built-in template with all capabilities', async function (assert) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default create({
|
||||
menu: clickable('[data-test-popup-menu-trigger]'),
|
||||
name: text('[data-test-identity-link]'),
|
||||
}),
|
||||
delete: clickable('[data-test-item-delete]', {
|
||||
delete: clickable('[data-test-popup-menu="delete"]', {
|
||||
testContainer: '#ember-testing',
|
||||
}),
|
||||
confirmDelete: clickable('[data-test-confirm-button]'),
|
||||
|
||||
@@ -14,7 +14,7 @@ export default create({
|
||||
name: text('[data-test-identity-link]'),
|
||||
}),
|
||||
|
||||
delete: clickable('[data-test-item-delete]', {
|
||||
delete: clickable('[data-test-popup-menu="delete"]', {
|
||||
testContainer: '#ember-testing',
|
||||
}),
|
||||
confirmDelete: clickable('[data-test-confirm-button]'),
|
||||
|
||||
Reference in New Issue
Block a user