Merge remote-tracking branch 'origin/develop' into add-manage-button-to-edit-form
This commit is contained in:
28 files changed
+544
-41
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import RESTAdapter from '@ember-data/adapter/rest';
|
||||
import { serializeIntoHash } from '@ember-data/adapter/-private';
|
||||
import AdapterBuildURLMixin from '../mixins/adapter-build-url';
|
||||
import config from 'ember-get-config';
|
||||
import { get, getWithDefault } from '@ember/object';
|
||||
@@ -143,6 +144,26 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
return super.query(...arguments).then(prenormalizeArrayResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Our API expects update calls with a custom method suffix
|
||||
* (e.g. `:set-grants`) to be made with POST instead of PUT/PATCH.
|
||||
* If this method is called with a custom method via `adapterOptions.method`,
|
||||
* the API request will be made with POST. All other update requests are
|
||||
* made as normal.
|
||||
* @override
|
||||
* @param {Store} store
|
||||
* @param {Model} type
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
updateRecord(store, type, snapshot) {
|
||||
const method = get(snapshot, 'adapterOptions.method');
|
||||
const data = serializeIntoHash(store, type, snapshot, {});
|
||||
const id = snapshot.id;
|
||||
const url = this.buildURL(type.modelName, id, snapshot, 'updateRecord');
|
||||
return this.ajax(url, method ? 'POST' : 'PUT', { data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides default ajax method by rewritting PUT requests as PATCH.
|
||||
* PATCH is the request method used by our API for updates.
|
||||
|
||||
@@ -33,4 +33,10 @@ export default class GeneratedRoleModel extends BaseModel {
|
||||
description: 'Whether the resource is disabled',
|
||||
})
|
||||
disabled;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
/**
|
||||
* Represents an item in an array of strings.
|
||||
*/
|
||||
export default class FragmentStringModel extends Fragment {
|
||||
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'The value of the string.'
|
||||
})
|
||||
value;
|
||||
|
||||
}
|
||||
@@ -1,3 +1,30 @@
|
||||
import GeneratedRoleModel from '../generated/models/role';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default class RoleModel extends GeneratedRoleModel {}
|
||||
export default class RoleModel extends GeneratedRoleModel {
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Grants is read-only _most_ under normal circumstances. But grants can
|
||||
* be persisted via a dedicated call to `saveGrants()`.
|
||||
*/
|
||||
@fragmentArray('fragment-string', {readOnly: true}) grants;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Saves the `grants` array on the role via the `set-grants` method.
|
||||
* See serializer and adapter for more information.
|
||||
* @return {Promise}
|
||||
*/
|
||||
saveGrants() {
|
||||
return this.save({
|
||||
adapterOptions: {
|
||||
method: 'set-grants',
|
||||
serializeGrants: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import ApplicationSerializer from './application';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default class RoleSerializer extends ApplicationSerializer {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* If `adapterOptions.serializeGrants` is true, the serialization should
|
||||
* include **only grants** and the version. Normally, grants are not
|
||||
* serialized.
|
||||
* @override
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
const serializeGrants = get(snapshot, 'adapterOptions.serializeGrants');
|
||||
let serialized = super.serialize(...arguments);
|
||||
if (serializeGrants) serialized = this.serializeWithGrants(snapshot);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a payload containing only the grants array.
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
*/
|
||||
serializeWithGrants(snapshot) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
grant_strings: snapshot.attr('grants').map(grant => grant.attr('value'))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string arrays to object arrays for use with `fragment-string`.
|
||||
* E.g. `"foo"` -> `{value: "foo"}`
|
||||
* @override
|
||||
* @param {Model} typeClass
|
||||
* @param {Object} hash
|
||||
* @return {Object}
|
||||
*/
|
||||
normalize(modelClass, resourceHash) {
|
||||
// TODO: can fragment string normalization be handled generically?
|
||||
if (resourceHash.grant_strings) {
|
||||
resourceHash.grants =
|
||||
resourceHash.grant_strings.map(value => ({ value }));
|
||||
}
|
||||
return super.normalize(modelClass, resourceHash);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-string';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/role';
|
||||
@@ -112,6 +112,23 @@ module('Unit | Adapter | application', function (hooks) {
|
||||
adapter.ajax('/', 'PUT');
|
||||
});
|
||||
|
||||
test('it rewrites PUT/PATCH to POST when `adapterOptions.method` is passed to `updateRecord`', function (assert) {
|
||||
assert.expect(1);
|
||||
const adapter = this.owner.lookup('adapter:application');
|
||||
const store = this.owner.lookup('service:store');
|
||||
const snapshot = store.createRecord('role', {})._createSnapshot();
|
||||
snapshot.adapterOptions = {
|
||||
method: 'custom-method'
|
||||
}
|
||||
// TODO this is icky, should be changed to a spy or stub
|
||||
const originalAjax = RESTAdapter.prototype.ajax;
|
||||
RESTAdapter.prototype.ajax = (url, type) => {
|
||||
assert.equal(type, 'POST');
|
||||
RESTAdapter.prototype.ajax = originalAjax;
|
||||
};
|
||||
adapter.updateRecord(store, {modelName: 'user'}, snapshot);
|
||||
});
|
||||
|
||||
test('it prenormalizes "empty" responses into a form that the fetch-manager will not reject', async function (assert) {
|
||||
assert.expect(1);
|
||||
const adapter = this.owner.lookup('adapter:application');
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | fragment string', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-string', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,44 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
|
||||
module('Unit | Model | role', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('role', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it has a `saveGrants` method that targets a specific POST API endpoint and serialization', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post('/v1/scopes/o_1/roles/123abc:set-grants', (schema, request) => {
|
||||
const body = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(body, {
|
||||
grant_strings: ['grant1', 'grant2'],
|
||||
version: 1
|
||||
});
|
||||
});
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'role',
|
||||
attributes: {
|
||||
name: 'Role',
|
||||
description: 'Description',
|
||||
grants: [{value: 'grant1'}, {value: 'grant2'}],
|
||||
version: 1,
|
||||
scope: {
|
||||
scope_id: 'o_1',
|
||||
type: 'scope'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const model = store.peekRecord('role', '123abc');
|
||||
await model.saveGrants();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | role', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes roles normally, without grants', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('role');
|
||||
const record = store.createRecord('role', {
|
||||
name: 'User',
|
||||
description: 'Description',
|
||||
grants: [{value: 'foo'}, {value: 'bar'}],
|
||||
version: 1
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
name: 'User',
|
||||
description: 'Description',
|
||||
version: 1
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes only grants when `adapterOptions.serializeGrants` is true', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('role');
|
||||
const record = store.createRecord('role', {
|
||||
name: 'User',
|
||||
description: 'Description',
|
||||
grants: [{value: 'foo'}, {value: 'bar'}],
|
||||
version: 1
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {
|
||||
serializeGrants: true
|
||||
};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
grant_strings: ['foo', 'bar'],
|
||||
version: 1
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes records with array fields', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('role');
|
||||
const roleModelClass = store.createRecord('role').constructor;
|
||||
const payload = {
|
||||
id: '1',
|
||||
name: 'Role 1',
|
||||
grant_strings: ['*', '*']
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
roleModelClass,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'role',
|
||||
attributes: {
|
||||
name: 'Role 1',
|
||||
grants: [{value: '*'}, {value: '*'}],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -49,19 +49,19 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
||||
*
|
||||
* @override
|
||||
* @param {object} creds
|
||||
* @param {string} creds.identification is sent to server as `name`
|
||||
* @param {string} creds.identification is sent to server as `login_name`
|
||||
* @param {string} creds.password
|
||||
* @param {boolean} requestCookies request cookie tokens (default `true`)
|
||||
* @return {Promise}
|
||||
*/
|
||||
async authenticate(
|
||||
{ identification: name, password },
|
||||
{ identification: login_name, password },
|
||||
requestCookies = true,
|
||||
options
|
||||
) {
|
||||
const body = JSON.stringify({
|
||||
token_type: requestCookies ? 'cookie' : null,
|
||||
credentials: { name, password },
|
||||
credentials: { login_name, password },
|
||||
});
|
||||
const authEndpointURL = this.buildAuthEndpointURL(options);
|
||||
const response = await fetch(authEndpointURL, { method: 'post', body });
|
||||
|
||||
@@ -41,7 +41,7 @@ module('Unit | Authenticator | password', function (hooks) {
|
||||
assert.deepEqual(json, {
|
||||
token_type: 'cookie',
|
||||
credentials: {
|
||||
name: 'foo',
|
||||
login_name: 'foo',
|
||||
password: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
@use '../../variables/typography';
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-list-key-value {
|
||||
margin-bottom: sizing.rems(m);
|
||||
}
|
||||
|
||||
.rose-list-key-value-item {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--ui-gray-subtler-3);
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
novalidate
|
||||
method="post"
|
||||
class="rose-form"
|
||||
autocomplete="off"
|
||||
{{on "submit" this.handleSubmit}}
|
||||
>
|
||||
>
|
||||
{{yield (hash
|
||||
radioGroup=(component 'rose/form/radio/group' disabled=@disabled)
|
||||
checkbox=(component 'rose/form/checkbox' disabled=@disabled)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{fn this.createGrant @addGrant}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
as |form|
|
||||
>
|
||||
<Rose::List::KeyValue as |list|>
|
||||
{{!-- Add grant field (only one per form) --}}
|
||||
<list.item as |item|>
|
||||
<form.input
|
||||
@contextual={{true}}
|
||||
@name="grant"
|
||||
@type="text"
|
||||
@value={{this.newGrantString}}
|
||||
as |input|>
|
||||
<item.key>
|
||||
<input.label>{{t "titles.new-grant"}}</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button
|
||||
@submit={{true}}
|
||||
@disabled={{this.cannotSave}}
|
||||
@style="secondary"
|
||||
title={{t "actions.add"}}
|
||||
>
|
||||
{{t "actions.add"}}
|
||||
</Rose::Button>
|
||||
</item.cell>
|
||||
</form.input>
|
||||
</list.item>
|
||||
</Rose::List::KeyValue>
|
||||
</Rose::Form>
|
||||
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
{{!--
|
||||
{{#if @model.errors.grants}}
|
||||
<input.errors as |errors|>
|
||||
{{#each @model.errors.grants as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</input.errors>
|
||||
{{/if}}
|
||||
--}}
|
||||
|
||||
<Rose::List::KeyValue as |list|>
|
||||
{{#each @model.grants as |grant|}}
|
||||
<list.item as |item|>
|
||||
<form.input
|
||||
@contextual={{true}}
|
||||
@name="grant"
|
||||
@type="text"
|
||||
@value={{grant.value}}
|
||||
@error={{@model.errors.grants}}
|
||||
as |input|>
|
||||
<item.key>
|
||||
<input.label>{{t "form.label.grant"}}</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
</form.input>
|
||||
<item.cell>
|
||||
<Rose::Button
|
||||
@style="secondary"
|
||||
@iconOnly="trash"
|
||||
title={{t "actions.remove"}}
|
||||
{{on "click" (fn @removeGrant grant)}}
|
||||
>
|
||||
{{t "actions.remove"}}
|
||||
</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
{{/each}}
|
||||
</Rose::List::KeyValue>
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@submitText={{t "actions.save"}}
|
||||
@cancelText={{t "actions.cancel"}} />
|
||||
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,37 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { computed, action } from '@ember/object';
|
||||
|
||||
export default class FormRoleGrantsComponent extends Component {
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked newGrantString = '';
|
||||
|
||||
/**
|
||||
* True if the grant string field is empty, false otherwise. This is used
|
||||
* to disable the submit button.
|
||||
* @return {boolean}
|
||||
*/
|
||||
@computed('newGrantString')
|
||||
get cannotSave() {
|
||||
return !this.newGrantString;
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Calls the passed function with the grant string as an argument and then
|
||||
* clears the value of the grant string field.
|
||||
* `@addGrant` should be passed by the context calling this component.
|
||||
* @param {Function} addGrantFn
|
||||
*/
|
||||
@action
|
||||
createGrant(addGrantFn) {
|
||||
addGrantFn(this.newGrantString);
|
||||
this.newGrantString = '';
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ Router.map(function () {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('roles', function () {
|
||||
this.route('role', { path: ':role_id' }, function () {});
|
||||
this.route('role', { path: ':role_id' }, function () {
|
||||
this.route('grants');
|
||||
});
|
||||
this.route('new');
|
||||
});
|
||||
this.route('projects', function() {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeRolesRoleGrantsRoute extends Route {
|
||||
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
@service notify;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Adds a new grant to the role at the beginning of the grants list.
|
||||
* Grant creation is not immediately permanent; users may rollback the change
|
||||
* via "cancel" or commit it via "save".
|
||||
* @param {RoleModel} role
|
||||
* @param {string} grantString
|
||||
*/
|
||||
@action
|
||||
addGrant(role, grantString) {
|
||||
role.grants.unshiftObject({ value: grantString });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a grant from the role. Grant removal is not immediately permanent;
|
||||
* users may rollback the change via "cancel" or commit it via "save".
|
||||
* @param {RoleModel} role
|
||||
* @param {FragmentString} grant
|
||||
*/
|
||||
@action
|
||||
removeGrant(role, grant) {
|
||||
role.grants.removeFragment(grant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an role in current scope.
|
||||
* @param {RoleModel} role
|
||||
*/
|
||||
@action
|
||||
async save(role) {
|
||||
try {
|
||||
await role.saveGrants();
|
||||
this.notify.success(
|
||||
this.intl.t('notify.save-success')
|
||||
);
|
||||
} catch (error) {
|
||||
// TODO: replace with translated strings
|
||||
this.notify.error(error.message, { closeAfter: null });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,28 @@
|
||||
{{title @model.displayName}}
|
||||
|
||||
{{outlet}}
|
||||
<Rose::Layout::Page as |page|>
|
||||
|
||||
<page.breadcrumbs>
|
||||
<BreadCrumbs />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.role"}}</h2>
|
||||
</page.header>
|
||||
|
||||
<page.navigation>
|
||||
<Rose::Nav::Tabs as |nav|>
|
||||
<nav.link @route='scopes.scope.roles.role.index'>
|
||||
{{t 'titles.settings'}}
|
||||
</nav.link>
|
||||
<nav.link @route='scopes.scope.roles.role.grants'>
|
||||
{{t 'titles.grants'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Tabs>
|
||||
</page.navigation>
|
||||
|
||||
<page.body>
|
||||
{{outlet}}
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,9 @@
|
||||
{{title (t 'titles.grants')}}
|
||||
|
||||
<Form::Role::Grants
|
||||
@model={{@model}}
|
||||
@submit={{route-action "save" @model}}
|
||||
@cancel={{route-action "cancel" @model}}
|
||||
@addGrant={{route-action "addGrant" @model}}
|
||||
@removeGrant={{route-action "removeGrant" @model}}
|
||||
/>
|
||||
@@ -1,31 +1,6 @@
|
||||
<Rose::Layout::Page as |page|>
|
||||
<Form::Role
|
||||
@model={{@model}}
|
||||
@submit={{route-action "save" @model}}
|
||||
@cancel={{route-action "cancel" @model}}
|
||||
/>
|
||||
|
||||
<page.breadcrumbs>
|
||||
<BreadCrumbs />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>{{t "resources.role"}}</h2>
|
||||
</page.header>
|
||||
|
||||
<page.actions>
|
||||
<Rose::Dropdown @text="{{t 'form.label.manage'}}" @alignRight={{true}} as |dropdown|>
|
||||
<dropdown.button
|
||||
@style="warning"
|
||||
@disabled={{@model.canSave}}
|
||||
{{on "click" (route-action "delete" @model)}}
|
||||
>
|
||||
{{t 'actions.delete-role'}}
|
||||
</dropdown.button>
|
||||
</Rose::Dropdown>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::Role
|
||||
@model={{@model}}
|
||||
@submit={{route-action "save" @model}}
|
||||
@cancel={{route-action "cancel" @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -64,6 +64,13 @@ export default function() {
|
||||
this.get('/scopes/:scope_id/roles/:id');
|
||||
this.patch('/scopes/:scope_id/roles/:id');
|
||||
this.del('/scopes/:scope_id/roles/:id');
|
||||
this.post('/scopes/:scope_id/roles/:id', function ({ roles }, request) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const id = request.params.id.split(':')[0];
|
||||
const role = roles.find(id);
|
||||
attrs.id = id;
|
||||
return role.update(attrs);
|
||||
});
|
||||
|
||||
// IAM: Groups
|
||||
this.get('/scopes/:scope_id/groups');
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import factory from '../generated/factories/role';
|
||||
|
||||
export default factory.extend();
|
||||
export default factory.extend({
|
||||
|
||||
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
|
||||
grant_strings: [
|
||||
'id=*;action=*',
|
||||
'id=*;type=host-catalog;actions=create,read'
|
||||
],
|
||||
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import { Response } from 'miragejs';
|
||||
// */
|
||||
// export default function authenticateHandler(schema, request) {
|
||||
// const payload = JSON.parse(request.requestBody);
|
||||
// if (payload.credentials.name === 'error') {
|
||||
// if (payload.credentials.login_name === 'error') {
|
||||
// return new Response(400);
|
||||
// } else {
|
||||
// const cookieName = config.auth.passwordCookieName;
|
||||
@@ -25,7 +25,7 @@ import { Response } from 'miragejs';
|
||||
|
||||
export function authHandler({ scopes }, request) {
|
||||
const payload = JSON.parse(request.requestBody);
|
||||
if (payload.credentials.name === 'error') {
|
||||
if (payload.credentials.login_name === 'error') {
|
||||
return new Response(400);
|
||||
} else {
|
||||
const scopeAttrs =
|
||||
|
||||
@@ -217,4 +217,16 @@ module('Acceptance | roles', function (hooks) {
|
||||
'Displays primary error message.'
|
||||
);
|
||||
});
|
||||
|
||||
test('grants can be updated and saved or canceled', async function (assert) {
|
||||
assert.expect(0);
|
||||
});
|
||||
|
||||
test('grants can be removed and saved or canceled', async function (assert) {
|
||||
assert.expect(0);
|
||||
});
|
||||
|
||||
test('new grants can be added and saved or canceled', async function (assert) {
|
||||
assert.expect(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | scopes/scope/roles/role/grants', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:scopes/scope/roles/role/grants');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
actions:
|
||||
add: Add
|
||||
cancel: Cancel
|
||||
delete: Delete
|
||||
dismiss: Dismiss
|
||||
@@ -47,6 +48,8 @@ titles:
|
||||
users-welcome: Welcome to Users
|
||||
roles-welcome: Welcome to Roles
|
||||
groups-welcome: Welcome to Groups
|
||||
grants: Permissions
|
||||
new-grant: New Grant
|
||||
descriptions:
|
||||
host-catalogs: Overview of collections of hosts and host sets.
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
@@ -69,6 +72,7 @@ form:
|
||||
identification: Username
|
||||
password: Password
|
||||
manage: Manage
|
||||
grant: Grant
|
||||
errors:
|
||||
titles:
|
||||
unauthenticated-error: You are not signed in
|
||||
|
||||
Reference in new issue
Block a user