Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: Use DataSources in ACLs area #7681

Merged
merged 6 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion ui-v2/app/components/child-selector/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
<YieldSlot @name="create">{{yield}}</YieldSlot>
<label class="type-text">
<span><YieldSlot @name="label">{{yield}}</YieldSlot></span>
{{#if isOpen}}
<DataSource
@src={{concat '/' (or nspace 'default') '/' dc '/' (pluralize type)}}
@onchange={{action (mut allOptions) value="data"}}
/>
{{/if}}
<PowerSelect
@search={{action "search"}}
@options={{options}}
@loadingMessage="Loading..."
@searchMessage="No possible options"
@searchPlaceholder={{placeholder}}
@onOpen={{action "open"}}
@onOpen={{action (mut isOpen) true}}
@onClose={{action (mut isOpen) false}}
@onChange={{action "change" "items[]" items}} as |item|>
<YieldSlot @name="option" @params={{block-params item}}>{{yield}}</YieldSlot>
</PowerSelect>
Expand Down
31 changes: 14 additions & 17 deletions ui-v2/app/components/child-selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ export default Component.extend(SlotsMixin, WithListeners, {
reset: function() {
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
},
open: function() {
if (!get(this, 'allOptions.closed')) {
set(this, 'allOptions', this.repo.findAllByDatacenter(this.dc, this.nspace));
}
},
save: function(item, items, success = function() {}) {
// Specifically this saves an 'new' option/child
// and then adds it to the selectedOptions, not options
Expand All @@ -68,20 +63,22 @@ export default Component.extend(SlotsMixin, WithListeners, {
// need to be sure that its saved before adding/closing the modal for now
// and we don't open the modal on prop change yet
item = repo.persist(item);
this.listen(item, 'message', e => {
this.actions.change.bind(this)(
{
target: {
name: 'items[]',
value: items,
this.listen(item, {
message: e => {
this.actions.change.apply(this, [
{
target: {
name: 'items[]',
value: items,
},
},
},
items,
e.data
);
success();
items,
e.data,
]);
success();
},
error: e => this.error(e),
});
this.listen(item, 'error', this.error.bind(this));
},
remove: function(item, items) {
const prop = this.repo.getSlugKey();
Expand Down
3 changes: 1 addition & 2 deletions ui-v2/app/components/data-source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { inject as service } from '@ember/service';
import { set } from '@ember/object';
import { schedule } from '@ember/runloop';

import Ember from 'ember';
/**
* Utility function to set, but actually replace if we should replace
* then call a function on the thing to be replaced (usually a clean up function)
Expand Down Expand Up @@ -57,7 +56,7 @@ export default Component.extend({
if (this.loading === 'lazy') {
this._lazyListeners.add(
this.dom.isInViewport(this.dom.element(`#${this.guid}`), inViewport => {
set(this, 'isIntersecting', inViewport || Ember.testing);
set(this, 'isIntersecting', inViewport);
if (!this.isIntersecting) {
this.actions.close.bind(this)();
} else {
Expand Down
3 changes: 3 additions & 0 deletions ui-v2/app/components/policy-form/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
</label>
</div>
{{#if isScoped }}
<DataSource @src="/*/*/datacenters"
@onchange={{action (mut datacenters) value="data"}}
/>
<div class="checkbox-group" role="group">
{{#each datacenters as |dc| }}
<label class="type-checkbox">
Expand Down
4 changes: 0 additions & 4 deletions ui-v2/app/components/policy-form/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import FormComponent from '../form-component/index';
import { inject as service } from '@ember/service';
import { get, set } from '@ember/object';

export default FormComponent.extend({
repo: service('repository/policy/component'),
datacenterRepo: service('repository/dc/component'),
type: 'policy',
name: 'policy',
allowServiceIdentity: true,
Expand All @@ -14,7 +11,6 @@ export default FormComponent.extend({
init: function() {
this._super(...arguments);
set(this, 'isScoped', get(this, 'item.Datacenters.length') > 0);
set(this, 'datacenters', this.datacenterRepo.findAll());
this.templates = [
{
name: 'Policy',
Expand Down
15 changes: 8 additions & 7 deletions ui-v2/app/components/policy-selector/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,31 @@
<BlockSlot @name="set">
<TabularDetails
data-test-policies
@onchange={{action 'loadItem'}}
@onchange={{action 'open'}}
@items={{sort-by 'CreateTime:desc' 'Name:asc' items}} as |item index|
>
<BlockSlot @name="header">
<th>Name</th>
<th>Datacenters</th>
</BlockSlot>
<BlockSlot @name="row">
<td class={{policy/typeof item}}>
{{#if item.ID }}
<a href={{href-to 'dc.acls.policies.edit' item.ID}}>{{item.Name}}</a>
<a href={{href-to 'dc.acls.policies.edit' item.ID}}>{{item.Name}}</a>
{{else}}
<a name={{item.Name}}>{{item.Name}}</a>
{{/if}}
</td>
<td>
{{if (not item.isSaving) (join ', ' (policy/datacenters item)) 'Saving...'}}
</td>
</BlockSlot>
<BlockSlot @name="details">
<label class="type-text">
<span>Rules <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
{{#if (eq item.template '')}}
<CodeEditor @syntax="hcl" @readonly={{true}} @value={{item.Rules}} />
<DataSource
@src={{concat '/' item.Namespace '/' item.Datacenter '/policy/' item.ID}}
@onchange={{action (mut loadedItem) value="data"}}
@loading="lazy"
/>
<CodeEditor @syntax="hcl" @readonly={{true}} @value={{or loadedItem.Rules item.Rules}} />
{{else}}
<CodeEditor @syntax="hcl" @readonly={{true}}>
{{~component 'service-identity' name=item.Name~}}
Expand Down
22 changes: 1 addition & 21 deletions ui-v2/app/components/policy-selector/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import ChildSelectorComponent from '../child-selector/index';
import { get, set } from '@ember/object';
import { set } from '@ember/object';
import { inject as service } from '@ember/service';
import updateArrayObject from 'consul-ui/utils/update-array-object';

const ERROR_PARSE_RULES = 'Failed to parse ACL rules';
const ERROR_INVALID_POLICY = 'Invalid service policy';
const ERROR_NAME_EXISTS = 'Invalid Policy: A Policy with Name';

export default ChildSelectorComponent.extend({
repo: service('repository/policy/component'),
datacenterRepo: service('repository/dc/component'),
name: 'policy',
type: 'policy',
allowServiceIdentity: true,
Expand All @@ -27,7 +25,6 @@ export default ChildSelectorComponent.extend({
reset: function(e) {
this._super(...arguments);
set(this, 'isScoped', false);
set(this, 'datacenters', this.datacenterRepo.findAll());
},
refreshCodeEditor: function(e, target) {
const selector = '.code-editor';
Expand Down Expand Up @@ -63,22 +60,5 @@ export default ChildSelectorComponent.extend({
open: function(e) {
this.refreshCodeEditor(e, e.target.parentElement);
},
loadItem: function(e, item, items) {
const target = e.target;
// the Details expander toggle, only load on opening
if (target.checked) {
const value = item;
this.refreshCodeEditor(e, target.parentNode);
if (get(item, 'template') === 'service-identity') {
return;
}
// potentially the item could change between load, so we don't check
// anything to see if its already loaded here
// TODO: Temporarily add dc here, will soon be serialized onto the policy itself
const slugKey = this.repo.getSlugKey();
const slug = get(value, slugKey);
updateArrayObject(items, this.repo.findBySlug(slug, this.dc, this.nspace), slugKey, slug);
}
},
},
});
32 changes: 15 additions & 17 deletions ui-v2/app/mixins/with-event-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,25 @@ export default Mixin.create(WithListeners, {
return this._super(_model);
},
reset: function(exiting) {
if (exiting) {
Object.keys(this).forEach(prop => {
if (this[prop] && typeof this[prop].close === 'function') {
this[prop].close();
// ember doesn't delete on 'resetController' by default
// right now we only call reset when we are exiting, therefore a full
// setProperties will be called the next time we enter the Route so this
// is ok for what we need and means that the above conditional works
// as expected (see 'here' comment above)
// delete this[prop];
// TODO: Check that nulling this out instead of deleting is fine
// pretty sure it is as above is just a falsey check
set(this, prop, null);
}
});
}
Object.keys(this).forEach(prop => {
if (this[prop] && typeof this[prop].close === 'function') {
this[prop].willDestroy();
// ember doesn't delete on 'resetController' by default
// right now we only call reset when we are exiting, therefore a full
// setProperties will be called the next time we enter the Route so this
// is ok for what we need and means that the above conditional works
// as expected (see 'here' comment above)
// delete this[prop];
// TODO: Check that nulling this out instead of deleting is fine
// pretty sure it is as above is just a falsey check
set(this, prop, null);
}
});
return this._super(...arguments);
},
willDestroy: function() {
this._super(...arguments);
this.reset(true);
this._super(...arguments);
},
});
export const listen = purify(catchable, function(props) {
Expand Down
2 changes: 2 additions & 0 deletions ui-v2/app/models/policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default Model.extend({
//
Datacenter: attr('string'),
Namespace: attr('string'),
SyncTime: attr('number'),
meta: attr(),
Datacenters: attr(),
CreateIndex: attr('number'),
ModifyIndex: attr('number'),
Expand Down
1 change: 1 addition & 0 deletions ui-v2/app/models/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default Model.extend({
//
Datacenter: attr('string'),
Namespace: attr('string'),
SyncTime: attr('number'),
// TODO: Figure out whether we need this or not
Datacenters: attr(),
Hash: attr('string'),
Expand Down
18 changes: 17 additions & 1 deletion ui-v2/app/services/data-source/protocols/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ export default Service.extend({
datacenters: service('repository/dc'),
namespaces: service('repository/nspace'),
token: service('repository/token'),
policies: service('repository/policy'),
policy: service('repository/policy'),
roles: service('repository/role'),
type: service('data-source/protocols/http/blocking'),
source: function(src, configuration) {
const [, , /*nspace*/ dc, model, ...rest] = src.split('/');
// TODO: Consider adding/requiring nspace, dc, model, action, ...rest
const [, nspace, dc, model, ...rest] = src.split('/');
// TODO: Consider throwing if we have an empty nspace or dc
// we are going to use '*' for 'all' when we need that
// and an empty value is the same as 'default'
// reasoning for potentially doing it here is, uri's should
// always be complete, they should never have things like '///model'
let find;
const repo = this[model];
if (typeof repo.reconcile === 'function') {
Expand All @@ -32,6 +41,13 @@ export default Service.extend({
case 'token':
find = configuration => repo.self(rest[1], dc);
break;
case 'roles':
case 'policies':
find = configuration => repo.findAllByDatacenter(dc, nspace, configuration);
break;
case 'policy':
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
break;
}
return this.type.source(find, configuration);
},
Expand Down
3 changes: 0 additions & 3 deletions ui-v2/app/services/data-source/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ export default Service.extend({
}
if (!sources.has(uri)) {
let [providerName, pathname] = uri.split('://');
if (pathname.startsWith('//')) {
pathname = pathname.substr(2);
}
const provider = this[providerName];

let configuration = {};
Expand Down
19 changes: 15 additions & 4 deletions ui-v2/app/services/repository.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Service, { inject as service } from '@ember/service';
import { assert } from '@ember/debug';
import { typeOf } from '@ember/utils';
import { get } from '@ember/object';

export default Service.extend({
getModelName: function() {
assert('RepositoryService.getModelName should be overridden', false);
Expand All @@ -15,12 +17,21 @@ export default Service.extend({
store: service('store'),
reconcile: function(meta = {}) {
// unload anything older than our current sync date/time
// FIXME: This needs fixing once again to take nspaces into account
if (typeof meta.date !== 'undefined') {
const checkNspace = meta.nspace !== '';
this.store.peekAll(this.getModelName()).forEach(item => {
const date = item.SyncTime;
if (typeof date !== 'undefined' && date != meta.date) {
this.store.unloadRecord(item);
const dc = get(item, 'Datacenter');
if (dc === meta.dc) {
if (checkNspace) {
const nspace = get(item, 'Namespace');
if (nspace !== meta.namespace) {
return;
}
}
const date = get(item, 'SyncTime');
if (typeof date !== 'undefined' && date != meta.date) {
this.store.unloadRecord(item);
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Feature: dc / acls / policies / as many / remove: Remove
Then the url should be /datacenter/acls/[Model]s/key
And I see 1 policy model on the policies component
And I click expand on the policies.selectedOptions
And a GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter&ns=@namespace"
# Until we have a reliable way of mocking out and controlling IntersectionObserver
# this will need to stay commented out
# And a GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter&ns=@namespace"
And I click delete on the policies.selectedOptions
And I click confirmDelete on the policies.selectedOptions
And I see 0 policy models on the policies component
Expand Down
11 changes: 11 additions & 0 deletions ui-v2/tests/integration/services/repository/policy-test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { moduleFor, test, skip } from 'ember-qunit';
import { get } from '@ember/object';
import repo from 'consul-ui/tests/helpers/repo';
const NAME = 'policy';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test.
integration: true,
});
skip('translate returns the correct data for the translate endpoint');
const now = new Date().getTime();
const dc = 'dc-1';
const id = 'policy-name';
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`findByDatacenter returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo(
'Policy',
'findAllByDatacenter',
Expand All @@ -32,6 +37,7 @@ const undefinedNspace = 'default';
expected(function(payload) {
return payload.map(item =>
Object.assign({}, item, {
SyncTime: now,
Datacenter: dc,
Namespace: item.Namespace || undefinedNspace,
uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`,
Expand Down Expand Up @@ -64,6 +70,11 @@ const undefinedNspace = 'default';
Datacenter: dc,
Namespace: item.Namespace || undefinedNspace,
uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`,
meta: {
cursor: undefined,
dc: dc,
nspace: item.Namespace || undefinedNspace,
},
});
})
);
Expand Down
Loading