diff --git a/changelog/25321.txt b/changelog/25321.txt
new file mode 100644
index 000000000000..247861c69caa
--- /dev/null
+++ b/changelog/25321.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: Use Hds::Dropdown component to replace list view popup menus
+```
\ No newline at end of file
diff --git a/ui/app/components/identity/_popup-base.js b/ui/app/components/identity/_popup-base.js
deleted file mode 100644
index f7ef118a9541..000000000000
--- a/ui/app/components/identity/_popup-base.js
+++ /dev/null
@@ -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));
- });
- },
- },
-});
diff --git a/ui/app/components/identity/popup-alias.js b/ui/app/components/identity/popup-alias.js
index 3579039738da..919cb74ed42e 100644
--- a/ui/app/components/identity/popup-alias.js
+++ b/ui/app/components/identity/popup-alias.js
@@ -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);
+ }
+ }
+}
diff --git a/ui/app/components/identity/popup-members.js b/ui/app/components/identity/popup-members.js
index cd6bffb917f2..fdafd79dba9b 100644
--- a/ui/app/components/identity/popup-members.js
+++ b/ui/app/components/identity/popup-members.js
@@ -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';
-
-export default Base.extend({
- model: alias('params.firstObject'),
-
- groupArray: computed('params', function () {
- return this.params.objectAt(1);
- }),
-
- memberId: computed('params', function () {
- return this.params.objectAt(2);
- }),
-
- 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();
- },
-});
+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 class IdentityPopupMembers extends Component {
+ @service flashMessages;
+ @tracked showConfirmModal = false;
+
+ 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}`);
+ }
+
+ 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();
+ }
+
+ @action
+ async removeGroup() {
+ const memberId = this.args.memberId;
+ try {
+ await this.transaction();
+ this.onSuccess(memberId);
+ } catch (e) {
+ this.onError(e, memberId);
+ }
+ }
+}
diff --git a/ui/app/components/identity/popup-metadata.js b/ui/app/components/identity/popup-metadata.js
index c9097f2e596b..2089f4a90e9c 100644
--- a/ui/app/components/identity/popup-metadata.js
+++ b/ui/app/components/identity/popup-metadata.js
@@ -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);
+ }
+ }
+}
diff --git a/ui/app/components/identity/popup-policy.js b/ui/app/components/identity/popup-policy.js
index 94eb5d9244ee..591e3d17e80a 100644
--- a/ui/app/components/identity/popup-policy.js
+++ b/ui/app/components/identity/popup-policy.js
@@ -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);
+ }
+ }
+}
diff --git a/ui/app/components/secret-list/aws-role-item.js b/ui/app/components/secret-list/aws-role-item.js
new file mode 100644
index 000000000000..cd95580a36b7
--- /dev/null
+++ b/ui/app/components/secret-list/aws-role-item.js
@@ -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;
+}
diff --git a/ui/app/components/secret-list/database-list-item.js b/ui/app/components/secret-list/database-list-item.js
index 46adb4c0e303..84a9cb3e850a 100644
--- a/ui/app/components/secret-list/database-list-item.js
+++ b/ui/app/components/secret-list/database-list-item.js
@@ -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));
}
}
diff --git a/ui/app/components/secret-list/item.js b/ui/app/components/secret-list/item.js
new file mode 100644
index 000000000000..db17c97fda82
--- /dev/null
+++ b/ui/app/components/secret-list/item.js
@@ -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;
+}
diff --git a/ui/app/components/secret-list/ssh-role-item.js b/ui/app/components/secret-list/ssh-role-item.js
new file mode 100644
index 000000000000..d753751d71cf
--- /dev/null
+++ b/ui/app/components/secret-list/ssh-role-item.js
@@ -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;
+}
diff --git a/ui/app/controllers/vault/cluster/access/identity/index.js b/ui/app/controllers/vault/cluster/access/identity/index.js
index db9da7a80abd..bb8eb9843ada 100644
--- a/ui/app/controllers/vault/cluster/access/identity/index.js
+++ b/ui/app/controllers/vault/cluster/access/identity/index.js
@@ -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();
diff --git a/ui/app/controllers/vault/cluster/policies/index.js b/ui/app/controllers/vault/cluster/policies/index.js
index 4d41580889af..355f6cd4f543 100644
--- a/ui/app/controllers/vault/cluster/policies/index.js
+++ b/ui/app/controllers/vault/cluster/policies/index.js
@@ -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));
},
},
});
diff --git a/ui/app/controllers/vault/cluster/secrets/backends.js b/ui/app/controllers/vault/cluster/secrets/backends.js
index a24396f7ba57..8c8d7f10c6ad 100644
--- a/ui/app/controllers/vault/cluster/secrets/backends.js
+++ b/ui/app/controllers/vault/cluster/secrets/backends.js
@@ -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;
}
}
}
diff --git a/ui/app/models/identity/group.js b/ui/app/models/identity/group.js
index 79e5f3efe783..2f25e6ec7547 100644
--- a/ui/app/models/identity/group.js
+++ b/ui/app/models/identity/group.js
@@ -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'),
});
diff --git a/ui/app/models/oidc/client.js b/ui/app/models/oidc/client.js
index 2829d29b50d7..be4761ed68b3 100644
--- a/ui/app/models/oidc/client.js
+++ b/ui/app/models/oidc/client.js
@@ -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;
}
}
diff --git a/ui/app/models/oidc/provider.js b/ui/app/models/oidc/provider.js
index c2d72dff3158..36ed9d9334c9 100644
--- a/ui/app/models/oidc/provider.js
+++ b/ui/app/models/oidc/provider.js
@@ -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;
}
}
diff --git a/ui/app/templates/components/identity/item-aliases.hbs b/ui/app/templates/components/identity/item-aliases.hbs
index ccc6a0fee457..f0fffc814c1d 100644
--- a/ui/app/templates/components/identity/item-aliases.hbs
+++ b/ui/app/templates/components/identity/item-aliases.hbs
@@ -24,7 +24,7 @@
-
+
diff --git a/ui/app/templates/components/identity/item-members.hbs b/ui/app/templates/components/identity/item-members.hbs
index e153e9a068a6..3b149199f04e 100644
--- a/ui/app/templates/components/identity/item-members.hbs
+++ b/ui/app/templates/components/identity/item-members.hbs
@@ -18,7 +18,7 @@
{{#if @model.canEdit}}
-
+
{{/if}}
@@ -38,7 +38,7 @@
{{#if @model.canEdit}}
-
+
{{/if}}
diff --git a/ui/app/templates/components/identity/item-metadata.hbs b/ui/app/templates/components/identity/item-metadata.hbs
index 6fe217e45968..a9012398ef93 100644
--- a/ui/app/templates/components/identity/item-metadata.hbs
+++ b/ui/app/templates/components/identity/item-metadata.hbs
@@ -16,7 +16,7 @@
{{#if @model.canEdit}}
-
+
{{/if}}
diff --git a/ui/app/templates/components/identity/item-policies.hbs b/ui/app/templates/components/identity/item-policies.hbs
index e94df943a7ee..987859059900 100644
--- a/ui/app/templates/components/identity/item-policies.hbs
+++ b/ui/app/templates/components/identity/item-policies.hbs
@@ -17,7 +17,7 @@
{{#if @model.canEdit}}
-
+
{{/if}}
diff --git a/ui/app/templates/components/identity/popup-alias.hbs b/ui/app/templates/components/identity/popup-alias.hbs
index ffeb2d9572eb..b73c9a728db0 100644
--- a/ui/app/templates/components/identity/popup-alias.hbs
+++ b/ui/app/templates/components/identity/popup-alias.hbs
@@ -3,43 +3,38 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-
- {{#let (get this.params "0") as |item|}}
-
- {{/let}}
-
\ No newline at end of file
+
+
+
+
+ {{#if @item.updatePath.isPending}}
+
+
+
+ {{else}}
+ {{#if @item.canEdit}}
+
+ {{/if}}
+ {{#if @item.canDelete}}
+
+ {{/if}}
+ {{/if}}
+
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/identity/popup-members.hbs b/ui/app/templates/components/identity/popup-members.hbs
index 0fd07c556127..ff2b30441421 100644
--- a/ui/app/templates/components/identity/popup-members.hbs
+++ b/ui/app/templates/components/identity/popup-members.hbs
@@ -3,18 +3,24 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/identity/popup-metadata.hbs b/ui/app/templates/components/identity/popup-metadata.hbs
index c40109151ea0..9954f1c78534 100644
--- a/ui/app/templates/components/identity/popup-metadata.hbs
+++ b/ui/app/templates/components/identity/popup-metadata.hbs
@@ -3,18 +3,19 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/identity/popup-policy.hbs b/ui/app/templates/components/identity/popup-policy.hbs
index fc77051cc602..eef6d33d5829 100644
--- a/ui/app/templates/components/identity/popup-policy.hbs
+++ b/ui/app/templates/components/identity/popup-policy.hbs
@@ -3,28 +3,30 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/mfa/login-enforcement-list-item.hbs b/ui/app/templates/components/mfa/login-enforcement-list-item.hbs
index 544e0980d6aa..1827e60c27b5 100644
--- a/ui/app/templates/components/mfa/login-enforcement-list-item.hbs
+++ b/ui/app/templates/components/mfa/login-enforcement-list-item.hbs
@@ -19,30 +19,26 @@
diff --git a/ui/app/templates/components/mfa/method-list-item.hbs b/ui/app/templates/components/mfa/method-list-item.hbs
index baff7ab01e66..75a719a5a3e6 100644
--- a/ui/app/templates/components/mfa/method-list-item.hbs
+++ b/ui/app/templates/components/mfa/method-list-item.hbs
@@ -30,30 +30,26 @@
diff --git a/ui/app/templates/components/oidc/client-list.hbs b/ui/app/templates/components/oidc/client-list.hbs
index 3473e135f3ae..27f00eeea09c 100644
--- a/ui/app/templates/components/oidc/client-list.hbs
+++ b/ui/app/templates/components/oidc/client-list.hbs
@@ -24,32 +24,32 @@
-
-
-
+ {{#if (or client.canRead client.canEdit)}}
+
+
+ {{#if client.canRead}}
+
+ {{/if}}
+ {{#if client.canEdit}}
+
+ {{/if}}
+
+ {{/if}}
diff --git a/ui/app/templates/components/oidc/provider-list.hbs b/ui/app/templates/components/oidc/provider-list.hbs
index 401f69039306..d684399fb062 100644
--- a/ui/app/templates/components/oidc/provider-list.hbs
+++ b/ui/app/templates/components/oidc/provider-list.hbs
@@ -24,32 +24,32 @@
-
-
-
+ {{#if (or provider.canRead provider.canEdit)}}
+
+
+ {{#if provider.canRead}}
+
+ {{/if}}
+ {{#if provider.canEdit}}
+
+ {{/if}}
+
+ {{/if}}
diff --git a/ui/app/templates/components/secret-list/aws-role-item.hbs b/ui/app/templates/components/secret-list/aws-role-item.hbs
index 10a67616b30b..a995f16e4f15 100644
--- a/ui/app/templates/components/secret-list/aws-role-item.hbs
+++ b/ui/app/templates/components/secret-list/aws-role-item.hbs
@@ -23,58 +23,60 @@
-
-
-
+
+
+ {{#if @item.generatePath.isPending}}
+
+
+
+ {{else if @item.canGenerate}}
+
+ {{/if}}
+ {{#if @item.updatePath.isPending}}
+
+
+
+ {{else}}
+ {{#if @item.canRead}}
+
+ {{/if}}
+ {{#if @item.canEdit}}
+
+ {{/if}}
+ {{#if @item.canDelete}}
+
+ {{/if}}
+ {{/if}}
+
-
\ No newline at end of file
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/secret-list/database-list-item.hbs b/ui/app/templates/components/secret-list/database-list-item.hbs
index 8a65fc9297e9..409429dfd3aa 100644
--- a/ui/app/templates/components/secret-list/database-list-item.hbs
+++ b/ui/app/templates/components/secret-list/database-list-item.hbs
@@ -26,77 +26,56 @@
-
-
-
+
+
+ {{#if @item.canEdit}}
+
+ {{/if}}
+ {{#if @item.canEditRole}}
+
+ {{/if}}
+ {{#if @item.canReset}}
+
+ {{/if}}
+ {{#if (and (eq @item.type "dynamic") @item.canGenerateCredentials)}}
+
+ {{else if (and (eq @item.type "static") @item.canGetCredentials)}}
+
+ {{/if}}
+ {{#if (and @item.canRotateRoleCredentials (eq this.keyTypeValue "static"))}}
+
+ {{/if}}
+ {{#if @item.canRotateRoot}}
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/ui/app/templates/components/secret-list/item.hbs b/ui/app/templates/components/secret-list/item.hbs
index 652d96444100..26f7c704fc46 100644
--- a/ui/app/templates/components/secret-list/item.hbs
+++ b/ui/app/templates/components/secret-list/item.hbs
@@ -30,56 +30,57 @@
-
-
-
+ {{/if}}
+ {{/if}}
+
-
\ No newline at end of file
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/secret-list/ssh-role-item.hbs b/ui/app/templates/components/secret-list/ssh-role-item.hbs
index 26294b6683f2..aa6c295704e8 100644
--- a/ui/app/templates/components/secret-list/ssh-role-item.hbs
+++ b/ui/app/templates/components/secret-list/ssh-role-item.hbs
@@ -36,89 +36,86 @@
{{#if (eq @backendType "ssh")}}
-
-
-
+
+
+ {{#if (eq @item.keyType "otp")}}
+ {{#if @item.generatePath.isPending}}
+
+
+
+ {{else if @item.canGenerate}}
+
+ {{/if}}
+ {{else if (eq @item.keyType "ca")}}
+ {{#if @item.signPath.isPending}}
+
+
+
+ {{else if @item.canGenerate}}
+
+ {{/if}}
+ {{/if}}
+ {{#if @loadingToggleZeroAddress}}
+
+
+
+ {{else if @item.canEditZeroAddress}}
+
+ {{/if}}
+ {{#if @item.updatePath.isPending}}
+
+
+
+ {{else}}
+ {{#if @item.canRead}}
+
+ {{/if}}
+ {{#if @item.canEdit}}
+
+ {{/if}}
+ {{#if @item.canDelete}}
+
+ {{/if}}
+ {{/if}}
+
{{/if}}
-
\ No newline at end of file
+
+
+{{#if this.showConfirmModal}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/components/secret-list/transform-list-item.hbs b/ui/app/templates/components/secret-list/transform-list-item.hbs
index 2b7bf38718f8..18c176ac0db7 100644
--- a/ui/app/templates/components/secret-list/transform-list-item.hbs
+++ b/ui/app/templates/components/secret-list/transform-list-item.hbs
@@ -25,26 +25,20 @@
{{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
-
-
-
+
+
+ {{#if @item.updatePath.canRead}}
+
+ {{/if}}
+ {{#if @item.updatePath.canUpdate}}
+
+ {{/if}}
+
{{/if}}
@@ -55,17 +49,13 @@
{{#if this.isBuiltin}}
-
-
- {{@item.id}}
-
-
-
- This is a built-in HashiCorp
- {{@itemType}}. It can't be viewed or edited.
-
-
-
+
+ {{@item.id}}
+
{{else}}
{{@item.id}}
{{/if}}
diff --git a/ui/app/templates/components/secret-list/transform-transformation-item.hbs b/ui/app/templates/components/secret-list/transform-transformation-item.hbs
index 798902453083..30095e1cb0e1 100644
--- a/ui/app/templates/components/secret-list/transform-transformation-item.hbs
+++ b/ui/app/templates/components/secret-list/transform-transformation-item.hbs
@@ -3,66 +3,42 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-{{! CBS TODO do not let click if !canRead }}
-{{#if (eq @options.item "transformation")}}
-
-
-
-
-
- {{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}
-
-
-
- {{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
-
-
-
- {{/if}}
-
-
-
-{{else}}
-
-
-
+
+
+
+
{{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}
-
+
+
+
+ {{#if (or @item.updatePath.canRead @item.updatePath.canUpdate)}}
+
+
+ {{#if @item.updatePath.canRead}}
+
+ {{/if}}
+ {{#if @item.updatePath.canUpdate}}
+
+ {{/if}}
+
+ {{/if}}
-{{/if}}
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/app/templates/components/transit-form-show.hbs b/ui/app/templates/components/transit-form-show.hbs
index f08210e264f9..559be499fc2a 100644
--- a/ui/app/templates/components/transit-form-show.hbs
+++ b/ui/app/templates/components/transit-form-show.hbs
@@ -151,21 +151,15 @@
diff --git a/ui/app/templates/components/wizard-content.hbs b/ui/app/templates/components/wizard-content.hbs
index e189376fea78..61a50dd5c4bd 100644
--- a/ui/app/templates/components/wizard-content.hbs
+++ b/ui/app/templates/components/wizard-content.hbs
@@ -5,15 +5,10 @@
-
+
diff --git a/ui/app/templates/vault/cluster/access/identity/index.hbs b/ui/app/templates/vault/cluster/access/identity/index.hbs
index 88cd07db0461..de21aa26f668 100644
--- a/ui/app/templates/vault/cluster/access/identity/index.hbs
+++ b/ui/app/templates/vault/cluster/access/identity/index.hbs
@@ -9,7 +9,7 @@
@@ -32,63 +32,53 @@
{{/if}}
-
-
-
+ {{/if}}
+ {{#if item.canEdit}}
+
+ {{#if item.disabled}}
+
+ {{else if (eq this.identityType "entity")}}
+
+ {{/if}}
+ {{/if}}
+ {{#if item.canDelete}}
+
+ {{/if}}
+ {{/if}}
+
@@ -117,4 +107,22 @@
@iconPosition="trailing"
/>
+{{/if}}
+
+{{#if this.entityToDisable}}
+
+{{/if}}
+
+{{#if this.itemToDelete}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/vault/cluster/access/mfa/enforcements/enforcement/index.hbs b/ui/app/templates/vault/cluster/access/mfa/enforcements/enforcement/index.hbs
index a93bce4935de..f84bd9eb9860 100644
--- a/ui/app/templates/vault/cluster/access/mfa/enforcements/enforcement/index.hbs
+++ b/ui/app/templates/vault/cluster/access/mfa/enforcements/enforcement/index.hbs
@@ -83,17 +83,20 @@
{{#if target.link}}
{{/if}}
diff --git a/ui/app/templates/vault/cluster/access/oidc/assignments/index.hbs b/ui/app/templates/vault/cluster/access/oidc/assignments/index.hbs
index a767d31f9fd7..25f46ddbb020 100644
--- a/ui/app/templates/vault/cluster/access/oidc/assignments/index.hbs
+++ b/ui/app/templates/vault/cluster/access/oidc/assignments/index.hbs
@@ -38,32 +38,28 @@
{{#if (not-eq model.name "allow_all")}}
{{/if}}
diff --git a/ui/app/templates/vault/cluster/access/oidc/keys/index.hbs b/ui/app/templates/vault/cluster/access/oidc/keys/index.hbs
index 2c81e37029f4..2995b2cb5347 100644
--- a/ui/app/templates/vault/cluster/access/oidc/keys/index.hbs
+++ b/ui/app/templates/vault/cluster/access/oidc/keys/index.hbs
@@ -28,32 +28,28 @@
diff --git a/ui/app/templates/vault/cluster/access/oidc/scopes/index.hbs b/ui/app/templates/vault/cluster/access/oidc/scopes/index.hbs
index 5642457be179..0747bde83971 100644
--- a/ui/app/templates/vault/cluster/access/oidc/scopes/index.hbs
+++ b/ui/app/templates/vault/cluster/access/oidc/scopes/index.hbs
@@ -29,32 +29,28 @@
diff --git a/ui/app/templates/vault/cluster/policies/index.hbs b/ui/app/templates/vault/cluster/policies/index.hbs
index 7f477cb6bdf4..df42c3bd8735 100644
--- a/ui/app/templates/vault/cluster/policies/index.hbs
+++ b/ui/app/templates/vault/cluster/policies/index.hbs
@@ -91,52 +91,44 @@
-
-
-
+
+
+ {{#if item.updatePath.isPending}}
+
+
+
+ {{else}}
+ {{#if item.canRead}}
+
+ {{/if}}
+ {{#if item.canEdit}}
+
+ {{/if}}
+ {{#if (and item.canDelete (not-eq item.name "default"))}}
+
+ {{/if}}
+ {{/if}}
+
@@ -180,4 +172,14 @@
{{/if}}
{{else}}
+{{/if}}
+
+{{#if this.policyToDelete}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/vault/cluster/secrets/backends.hbs b/ui/app/templates/vault/cluster/secrets/backends.hbs
index 352bc4e5d9bf..5a86dd7e446c 100644
--- a/ui/app/templates/vault/cluster/secrets/backends.hbs
+++ b/ui/app/templates/vault/cluster/secrets/backends.hbs
@@ -86,28 +86,39 @@
{{/if}}
- {{! meatball sandwich menu }}
-{{/each}}
\ No newline at end of file
+{{/each}}
+
+{{#if this.engineToDisable}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/lib/config-ui/addon/components/messages/page/list.hbs b/ui/lib/config-ui/addon/components/messages/page/list.hbs
index f105c25ab24e..851bd4ab9b52 100644
--- a/ui/lib/config-ui/addon/components/messages/page/list.hbs
+++ b/ui/lib/config-ui/addon/components/messages/page/list.hbs
@@ -56,28 +56,22 @@
-
-
-
+ {{#if (or message.canEditCustomMessages message.canDeleteCustomMessages)}}
+
+
+ {{#if message.canEditCustomMessages}}
+
+ {{/if}}
+ {{#if message.canDeleteCustomMessages}}
+
+ {{/if}}
+
+ {{/if}}
@@ -117,4 +111,13 @@
+{{/if}}
+
+{{#if this.messageToDelete}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/config-ui/addon/components/messages/page/list.js b/ui/lib/config-ui/addon/components/messages/page/list.js
index f97e477555b5..6ed34a1e4b03 100644
--- a/ui/lib/config-ui/addon/components/messages/page/list.js
+++ b/ui/lib/config-ui/addon/components/messages/page/list.js
@@ -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;
}
}
diff --git a/ui/lib/core/addon/components/confirm-action.hbs b/ui/lib/core/addon/components/confirm-action.hbs
index 2c22eedd8ea8..019d1246fce7 100644
--- a/ui/lib/core/addon/components/confirm-action.hbs
+++ b/ui/lib/core/addon/components/confirm-action.hbs
@@ -25,44 +25,13 @@
{{/if}}
{{#if this.showConfirmModal}}
-
- {{#if @disabledMessage}}
-
- Not allowed
-
-
- {{@disabledMessage}}
-
-
-
-
- {{else}}
-
- {{or @confirmTitle "Are you sure?"}}
-
-
- {{this.confirmMessage}}
-
-
-
-
-
-
-
- {{/if}}
-
+ @onConfirm={{this.onConfirm}}
+ @confirmTitle={{@confirmTitle}}
+ @confirmMessage={{this.confirmMessage}}
+ @disabledMessage={{@disabledMessage}}
+ @isRunning={{@isRunning}}
+ />
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/confirm-modal.hbs b/ui/lib/core/addon/components/confirm-modal.hbs
new file mode 100644
index 000000000000..c6021b0bfd34
--- /dev/null
+++ b/ui/lib/core/addon/components/confirm-modal.hbs
@@ -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 }}
+
+
+ {{#if @disabledMessage}}
+
+ Not allowed
+
+
+ {{@disabledMessage}}
+
+
+
+
+ {{else}}
+
+ {{or @confirmTitle "Are you sure?"}}
+
+
+ {{or @confirmMessage "You will not be able to recover it later."}}
+
+
+
+
+
+
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/confirmation-modal.js b/ui/lib/core/addon/components/confirmation-modal.js
index 06878dfefd35..6a5a4bd6b6b6 100644
--- a/ui/lib/core/addon/components/confirmation-modal.js
+++ b/ui/lib/core/addon/components/confirmation-modal.js
@@ -5,7 +5,8 @@
import Component from '@glimmer/component';
/**
* @module ConfirmationModal
- * ConfirmationModal components wrap the component to present a critical (red) type-to-confirm modal.
+ * ConfirmationModal components wrap the 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
diff --git a/ui/lib/core/app/components/confirm-modal.js b/ui/lib/core/app/components/confirm-modal.js
new file mode 100644
index 000000000000..e4ceff2d8c1d
--- /dev/null
+++ b/ui/lib/core/app/components/confirm-modal.js
@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+export { default } from 'core/components/confirm-modal';
diff --git a/ui/lib/kv/addon/components/page/list.hbs b/ui/lib/kv/addon/components/page/list.hbs
index ff9436c2f03a..f503ef29d153 100644
--- a/ui/lib/kv/addon/components/page/list.hbs
+++ b/ui/lib/kv/addon/components/page/list.hbs
@@ -71,57 +71,55 @@
-
-
-
+
+
+ {{#if metadata.pathIsDirectory}}
+
+ {{else}}
+
+ {{#if metadata.canReadMetadata}}
+
+ {{/if}}
+ {{#if metadata.canCreateVersionData}}
+
+ {{/if}}
+ {{#if metadata.canDeleteMetadata}}
+
+ {{/if}}
+ {{/if}}
+
{{/each}}
+ {{#if this.metadataToDelete}}
+
+ {{/if}}
{{! Pagination }}
-
-
-
+
+
+
+ {{#if (and @metadata.canCreateVersionData (not versionData.destroyed) (not versionData.isSecretDeleted))}}
+
+ {{/if}}
+
diff --git a/ui/lib/pki/addon/components/page/pki-issuer-list.hbs b/ui/lib/pki/addon/components/page/pki-issuer-list.hbs
index 95ee744127df..b6d5ab63f5b8 100644
--- a/ui/lib/pki/addon/components/page/pki-issuer-list.hbs
+++ b/ui/lib/pki/addon/components/page/pki-issuer-list.hbs
@@ -84,22 +84,21 @@
diff --git a/ui/lib/pki/addon/components/page/pki-key-list.hbs b/ui/lib/pki/addon/components/page/pki-key-list.hbs
index 08131d0247ce..a8d13458f422 100644
--- a/ui/lib/pki/addon/components/page/pki-key-list.hbs
+++ b/ui/lib/pki/addon/components/page/pki-key-list.hbs
@@ -40,32 +40,32 @@
-
-
-
+ {{#if (or @canRead @canEdit)}}
+
+
+ {{#if @canRead}}
+
+ {{/if}}
+ {{#if @canEdit}}
+
+ {{/if}}
+
+ {{/if}}
diff --git a/ui/lib/pki/addon/templates/certificates/index.hbs b/ui/lib/pki/addon/templates/certificates/index.hbs
index f14b4bb12560..96e2d394af36 100644
--- a/ui/lib/pki/addon/templates/certificates/index.hbs
+++ b/ui/lib/pki/addon/templates/certificates/index.hbs
@@ -26,17 +26,15 @@
diff --git a/ui/lib/pki/addon/templates/roles/index.hbs b/ui/lib/pki/addon/templates/roles/index.hbs
index 265a2c192f0d..956466968c4a 100644
--- a/ui/lib/pki/addon/templates/roles/index.hbs
+++ b/ui/lib/pki/addon/templates/roles/index.hbs
@@ -27,22 +27,16 @@
diff --git a/ui/lib/replication/addon/controllers/application.js b/ui/lib/replication/addon/controllers/application.js
index ed72429dd1cb..a4674791d903 100644
--- a/ui/lib/replication/addon/controllers/application.js
+++ b/ui/lib/replication/addon/controllers/application.js
@@ -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: {
diff --git a/ui/lib/replication/addon/templates/mode/secondaries/index.hbs b/ui/lib/replication/addon/templates/mode/secondaries/index.hbs
index d41be803a22e..5f5b63cf30e8 100644
--- a/ui/lib/replication/addon/templates/mode/secondaries/index.hbs
+++ b/ui/lib/replication/addon/templates/mode/secondaries/index.hbs
@@ -29,34 +29,29 @@
{{#if (or (eq this.replicationMode "performance") this.model.canRevokeSecondary)}}
-
-
-
+
+
+ {{#if (eq this.replicationMode "performance")}}
+
+ {{/if}}
+ {{#if this.model.canRevokeSecondary}}
+
+ {{/if}}
+
{{/if}}
@@ -76,4 +71,14 @@
/>
{{/if}}
+{{/if}}
+
+{{#if this.secondaryToRevoke}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/sync/addon/components/secrets/page/overview.hbs b/ui/lib/sync/addon/components/secrets/page/overview.hbs
index 1273e241a21e..bc422385dd1b 100644
--- a/ui/lib/sync/addon/components/secrets/page/overview.hbs
+++ b/ui/lib/sync/addon/components/secrets/page/overview.hbs
@@ -90,7 +90,7 @@
{{/if}}
-
+
`[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(
diff --git a/ui/tests/acceptance/access/identity/entities/index-test.js b/ui/tests/acceptance/access/identity/entities/index-test.js
index fa9bcc5de414..8bb80fb28fab 100644
--- a/ui/tests/acceptance/access/identity/entities/index-test.js
+++ b/ui/tests/acceptance/access/identity/entities/index-test.js
@@ -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);
+ });
});
diff --git a/ui/tests/acceptance/mfa-method-test.js b/ui/tests/acceptance/mfa-method-test.js
index 107c0648f01b..afc5616ff016 100644
--- a/ui/tests/acceptance/mfa-method-test.js
+++ b/ui/tests/acceptance/mfa-method-test.js
@@ -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'];
diff --git a/ui/tests/acceptance/pki/pki-engine-workflow-test.js b/ui/tests/acceptance/pki/pki-engine-workflow-test.js
index de3a2bf81150..395bf41c19a4 100644
--- a/ui/tests/acceptance/pki/pki-engine-workflow-test.js
+++ b/ui/tests/acceptance/pki/pki-engine-workflow-test.js
@@ -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');
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js
index 3ea21ed28e78..bf94b8018838 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-edge-cases-test.js
@@ -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) {
diff --git a/ui/tests/acceptance/secrets/backend/ssh/role-test.js b/ui/tests/acceptance/secrets/backend/ssh/role-test.js
index fee830da1bbe..f4d3ab8d932c 100644
--- a/ui/tests/acceptance/secrets/backend/ssh/role-test.js
+++ b/ui/tests/acceptance/secrets/backend/ssh/role-test.js
@@ -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) {
diff --git a/ui/tests/acceptance/transit-test.js b/ui/tests/acceptance/transit-test.js
index 82d89de6e01e..4587150a2523 100644
--- a/ui/tests/acceptance/transit-test.js
+++ b/ui/tests/acceptance/transit-test.js
@@ -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);
diff --git a/ui/tests/helpers/pki/workflow.js b/ui/tests/helpers/pki/workflow.js
index d0c1676898f4..c962265dd630 100644
--- a/ui/tests/helpers/pki/workflow.js
+++ b/ui/tests/helpers/pki/workflow.js
@@ -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,
diff --git a/ui/tests/integration/components/auth-config-form/options-test.js b/ui/tests/integration/components/auth-config-form/options-test.js
index edad9d815341..f267dad0f195 100644
--- a/ui/tests/integration/components/auth-config-form/options-test.js
+++ b/ui/tests/integration/components/auth-config-form/options-test.js
@@ -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``);
component.save();
return settled().then(() => {
- assert.ok(model.config.serialize.calledOnce);
+ assert.strictEqual(model.config.serialize.callCount, 1, 'config serialize was called once');
});
});
});
diff --git a/ui/tests/integration/components/confirm-modal-test.js b/ui/tests/integration/components/confirm-modal-test.js
new file mode 100644
index 000000000000..464db30283c4
--- /dev/null
+++ b/ui/tests/integration/components/confirm-modal-test.js
@@ -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``);
+ 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');
+ });
+});
diff --git a/ui/tests/integration/components/kv/page/kv-page-list-test.js b/ui/tests/integration/components/kv/page/kv-page-list-test.js
index 001a6cfad324..12c8de3abd04 100644
--- a/ui/tests/integration/components/kv/page/kv-page-list-test.js
+++ b/ui/tests/integration/components/kv/page/kv-page-list-test.js
@@ -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');
});
diff --git a/ui/tests/integration/components/kv/page/kv-page-version-history-test.js b/ui/tests/integration/components/kv/page/kv-page-version-history-test.js
index 012dadbded24..6461d6d0a64b 100644
--- a/ui/tests/integration/components/kv/page/kv-page-version-history-test.js
+++ b/ui/tests/integration/components/kv/page/kv-page-version-history-test.js
@@ -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.');
});
});
diff --git a/ui/tests/integration/components/oidc/client-list-test.js b/ui/tests/integration/components/oidc/client-list-test.js
new file mode 100644
index 000000000000..e65063164892
--- /dev/null
+++ b/ui/tests/integration/components/oidc/client-list-test.js
@@ -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``);
+
+ 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``);
+
+ 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');
+ });
+});
diff --git a/ui/tests/integration/components/oidc/provider-list-test.js b/ui/tests/integration/components/oidc/provider-list-test.js
new file mode 100644
index 000000000000..6bb194954cb5
--- /dev/null
+++ b/ui/tests/integration/components/oidc/provider-list-test.js
@@ -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``);
+
+ 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``);
+ 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');
+ });
+});
diff --git a/ui/tests/integration/components/pki/page/pki-key-list-test.js b/ui/tests/integration/components/pki/page/pki-key-list-test.js
index 2562d28c4eda..a9f56c301531 100644
--- a/ui/tests/integration/components/pki/page/pki-key-list-test.js
+++ b/ui/tests/integration/components/pki/page/pki-key-list-test.js
@@ -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`
`);
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) {
diff --git a/ui/tests/pages/access/identity/aliases/index.js b/ui/tests/pages/access/identity/aliases/index.js
index e43fd116d526..8fd07de9ceb1 100644
--- a/ui/tests/pages/access/identity/aliases/index.js
+++ b/ui/tests/pages/access/identity/aliases/index.js
@@ -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]'),
diff --git a/ui/tests/pages/access/identity/index.js b/ui/tests/pages/access/identity/index.js
index 73367d0db1a0..a034edb77707 100644
--- a/ui/tests/pages/access/identity/index.js
+++ b/ui/tests/pages/access/identity/index.js
@@ -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]'),