diff --git a/ui/app/components/generate-credentials-database.hbs b/ui/app/components/generate-credentials-database.hbs index 3df000fc27..b2c900f36d 100644 --- a/ui/app/components/generate-credentials-database.hbs +++ b/ui/app/components/generate-credentials-database.hbs @@ -41,22 +41,22 @@ - - + + {{/if}} {{! STATIC ROLE }} {{#if (and (eq @roleType "static") @model.username)}} - + {{/if}} diff --git a/ui/app/components/secret-engine/list.ts b/ui/app/components/secret-engine/list.ts index 35c5aa13d7..1c39757fab 100644 --- a/ui/app/components/secret-engine/list.ts +++ b/ui/app/components/secret-engine/list.ts @@ -62,23 +62,23 @@ export default class SecretEngineList extends Component { { key: 'accessor', label: 'Accessor', - width: '175px', + width: '205px', }, { key: 'description', label: 'Description', - width: '300px', + width: '320px', }, { key: 'running_plugin_version', label: 'Version', isSortable: true, - width: '170px', + width: '175px', }, { key: 'popupMenu', label: 'Action', - width: '75px', + width: '80px', }, ]; diff --git a/ui/app/components/toolbar-secret-link.hbs b/ui/app/components/toolbar-secret-link.hbs index 9cbc026931..f324566827 100644 --- a/ui/app/components/toolbar-secret-link.hbs +++ b/ui/app/components/toolbar-secret-link.hbs @@ -14,5 +14,5 @@ ...attributes > {{yield}} - + \ No newline at end of file diff --git a/ui/app/routes/vault/cluster/secrets/backend/credentials.js b/ui/app/routes/vault/cluster/secrets/backend/credentials.js index 69a8dbead4..c30c2e8948 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/credentials.js +++ b/ui/app/routes/vault/cluster/secrets/backend/credentials.js @@ -5,7 +5,6 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; -import ControlGroupError from 'vault/lib/control-group-error'; const SUPPORTED_DYNAMIC_BACKENDS = ['database', 'ssh', 'aws', 'totp']; @@ -13,7 +12,6 @@ export default Route.extend({ templateName: 'vault/cluster/secrets/backend/credentials', pathHelp: service('path-help'), router: service(), - store: service(), api: service(), beforeModel(transition) { @@ -34,21 +32,42 @@ export default Route.extend({ } }, - getDatabaseCredential(backend, secret, roleType = '') { - return this.store.queryRecord('database/credential', { backend, secret, roleType }).catch((error) => { - if (error instanceof ControlGroupError) { - throw error; + async getDatabaseCredential(backend, secret, roleType = '') { + try { + if (roleType === 'static') { + const { last_vault_rotation, lease_duration, data } = + await this.api.secrets.databaseReadStaticRoleCredentials(secret, backend); + return { + last_vault_rotation, + lease_duration, + ...data, + }; + } else { + const { data, lease_id, lease_duration } = await this.api.secrets.databaseGenerateCredentials( + secret, + backend + ); + return { + ...data, + lease_id, + lease_duration, + }; + } + } catch (error) { + const { response } = await this.api.parseError(error); + if (response.isControlGroupError) { + throw response; } // Unless it's a control group error, we want to pass back error info // so we can render it on the GenerateCredentialsDatabase component - return error; - }); + return response; + } }, async getAwsRole(backend, id) { try { - const role = await this.store.queryRecord('role-aws', { backend, id }); - return role; + const { data } = await this.api.secrets.awsReadRole(id, backend); + return data; } catch (e) { // swallow error, non-essential data return; @@ -57,8 +76,8 @@ export default Route.extend({ async getTotpKey(backend, keyName) { try { - const resp = await this.api.secrets.totpReadKey(keyName, backend); - return resp.data || {}; + const { data } = await this.api.secrets.totpReadKey(keyName, backend); + return data || {}; } catch (e) { // swallow error, non-essential data return {}; @@ -86,19 +105,11 @@ export default Route.extend({ roleName: role, roleType, dbCred, - awsRoleType: awsRole?.credentialType, + awsRoleType: awsRole?.credential_type, }; }, resetController(controller) { controller.reset(); }, - - actions: { - willTransition() { - // we do not want to save any of the credential information in the store. - // once the user navigates away from this page, remove all credential info. - this.store.unloadAll('database/credential'); - }, - }, }); diff --git a/ui/app/routes/vault/cluster/secrets/backend/overview.js b/ui/app/routes/vault/cluster/secrets/backend/overview.js index f3b7c21d6d..3ce1ae2587 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/overview.js +++ b/ui/app/routes/vault/cluster/secrets/backend/overview.js @@ -7,54 +7,96 @@ import Route from '@ember/routing/route'; import { hash } from 'rsvp'; import { service } from '@ember/service'; import { getEnginePathParam } from 'vault/utils/backend-route-helpers'; +import { + SecretsApiDatabaseListStaticRolesListEnum, + SecretsApiDatabaseListRolesListEnum, + SecretsApiDatabaseListConnectionsListEnum, +} from '@hashicorp/vault-client-typescript'; export default Route.extend({ - store: service(), + capabilities: service(), + api: service(), type: '', + // this only grabs connections for current db backend, only used to populate # of connections async fetchConnection(queryOptions) { try { - return await this.store.query('database/connection', queryOptions); - } catch (e) { - return e.httpStatus; + const { keys } = await this.api.secrets.databaseListConnections( + queryOptions.backend, + SecretsApiDatabaseListConnectionsListEnum.TRUE + ); + return keys; + } catch (error) { + const { status } = await this.api.parseError(error); + if (status === 404) { + return status; + } } }, + // this grabs both dynamic and static roles for current db backend, only used to populate # of roles async fetchAllRoles(queryOptions) { try { - return await this.store.query('database/role', queryOptions); - } catch (e) { - return e.httpStatus; - } - }, + const roles = []; + const { backend } = queryOptions; + const [staticResp, dynamicResp] = await Promise.allSettled([ + this.api.secrets.databaseListStaticRoles(backend, SecretsApiDatabaseListStaticRolesListEnum.TRUE), + this.api.secrets.databaseListRoles(backend, SecretsApiDatabaseListRolesListEnum.TRUE), + ]); - pathQuery(backend, endpoint) { - return { - id: `${backend}/${endpoint}/`, - }; + if (staticResp.status === 'rejected' && dynamicResp.status === 'rejected') { + const { response: staticError, status: staticStatus } = await this.api.parseError(staticResp.reason); + const { response: dynamicError, status: dynamicStatus } = await this.api.parseError( + dynamicResp.reason + ); + if (staticError?.isControlGroupError) { + throw staticError; + } + throw staticStatus < dynamicStatus ? dynamicError : staticError; + } else { + if (staticResp.value) { + roles.push(...staticResp.value.keys); + } + if (dynamicResp.value) { + roles.push(...dynamicResp.value.keys); + } + return roles; + } + } catch (error) { + const { status } = await this.api.parseError(error); + if (status === 404) { + return status; + } + } }, async fetchCapabilitiesRole(queryOptions) { - return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'roles')); + const paths = [this.capabilities.pathFor('databaseRoles', { backend: queryOptions.backend })]; + const capabilities = paths ? await this.capabilities.fetch(paths) : {}; + return capabilities[paths[0]]; }, async fetchCapabilitiesStaticRole(queryOptions) { - return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'static-roles')); + const paths = [this.capabilities.pathFor('databaseStaticRoles', { backend: queryOptions.backend })]; + const capabilities = paths ? await this.capabilities.fetch(paths) : {}; + return capabilities[paths[0]]; }, async fetchCapabilitiesConnection(queryOptions) { - return this.store.queryRecord('capabilities', this.pathQuery(queryOptions.backend, 'config')); + const paths = [this.capabilities.pathFor('databaseConfig', { backend: queryOptions.backend })]; + const capabilities = paths ? await this.capabilities.fetch(paths) : {}; + return capabilities[paths[0]]; }, - model() { + async model() { const backend = getEnginePathParam(this); const queryOptions = { backend, id: '' }; - const connection = this.fetchConnection(queryOptions); - const role = this.fetchAllRoles(queryOptions); - const roleCapabilities = this.fetchCapabilitiesRole(queryOptions); - const staticRoleCapabilities = this.fetchCapabilitiesStaticRole(queryOptions); - const connectionCapabilities = this.fetchCapabilitiesConnection(queryOptions); + const connection = await this.fetchConnection(queryOptions); + const role = await this.fetchAllRoles(queryOptions); + const roleCapabilities = await this.fetchCapabilitiesRole(queryOptions); + const staticRoleCapabilities = await this.fetchCapabilitiesStaticRole(queryOptions); + const connectionCapabilities = await this.fetchCapabilitiesConnection(queryOptions); return hash({ backend, @@ -71,7 +113,7 @@ export default Route.extend({ setupController(controller, model) { this._super(...arguments); - const showEmptyState = model.connections === 404 && model.roles === 404; + const showEmptyState = model.connections === 404 && (model.roles === undefined || model.roles === 404); const noConnectionCapabilities = !model.connectionCapabilities.canList && !model.connectionCapabilities.canCreate && diff --git a/ui/app/templates/vault/cluster/secrets/backend/credentials.hbs b/ui/app/templates/vault/cluster/secrets/backend/credentials.hbs index e072608729..a8249b31c3 100644 --- a/ui/app/templates/vault/cluster/secrets/backend/credentials.hbs +++ b/ui/app/templates/vault/cluster/secrets/backend/credentials.hbs @@ -7,7 +7,7 @@ {{else if (eq this.model.backendType "totp")}} diff --git a/ui/tests/acceptance/secrets/backend/database/secret-test.js b/ui/tests/acceptance/secrets/backend/database/secret-test.js index dba92d0843..36e20a6e44 100644 --- a/ui/tests/acceptance/secrets/backend/database/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/database/secret-test.js @@ -644,10 +644,7 @@ module('Acceptance | secrets/database/*', function (hooks) { assert .dom('[data-test-secret-list-tab="Roles"]') .doesNotExist(`does not show the roles tab because it does not have permissions`); - assert - .dom('[data-test-overview-card="Connections"]') - .exists({ count: 1 }, 'renders only the connection card'); - await click('[data-test-action-text="Configure new"]'); + await click(SES.createSecretLink); assert.strictEqual(currentURL(), `/vault/secrets-engines/${backend}/create?itemType=connection`); }); }); diff --git a/ui/tests/acceptance/secrets/backend/database/workflow-test.js b/ui/tests/acceptance/secrets/backend/database/workflow-test.js index 0997e9d20e..4f521eccc9 100644 --- a/ui/tests/acceptance/secrets/backend/database/workflow-test.js +++ b/ui/tests/acceptance/secrets/backend/database/workflow-test.js @@ -335,7 +335,7 @@ module('Acceptance | database workflow', function (hooks) { .hasText('generated-password', 'Password is generated'); assert .dom(GENERAL.infoRowValue('Lease Duration')) - .hasText('3600', 'shows lease duration from response'); + .hasText('1 hour', 'shows lease duration from response'); assert .dom(GENERAL.infoRowValue('Lease ID')) .hasText(`database/creds/${roleName}/abcd`, 'shows lease ID from response');