Merge branch 'main' into target-credential-library-api-modeling

This commit is contained in:
Susmitha Girumala authored and GitHub committed 2021-07-23 13:08:19 -04:00
commit d2a42f7468
20 files changed
+360 -7

No files matched your search

@@ -2,6 +2,13 @@ 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_method: ['GET', 'POST'],
};
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
@@ -1,5 +1,6 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
import { options } from './credential-library';
export default class FragmentCredentialLibraryAttributesModel extends Fragment {
//= attributes (vault)
@@ -7,6 +8,7 @@ export default class FragmentCredentialLibraryAttributesModel extends Fragment {
@attr('string', {
description:
'The HTTP method the library uses when requesting credentials from Vault.',
defaultValue: () => options.http_method[0],
})
http_method;
+9 -1
View File
@@ -497,7 +497,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');
+12 -2
View File
@@ -277,9 +277,19 @@ 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
description: No Credential Libraries available in this credential store.
actions:
create: New Credential Library
form:
vault_path:
label: Vault Path
help: The path the library uses when requesting credentials from Vault.
http_method:
label: HTTP Method
help: The HTTP method the library uses when requesting credentials from Vault.
http_request_body:
label: HTTP Method POST Request Body
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
@@ -20,6 +20,7 @@
input=(component 'rose/form/input' disabled=this.disabled)
select=(component 'rose/form/select' disabled=this.disabled)
textarea=(component 'rose/form/textarea' disabled=this.disabled)
fieldset=(component 'rose/form/fieldset')
actions=(component
(if this.showEditToggleButton
'rose/form/actions/edit-toggle'
@@ -5,7 +5,7 @@
margin-bottom: sizing.rems(m);
.rose-form-fieldset-title {
@include type.type(s, bold);
@include type.type(s, semibold);
}
.rose-form-fieldset-description {
@@ -0,0 +1,116 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<form.input
@name='name'
@type='name'
@value={{@model.name}}
@label={{t 'form.name.label'}}
@error={{@model.errors.name}}
@helperText={{t 'form.name.help'}}
readonly={{false}}
as |field|
>
{{#if @model.errors.name}}
<field.errors as |errors|>
{{#each @model.errors.name as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
<form.textarea
@name='description'
@type='description'
@value={{@model.description}}
@label={{t 'form.description.label'}}
@error={{@model.errors.description}}
@helperText={{t 'form.description.help'}}
@disabled={{@model.isSaving}}
readonly={{false}}
as |field|
>
{{#if @model.errors.description}}
<field.errors as |errors|>
{{#each @model.errors.description as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.textarea>
<form.input
@name='vault_path'
@type='text'
@value={{@model.attributes.path}}
@label={{t 'resources.credential-library.form.vault_path.label'}}
@error={{@model.errors.attributes_path}}
@helperText={{t 'resources.credential-library.form.vault_path.help'}}
readonly={{false}}
as |field|
>
{{#if @model.errors.attributes_path}}
<field.errors as |errors|>
{{#each @model.errors.attributes_path as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
<form.fieldset>
<:title>{{t 'resources.credential-library.form.http_method.label'}}</:title>
<:description>{{t
'resources.credential-library.form.http_method.help'
}}</:description>
<:body>
<form.radioGroup
@name='http_method'
@selectedValue={{@model.attributes.http_method}}
@changed={{fn (mut @model.attributes.http_method)}}
@inline={{true}}
as |radioGroup|
>
{{#each this.httpMethodOptions as |httpMethod|}}
<radioGroup.radio @label={{httpMethod}} @value={{httpMethod}} />
{{/each}}
</form.radioGroup>
</:body>
</form.fieldset>
{{#if this.isHttpRequestBodyAllowed}}
<form.textarea
@name='http_request_body'
@type='text'
@value={{@model.attributes.http_request_body}}
@label={{t 'resources.credential-library.form.http_request_body.label'}}
@error={{@model.errors.attributes_http_request_body}}
@helperText={{t
'resources.credential-library.form.http_request_body.help'
}}
@disabled={{this.cannotSaveHttpRequestBody}}
readonly={{false}}
as |field|
>
{{#if @model.errors.attributes_http_request_body}}
<field.errors as |errors|>
{{#each @model.errors.attributes_http_request_body as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.textarea>
{{/if}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
</Rose::Form>
@@ -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}
*/
httpMethodOptions = options.http_method;
/**
* 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);
}
}
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesController extends Controller {
// =services
@service intl;
// =attributes
/**
* Translated credential libraries breadcrumb
* @type {string}
*/
get breadCrumb() {
return this.intl.t('resources.credential-library.title_plural');
}
}
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewController extends Controller {
// =services
@service intl;
// =attributes
/**
* Translated new credential libraries breadcrumb
* @type {string}
*/
get breadCrumb() {
return this.intl.t('titles.new');
}
}
+3 -1
View File
@@ -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');
@@ -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();
}
}
@@ -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',
});
}
}
@@ -3,10 +3,13 @@
@dropdownRight={{true}}
as |dropdown|
>
{{!-- <dropdown.link @route='scopes.scope.credential-stores.new' @model={{@model}}>
<dropdown.link
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
@model={{@model}}
>
{{t 'resources.credential-library.actions.create'}}
</dropdown.link>
<dropdown.separator /> --}}
<dropdown.separator />
<dropdown.button
@style='danger'
@disabled={{@model.canSave}}
@@ -38,6 +38,12 @@
<message.description>
{{t 'resources.credential-library.messages.none.description'}}
</message.description>
<message.link
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
>
<Rose::Icon @name='plus-circle-outline' />
{{t 'titles.new'}}
</message.link>
</Rose::Message>
</Rose::Layout::Centered>
@@ -0,0 +1,7 @@
{{page-title (t 'titles.new')}}
<Form::CredentialLibrary
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
/>
@@ -0,0 +1,3 @@
<h2>
{{t 'titles.new'}}
</h2>
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries',
function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/credential-stores/credential-store/credential-libraries'
);
assert.ok(controller);
});
}
);
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries/new',
function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/credential-stores/credential-store/credential-libraries/new'
);
assert.ok(controller);
});
}
);
@@ -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);
});
}
);