diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index ebf0d5177..289959e6c 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -378,6 +378,20 @@ credential-library: credential: title: Credential title_plural: Credentials + description: Credentials are static resources like usernames and passwords. + actions: + create: New Credential + delete: Delete Credential + messages: + none: + title: No Credentials Available + description: No Credentials available in this credential store. + types: + username_password: Username & Password + help: + username_password: This host requires a username and password to connect. + username: The username to use when signing in with this credential store. + password: The password to use when signing in with this credential store. scope: title: Scope title_plural: Scopes diff --git a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs index 4ba98f07d..7f6981d64 100644 --- a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs +++ b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs @@ -12,6 +12,14 @@ {{t 'resources.credential-library.actions.create'}} {{/if}} + {{#if (can 'create model' @model collection='credentials')}} + + {{t 'resources.credential.actions.create'}} + + {{/if}} {{#if (can 'delete model' @model)}} {{/if}} {{else}} - - {{t 'resources.credential.title_plural'}} - + {{#if (can 'navigate model' @model collection='credentials')}} + + {{t 'resources.credential.title_plural'}} + + {{/if}} {{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/form/credential/index.hbs b/ui/admin/app/components/form/credential/index.hbs new file mode 100644 index 000000000..16e50d4b0 --- /dev/null +++ b/ui/admin/app/components/form/credential/index.hbs @@ -0,0 +1,117 @@ + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + <:title>{{t 'form.type.label'}} + <:body> + + + + + + + + {{#if @model.errors.username}} + + {{#each @model.errors.username as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + {{#if @model.isNew}} + + {{#if @model.errors.password}} + + {{#each @model.errors.password as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + {{/if}} + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials.js new file mode 100644 index 000000000..4f001cd74 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials.js @@ -0,0 +1,18 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Translated credential breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.credential.title_plural'); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/credential.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/credential.js new file mode 100644 index 000000000..bb996f403 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/credential.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCredentialController extends Controller { + // =attributes + + /** + * A model breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/new.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/new.js new file mode 100644 index 000000000..630034ec3 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/new.js @@ -0,0 +1,18 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Translated new credentials breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('titles.new'); + } +} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials.js index 6fe1ecf81..6a20de805 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials.js @@ -1,5 +1,8 @@ import Route from '@ember/routing/route'; - +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { confirm } from 'core/decorators/confirm'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; import { inject as service } from '@ember/service'; export default class ScopesScopeCredentialStoresCredentialStoreCredentialsRoute extends Route { @@ -27,4 +30,64 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsRoute return this.store.query('credential', { credential_store_id }); } } + + // =actions + + /** + * Rollback changes on a credential. + * @param {CredentialModel} credential + */ + @action + cancel(credential) { + const { isNew } = credential; + credential.rollbackAttributes(); + if (isNew) + this.router.transitionTo( + 'scopes.scope.credential-stores.credential-store.credentials' + ); + } + + /** + * Handle save of a credential + * @param {CredentialModel} credential + */ + @action + @loading + @notifyError(({ message }) => message) + @notifySuccess(({ isNew }) => + isNew ? 'notifications.create-success' : 'notifications.save-success' + ) + async save(credential) { + //hardcoding the type to its subtype + //as the api expects the subtype `username_password` + credential.type = 'username_password'; + await credential.save(); + if (this.can.can('read model', credential)) { + await this.router.transitionTo( + 'scopes.scope.credential-stores.credential-store.credentials.credential', + credential + ); + } else { + await this.router.transitionTo( + 'scopes.scope.credential-stores.credential-store.credentials' + ); + } + this.refresh(); + } + /** + * Handle delete of a credential + * @param {Credential} credential + */ + @action + @loading + @confirm('questions.delete-confirm') + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.delete-success') + async deleteCredential(credential) { + await credential.destroyRecord(); + await this.router.replaceWith( + 'scopes.scope.credential-stores.credential-store.credentials' + ); + this.refresh(); + } } diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential.js new file mode 100644 index 000000000..eac5b7eb9 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential.js @@ -0,0 +1,17 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCredentialRoute extends Route { + // =methods + + /** + * Load a credential using current credential store and its parent scope. + * @param {object} params + * @param {string} params.credential_id + * @return {CredentialModel} + */ + async model({ credential_id }) { + return this.store.findRecord('credential', credential_id, { + reload: true, + }); + } +} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential/index.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential/index.js new file mode 100644 index 000000000..b00b2fa40 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/credential/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCredentialIndexRoute extends Route {} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js new file mode 100644 index 000000000..0541aebe4 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js @@ -0,0 +1,19 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved credential in current credential store. + * @return {CredentialModel} + */ + model() { + const { id: credential_store_id, type } = this.modelFor( + 'scopes.scope.credential-stores.credential-store' + ); + return this.store.createRecord('credential', { + type, + credential_store_id, + }); + } +} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential.hbs new file mode 100644 index 000000000..7d32ecb0d --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential.hbs @@ -0,0 +1,3 @@ +{{page-title @model.displayName}} + +{{outlet}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential/index.hbs new file mode 100644 index 000000000..3b9aff7f6 --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/credential/index.hbs @@ -0,0 +1,56 @@ + + + + + + + +

+ {{t 'resources.credential.title'}} +

+

{{t 'resources.credential.description'}}

+ + {{@model.id}} + +
+ + + + + {{t 'titles.details'}} + + + + + + {{#if (can 'delete model' @model)}} + + + {{t 'resources.credential.actions.delete'}} + + + {{/if}} + + + + + +
\ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs index 2721e31a3..1937582e0 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs @@ -1,3 +1,116 @@ -{{page-title (t 'resources.credential.title_plural')}} + -{{outlet}} \ No newline at end of file + + + + + + + + + + + + + + + + + + {{#if @model}} + + + + + {{t 'form.name.label'}} + {{t 'form.type.label'}} + {{t 'form.id.label'}} + + + + {{#each @model as |credential|}} + + + {{#if (can 'read model' credential)}} + + {{credential.displayName}} + + {{else}} + {{credential.displayName}} + {{/if}} +

{{credential.description}}

+
+ + {{t + (concat 'resources.credential.types.' credential.type) + }} + + + + {{credential.id}} + + +
+ {{/each}} +
+
+ + {{else}} + + + + + {{#if + (can 'list model' this.credentialStore collection='credentials') + }} + {{! can list (at least): show default welcome message}} + {{t 'resources.credential.description'}} + + {{else if (can + 'create model' this.credentialStore collection='credentials' + )}} + {{! can create (only): show create-but-not-list welcome message}} + {{t + 'descriptions.create-but-not-list' + resource=(t 'resources.credential.title_plural') + }} + {{else}} + {{! can neither list nor create }} + {{t + 'descriptions.neither-list-nor-create' + resource=(t 'resources.credential.title_plural') + }} + {{/if}} + + + + {{#if + (can 'create model' this.credentialStore collection='credentials') + }} + + + {{t 'titles.new'}} + + {{/if}} + + + + {{/if}} +
+
\ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/new.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/new.hbs new file mode 100644 index 000000000..58ffeb0a7 --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/new.hbs @@ -0,0 +1,22 @@ +{{page-title (t 'titles.new')}} + + + + + + + + +

+ {{t 'titles.new'}} +

+
+ + + + +
\ No newline at end of file