diff --git a/addons/api/addon/models/credential-library.js b/addons/api/addon/models/credential-library.js
index c95b9f73e..4ca321a18 100644
--- a/addons/api/addon/models/credential-library.js
+++ b/addons/api/addon/models/credential-library.js
@@ -2,13 +2,24 @@ import GeneratedCredentialLibraryModel from '../generated/models/credential-libr
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
+/**
+ * Enum options for credential library.
+ */
+export const options = {
+ http_methods: ['GET', 'POST'],
+};
+
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
- @attr('string', { readOnly: true }) attributes_http_method;
+ @attr('string', {
+ readOnly: true,
+ defaultValue: options.http_methods[0],
+ })
+ attributes_http_method;
@attr('string', { readOnly: true }) attributes_http_request_body;
@attr('string', { readOnly: true }) attributes_path;
diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js
index 1fd364182..a45cd1bab 100644
--- a/addons/api/mirage/config.js
+++ b/addons/api/mirage/config.js
@@ -478,7 +478,15 @@ export default function () {
) => credentialLibraries.where({ credentialStoreId })
);
this.get('/credential-libraries/:id');
- this.post('/credential-libraries/:id');
+ this.post(
+ '/credential-libraries',
+ function ({ credentialStores, credentialLibraries }) {
+ const attrs = this.normalizedRequestAttrs();
+ const credentialStore = credentialStores.find(attrs.credentialStoreId);
+ attrs.scopeId = credentialStore.scope.id;
+ return credentialLibraries.create(attrs);
+ }
+ );
this.del('/credential-libraries/:id');
this.patch('/credential-libraries/:id');
diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml
index cb7b69832..b59b55b91 100644
--- a/addons/core/translations/form/en-us.yaml
+++ b/addons/core/translations/form/en-us.yaml
@@ -115,3 +115,12 @@ value:
callback_url:
label: Callback URL
help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration.
+vault_path:
+ label: Vault Path
+ help: The path in Vault to request credentials from.
+http_method:
+ label: HTTP Method
+ help: The HTTP method the library uses when requesting credentials from Vault.
+http_request_body:
+ label: HTTP Request Body
+ help: The body of the HTTP request the library sends to Vault when requesting credentials.
diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml
index cc9fed978..9468e3500 100644
--- a/addons/core/translations/resources/en-us.yaml
+++ b/addons/core/translations/resources/en-us.yaml
@@ -275,6 +275,8 @@ credential-library:
title: Credential Library
title_plural: Credential Libraries
description: A credential library is a resource that provides credentials.
+ actions:
+ create: New Credential Library
messages:
none:
title: No Credential Libraries Available
diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs
new file mode 100644
index 000000000..f5bd6c4eb
--- /dev/null
+++ b/ui/admin/app/components/form/credential-library/index.hbs
@@ -0,0 +1,105 @@
+
+
+ {{#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}}
+
+
+
+ {{#if @model.errors.name}}
+
+ {{#each @model.errors.name as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+ {{#each this.httpMethods as |httpMethod|}}
+
+ {{/each}}
+
+
+ {{#if this.isHttpRequestBodyAllowed}}
+
+ {{#if @model.errors.description}}
+
+ {{#each @model.errors.description as |error|}}
+ {{error.message}}
+ {{/each}}
+
+ {{/if}}
+
+ {{/if}}
+
+
+
\ No newline at end of file
diff --git a/ui/admin/app/components/form/credential-library/index.js b/ui/admin/app/components/form/credential-library/index.js
new file mode 100644
index 000000000..69427c13b
--- /dev/null
+++ b/ui/admin/app/components/form/credential-library/index.js
@@ -0,0 +1,20 @@
+import Component from '@glimmer/component';
+import { options } from 'api/models/credential-library';
+import { computed } from '@ember/object';
+
+export default class FormCredentialLibraryIndexComponent extends Component {
+ // =properties
+ /**
+ * @type {object}
+ */
+ httpMethods = options.http_methods;
+
+ /**
+ * Only allow HTTP request body field if http_method is set to POST.
+ * @type {boolean}
+ */
+ @computed('args.model.attributes_http_method')
+ get isHttpRequestBodyAllowed() {
+ return this.args.model.attributes_http_method?.match(/post/i);
+ }
+}
diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js
index 4012791b2..3c359e268 100644
--- a/ui/admin/app/router.js
+++ b/ui/admin/app/router.js
@@ -91,7 +91,9 @@ Router.map(function () {
'credential-store',
{ path: ':credential_store_id' },
function () {
- this.route('credential-libraries', function () {});
+ this.route('credential-libraries', function () {
+ this.route('new');
+ });
}
);
this.route('new');
diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js
index 307c11079..fa5b56baf 100644
--- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js
+++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js
@@ -10,7 +10,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Rou
}
/**
- * Render credential store header and navigation page sections.
+ * Render credential store header, navigation, and actions page sections.
* @override
*/
renderTemplate() {
@@ -25,5 +25,10 @@ export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Rou
into: 'scopes/scope/credential-stores/credential-store',
outlet: 'navigation',
});
+
+ this.render('scopes/scope/credential-stores/credential-store/-actions', {
+ into: 'scopes/scope/credential-stores/credential-store',
+ outlet: 'actions',
+ });
}
}
diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js
index 7c8ade755..fa54e9691 100644
--- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js
+++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js
@@ -1,4 +1,7 @@
import Route from '@ember/routing/route';
+import { action } from '@ember/object';
+import loading from 'ember-loading/decorator';
+import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route {
// =methods
@@ -13,4 +16,38 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
);
return this.store.query('credential-library', { credential_store_id });
}
+
+ // =actions
+
+ /**
+ * Rollback changes on a credential library.
+ * @param {CredentialLibraryModel} credentialLibrary
+ */
+ @action
+ cancel(credentialLibrary) {
+ const { isNew } = credentialLibrary;
+ credentialLibrary.rollbackAttributes();
+ if (isNew)
+ this.transitionTo(
+ 'scopes.scope.credential-stores.credential-store.credential-libraries'
+ );
+ }
+
+ /**
+ * Handle save of a credential library
+ * @param {CredentialLibraryModel} credentialLibrary
+ */
+ @action
+ @loading
+ @notifyError(({ message }) => message)
+ @notifySuccess(({ isNew }) =>
+ isNew ? 'notifications.create-success' : 'notifications.save-success'
+ )
+ async save(credentialLibrary) {
+ await credentialLibrary.save();
+ await this.transitionTo(
+ 'scopes.scope.credential-stores.credential-store.credential-libraries'
+ );
+ this.refresh();
+ }
}
diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js
new file mode 100644
index 000000000..d83109dda
--- /dev/null
+++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js
@@ -0,0 +1,45 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewRoute extends Route {
+ // =methods
+
+ /**
+ * Creates a new unsaved credential library in current credential store.
+ * @return {CredentialLibraryModel}
+ */
+ model() {
+ const { id: credential_store_id } = this.modelFor(
+ 'scopes.scope.credential-stores.credential-store'
+ );
+ return this.store.createRecord('credential-library', {
+ credential_store_id,
+ });
+ }
+
+ /**
+ * Renders new credential library specific templates for header, navigation,
+ * and action page sections.
+ * @override
+ */
+ renderTemplate() {
+ super.renderTemplate(...arguments);
+
+ this.render(
+ 'scopes/scope/credential-stores/credential-store/credential-libraries/new/-header',
+ {
+ into: 'scopes/scope/credential-stores/credential-store',
+ outlet: 'header',
+ }
+ );
+
+ this.render('_empty', {
+ into: 'scopes/scope/credential-stores/credential-store',
+ outlet: 'navigation',
+ });
+
+ this.render('_empty', {
+ into: 'scopes/scope/credential-stores/credential-store',
+ outlet: 'actions',
+ });
+ }
+}
diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs
index a6ecd7f81..efc46641a 100644
--- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs
+++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs
@@ -15,7 +15,7 @@
- {{!-- {{outlet 'actions'}} --}}
+ {{outlet 'actions'}}
diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs
new file mode 100644
index 000000000..587d095db
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs
@@ -0,0 +1,12 @@
+
+
+ {{t 'resources.credential-library.actions.create'}}
+
+
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs
new file mode 100644
index 000000000..5d03e5b9f
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs
@@ -0,0 +1,7 @@
+{{page-title (t 'titles.new')}}
+
+
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs
new file mode 100644
index 000000000..51755b136
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs
@@ -0,0 +1,3 @@
+
+ {{t 'titles.new'}}
+
\ No newline at end of file
diff --git a/ui/admin/tests/integration/components/form/credential-library/index-test.js b/ui/admin/tests/integration/components/form/credential-library/index-test.js
new file mode 100644
index 000000000..e0d24a8c2
--- /dev/null
+++ b/ui/admin/tests/integration/components/form/credential-library/index-test.js
@@ -0,0 +1,29 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module(
+ 'Integration | Component | form/credential-library/index',
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.equal(this.element.textContent.trim(), '');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.equal(this.element.textContent.trim(), 'template block text');
+ });
+ }
+);
diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js
new file mode 100644
index 000000000..3c4efdd66
--- /dev/null
+++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js
@@ -0,0 +1,16 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module(
+ 'Unit | Route | scopes/scope/credential-stores/credential-store/credential-libraries/new',
+ function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup(
+ 'route:scopes/scope/credential-stores/credential-store/credential-libraries/new'
+ );
+ assert.ok(route);
+ });
+ }
+);