mirror of
https://github.com/status-im/consul.git
synced 2025-01-12 23:05:28 +00:00
Merge pull request #5729 from hashicorp/ui-staging
UI: ui-staging merge
This commit is contained in:
commit
e31c285565
1
ui-v2/.eslintignore
Normal file
1
ui-v2/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
app/utils/dom/event-target/event-target-shim/event.js
|
@ -25,6 +25,9 @@ lint: deps
|
||||
format: deps
|
||||
yarn run format:js
|
||||
|
||||
steps:
|
||||
yarn run steps:list
|
||||
|
||||
node_modules: yarn.lock package.json
|
||||
yarn install
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import Adapter from 'ember-data/adapters/rest';
|
||||
import { AbortError } from 'ember-data/adapters/errors';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import URL from 'url';
|
||||
import createURL from 'consul-ui/utils/createURL';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL } from 'consul-ui/utils/http/consul';
|
||||
|
||||
export const REQUEST_CREATE = 'createRecord';
|
||||
export const REQUEST_READ = 'queryRecord';
|
||||
@ -16,12 +19,68 @@ export const DATACENTER_QUERY_PARAM = 'dc';
|
||||
export default Adapter.extend({
|
||||
namespace: 'v1',
|
||||
repo: service('settings'),
|
||||
client: service('client/http'),
|
||||
manageConnection: function(options) {
|
||||
const client = get(this, 'client');
|
||||
const complete = options.complete;
|
||||
const beforeSend = options.beforeSend;
|
||||
options.beforeSend = function(xhr) {
|
||||
if (typeof beforeSend === 'function') {
|
||||
beforeSend(...arguments);
|
||||
}
|
||||
options.id = client.request(options, xhr);
|
||||
};
|
||||
options.complete = function(xhr, textStatus) {
|
||||
client.complete(options.id);
|
||||
if (typeof complete === 'function') {
|
||||
complete(...arguments);
|
||||
}
|
||||
};
|
||||
return options;
|
||||
},
|
||||
_ajaxRequest: function(options) {
|
||||
return this._super(this.manageConnection(options));
|
||||
},
|
||||
queryRecord: function() {
|
||||
return this._super(...arguments).catch(function(e) {
|
||||
if (e instanceof AbortError) {
|
||||
e.errors[0].status = '0';
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
query: function() {
|
||||
return this._super(...arguments).catch(function(e) {
|
||||
if (e instanceof AbortError) {
|
||||
e.errors[0].status = '0';
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
headersForRequest: function(params) {
|
||||
return {
|
||||
...this.get('repo').findHeaders(),
|
||||
...this._super(...arguments),
|
||||
};
|
||||
},
|
||||
handleResponse: function(status, headers, response, requestData) {
|
||||
// The ember-data RESTAdapter drops the headers after this call,
|
||||
// and there is no where else to get to these
|
||||
// save them to response[HTTP_HEADERS_SYMBOL] for the moment
|
||||
// so we can save them as meta in the serializer...
|
||||
if (
|
||||
(typeof response == 'object' && response.constructor == Object) ||
|
||||
Array.isArray(response)
|
||||
) {
|
||||
// lowercase everything incase we get browser inconsistencies
|
||||
const lower = {};
|
||||
Object.keys(headers).forEach(function(key) {
|
||||
lower[key.toLowerCase()] = headers[key];
|
||||
});
|
||||
response[HTTP_HEADERS_SYMBOL] = lower;
|
||||
}
|
||||
return this._super(status, headers, response, requestData);
|
||||
},
|
||||
handleBooleanResponse: function(url, response, primary, slug) {
|
||||
return {
|
||||
// consider a check for a boolean, also for future me,
|
||||
@ -52,6 +111,12 @@ export default Adapter.extend({
|
||||
delete _query.id;
|
||||
}
|
||||
const query = { ..._query };
|
||||
if (typeof query.separator !== 'undefined') {
|
||||
delete query.separator;
|
||||
}
|
||||
if (typeof query.index !== 'undefined') {
|
||||
delete query.index;
|
||||
}
|
||||
delete _query[DATACENTER_QUERY_PARAM];
|
||||
return query;
|
||||
},
|
||||
|
@ -136,6 +136,7 @@ export default Adapter.extend({
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
methodForRequest: function(params) {
|
||||
switch (params.requestType) {
|
||||
|
20
ui-v2/app/adapters/proxy.js
Normal file
20
ui-v2/app/adapters/proxy.js
Normal file
@ -0,0 +1,20 @@
|
||||
import Adapter from './application';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/proxy';
|
||||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
export default Adapter.extend({
|
||||
urlForQuery: function(query, modelName) {
|
||||
if (typeof query.id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
// https://www.consul.io/api/catalog.html#list-nodes-for-connect-capable-service
|
||||
return this.appendURL('catalog/connect', [query.id], this.cleanQuery(query));
|
||||
},
|
||||
handleResponse: function(status, headers, payload, requestData) {
|
||||
let response = payload;
|
||||
if (status === HTTP_OK) {
|
||||
const url = this.parseURL(requestData.url);
|
||||
response = this.handleBatchResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
}
|
||||
return this._super(status, headers, response, requestData);
|
||||
},
|
||||
});
|
72
ui-v2/app/adapters/role.js
Normal file
72
ui-v2/app/adapters/role.js
Normal file
@ -0,0 +1,72 @@
|
||||
import Adapter, {
|
||||
REQUEST_CREATE,
|
||||
REQUEST_UPDATE,
|
||||
DATACENTER_QUERY_PARAM as API_DATACENTER_KEY,
|
||||
} from './application';
|
||||
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
import { PUT as HTTP_PUT } from 'consul-ui/utils/http/method';
|
||||
|
||||
import WithPolicies from 'consul-ui/mixins/policy/as-many';
|
||||
|
||||
export default Adapter.extend(WithPolicies, {
|
||||
urlForQuery: function(query, modelName) {
|
||||
return this.appendURL('acl/roles', [], this.cleanQuery(query));
|
||||
},
|
||||
urlForQueryRecord: function(query, modelName) {
|
||||
if (typeof query.id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return this.appendURL('acl/role', [query.id], this.cleanQuery(query));
|
||||
},
|
||||
urlForCreateRecord: function(modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForUpdateRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForDeleteRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
handleResponse: function(status, headers, payload, requestData) {
|
||||
let response = payload;
|
||||
if (status === HTTP_OK) {
|
||||
const url = this.parseURL(requestData.url);
|
||||
switch (true) {
|
||||
case response === true:
|
||||
response = this.handleBooleanResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
break;
|
||||
case Array.isArray(response):
|
||||
response = this.handleBatchResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
break;
|
||||
default:
|
||||
response = this.handleSingleResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
}
|
||||
}
|
||||
return this._super(status, headers, response, requestData);
|
||||
},
|
||||
methodForRequest: function(params) {
|
||||
switch (params.requestType) {
|
||||
case REQUEST_CREATE:
|
||||
return HTTP_PUT;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const data = this._super(...arguments);
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
case REQUEST_CREATE:
|
||||
return data.role;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
@ -10,12 +10,15 @@ import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
import { PUT as HTTP_PUT } from 'consul-ui/utils/http/method';
|
||||
|
||||
import WithPolicies from 'consul-ui/mixins/policy/as-many';
|
||||
import WithRoles from 'consul-ui/mixins/role/as-many';
|
||||
|
||||
import { get } from '@ember/object';
|
||||
|
||||
const REQUEST_CLONE = 'cloneRecord';
|
||||
const REQUEST_SELF = 'querySelf';
|
||||
|
||||
export default Adapter.extend({
|
||||
export default Adapter.extend(WithRoles, WithPolicies, {
|
||||
store: service('store'),
|
||||
cleanQuery: function(_query) {
|
||||
const query = this._super(...arguments);
|
||||
@ -108,10 +111,6 @@ export default Adapter.extend({
|
||||
return this._makeRequest(request);
|
||||
},
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
// Sometimes we get `Policies: null`, make null equal an empty array
|
||||
if (typeof response.Policies === 'undefined' || response.Policies === null) {
|
||||
response.Policies = [];
|
||||
}
|
||||
// Convert an old style update response to a new style
|
||||
if (typeof response['ID'] !== 'undefined') {
|
||||
const item = get(this, 'store')
|
||||
@ -169,19 +168,6 @@ export default Adapter.extend({
|
||||
}
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
if (Array.isArray(data.token.Policies)) {
|
||||
data.token.Policies = data.token.Policies.filter(function(item) {
|
||||
// Just incase, don't save any policies that aren't saved
|
||||
return !get(item, 'isNew');
|
||||
}).map(function(item) {
|
||||
return {
|
||||
ID: get(item, 'ID'),
|
||||
Name: get(item, 'Name'),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
delete data.token.Policies;
|
||||
}
|
||||
data = data.token;
|
||||
break;
|
||||
case REQUEST_SELF:
|
||||
|
@ -1,31 +1,33 @@
|
||||
import Component from '@ember/component';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import templatize from 'consul-ui/utils/templatize';
|
||||
const $html = document.documentElement;
|
||||
export default Component.extend(SlotsMixin, {
|
||||
loading: false,
|
||||
authorized: true,
|
||||
enabled: true,
|
||||
classNames: ['app-view'],
|
||||
classNameBindings: ['enabled::disabled', 'authorized::unauthorized'],
|
||||
dom: service('dom'),
|
||||
didReceiveAttrs: function() {
|
||||
// right now only manually added classes are hoisted to <html>
|
||||
const $root = get(this, 'dom').root();
|
||||
let cls = get(this, 'class') || '';
|
||||
if (get(this, 'loading')) {
|
||||
cls += ' loading';
|
||||
} else {
|
||||
$html.classList.remove(...templatize(['loading']));
|
||||
$root.classList.remove(...templatize(['loading']));
|
||||
}
|
||||
if (cls) {
|
||||
// its possible for 'layout' templates to change after insert
|
||||
// check for these specific layouts and clear them out
|
||||
[...$html.classList].forEach(function(item, i) {
|
||||
[...$root.classList].forEach(function(item, i) {
|
||||
if (templatize(['edit', 'show', 'list']).indexOf(item) !== -1) {
|
||||
$html.classList.remove(item);
|
||||
$root.classList.remove(item);
|
||||
}
|
||||
});
|
||||
$html.classList.add(...templatize(cls.split(' ')));
|
||||
$root.classList.add(...templatize(cls.split(' ')));
|
||||
}
|
||||
},
|
||||
didInsertElement: function() {
|
||||
@ -34,7 +36,8 @@ export default Component.extend(SlotsMixin, {
|
||||
didDestroyElement: function() {
|
||||
const cls = get(this, 'class') + ' loading';
|
||||
if (cls) {
|
||||
$html.classList.remove(...templatize(cls.split(' ')));
|
||||
const $root = get(this, 'dom').root();
|
||||
$root.classList.remove(...templatize(cls.split(' ')));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
19
ui-v2/app/components/changeable-set.js
Normal file
19
ui-v2/app/components/changeable-set.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
|
||||
export default Component.extend(WithListeners, SlotsMixin, {
|
||||
tagName: '',
|
||||
didReceiveAttrs: function() {
|
||||
this._super(...arguments);
|
||||
this.removeListeners();
|
||||
const dispatcher = get(this, 'dispatcher');
|
||||
if (dispatcher) {
|
||||
this.listen(dispatcher, 'change', e => {
|
||||
set(this, 'items', e.target.data);
|
||||
});
|
||||
set(this, 'items', get(dispatcher, 'data'));
|
||||
}
|
||||
},
|
||||
});
|
113
ui-v2/app/components/child-selector.js
Normal file
113
ui-v2/app/components/child-selector.js
Normal file
@ -0,0 +1,113 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
import SlotsMixin from 'block-slots';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
|
||||
export default Component.extend(SlotsMixin, WithListeners, {
|
||||
onchange: function() {},
|
||||
|
||||
error: function() {},
|
||||
type: '',
|
||||
|
||||
dom: service('dom'),
|
||||
container: service('search'),
|
||||
formContainer: service('form'),
|
||||
|
||||
item: alias('form.data'),
|
||||
|
||||
selectedOptions: alias('items'),
|
||||
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.searchable = get(this, 'container').searchable(get(this, 'type'));
|
||||
this.form = get(this, 'formContainer').form(get(this, 'type'));
|
||||
this.form.clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
options: computed('selectedOptions.[]', 'allOptions.[]', function() {
|
||||
// It's not massively important here that we are defaulting `items` and
|
||||
// losing reference as its just to figure out the diff
|
||||
let options = get(this, 'allOptions') || [];
|
||||
const items = get(this, 'selectedOptions') || [];
|
||||
if (get(items, 'length') > 0) {
|
||||
// find a proper ember-data diff
|
||||
options = options.filter(item => !items.findBy('ID', get(item, 'ID')));
|
||||
this.searchable.add(options);
|
||||
}
|
||||
return options;
|
||||
}),
|
||||
actions: {
|
||||
search: function(term) {
|
||||
// TODO: make sure we can either search before things are loaded
|
||||
// or wait until we are loaded, guess power select take care of that
|
||||
return new Promise(resolve => {
|
||||
const remove = this.listen(this.searchable, 'change', function(e) {
|
||||
remove();
|
||||
resolve(e.target.data);
|
||||
});
|
||||
this.searchable.search(term);
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
get(this, 'form').clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
open: function() {
|
||||
if (!get(this, 'allOptions.closed')) {
|
||||
set(this, 'allOptions', get(this, 'repo').findAllByDatacenter(get(this, 'dc')));
|
||||
}
|
||||
},
|
||||
save: function(item, items, success = function() {}) {
|
||||
// Specifically this saves an 'new' option/child
|
||||
// and then adds it to the selectedOptions, not options
|
||||
const repo = get(this, 'repo');
|
||||
set(item, 'CreateTime', new Date().getTime());
|
||||
// TODO: temporary async
|
||||
// this should be `set(this, 'item', repo.persist(item));`
|
||||
// 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,
|
||||
},
|
||||
},
|
||||
items,
|
||||
e.data
|
||||
);
|
||||
success();
|
||||
});
|
||||
this.listen(item, 'error', this.error.bind(this));
|
||||
},
|
||||
remove: function(item, items) {
|
||||
const prop = get(this, 'repo').getSlugKey();
|
||||
const value = get(item, prop);
|
||||
const pos = items.findIndex(function(item) {
|
||||
return get(item, prop) === value;
|
||||
});
|
||||
if (pos !== -1) {
|
||||
return items.removeAt(pos, 1);
|
||||
}
|
||||
this.onchange({ target: this });
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(...arguments);
|
||||
const items = value;
|
||||
switch (event.target.name) {
|
||||
case 'items[]':
|
||||
set(item, 'CreateTime', new Date().getTime());
|
||||
// this always happens synchronously
|
||||
items.pushObject(item);
|
||||
// TODO: Fire a proper event
|
||||
this.onchange({ target: this });
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
@ -11,31 +11,57 @@ const DEFAULTS = {
|
||||
};
|
||||
export default Component.extend({
|
||||
settings: service('settings'),
|
||||
helper: service('code-mirror'),
|
||||
dom: service('dom'),
|
||||
helper: service('code-mirror/linter'),
|
||||
classNames: ['code-editor'],
|
||||
readonly: false,
|
||||
syntax: '',
|
||||
onchange: function(value) {
|
||||
get(this, 'settings').persist({
|
||||
'code-editor': value,
|
||||
});
|
||||
this.setMode(value);
|
||||
},
|
||||
// TODO: Change this to oninput to be consistent? We'll have to do it throughout the templates
|
||||
onkeyup: function() {},
|
||||
oninput: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'modes', get(this, 'helper').modes());
|
||||
},
|
||||
didReceiveAttrs: function() {
|
||||
this._super(...arguments);
|
||||
const editor = get(this, 'editor');
|
||||
if (editor) {
|
||||
editor.setOption('readOnly', get(this, 'readonly'));
|
||||
}
|
||||
},
|
||||
setMode: function(mode) {
|
||||
set(this, 'options', {
|
||||
...DEFAULTS,
|
||||
mode: mode.mime,
|
||||
readOnly: get(this, 'readonly'),
|
||||
});
|
||||
const editor = get(this, 'editor');
|
||||
editor.setOption('mode', mode.mime);
|
||||
get(this, 'helper').lint(editor, mode.mode);
|
||||
set(this, 'mode', mode);
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
const $code = get(this, 'dom').element('textarea ~ pre code', get(this, 'element'));
|
||||
if ($code.firstChild) {
|
||||
this.observer = new MutationObserver(([e]) => {
|
||||
this.oninput(set(this, 'value', e.target.wholeText));
|
||||
});
|
||||
this.observer.observe($code, {
|
||||
attributes: false,
|
||||
subtree: true,
|
||||
childList: false,
|
||||
characterData: true,
|
||||
});
|
||||
set(this, 'value', $code.firstChild.wholeText);
|
||||
}
|
||||
set(this, 'editor', get(this, 'helper').getEditor(this.element));
|
||||
get(this, 'settings')
|
||||
.findBySlug('code-editor')
|
||||
@ -54,4 +80,12 @@ export default Component.extend({
|
||||
didAppear: function() {
|
||||
get(this, 'editor').refresh();
|
||||
},
|
||||
actions: {
|
||||
change: function(value) {
|
||||
get(this, 'settings').persist({
|
||||
'code-editor': value,
|
||||
});
|
||||
this.setMode(value);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -11,9 +11,11 @@ export default Component.extend({
|
||||
this.append = append.bind(this);
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
get(this, 'buffer').on('add', this.append);
|
||||
},
|
||||
didDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
get(this, 'buffer').off('add', this.append);
|
||||
},
|
||||
});
|
||||
|
@ -9,9 +9,11 @@ export default Component.extend({
|
||||
return 'modal';
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
get(this, 'buffer').add(this.getBufferName(), this.element);
|
||||
},
|
||||
didDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
get(this, 'buffer').remove(this.getBufferName());
|
||||
},
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
const $$ = qsaFactory();
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
import SlotsMixin from 'block-slots';
|
||||
const STATE_READY = 'ready';
|
||||
@ -10,6 +9,7 @@ const STATE_SUCCESS = 'success';
|
||||
const STATE_ERROR = 'error';
|
||||
export default Component.extend(SlotsMixin, {
|
||||
wait: service('timeout'),
|
||||
dom: service('dom'),
|
||||
classNames: ['with-feedback'],
|
||||
transition: '',
|
||||
transitionClassName: 'feedback-dialog-out',
|
||||
@ -23,6 +23,7 @@ export default Component.extend(SlotsMixin, {
|
||||
applyTransition: function() {
|
||||
const wait = get(this, 'wait').execute;
|
||||
const className = get(this, 'transitionClassName');
|
||||
// TODO: Make 0 default in wait
|
||||
wait(0)
|
||||
.then(() => {
|
||||
set(this, 'transition', className);
|
||||
@ -30,7 +31,9 @@ export default Component.extend(SlotsMixin, {
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise(resolve => {
|
||||
$$(`.${className}`, this.element)[0].addEventListener('transitionend', resolve);
|
||||
get(this, 'dom')
|
||||
.element(`.${className}`, this.element)
|
||||
.addEventListener('transitionend', resolve);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
42
ui-v2/app/components/form-component.js
Normal file
42
ui-v2/app/components/form-component.js
Normal file
@ -0,0 +1,42 @@
|
||||
import Component from '@ember/component';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
// match anything that isn't a [ or ] into multiple groups
|
||||
const propRe = /([^[\]])+/g;
|
||||
export default Component.extend(WithListeners, SlotsMixin, {
|
||||
onreset: function() {},
|
||||
onchange: function() {},
|
||||
onerror: function() {},
|
||||
onsuccess: function() {},
|
||||
|
||||
data: alias('form.data'),
|
||||
item: alias('form.data'),
|
||||
// TODO: Could probably alias item
|
||||
// or just use data/value instead
|
||||
|
||||
dom: service('dom'),
|
||||
container: service('form'),
|
||||
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
let event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const matches = [...event.target.name.matchAll(propRe)];
|
||||
const prop = matches[matches.length - 1][0];
|
||||
event = get(this, 'dom').normalizeEvent(
|
||||
`${get(this, 'type')}[${prop}]`,
|
||||
event.target.value,
|
||||
event.target
|
||||
);
|
||||
const form = get(this, 'form');
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
this.onchange({ target: this });
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
@ -1,7 +1,15 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
import { get } from '@ember/object';
|
||||
export default Component.extend({
|
||||
tagName: 'fieldset',
|
||||
classNames: ['freetext-filter'],
|
||||
onchange: function(){}
|
||||
onchange: function(e) {
|
||||
let searchable = get(this, 'searchable');
|
||||
if (!Array.isArray(searchable)) {
|
||||
searchable = [searchable];
|
||||
}
|
||||
searchable.forEach(function(item) {
|
||||
item.search(e.target.value);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,11 +1,13 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
const $html = document.documentElement;
|
||||
const $body = document.body;
|
||||
import { inject as service } from '@ember/service';
|
||||
export default Component.extend({
|
||||
dom: service('dom'),
|
||||
isDropdownVisible: false,
|
||||
didInsertElement: function() {
|
||||
$html.classList.remove('template-with-vertical-menu');
|
||||
get(this, 'dom')
|
||||
.root()
|
||||
.classList.remove('template-with-vertical-menu');
|
||||
},
|
||||
actions: {
|
||||
dropdown: function(e) {
|
||||
@ -14,12 +16,16 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
change: function(e) {
|
||||
const dom = get(this, 'dom');
|
||||
const win = dom.viewport();
|
||||
const $root = dom.root();
|
||||
const $body = dom.element('body');
|
||||
if (e.target.checked) {
|
||||
$html.classList.add('template-with-vertical-menu');
|
||||
$body.style.height = $html.style.height = window.innerHeight + 'px';
|
||||
$root.classList.add('template-with-vertical-menu');
|
||||
$body.style.height = $root.style.height = win.innerHeight + 'px';
|
||||
} else {
|
||||
$html.classList.remove('template-with-vertical-menu');
|
||||
$body.style.height = $html.style.height = null;
|
||||
$root.classList.remove('template-with-vertical-menu');
|
||||
$body.style.height = $root.style.height = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
4
ui-v2/app/components/healthcheck-info.js
Normal file
4
ui-v2/app/components/healthcheck-info.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Component from '@ember/component';
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
36
ui-v2/app/components/healthcheck-list.js
Normal file
36
ui-v2/app/components/healthcheck-list.js
Normal file
@ -0,0 +1,36 @@
|
||||
import Component from '@ember/component';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
// TODO: Could potentially do this on attr change
|
||||
actions: {
|
||||
sortChecksByImportance: function(a, b) {
|
||||
const statusA = get(a, 'Status');
|
||||
const statusB = get(b, 'Status');
|
||||
switch (statusA) {
|
||||
case 'passing':
|
||||
// a = passing
|
||||
// unless b is also passing then a is less important
|
||||
return statusB === 'passing' ? 0 : 1;
|
||||
case 'critical':
|
||||
// a = critical
|
||||
// unless b is also critical then a is more important
|
||||
return statusB === 'critical' ? 0 : -1;
|
||||
case 'warning':
|
||||
// a = warning
|
||||
switch (statusB) {
|
||||
// b is passing so a is more important
|
||||
case 'passing':
|
||||
return -1;
|
||||
// b is critical so a is less important
|
||||
case 'critical':
|
||||
return 1;
|
||||
// a and b are both warning, therefore equal
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
});
|
5
ui-v2/app/components/healthcheck-output.js
Normal file
5
ui-v2/app/components/healthcheck-output.js
Normal file
@ -0,0 +1,5 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['healthcheck-output'],
|
||||
});
|
@ -1,5 +1,12 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
import { get, computed } from '@ember/object';
|
||||
export default Component.extend({
|
||||
classNames: ['healthcheck-status'],
|
||||
tagName: '',
|
||||
count: computed('value', function() {
|
||||
const value = get(this, 'value');
|
||||
if (Array.isArray(value)) {
|
||||
return value.length;
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
});
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed, get, set } from '@ember/object';
|
||||
import Component from 'ember-collection/components/ember-collection';
|
||||
import PercentageColumns from 'ember-collection/layouts/percentage-columns';
|
||||
import style from 'ember-computed-style';
|
||||
import WithResizing from 'consul-ui/mixins/with-resizing';
|
||||
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
const $$ = qsaFactory();
|
||||
|
||||
export default Component.extend(WithResizing, {
|
||||
dom: service('dom'),
|
||||
tagName: 'div',
|
||||
attributeBindings: ['style'],
|
||||
height: 500,
|
||||
@ -30,11 +31,13 @@ export default Component.extend(WithResizing, {
|
||||
};
|
||||
}),
|
||||
resize: function(e) {
|
||||
const $self = this.element;
|
||||
const $appContent = [...$$('main > div')][0];
|
||||
// TODO: This top part is very similar to resize in tabular-collection
|
||||
// see if it make sense to DRY out
|
||||
const dom = get(this, 'dom');
|
||||
const $appContent = dom.element('main > div');
|
||||
if ($appContent) {
|
||||
const rect = $self.getBoundingClientRect();
|
||||
const $footer = [...$$('footer[role="contentinfo"]')][0];
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
const $footer = dom.element('footer[role="contentinfo"]');
|
||||
const space = rect.top + $footer.clientHeight;
|
||||
const height = e.detail.height - space;
|
||||
this.set('height', Math.max(0, height));
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from 'consul-ui/components/dom-buffer';
|
||||
import DomBufferComponent from 'consul-ui/components/dom-buffer';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import WithResizing from 'consul-ui/mixins/with-resizing';
|
||||
|
||||
import templatize from 'consul-ui/utils/templatize';
|
||||
export default Component.extend(SlotsMixin, WithResizing, {
|
||||
export default DomBufferComponent.extend(SlotsMixin, WithResizing, {
|
||||
dom: service('dom'),
|
||||
checked: true,
|
||||
height: null,
|
||||
@ -38,9 +38,11 @@ export default Component.extend(SlotsMixin, WithResizing, {
|
||||
_close: function(e) {
|
||||
set(this, 'checked', false);
|
||||
const dialogPanel = get(this, 'dialog');
|
||||
const overflowing = get(this, 'overflowingClass');
|
||||
if (dialogPanel.classList.contains(overflowing)) {
|
||||
dialogPanel.classList.remove(overflowing);
|
||||
if (dialogPanel) {
|
||||
const overflowing = get(this, 'overflowingClass');
|
||||
if (dialogPanel.classList.contains(overflowing)) {
|
||||
dialogPanel.classList.remove(overflowing);
|
||||
}
|
||||
}
|
||||
// TODO: should we make a didDisappear?
|
||||
get(this, 'dom')
|
||||
@ -105,15 +107,17 @@ export default Component.extend(SlotsMixin, WithResizing, {
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
if (e && e.target && e.target.checked) {
|
||||
if (get(e, 'target.checked')) {
|
||||
this._open(e);
|
||||
} else {
|
||||
this._close();
|
||||
this._close(e);
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
get(this, 'dom').element('#modal_close').checked = true;
|
||||
this.onclose();
|
||||
const $close = get(this, 'dom').element('#modal_close');
|
||||
$close.checked = true;
|
||||
const $input = get(this, 'dom').element('input[name="modal"]', this.element);
|
||||
$input.onchange({ target: $input });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Component from 'consul-ui/components/dom-buffer-flush';
|
||||
import DomBufferFlushComponent from 'consul-ui/components/dom-buffer-flush';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
export default DomBufferFlushComponent.extend({
|
||||
dom: service('dom'),
|
||||
actions: {
|
||||
change: function(e) {
|
||||
@ -10,8 +10,10 @@ export default Component.extend({
|
||||
.filter(function(item) {
|
||||
return item.getAttribute('id') !== 'modal_close';
|
||||
})
|
||||
.forEach(function(item) {
|
||||
item.onchange();
|
||||
.forEach(function(item, i) {
|
||||
if (item.getAttribute('data-checked') === 'true') {
|
||||
item.onchange({ target: item });
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
44
ui-v2/app/components/phrase-editor.js
Normal file
44
ui-v2/app/components/phrase-editor.js
Normal file
@ -0,0 +1,44 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['phrase-editor'],
|
||||
item: '',
|
||||
remove: function(index, e) {
|
||||
this.items.removeAt(index, 1);
|
||||
this.onchange(e);
|
||||
},
|
||||
add: function(e) {
|
||||
const value = get(this, 'item').trim();
|
||||
if (value !== '') {
|
||||
set(this, 'item', '');
|
||||
const currentItems = get(this, 'items') || [];
|
||||
const items = new Set(currentItems).add(value);
|
||||
if (items.size > currentItems.length) {
|
||||
set(this, 'items', [...items]);
|
||||
this.onchange(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
onkeydown: function(e) {
|
||||
switch (e.keyCode) {
|
||||
case 8:
|
||||
if (e.target.value == '' && this.items.length > 0) {
|
||||
this.remove(this.items.length - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
oninput: function(e) {
|
||||
set(this, 'item', e.target.value);
|
||||
},
|
||||
onchange: function(e) {
|
||||
let searchable = get(this, 'searchable');
|
||||
if (!Array.isArray(searchable)) {
|
||||
searchable = [searchable];
|
||||
}
|
||||
searchable.forEach(item => {
|
||||
item.search(get(this, 'items'));
|
||||
});
|
||||
},
|
||||
});
|
53
ui-v2/app/components/policy-form.js
Normal file
53
ui-v2/app/components/policy-form.js
Normal file
@ -0,0 +1,53 @@
|
||||
import FormComponent from './form-component';
|
||||
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',
|
||||
classNames: ['policy-form'],
|
||||
|
||||
isScoped: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'isScoped', get(this, 'item.Datacenters.length') > 0);
|
||||
set(this, 'datacenters', get(this, 'datacenterRepo').findAll());
|
||||
this.templates = [
|
||||
{
|
||||
name: 'Policy',
|
||||
template: '',
|
||||
},
|
||||
{
|
||||
name: 'Service Identity',
|
||||
template: 'service-identity',
|
||||
},
|
||||
];
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
try {
|
||||
this._super(...arguments);
|
||||
} catch (err) {
|
||||
const scoped = get(this, 'isScoped');
|
||||
const name = err.target.name;
|
||||
switch (name) {
|
||||
case 'policy[isScoped]':
|
||||
if (scoped) {
|
||||
set(this, 'previousDatacenters', get(this.item, 'Datacenters'));
|
||||
set(this.item, 'Datacenters', null);
|
||||
} else {
|
||||
set(this.item, 'Datacenters', get(this, 'previousDatacenters'));
|
||||
set(this, 'previousDatacenters', null);
|
||||
}
|
||||
set(this, 'isScoped', !scoped);
|
||||
break;
|
||||
default:
|
||||
this.onerror(err);
|
||||
}
|
||||
this.onchange({ target: get(this, 'form') });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
82
ui-v2/app/components/policy-selector.js
Normal file
82
ui-v2/app/components/policy-selector.js
Normal file
@ -0,0 +1,82 @@
|
||||
import ChildSelectorComponent from './child-selector';
|
||||
import { get, 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_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',
|
||||
classNames: ['policy-selector'],
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
const source = get(this, 'source');
|
||||
if (source) {
|
||||
const event = 'save';
|
||||
this.listen(source, event, e => {
|
||||
this.actions[event].bind(this)(...e.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
reset: function(e) {
|
||||
this._super(...arguments);
|
||||
set(this, 'isScoped', false);
|
||||
set(this, 'datacenters', get(this, 'datacenterRepo').findAll());
|
||||
},
|
||||
refreshCodeEditor: function(e, target) {
|
||||
const selector = '.code-editor';
|
||||
get(this, 'dom')
|
||||
.component(selector, target)
|
||||
.didAppear();
|
||||
},
|
||||
error: function(e) {
|
||||
const item = get(this, 'item');
|
||||
const err = e.error;
|
||||
if (typeof err.errors !== 'undefined') {
|
||||
const error = err.errors[0];
|
||||
let prop;
|
||||
let message = error.detail;
|
||||
switch (true) {
|
||||
case message.indexOf(ERROR_PARSE_RULES) === 0:
|
||||
prop = 'Rules';
|
||||
message = error.detail;
|
||||
break;
|
||||
case message.indexOf(ERROR_NAME_EXISTS) === 0:
|
||||
prop = 'Name';
|
||||
message = message.substr(ERROR_NAME_EXISTS.indexOf(':') + 1);
|
||||
break;
|
||||
}
|
||||
if (prop) {
|
||||
item.addError(prop, message);
|
||||
}
|
||||
} else {
|
||||
// TODO: Conponents can't throw, use onerror
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
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
|
||||
const repo = get(this, 'repo');
|
||||
// TODO: Temporarily add dc here, will soon be serialized onto the policy itself
|
||||
const dc = get(this, 'dc');
|
||||
const slugKey = repo.getSlugKey();
|
||||
const slug = get(value, slugKey);
|
||||
updateArrayObject(items, repo.findBySlug(slug, dc), slugKey, slug);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
6
ui-v2/app/components/role-form.js
Normal file
6
ui-v2/app/components/role-form.js
Normal file
@ -0,0 +1,6 @@
|
||||
import FormComponent from './form-component';
|
||||
export default FormComponent.extend({
|
||||
type: 'role',
|
||||
name: 'role',
|
||||
classNames: ['role-form'],
|
||||
});
|
42
ui-v2/app/components/role-selector.js
Normal file
42
ui-v2/app/components/role-selector.js
Normal file
@ -0,0 +1,42 @@
|
||||
import ChildSelectorComponent from './child-selector';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
import { CallableEventSource as EventSource } from 'consul-ui/utils/dom/event-source';
|
||||
|
||||
export default ChildSelectorComponent.extend({
|
||||
repo: service('repository/role/component'),
|
||||
name: 'role',
|
||||
type: 'role',
|
||||
classNames: ['role-selector'],
|
||||
state: 'role',
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.policyForm = get(this, 'formContainer').form('policy');
|
||||
this.source = new EventSource();
|
||||
},
|
||||
// You have to alias data
|
||||
// is you just set it it loses its reference?
|
||||
policy: alias('policyForm.data'),
|
||||
actions: {
|
||||
reset: function(e) {
|
||||
this._super(...arguments);
|
||||
get(this, 'policyForm').clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
dispatch: function(type, data) {
|
||||
this.source.dispatchEvent({ type: type, data: data });
|
||||
},
|
||||
change: function() {
|
||||
const event = get(this, 'dom').normalizeEvent(...arguments);
|
||||
switch (event.target.name) {
|
||||
case 'role[state]':
|
||||
set(this, 'state', event.target.value);
|
||||
break;
|
||||
default:
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
5
ui-v2/app/components/service-identity.js
Normal file
5
ui-v2/app/components/service-identity.js
Normal file
@ -0,0 +1,5 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
@ -3,4 +3,5 @@ import Component from '@ember/component';
|
||||
export default Component.extend({
|
||||
name: 'tab',
|
||||
tagName: 'nav',
|
||||
classNames: ['tab-nav'],
|
||||
});
|
||||
|
@ -1,16 +1,12 @@
|
||||
import Component from 'ember-collection/components/ember-collection';
|
||||
import CollectionComponent from 'ember-collection/components/ember-collection';
|
||||
import needsRevalidate from 'ember-collection/utils/needs-revalidate';
|
||||
import identity from 'ember-collection/utils/identity';
|
||||
import Grid from 'ember-collection/layouts/grid';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import WithResizing from 'consul-ui/mixins/with-resizing';
|
||||
import style from 'ember-computed-style';
|
||||
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
import sibling from 'consul-ui/utils/dom/sibling';
|
||||
import closest from 'consul-ui/utils/dom/closest';
|
||||
import clickFirstAnchorFactory from 'consul-ui/utils/dom/click-first-anchor';
|
||||
const clickFirstAnchor = clickFirstAnchorFactory(closest);
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed, get, set } from '@ember/object';
|
||||
/**
|
||||
* Heavily extended `ember-collection` component
|
||||
@ -24,8 +20,6 @@ import { computed, get, set } from '@ember/object';
|
||||
* in the future
|
||||
*/
|
||||
|
||||
// ember doesn't like you using `$` hence `$$`
|
||||
const $$ = qsaFactory();
|
||||
// need to copy Cell in wholesale as there is no way to import it
|
||||
// there is no change made to `Cell` here, its only here as its
|
||||
// private in `ember-collection`
|
||||
@ -85,13 +79,17 @@ const change = function(e) {
|
||||
// 'actions_close' would mean that all menus have been closed
|
||||
// therefore we don't need to calculate
|
||||
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
|
||||
const $tr = closest('tr', e.currentTarget);
|
||||
const $group = sibling(e.currentTarget, 'ul');
|
||||
const $footer = [...$$('footer[role="contentinfo"]')][0];
|
||||
const dom = get(this, 'dom');
|
||||
|
||||
const $tr = dom.closest('tr', e.currentTarget);
|
||||
const $group = dom.sibling(e.currentTarget, 'ul');
|
||||
const groupRect = $group.getBoundingClientRect();
|
||||
const footerRect = $footer.getBoundingClientRect();
|
||||
const groupBottom = groupRect.top + $group.clientHeight;
|
||||
|
||||
const $footer = dom.element('footer[role="contentinfo"]');
|
||||
const footerRect = $footer.getBoundingClientRect();
|
||||
const footerTop = footerRect.top;
|
||||
|
||||
if (groupBottom > footerTop) {
|
||||
$group.classList.add('above');
|
||||
} else {
|
||||
@ -113,39 +111,50 @@ const change = function(e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Component.extend(SlotsMixin, WithResizing, {
|
||||
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
||||
tagName: 'table',
|
||||
classNames: ['dom-recycling'],
|
||||
classNameBindings: ['hasActions'],
|
||||
attributeBindings: ['style'],
|
||||
width: 1150,
|
||||
height: 500,
|
||||
rowHeight: 50,
|
||||
maxHeight: 500,
|
||||
style: style('getStyle'),
|
||||
checked: null,
|
||||
hasCaption: false,
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.change = change.bind(this);
|
||||
this.confirming = [];
|
||||
// TODO: The row height should auto calculate properly from the CSS
|
||||
this['cell-layout'] = new ZIndexedGrid(get(this, 'width'), 50);
|
||||
this['cell-layout'] = new ZIndexedGrid(get(this, 'width'), get(this, 'rowHeight'));
|
||||
},
|
||||
getStyle: computed('height', function() {
|
||||
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
|
||||
const maxRows = get(this, 'rows');
|
||||
let height = get(this, 'maxHeight');
|
||||
if (maxRows) {
|
||||
let rows = Math.max(3, get(this._items || [], 'length'));
|
||||
rows = Math.min(maxRows, rows);
|
||||
height = get(this, 'rowHeight') * rows + 29;
|
||||
}
|
||||
return {
|
||||
height: get(this, 'height'),
|
||||
height: height,
|
||||
};
|
||||
}),
|
||||
resize: function(e) {
|
||||
const $tbody = this.element;
|
||||
const $appContent = [...$$('main > div')][0];
|
||||
const dom = get(this, 'dom');
|
||||
const $appContent = dom.element('main > div');
|
||||
if ($appContent) {
|
||||
const border = 1;
|
||||
const rect = $tbody.getBoundingClientRect();
|
||||
const $footer = [...$$('footer[role="contentinfo"]')][0];
|
||||
const $footer = dom.element('footer[role="contentinfo"]');
|
||||
const space = rect.top + $footer.clientHeight + border;
|
||||
const height = e.detail.height - space;
|
||||
this.set('height', Math.max(0, height));
|
||||
this.set('maxHeight', Math.max(0, height));
|
||||
// TODO: The row height should auto calculate properly from the CSS
|
||||
this['cell-layout'] = new ZIndexedGrid($appContent.clientWidth, 50);
|
||||
this['cell-layout'] = new ZIndexedGrid($appContent.clientWidth, get(this, 'rowHeight'));
|
||||
this.updateItems();
|
||||
this.updateScrollPosition();
|
||||
}
|
||||
@ -273,7 +282,7 @@ export default Component.extend(SlotsMixin, WithResizing, {
|
||||
},
|
||||
actions: {
|
||||
click: function(e) {
|
||||
return clickFirstAnchor(e);
|
||||
return get(this, 'dom').clickFirstAnchor(e);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,17 +1,26 @@
|
||||
import Component from '@ember/component';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import closest from 'consul-ui/utils/dom/closest';
|
||||
import clickFirstAnchorFactory from 'consul-ui/utils/dom/click-first-anchor';
|
||||
const clickFirstAnchor = clickFirstAnchorFactory(closest);
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { subscribe } from 'consul-ui/utils/computed/purify';
|
||||
|
||||
let uid = 0;
|
||||
export default Component.extend(SlotsMixin, {
|
||||
dom: service('dom'),
|
||||
onchange: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'uid', uid++);
|
||||
},
|
||||
inputId: subscribe('name', 'uid', function(name = 'name') {
|
||||
return `tabular-details-${name}-toggle-${uid}_`;
|
||||
}),
|
||||
actions: {
|
||||
click: function(e) {
|
||||
clickFirstAnchor(e);
|
||||
get(this, 'dom').clickFirstAnchor(e);
|
||||
},
|
||||
change: function(item, e) {
|
||||
this.onchange(e, item);
|
||||
change: function(item, items, e) {
|
||||
this.onchange(e, item, items);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
6
ui-v2/app/components/tag-list.js
Normal file
6
ui-v2/app/components/tag-list.js
Normal file
@ -0,0 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: 'dl',
|
||||
classNames: ['tag-list'],
|
||||
});
|
73
ui-v2/app/components/templated-anchor.js
Normal file
73
ui-v2/app/components/templated-anchor.js
Normal file
@ -0,0 +1,73 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
|
||||
const createWeak = function(wm = new WeakMap()) {
|
||||
return {
|
||||
get: function(ref, prop) {
|
||||
let map = wm.get(ref);
|
||||
if (map) {
|
||||
return map[prop];
|
||||
}
|
||||
},
|
||||
set: function(ref, prop, value) {
|
||||
let map = wm.get(ref);
|
||||
if (typeof map === 'undefined') {
|
||||
map = {};
|
||||
wm.set(ref, map);
|
||||
}
|
||||
map[prop] = value;
|
||||
return map[prop];
|
||||
},
|
||||
};
|
||||
};
|
||||
const weak = createWeak();
|
||||
// Covers alpha-capitalized dot separated API keys such as
|
||||
// `{{Name}}`, `{{Service.Name}}` etc. but not `{{}}`
|
||||
const templateRe = /{{([A-Za-z.0-9_-]+)}}/g;
|
||||
export default Component.extend({
|
||||
tagName: 'a',
|
||||
attributeBindings: ['href', 'rel', 'target'],
|
||||
rel: computed({
|
||||
get: function(prop) {
|
||||
return weak.get(this, prop);
|
||||
},
|
||||
set: function(prop, value) {
|
||||
switch (value) {
|
||||
case 'external':
|
||||
value = `${value} noopener noreferrer`;
|
||||
set(this, 'target', '_blank');
|
||||
break;
|
||||
}
|
||||
return weak.set(this, prop, value);
|
||||
},
|
||||
}),
|
||||
vars: computed({
|
||||
get: function(prop) {
|
||||
return weak.get(this, prop);
|
||||
},
|
||||
set: function(prop, value) {
|
||||
weak.set(this, prop, value);
|
||||
set(this, 'href', weak.get(this, 'template'));
|
||||
},
|
||||
}),
|
||||
href: computed({
|
||||
get: function(prop) {
|
||||
return weak.get(this, prop);
|
||||
},
|
||||
set: function(prop, value) {
|
||||
weak.set(this, 'template', value);
|
||||
const vars = weak.get(this, 'vars');
|
||||
if (typeof vars !== 'undefined' && typeof value !== 'undefined') {
|
||||
value = value.replace(templateRe, function(match, group) {
|
||||
try {
|
||||
return get(vars, group) || '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
return weak.set(this, prop, value);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
}),
|
||||
});
|
@ -28,25 +28,26 @@ export default Component.extend({
|
||||
];
|
||||
}),
|
||||
distances: computed('tomography', function() {
|
||||
const tomography = this.get('tomography');
|
||||
let distances = tomography.distances || [];
|
||||
const tomography = get(this, 'tomography');
|
||||
let distances = get(tomography, 'distances') || [];
|
||||
distances.forEach((d, i) => {
|
||||
if (d.distance > get(this, 'max')) {
|
||||
set(this, 'max', d.distance);
|
||||
}
|
||||
});
|
||||
if (tomography.n > 360) {
|
||||
let n = distances.length;
|
||||
let n = get(distances, 'length');
|
||||
if (n > 360) {
|
||||
// We have more nodes than we want to show, take a random sampling to keep
|
||||
// the number around 360.
|
||||
const sampling = 360 / tomography.n;
|
||||
const sampling = 360 / n;
|
||||
distances = distances.filter(function(_, i) {
|
||||
return i == 0 || i == n - 1 || Math.random() < sampling;
|
||||
});
|
||||
n = get(distances, 'length');
|
||||
}
|
||||
return distances.map((d, i) => {
|
||||
return {
|
||||
rotate: i * 360 / distances.length,
|
||||
rotate: (i * 360) / n,
|
||||
y2: -insetSize * (d.distance / get(this, 'max')),
|
||||
node: d.node,
|
||||
distance: d.distance,
|
||||
|
11
ui-v2/app/computed/catchable.js
Normal file
11
ui-v2/app/computed/catchable.js
Normal file
@ -0,0 +1,11 @@
|
||||
import ComputedProperty from '@ember/object/computed';
|
||||
import computedFactory from 'consul-ui/utils/computed/factory';
|
||||
|
||||
export default class Catchable extends ComputedProperty {
|
||||
catch(cb) {
|
||||
return this.meta({
|
||||
catch: cb,
|
||||
});
|
||||
}
|
||||
}
|
||||
export const computed = computedFactory(Catchable);
|
@ -1,30 +1,31 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { set } from '@ember/object';
|
||||
import Changeset from 'ember-changeset';
|
||||
import validations from 'consul-ui/validations/acl';
|
||||
import lookupValidator from 'ember-changeset-validations';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('acl');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
this.changeset = new Changeset(model.item, lookupValidator(validations), validations);
|
||||
this._super({
|
||||
...model,
|
||||
...{
|
||||
item: this.changeset,
|
||||
},
|
||||
});
|
||||
// essentially this replaces the data with changesets
|
||||
this._super(
|
||||
Object.keys(model).reduce((prev, key, i) => {
|
||||
switch (key) {
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
const target = e.target || { name: 'Rules', value: e };
|
||||
switch (target.name) {
|
||||
case 'Type':
|
||||
set(this.changeset, target.name, target.value);
|
||||
break;
|
||||
case 'Rules':
|
||||
set(this, 'item.Rules', target.value);
|
||||
break;
|
||||
}
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
get(this, 'form').handleEvent(event);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed, get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
import ucfirst from 'consul-ui/utils/ucfirst';
|
||||
const countType = function(items, type) {
|
||||
return type === '' ? get(items, 'length') : items.filterBy('Type', type).length;
|
||||
};
|
||||
export default Controller.extend(WithFiltering, {
|
||||
export default Controller.extend(WithSearching, WithFiltering, {
|
||||
queryParams: {
|
||||
type: {
|
||||
as: 'type',
|
||||
@ -15,6 +16,17 @@ export default Controller.extend(WithFiltering, {
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
acl: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('filtered', function() {
|
||||
return get(this, 'searchables.acl')
|
||||
.add(get(this, 'filtered'))
|
||||
.search(get(this, this.searchParams.acl));
|
||||
}),
|
||||
typeFilters: computed('items', function() {
|
||||
const items = get(this, 'items');
|
||||
return ['', 'management', 'client'].map(function(item) {
|
||||
@ -27,17 +39,8 @@ export default Controller.extend(WithFiltering, {
|
||||
};
|
||||
});
|
||||
}),
|
||||
filter: function(item, { s = '', type = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
(get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'ID')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1) &&
|
||||
(type === '' || get(item, 'Type') === type)
|
||||
);
|
||||
filter: function(item, { type = '' }) {
|
||||
return type === '' || get(item, 'Type') === type;
|
||||
},
|
||||
actions: {
|
||||
sendClone: function(item) {
|
||||
|
@ -1,10 +1,8 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
dom: service('dom'),
|
||||
isScoped: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('policy');
|
||||
@ -21,25 +19,5 @@ export default Controller.extend({
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
set(this, 'isScoped', get(model.item, 'Datacenters.length') > 0);
|
||||
},
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
const form = get(this, 'form');
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
} catch (err) {
|
||||
const target = event.target;
|
||||
switch (target.name) {
|
||||
case 'policy[isScoped]':
|
||||
set(this, 'isScoped', !get(this, 'isScoped'));
|
||||
set(this.item, 'Datacenters', null);
|
||||
break;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,23 +1,23 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
export default Controller.extend(WithFiltering, {
|
||||
import { get, computed } from '@ember/object';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
filter: function(item, { s = '', type = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Description')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1
|
||||
);
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
policy: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.policy')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.policy));
|
||||
}),
|
||||
actions: {},
|
||||
});
|
||||
|
2
ui-v2/app/controllers/dc/acls/roles/create.js
Normal file
2
ui-v2/app/controllers/dc/acls/roles/create.js
Normal file
@ -0,0 +1,2 @@
|
||||
import Controller from './edit';
|
||||
export default Controller.extend();
|
23
ui-v2/app/controllers/dc/acls/roles/edit.js
Normal file
23
ui-v2/app/controllers/dc/acls/roles/edit.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('role');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
// essentially this replaces the data with changesets
|
||||
this._super(
|
||||
Object.keys(model).reduce((prev, key, i) => {
|
||||
switch (key) {
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
});
|
23
ui-v2/app/controllers/dc/acls/roles/index.js
Normal file
23
ui-v2/app/controllers/dc/acls/roles/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, computed } from '@ember/object';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
role: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.role')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.role));
|
||||
}),
|
||||
actions: {},
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
dom: service('dom'),
|
||||
builder: service('form'),
|
||||
@ -17,59 +17,20 @@ export default Controller.extend({
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
case 'policy':
|
||||
prev[key] = this.form
|
||||
.form(key)
|
||||
.setData(prev[key])
|
||||
.getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
actions: {
|
||||
sendClearPolicy: function(item) {
|
||||
set(this, 'isScoped', false);
|
||||
this.send('clearPolicy');
|
||||
},
|
||||
sendCreatePolicy: function(item, policies, success) {
|
||||
this.send('createPolicy', item, policies, success);
|
||||
},
|
||||
refreshCodeEditor: function(selector, parent) {
|
||||
if (parent.target) {
|
||||
parent = undefined;
|
||||
}
|
||||
get(this, 'dom')
|
||||
.component(selector, parent)
|
||||
.didAppear();
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const form = get(this, 'form');
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const form = get(this, 'form');
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
} catch (err) {
|
||||
const target = event.target;
|
||||
switch (target.name) {
|
||||
case 'policy[isScoped]':
|
||||
set(this, 'isScoped', !get(this, 'isScoped'));
|
||||
set(this.policy, 'Datacenters', null);
|
||||
break;
|
||||
case 'Policy':
|
||||
set(value, 'CreateTime', new Date().getTime());
|
||||
get(this, 'item.Policies').pushObject(value);
|
||||
break;
|
||||
case 'Details':
|
||||
// the Details expander toggle
|
||||
// only load on opening
|
||||
if (target.checked) {
|
||||
this.send('refreshCodeEditor', '.code-editor', target.parentNode);
|
||||
if (!get(value, 'Rules')) {
|
||||
this.send('loadPolicy', value, get(this, 'item.Policies'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
|
@ -1,30 +1,24 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
export default Controller.extend(WithFiltering, {
|
||||
import { computed, get } from '@ember/object';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
filter: function(item, { s = '', type = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'AccessorID')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Description')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
(get(item, 'Policies') || []).some(function(item) {
|
||||
return item.Name.toLowerCase().indexOf(sLower) !== -1;
|
||||
})
|
||||
);
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
token: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.token')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.token));
|
||||
}),
|
||||
actions: {
|
||||
sendClone: function(item) {
|
||||
this.send('clone', item);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import Changeset from 'ember-changeset';
|
||||
import lookupValidator from 'ember-changeset-validations';
|
||||
|
||||
import validations from 'consul-ui/validations/intention';
|
||||
|
||||
export default Controller.extend({
|
||||
dom: service('dom'),
|
||||
builder: service('form'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('intention');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
this.changeset = new Changeset(model.item, lookupValidator(validations), validations);
|
||||
const sourceName = get(model.item, 'SourceName');
|
||||
const destinationName = get(model.item, 'DestinationName');
|
||||
let source = model.items.findBy('Name', sourceName);
|
||||
@ -23,50 +24,57 @@ export default Controller.extend({
|
||||
this._super({
|
||||
...model,
|
||||
...{
|
||||
item: this.changeset,
|
||||
item: this.form.setData(model.item).getData(),
|
||||
SourceName: source,
|
||||
DestinationName: destination,
|
||||
},
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
createNewLabel: function(term) {
|
||||
return `Use a future Consul Service called '${term}'`;
|
||||
createNewLabel: function(template, term) {
|
||||
return template.replace(/{{term}}/g, term);
|
||||
},
|
||||
isUnique: function(term) {
|
||||
return !get(this, 'items').findBy('Name', term);
|
||||
},
|
||||
change: function(e, value, _target) {
|
||||
// normalize back to standard event
|
||||
const target = e.target || { ..._target, ...{ name: e, value: value } };
|
||||
let name, selected;
|
||||
name = selected = target.value;
|
||||
// TODO:
|
||||
// linter needs this here?
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const form = get(this, 'form');
|
||||
const target = event.target;
|
||||
|
||||
let name;
|
||||
let selected;
|
||||
let match;
|
||||
switch (target.name) {
|
||||
case 'Description':
|
||||
case 'Action':
|
||||
set(this.changeset, target.name, target.value);
|
||||
break;
|
||||
case 'SourceName':
|
||||
case 'DestinationName':
|
||||
name = selected = target.value;
|
||||
// Names can be selected Service EmberObjects or typed in strings
|
||||
// if its not a string, use the `Name` from the Service EmberObject
|
||||
if (typeof name !== 'string') {
|
||||
name = get(target.value, 'Name');
|
||||
}
|
||||
// linter doesn't like const here
|
||||
// see if the name is already in the list
|
||||
match = get(this, 'items').filterBy('Name', name);
|
||||
if (match.length === 0) {
|
||||
// if its not make a new 'fake' Service that doesn't exist yet
|
||||
// and add it to the possible services to make an intention between
|
||||
selected = { Name: name };
|
||||
// linter doesn't mind const here?
|
||||
const items = [selected].concat(this.items.toArray());
|
||||
set(this, 'items', items);
|
||||
}
|
||||
set(this.changeset, target.name, name);
|
||||
// mutate the value with the string name
|
||||
// which will be handled by the form
|
||||
target.value = name;
|
||||
// these are 'non-form' variables so not on `item`
|
||||
// these variables also exist in the template so we know
|
||||
// the current selection
|
||||
// basically the difference between
|
||||
// `item.DestinationName` and just `DestinationName`
|
||||
set(this, target.name, selected);
|
||||
break;
|
||||
}
|
||||
this.changeset.validate();
|
||||
form.handleEvent(event);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed, get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
import ucfirst from 'consul-ui/utils/ucfirst';
|
||||
// TODO: DRY out in acls at least
|
||||
const createCounter = function(prop) {
|
||||
@ -9,7 +10,7 @@ const createCounter = function(prop) {
|
||||
};
|
||||
};
|
||||
const countAction = createCounter('Action');
|
||||
export default Controller.extend(WithFiltering, {
|
||||
export default Controller.extend(WithSearching, WithFiltering, {
|
||||
queryParams: {
|
||||
action: {
|
||||
as: 'action',
|
||||
@ -19,6 +20,17 @@ export default Controller.extend(WithFiltering, {
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
intention: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('filtered', function() {
|
||||
return get(this, 'searchables.intention')
|
||||
.add(get(this, 'filtered'))
|
||||
.search(get(this, this.searchParams.intention));
|
||||
}),
|
||||
actionFilters: computed('items', function() {
|
||||
const items = get(this, 'items');
|
||||
return ['', 'allow', 'deny'].map(function(item) {
|
||||
@ -32,16 +44,6 @@ export default Controller.extend(WithFiltering, {
|
||||
});
|
||||
}),
|
||||
filter: function(item, { s = '', action = '' }) {
|
||||
const source = get(item, 'SourceName').toLowerCase();
|
||||
const destination = get(item, 'DestinationName').toLowerCase();
|
||||
const sLower = s.toLowerCase();
|
||||
const allLabel = 'All Services (*)'.toLowerCase();
|
||||
return (
|
||||
(source.indexOf(sLower) !== -1 ||
|
||||
destination.indexOf(sLower) !== -1 ||
|
||||
(source === '*' && allLabel.indexOf(sLower) !== -1) ||
|
||||
(destination === '*' && allLabel.indexOf(sLower) !== -1)) &&
|
||||
(action === '' || get(item, 'Action') === action)
|
||||
);
|
||||
return action === '' || get(item, 'Action') === action;
|
||||
},
|
||||
});
|
||||
|
@ -2,41 +2,56 @@ import Controller from '@ember/controller';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import Changeset from 'ember-changeset';
|
||||
import validations from 'consul-ui/validations/kv';
|
||||
import lookupValidator from 'ember-changeset-validations';
|
||||
export default Controller.extend({
|
||||
json: true,
|
||||
dom: service('dom'),
|
||||
builder: service('form'),
|
||||
encoder: service('btoa'),
|
||||
json: true,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('kv');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
// TODO: Potentially save whether json has been clicked to the model,
|
||||
// setting set(this, 'json', true) here will force the form to always default to code=on
|
||||
// even if the user has selected code=off on another KV
|
||||
// ideally we would save the value per KV, but I'd like to not do that on the model
|
||||
// a set(this, 'json', valueFromSomeStorageJustForThisKV) would be added here
|
||||
this.changeset = new Changeset(model.item, lookupValidator(validations), validations);
|
||||
this._super({
|
||||
...model,
|
||||
...{
|
||||
item: this.changeset,
|
||||
},
|
||||
});
|
||||
// essentially this replaces the data with changesets
|
||||
this._super(
|
||||
Object.keys(model).reduce((prev, key, i) => {
|
||||
switch (key) {
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
const target = e.target || { name: 'value', value: e };
|
||||
var parent;
|
||||
switch (target.name) {
|
||||
case 'additional':
|
||||
parent = get(this, 'parent.Key');
|
||||
set(this.changeset, 'Key', `${parent !== '/' ? parent : ''}${target.value}`);
|
||||
break;
|
||||
case 'json':
|
||||
set(this, 'json', !get(this, 'json'));
|
||||
break;
|
||||
case 'value':
|
||||
set(this, 'item.Value', get(this, 'encoder').execute(target.value));
|
||||
break;
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const form = get(this, 'form');
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
} catch (err) {
|
||||
const target = event.target;
|
||||
let parent;
|
||||
switch (target.name) {
|
||||
case 'value':
|
||||
set(this.item, 'Value', get(this, 'encoder').execute(target.value));
|
||||
break;
|
||||
case 'additional':
|
||||
parent = get(this, 'parent.Key');
|
||||
set(this.item, 'Key', `${parent !== '/' ? parent : ''}${target.value}`);
|
||||
break;
|
||||
case 'json':
|
||||
// TODO: Potentially save whether json has been clicked to the model,
|
||||
// setting set(this, 'json', true) here will force the form to always default to code=on
|
||||
// even if the user has selected code=off on another KV
|
||||
// ideally we would save the value per KV, but I'd like to not do that on the model
|
||||
// a set(this, 'json', valueFromSomeStorageJustForThisKV) would be added here
|
||||
set(this, 'json', !get(this, 'json'));
|
||||
break;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -1,18 +1,22 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import rightTrim from 'consul-ui/utils/right-trim';
|
||||
export default Controller.extend(WithFiltering, {
|
||||
import { get, computed } from '@ember/object';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
filter: function(item, { s = '' }) {
|
||||
const key = rightTrim(get(item, 'Key'), '/')
|
||||
.split('/')
|
||||
.pop();
|
||||
return key.toLowerCase().indexOf(s.toLowerCase()) !== -1;
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
kv: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.kv')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.kv));
|
||||
}),
|
||||
});
|
||||
|
@ -1,12 +1,27 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import WithEventSource from 'consul-ui/mixins/with-event-source';
|
||||
import WithHealthFiltering from 'consul-ui/mixins/with-health-filtering';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend(WithHealthFiltering, {
|
||||
export default Controller.extend(WithEventSource, WithSearching, WithHealthFiltering, {
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
healthyNode: 's',
|
||||
unhealthyNode: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
this.columns = [25, 25, 25, 25];
|
||||
},
|
||||
searchableHealthy: computed('healthy', function() {
|
||||
return get(this, 'searchables.healthyNode')
|
||||
.add(get(this, 'healthy'))
|
||||
.search(get(this, this.searchParams.healthyNode));
|
||||
}),
|
||||
searchableUnhealthy: computed('unhealthy', function() {
|
||||
return get(this, 'searchables.unhealthyNode')
|
||||
.add(get(this, 'unhealthy'))
|
||||
.search(get(this, this.searchParams.unhealthyNode));
|
||||
}),
|
||||
unhealthy: computed('filtered', function() {
|
||||
return get(this, 'filtered').filter(function(item) {
|
||||
return get(item, 'isUnhealthy');
|
||||
@ -18,10 +33,6 @@ export default Controller.extend(WithHealthFiltering, {
|
||||
});
|
||||
}),
|
||||
filter: function(item, { s = '', status = '' }) {
|
||||
return (
|
||||
get(item, 'Node')
|
||||
.toLowerCase()
|
||||
.indexOf(s.toLowerCase()) !== -1 && item.hasStatus(status)
|
||||
);
|
||||
return item.hasStatus(status);
|
||||
},
|
||||
});
|
||||
|
@ -1,18 +1,46 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, set } from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
import getComponentFactory from 'consul-ui/utils/get-component-factory';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
import WithEventSource, { listen } from 'consul-ui/mixins/with-event-source';
|
||||
|
||||
const $$ = qsaFactory();
|
||||
export default Controller.extend(WithFiltering, {
|
||||
export default Controller.extend(WithEventSource, WithSearching, {
|
||||
dom: service('dom'),
|
||||
notify: service('flashMessages'),
|
||||
items: alias('item.Services'),
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
nodeservice: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
item: listen('item').catch(function(e) {
|
||||
if (e.target.readyState === 1) {
|
||||
// OPEN
|
||||
if (get(e, 'error.errors.firstObject.status') === '404') {
|
||||
get(this, 'notify').add({
|
||||
destroyOnClick: false,
|
||||
sticky: true,
|
||||
type: 'warning',
|
||||
action: 'update',
|
||||
});
|
||||
get(this, 'tomography').close();
|
||||
get(this, 'sessions').close();
|
||||
}
|
||||
}
|
||||
}),
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.nodeservice')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.nodeservice));
|
||||
}),
|
||||
setProperties: function() {
|
||||
this._super(...arguments);
|
||||
// the default selected tab depends on whether you have any healthchecks or not
|
||||
@ -20,38 +48,20 @@ export default Controller.extend(WithFiltering, {
|
||||
// This method is called immediately after `Route::setupController`, and done here rather than there
|
||||
// as this is a variable used purely for view level things, if the view was different we might not
|
||||
// need this variable
|
||||
set(this, 'selectedTab', get(this.item, 'Checks.length') > 0 ? 'health-checks' : 'services');
|
||||
},
|
||||
filter: function(item, { s = '' }) {
|
||||
const term = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Service')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
get(item, 'ID')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
(get(item, 'Tags') || []).some(function(item) {
|
||||
return item.toLowerCase().indexOf(term) !== -1;
|
||||
}) ||
|
||||
get(item, 'Port')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1
|
||||
);
|
||||
set(this, 'selectedTab', get(this, 'item.Checks.length') > 0 ? 'health-checks' : 'services');
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
set(this, 'selectedTab', e.target.value);
|
||||
const getComponent = getComponentFactory(getOwner(this));
|
||||
// Ensure tabular-collections sizing is recalculated
|
||||
// now it is visible in the DOM
|
||||
[...$$('.tab-section input[type="radio"]:checked + div table')].forEach(function(item) {
|
||||
const component = getComponent(item);
|
||||
if (component && typeof component.didAppear === 'function') {
|
||||
getComponent(item).didAppear();
|
||||
}
|
||||
});
|
||||
get(this, 'dom')
|
||||
.components('.tab-section input[type="radio"]:checked + div table')
|
||||
.forEach(function(item) {
|
||||
if (typeof item.didAppear === 'function') {
|
||||
item.didAppear();
|
||||
}
|
||||
});
|
||||
},
|
||||
sortChecksByImportance: function(a, b) {
|
||||
const statusA = get(a, 'Status');
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, computed } from '@ember/object';
|
||||
import { htmlSafe } from '@ember/string';
|
||||
import WithHealthFiltering from 'consul-ui/mixins/with-health-filtering';
|
||||
import WithEventSource from 'consul-ui/mixins/with-event-source';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
const max = function(arr, prop) {
|
||||
return arr.reduce(function(prev, item) {
|
||||
return Math.max(prev, get(item, prop));
|
||||
@ -24,19 +25,23 @@ const width = function(num) {
|
||||
const widthDeclaration = function(num) {
|
||||
return htmlSafe(`width: ${num}px`);
|
||||
};
|
||||
export default Controller.extend(WithHealthFiltering, {
|
||||
filter: function(item, { s = '', status = '' }) {
|
||||
const term = s.toLowerCase();
|
||||
return (
|
||||
(get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
(get(item, 'Tags') || []).some(function(item) {
|
||||
return item.toLowerCase().indexOf(term) !== -1;
|
||||
})) &&
|
||||
item.hasStatus(status)
|
||||
);
|
||||
export default Controller.extend(WithEventSource, WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
service: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items.[]', function() {
|
||||
return get(this, 'searchables.service')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, 'terms'));
|
||||
}),
|
||||
maxWidth: computed('{maxPassing,maxWarning,maxCritical}', function() {
|
||||
const PADDING = 32 * 3 + 13;
|
||||
return ['maxPassing', 'maxWarning', 'maxCritical'].reduce((prev, item) => {
|
||||
@ -47,15 +52,20 @@ export default Controller.extend(WithHealthFiltering, {
|
||||
return widthDeclaration(get(this, 'maxWidth'));
|
||||
}),
|
||||
remainingWidth: computed('maxWidth', function() {
|
||||
return htmlSafe(`width: calc(50% - ${Math.round(get(this, 'maxWidth') / 2)}px)`);
|
||||
// maxWidth is the maximum width of the healthchecks column
|
||||
// there are currently 2 other columns so divide it by 2 and
|
||||
// take that off 50% (100% / number of fluid columns)
|
||||
// also we added a Type column which we've currently fixed to 100px
|
||||
// so again divide that by 2 and take it off each fluid column
|
||||
return htmlSafe(`width: calc(50% - 50px - ${Math.round(get(this, 'maxWidth') / 2)}px)`);
|
||||
}),
|
||||
maxPassing: computed('items', function() {
|
||||
maxPassing: computed('items.[]', function() {
|
||||
return max(get(this, 'items'), 'ChecksPassing');
|
||||
}),
|
||||
maxWarning: computed('items', function() {
|
||||
maxWarning: computed('items.[]', function() {
|
||||
return max(get(this, 'items'), 'ChecksWarning');
|
||||
}),
|
||||
maxCritical: computed('items', function() {
|
||||
maxCritical: computed('items.[]', function() {
|
||||
return max(get(this, 'items'), 'ChecksCritical');
|
||||
}),
|
||||
passingWidth: computed('maxPassing', function() {
|
||||
|
37
ui-v2/app/controllers/dc/services/instance.js
Normal file
37
ui-v2/app/controllers/dc/services/instance.js
Normal file
@ -0,0 +1,37 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import WithEventSource, { listen } from 'consul-ui/mixins/with-event-source';
|
||||
|
||||
export default Controller.extend(WithEventSource, {
|
||||
notify: service('flashMessages'),
|
||||
setProperties: function() {
|
||||
this._super(...arguments);
|
||||
// This method is called immediately after `Route::setupController`, and done here rather than there
|
||||
// as this is a variable used purely for view level things, if the view was different we might not
|
||||
// need this variable
|
||||
set(this, 'selectedTab', 'service-checks');
|
||||
},
|
||||
item: listen('item').catch(function(e) {
|
||||
if (e.target.readyState === 1) {
|
||||
// OPEN
|
||||
if (get(e, 'error.errors.firstObject.status') === '404') {
|
||||
get(this, 'notify').add({
|
||||
destroyOnClick: false,
|
||||
sticky: true,
|
||||
type: 'warning',
|
||||
action: 'update',
|
||||
});
|
||||
const proxy = get(this, 'proxy');
|
||||
if (proxy) {
|
||||
proxy.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
actions: {
|
||||
change: function(e) {
|
||||
set(this, 'selectedTab', e.target.value);
|
||||
},
|
||||
},
|
||||
});
|
@ -1,34 +1,56 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get } from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import sumOfUnhealthy from 'consul-ui/utils/sumOfUnhealthy';
|
||||
import hasStatus from 'consul-ui/utils/hasStatus';
|
||||
import WithHealthFiltering from 'consul-ui/mixins/with-health-filtering';
|
||||
export default Controller.extend(WithHealthFiltering, {
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
import WithEventSource, { listen } from 'consul-ui/mixins/with-event-source';
|
||||
export default Controller.extend(WithEventSource, WithSearching, {
|
||||
dom: service('dom'),
|
||||
notify: service('flashMessages'),
|
||||
items: alias('item.Nodes'),
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
serviceInstance: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
unhealthy: computed('filtered', function() {
|
||||
return get(this, 'filtered').filter(function(item) {
|
||||
return sumOfUnhealthy(item.Checks) > 0;
|
||||
});
|
||||
setProperties: function() {
|
||||
this._super(...arguments);
|
||||
// This method is called immediately after `Route::setupController`, and done here rather than there
|
||||
// as this is a variable used purely for view level things, if the view was different we might not
|
||||
// need this variable
|
||||
set(this, 'selectedTab', 'instances');
|
||||
},
|
||||
item: listen('item').catch(function(e) {
|
||||
if (e.target.readyState === 1) {
|
||||
// OPEN
|
||||
if (get(e, 'error.errors.firstObject.status') === '404') {
|
||||
get(this, 'notify').add({
|
||||
destroyOnClick: false,
|
||||
sticky: true,
|
||||
type: 'warning',
|
||||
action: 'update',
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
healthy: computed('filtered', function() {
|
||||
return get(this, 'filtered').filter(function(item) {
|
||||
return sumOfUnhealthy(item.Checks) === 0;
|
||||
});
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.serviceInstance')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.serviceInstance));
|
||||
}),
|
||||
filter: function(item, { s = '', status = '' }) {
|
||||
const term = s.toLowerCase();
|
||||
|
||||
return (
|
||||
get(item, 'Node.Node')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
(get(item, 'Service.ID')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 &&
|
||||
hasStatus(get(item, 'Checks'), status))
|
||||
);
|
||||
actions: {
|
||||
change: function(e) {
|
||||
set(this, 'selectedTab', e.target.value);
|
||||
// Ensure tabular-collections sizing is recalculated
|
||||
// now it is visible in the DOM
|
||||
get(this, 'dom')
|
||||
.components('.tab-section input[type="radio"]:checked + div table')
|
||||
.forEach(function(item) {
|
||||
if (typeof item.didAppear === 'function') {
|
||||
item.didAppear();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
43
ui-v2/app/controllers/settings.js
Normal file
43
ui-v2/app/controllers/settings.js
Normal file
@ -0,0 +1,43 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
repo: service('settings'),
|
||||
dom: service('dom'),
|
||||
actions: {
|
||||
key: function(e) {
|
||||
switch (true) {
|
||||
case e.keyCode === 13:
|
||||
// disable ENTER
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
// TODO: Switch to using forms like the rest of the app
|
||||
// setting utils/form/builder for things to be done before we
|
||||
// can do that. For the moment just do things normally its a simple
|
||||
// enough form at the moment
|
||||
|
||||
const target = event.target;
|
||||
const blocking = get(this, 'item.client.blocking');
|
||||
switch (target.name) {
|
||||
case 'client[blocking]':
|
||||
if (typeof blocking === 'undefined') {
|
||||
set(this, 'item.client', {});
|
||||
}
|
||||
set(this, 'item.client.blocking', !blocking);
|
||||
this.send('update', get(this, 'item'));
|
||||
break;
|
||||
case 'urls[service]':
|
||||
if (typeof get(this, 'item.urls') === 'undefined') {
|
||||
set(this, 'item.urls', {});
|
||||
}
|
||||
set(this, 'item.urls.service', target.value);
|
||||
this.send('update', get(this, 'item'));
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
5
ui-v2/app/env.js
Normal file
5
ui-v2/app/env.js
Normal file
@ -0,0 +1,5 @@
|
||||
import config from './config/environment';
|
||||
export default function(str) {
|
||||
const user = window.localStorage.getItem(str);
|
||||
return user !== null ? user : config[str];
|
||||
}
|
6
ui-v2/app/forms/acl.js
Normal file
6
ui-v2/app/forms/acl.js
Normal file
@ -0,0 +1,6 @@
|
||||
import validations from 'consul-ui/validations/acl';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
6
ui-v2/app/forms/intention.js
Normal file
6
ui-v2/app/forms/intention.js
Normal file
@ -0,0 +1,6 @@
|
||||
import validations from 'consul-ui/validations/intention';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
6
ui-v2/app/forms/kv.js
Normal file
6
ui-v2/app/forms/kv.js
Normal file
@ -0,0 +1,6 @@
|
||||
import validations from 'consul-ui/validations/kv';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import validations from 'consul-ui/validations/policy';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = 'policy', v = validations, form = builder) {
|
||||
export default function(container, name = 'policy', v = validations, form = builder) {
|
||||
return form(name, {
|
||||
Datacenters: {
|
||||
type: 'array',
|
||||
|
8
ui-v2/app/forms/role.js
Normal file
8
ui-v2/app/forms/role.js
Normal file
@ -0,0 +1,8 @@
|
||||
import validations from 'consul-ui/validations/role';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = 'role', v = validations, form = builder) {
|
||||
return form(name, {})
|
||||
.setValidators(v)
|
||||
.add(container.form('policy'));
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
import validations from 'consul-ui/validations/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = '', v = validations, form = builder) {
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {})
|
||||
.setValidators(v)
|
||||
.add(policy());
|
||||
.add(container.form('policy'))
|
||||
.add(container.form('role'));
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
import { get } from '@ember/object';
|
||||
const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001';
|
||||
export function isManagement(params, hash) {
|
||||
return get(params[0], 'ID') === MANAGEMENT_ID;
|
||||
}
|
||||
|
||||
export default helper(isManagement);
|
18
ui-v2/app/helpers/policy/typeof.js
Normal file
18
ui-v2/app/helpers/policy/typeof.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
import { get } from '@ember/object';
|
||||
const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001';
|
||||
export function typeOf(params, hash) {
|
||||
const item = params[0];
|
||||
switch (true) {
|
||||
case get(item, 'ID') === MANAGEMENT_ID:
|
||||
return 'policy-management';
|
||||
case typeof get(item, 'template') === 'undefined':
|
||||
return 'role';
|
||||
case get(item, 'template') !== '':
|
||||
return 'policy-service-identity';
|
||||
default:
|
||||
return 'policy';
|
||||
}
|
||||
}
|
||||
|
||||
export default helper(typeOf);
|
15
ui-v2/app/initializers/client.js
Normal file
15
ui-v2/app/initializers/client.js
Normal file
@ -0,0 +1,15 @@
|
||||
const scripts = document.getElementsByTagName('script');
|
||||
const current = scripts[scripts.length - 1];
|
||||
|
||||
export function initialize(application) {
|
||||
const Client = application.resolveRegistration('service:client/http');
|
||||
Client.reopen({
|
||||
isCurrent: function(src) {
|
||||
return current.src === src;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
23
ui-v2/app/initializers/controller-lifecycle.js
Normal file
23
ui-v2/app/initializers/controller-lifecycle.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Route from '@ember/routing/route';
|
||||
/**
|
||||
* This initializer is very similar to:
|
||||
* https://github.com/kellyselden/ember-controller-lifecycle
|
||||
*
|
||||
* Why is this included here:
|
||||
* 1. Make sure lifecycle functions are functions, not just truthy.
|
||||
* 2. Right now we don't want a setup function (at least until we are definitely decided that we want one)
|
||||
* This is possibly a very personal opinion so it makes sense to just include this file here.
|
||||
*/
|
||||
Route.reopen({
|
||||
resetController(controller, exiting, transition) {
|
||||
this._super(...arguments);
|
||||
if (typeof controller.reset === 'function') {
|
||||
controller.reset(exiting);
|
||||
}
|
||||
},
|
||||
});
|
||||
export function initialize() {}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
@ -1,15 +1,40 @@
|
||||
import { get, set } from '@ember/object';
|
||||
|
||||
import kv from 'consul-ui/forms/kv';
|
||||
import acl from 'consul-ui/forms/acl';
|
||||
import token from 'consul-ui/forms/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import role from 'consul-ui/forms/role';
|
||||
import intention from 'consul-ui/forms/intention';
|
||||
|
||||
export function initialize(application) {
|
||||
// Service-less injection using private properties at a per-project level
|
||||
const FormBuilder = application.resolveRegistration('service:form');
|
||||
const forms = {
|
||||
token: token(),
|
||||
policy: policy(),
|
||||
kv: kv,
|
||||
acl: acl,
|
||||
token: token,
|
||||
policy: policy,
|
||||
role: role,
|
||||
intention: intention,
|
||||
};
|
||||
FormBuilder.reopen({
|
||||
form: function(name) {
|
||||
return forms[name];
|
||||
let form = get(this.forms, name);
|
||||
if (!form) {
|
||||
form = set(this.forms, name, forms[name](this));
|
||||
// only do special things for our new things for the moment
|
||||
if (name === 'role' || name === 'policy') {
|
||||
const repo = get(this, name);
|
||||
form.clear(function(obj) {
|
||||
return repo.create(obj);
|
||||
});
|
||||
form.submit(function(obj) {
|
||||
return repo.persist(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
return form;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,44 +1,9 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import lint from 'consul-ui/utils/editor/lint';
|
||||
const MODES = [
|
||||
{
|
||||
name: 'JSON',
|
||||
mime: 'application/json',
|
||||
mode: 'javascript',
|
||||
ext: ['json', 'map'],
|
||||
alias: ['json5'],
|
||||
},
|
||||
{
|
||||
name: 'HCL',
|
||||
mime: 'text/x-ruby',
|
||||
mode: 'ruby',
|
||||
ext: ['rb'],
|
||||
alias: ['jruby', 'macruby', 'rake', 'rb', 'rbx'],
|
||||
},
|
||||
{ name: 'YAML', mime: 'text/x-yaml', mode: 'yaml', ext: ['yaml', 'yml'], alias: ['yml'] },
|
||||
];
|
||||
export function initialize(application) {
|
||||
const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror');
|
||||
const IvyCodeMirrorService = application.resolveRegistration('service:code-mirror');
|
||||
// Make sure ivy-codemirror respects/maintains a `name=""` attribute
|
||||
IvyCodeMirrorComponent.reopen({
|
||||
attributeBindings: ['name'],
|
||||
});
|
||||
// Add some method to the code-mirror service so I don't have to have 2 services
|
||||
// for dealing with codemirror
|
||||
IvyCodeMirrorService.reopen({
|
||||
dom: service('dom'),
|
||||
modes: function() {
|
||||
return MODES;
|
||||
},
|
||||
lint: function() {
|
||||
return lint(...arguments);
|
||||
},
|
||||
getEditor: function(element) {
|
||||
return get(this, 'dom').element('textarea + div', element).CodeMirror;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
|
15
ui-v2/app/initializers/power-select.js
Normal file
15
ui-v2/app/initializers/power-select.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { get } from '@ember/object';
|
||||
export function initialize(application) {
|
||||
const PowerSelectComponent = application.resolveRegistration('component:power-select');
|
||||
PowerSelectComponent.reopen({
|
||||
updateState: function(changes) {
|
||||
if (!get(this, 'isDestroyed')) {
|
||||
return this._super(changes);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
40
ui-v2/app/initializers/search.js
Normal file
40
ui-v2/app/initializers/search.js
Normal file
@ -0,0 +1,40 @@
|
||||
import intention from 'consul-ui/search/filters/intention';
|
||||
import token from 'consul-ui/search/filters/token';
|
||||
import policy from 'consul-ui/search/filters/policy';
|
||||
import role from 'consul-ui/search/filters/role';
|
||||
import kv from 'consul-ui/search/filters/kv';
|
||||
import acl from 'consul-ui/search/filters/acl';
|
||||
import node from 'consul-ui/search/filters/node';
|
||||
// service instance
|
||||
import nodeService from 'consul-ui/search/filters/node/service';
|
||||
import serviceNode from 'consul-ui/search/filters/service/node';
|
||||
import service from 'consul-ui/search/filters/service';
|
||||
|
||||
import filterableFactory from 'consul-ui/utils/search/filterable';
|
||||
const filterable = filterableFactory();
|
||||
export function initialize(application) {
|
||||
// Service-less injection using private properties at a per-project level
|
||||
const Builder = application.resolveRegistration('service:search');
|
||||
const searchables = {
|
||||
intention: intention(filterable),
|
||||
token: token(filterable),
|
||||
acl: acl(filterable),
|
||||
policy: policy(filterable),
|
||||
role: role(filterable),
|
||||
kv: kv(filterable),
|
||||
healthyNode: node(filterable),
|
||||
unhealthyNode: node(filterable),
|
||||
serviceInstance: serviceNode(filterable),
|
||||
nodeservice: nodeService(filterable),
|
||||
service: service(filterable),
|
||||
};
|
||||
Builder.reopen({
|
||||
searchable: function(name) {
|
||||
return searchables[name];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
102
ui-v2/app/instance-initializers/event-source.js
Normal file
102
ui-v2/app/instance-initializers/event-source.js
Normal file
@ -0,0 +1,102 @@
|
||||
import env from 'consul-ui/env';
|
||||
|
||||
export function initialize(container) {
|
||||
if (env('CONSUL_UI_DISABLE_REALTIME')) {
|
||||
return;
|
||||
}
|
||||
['node', 'coordinate', 'session', 'service', 'proxy']
|
||||
.map(function(item) {
|
||||
// create repositories that return a promise resolving to an EventSource
|
||||
return {
|
||||
service: `repository/${item}/event-source`,
|
||||
extend: 'repository/type/event-source',
|
||||
// Inject our original respository that is used by this class
|
||||
// within the callable of the EventSource
|
||||
services: {
|
||||
content: `repository/${item}`,
|
||||
},
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
['dc', 'policy', 'role'].map(function(item) {
|
||||
// create repositories that return a promise resolving to an EventSource
|
||||
return {
|
||||
service: `repository/${item}/component`,
|
||||
extend: 'repository/type/component',
|
||||
// Inject our original respository that is used by this class
|
||||
// within the callable of the EventSource
|
||||
services: {
|
||||
content: `repository/${item}`,
|
||||
},
|
||||
};
|
||||
})
|
||||
)
|
||||
.concat([
|
||||
// These are the routes where we overwrite the 'default'
|
||||
// repo service. Default repos are repos that return a promise resolving to
|
||||
// an ember-data record or recordset
|
||||
{
|
||||
route: 'dc/nodes/index',
|
||||
services: {
|
||||
repo: 'repository/node/event-source',
|
||||
},
|
||||
},
|
||||
{
|
||||
route: 'dc/nodes/show',
|
||||
services: {
|
||||
repo: 'repository/node/event-source',
|
||||
coordinateRepo: 'repository/coordinate/event-source',
|
||||
sessionRepo: 'repository/session/event-source',
|
||||
},
|
||||
},
|
||||
{
|
||||
route: 'dc/services/index',
|
||||
services: {
|
||||
repo: 'repository/service/event-source',
|
||||
},
|
||||
},
|
||||
{
|
||||
route: 'dc/services/show',
|
||||
services: {
|
||||
repo: 'repository/service/event-source',
|
||||
},
|
||||
},
|
||||
{
|
||||
route: 'dc/services/instance',
|
||||
services: {
|
||||
repo: 'repository/service/event-source',
|
||||
proxyRepo: 'repository/proxy/event-source',
|
||||
},
|
||||
},
|
||||
{
|
||||
service: 'form',
|
||||
services: {
|
||||
role: 'repository/role/component',
|
||||
policy: 'repository/policy/component',
|
||||
},
|
||||
},
|
||||
])
|
||||
.forEach(function(definition) {
|
||||
if (typeof definition.extend !== 'undefined') {
|
||||
// Create the class instances that we need
|
||||
container.register(
|
||||
`service:${definition.service}`,
|
||||
container.resolveRegistration(`service:${definition.extend}`).extend({})
|
||||
);
|
||||
}
|
||||
Object.keys(definition.services).forEach(function(name) {
|
||||
const servicePath = definition.services[name];
|
||||
// inject its dependencies, this could probably detect the type
|
||||
// but hardcode this for the moment
|
||||
if (typeof definition.route !== 'undefined') {
|
||||
container.inject(`route:${definition.route}`, name, `service:${servicePath}`);
|
||||
} else {
|
||||
container.inject(`service:${definition.service}`, name, `service:${servicePath}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
@ -1,16 +1,19 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import { next } from '@ember/runloop';
|
||||
import { get } from '@ember/object';
|
||||
const isOutside = function(element, e) {
|
||||
|
||||
// TODO: Potentially move this to dom service
|
||||
const isOutside = function(element, e, doc = document) {
|
||||
if (element) {
|
||||
const isRemoved = !e.target || !document.contains(e.target);
|
||||
const isRemoved = !e.target || !doc.contains(e.target);
|
||||
const isInside = element === e.target || element.contains(e.target);
|
||||
return !isRemoved && !isInside;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handler = function(e) {
|
||||
const el = get(this, 'element');
|
||||
if (isOutside(el, e)) {
|
||||
@ -18,6 +21,7 @@ const handler = function(e) {
|
||||
}
|
||||
};
|
||||
export default Mixin.create({
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.handler = handler.bind(this);
|
||||
@ -26,12 +30,14 @@ export default Mixin.create({
|
||||
onblur: function() {},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
const doc = get(this, 'dom').document();
|
||||
next(this, () => {
|
||||
document.addEventListener('click', this.handler);
|
||||
doc.addEventListener('click', this.handler);
|
||||
});
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
document.removeEventListener('click', this.handler);
|
||||
const doc = get(this, 'dom').document();
|
||||
doc.removeEventListener('click', this.handler);
|
||||
},
|
||||
});
|
||||
|
70
ui-v2/app/mixins/policy/as-many.js
Normal file
70
ui-v2/app/mixins/policy/as-many.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { REQUEST_CREATE, REQUEST_UPDATE } from 'consul-ui/adapters/application';
|
||||
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import minimizeModel from 'consul-ui/utils/minimizeModel';
|
||||
|
||||
const normalizeServiceIdentities = function(items) {
|
||||
return (items || []).map(function(item) {
|
||||
const policy = {
|
||||
template: 'service-identity',
|
||||
Name: item.ServiceName,
|
||||
};
|
||||
if (typeof item.Datacenters !== 'undefined') {
|
||||
policy.Datacenters = item.Datacenters;
|
||||
}
|
||||
return policy;
|
||||
});
|
||||
};
|
||||
const normalizePolicies = function(items) {
|
||||
return (items || []).map(function(item) {
|
||||
return {
|
||||
template: '',
|
||||
...item,
|
||||
};
|
||||
});
|
||||
};
|
||||
const serializeServiceIdentities = function(items) {
|
||||
return items
|
||||
.filter(function(item) {
|
||||
return get(item, 'template') === 'service-identity';
|
||||
})
|
||||
.map(function(item) {
|
||||
const identity = {
|
||||
ServiceName: get(item, 'Name'),
|
||||
};
|
||||
if (get(item, 'Datacenters')) {
|
||||
identity.Datacenters = get(item, 'Datacenters');
|
||||
}
|
||||
return identity;
|
||||
});
|
||||
};
|
||||
const serializePolicies = function(items) {
|
||||
return items.filter(function(item) {
|
||||
return get(item, 'template') === '';
|
||||
});
|
||||
};
|
||||
|
||||
export default Mixin.create({
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
response.Policies = normalizePolicies(response.Policies).concat(
|
||||
normalizeServiceIdentities(response.ServiceIdentities)
|
||||
);
|
||||
return this._super(url, response, primary, slug);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const data = this._super(...arguments);
|
||||
const name = params.type.modelName;
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
// ServiceIdentities serialization must happen first, or a copy taken
|
||||
data[name].ServiceIdentities = serializeServiceIdentities(data[name].Policies);
|
||||
data[name].Policies = minimizeModel(serializePolicies(data[name].Policies));
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
28
ui-v2/app/mixins/role/as-many.js
Normal file
28
ui-v2/app/mixins/role/as-many.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { REQUEST_CREATE, REQUEST_UPDATE } from 'consul-ui/adapters/application';
|
||||
|
||||
import Mixin from '@ember/object/mixin';
|
||||
|
||||
import minimizeModel from 'consul-ui/utils/minimizeModel';
|
||||
|
||||
export default Mixin.create({
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
['Roles'].forEach(function(prop) {
|
||||
if (typeof response[prop] === 'undefined' || response[prop] === null) {
|
||||
response[prop] = [];
|
||||
}
|
||||
});
|
||||
return this._super(url, response, primary, slug);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const name = params.type.modelName;
|
||||
const data = this._super(...arguments);
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
data[name].Roles = minimizeModel(data[name].Roles);
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
4
ui-v2/app/mixins/role/with-actions.js
Normal file
4
ui-v2/app/mixins/role/with-actions.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
45
ui-v2/app/mixins/with-event-source.js
Normal file
45
ui-v2/app/mixins/with-event-source.js
Normal file
@ -0,0 +1,45 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { computed as catchable } from 'consul-ui/computed/catchable';
|
||||
import purify from 'consul-ui/utils/computed/purify';
|
||||
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
const PREFIX = '_';
|
||||
export default Mixin.create(WithListeners, {
|
||||
setProperties: function(model) {
|
||||
const _model = {};
|
||||
Object.keys(model).forEach(key => {
|
||||
// here (see comment below on deleting)
|
||||
if (typeof this[key] !== 'undefined' && this[key].isDescriptor) {
|
||||
_model[`${PREFIX}${key}`] = model[key];
|
||||
const meta = this.constructor.metaForProperty(key) || {};
|
||||
if (typeof meta.catch === 'function') {
|
||||
if (typeof _model[`${PREFIX}${key}`].addEventListener === 'function') {
|
||||
this.listen(_model[`_${key}`], 'error', meta.catch.bind(this));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_model[key] = model[key];
|
||||
}
|
||||
});
|
||||
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];
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
export const listen = purify(catchable, function(props) {
|
||||
return props.map(item => `${PREFIX}${item}`);
|
||||
});
|
@ -15,7 +15,7 @@ const toKeyValue = function(el) {
|
||||
};
|
||||
export default Mixin.create({
|
||||
filters: {},
|
||||
filtered: computed('items', 'filters', function() {
|
||||
filtered: computed('items.[]', 'filters', function() {
|
||||
const filters = get(this, 'filters');
|
||||
return get(this, 'items').filter(item => {
|
||||
return this.filter(item, filters);
|
||||
|
@ -1,25 +1,6 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import { computed, get } from '@ember/object';
|
||||
import ucfirst from 'consul-ui/utils/ucfirst';
|
||||
|
||||
const countStatus = function(items, status) {
|
||||
if (status === '') {
|
||||
return get(items, 'length');
|
||||
}
|
||||
const key = `Checks${ucfirst(status)}`;
|
||||
return items.reduce(function(prev, item, i, arr) {
|
||||
const num = get(item, key);
|
||||
return (
|
||||
prev +
|
||||
(typeof num !== 'undefined'
|
||||
? num
|
||||
: get(item, 'Checks').filter(function(item) {
|
||||
return item.Status === status;
|
||||
}).length) || 0
|
||||
);
|
||||
}, 0);
|
||||
};
|
||||
export default Mixin.create(WithFiltering, {
|
||||
queryParams: {
|
||||
status: {
|
||||
@ -29,22 +10,4 @@ export default Mixin.create(WithFiltering, {
|
||||
as: 'filter',
|
||||
},
|
||||
},
|
||||
healthFilters: computed('items', function() {
|
||||
const items = get(this, 'items');
|
||||
const objs = ['', 'passing', 'warning', 'critical'].map(function(item) {
|
||||
const count = countStatus(items, item);
|
||||
return {
|
||||
count: count,
|
||||
label: `${item === '' ? 'All' : ucfirst(item)} (${count.toLocaleString()})`,
|
||||
value: item,
|
||||
};
|
||||
});
|
||||
objs[0].label = `All (${objs
|
||||
.slice(1)
|
||||
.reduce(function(prev, item, i, arr) {
|
||||
return prev + item.count;
|
||||
}, 0)
|
||||
.toLocaleString()})`;
|
||||
return objs;
|
||||
}),
|
||||
});
|
||||
|
36
ui-v2/app/mixins/with-listeners.js
Normal file
36
ui-v2/app/mixins/with-listeners.js
Normal file
@ -0,0 +1,36 @@
|
||||
import Controller from '@ember/controller';
|
||||
import Component from '@ember/component';
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Mixin.create({
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = get(this, 'dom').listeners();
|
||||
let teardown = ['willDestroy'];
|
||||
if (this instanceof Component) {
|
||||
teardown = ['willDestroyElement'];
|
||||
} else if (this instanceof Controller) {
|
||||
if (typeof this.reset === 'function') {
|
||||
teardown.push('reset');
|
||||
}
|
||||
}
|
||||
teardown.forEach(method => {
|
||||
const destroy = this[method];
|
||||
this[method] = function() {
|
||||
if (typeof destroy === 'function') {
|
||||
destroy.apply(this, arguments);
|
||||
}
|
||||
this.removeListeners();
|
||||
};
|
||||
});
|
||||
},
|
||||
listen: function(target, event, handler) {
|
||||
return this._listeners.add(...arguments);
|
||||
},
|
||||
removeListeners: function() {
|
||||
return this._listeners.remove(...arguments);
|
||||
},
|
||||
});
|
@ -1,11 +1,12 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import { assert } from '@ember/debug';
|
||||
export default Mixin.create({
|
||||
dom: service('dom'),
|
||||
resize: function(e) {
|
||||
assert('with-resizing.resize needs to be overridden', false);
|
||||
},
|
||||
win: window,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.handler = e => {
|
||||
@ -17,14 +18,18 @@ export default Mixin.create({
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
get(this, 'win').addEventListener('resize', this.handler, false);
|
||||
get(this, 'dom')
|
||||
.viewport()
|
||||
.addEventListener('resize', this.handler, false);
|
||||
this.didAppear();
|
||||
},
|
||||
didAppear: function() {
|
||||
this.handler({ target: get(this, 'win') });
|
||||
this.handler({ target: get(this, 'dom').viewport() });
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
get(this, 'win').removeEventListener('resize', this.handler, false);
|
||||
get(this, 'dom')
|
||||
.viewport()
|
||||
.removeEventListener('resize', this.handler, false);
|
||||
this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
32
ui-v2/app/mixins/with-searching.js
Normal file
32
ui-v2/app/mixins/with-searching.js
Normal file
@ -0,0 +1,32 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
/**
|
||||
* WithSearching mostly depends on a `searchParams` object which must be set
|
||||
* inside the `init` function. The naming and usage of this is modelled on
|
||||
* `queryParams` but in contrast cannot _yet_ be 'hung' of the Controller
|
||||
* object, it MUST be set in the `init` method.
|
||||
* Reasons: As well as producing a eslint error, it can also be 'shared' amongst
|
||||
* child Classes of the component. It is not clear _yet_ whether mixing this in
|
||||
* avoids this and is something to be looked at in future to slightly improve DX
|
||||
* Please also see:
|
||||
* https://emberjs.com/api/ember/2.12/classes/Ember.Object/properties?anchor=mergedProperties
|
||||
*
|
||||
*/
|
||||
export default Mixin.create(WithListeners, {
|
||||
builder: service('search'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
const params = this.searchParams || {};
|
||||
this.searchables = {};
|
||||
Object.keys(params).forEach(type => {
|
||||
const key = params[type];
|
||||
this.searchables[type] = get(this, 'builder').searchable(type);
|
||||
this.listen(this.searchables[type], 'change', e => {
|
||||
const value = e.target.value;
|
||||
set(this, key, value === '' ? null : value);
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
@ -21,6 +21,7 @@ export default Model.extend({
|
||||
Datacenter: attr('string'),
|
||||
Segment: attr(),
|
||||
Coord: attr(),
|
||||
meta: attr(),
|
||||
hasStatus: function(status) {
|
||||
return hasStatus(get(this, 'Checks'), status);
|
||||
},
|
||||
|
@ -24,6 +24,10 @@ const model = Model.extend({
|
||||
Datacenters: attr(),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
|
||||
template: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
});
|
||||
export const ATTRS = writable(model, ['Name', 'Description', 'Rules', 'Datacenters']);
|
||||
export default model;
|
||||
|
12
ui-v2/app/models/proxy.js
Normal file
12
ui-v2/app/models/proxy.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
ServiceName: attr('string'),
|
||||
ServiceID: attr('string'),
|
||||
ServiceProxy: attr(),
|
||||
});
|
34
ui-v2/app/models/role.js
Normal file
34
ui-v2/app/models/role.js
Normal file
@ -0,0 +1,34 @@
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
Name: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
Description: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
Policies: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
ServiceIdentities: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
// frontend only for ordering where CreateIndex can't be used
|
||||
CreateTime: attr('date'),
|
||||
//
|
||||
Datacenter: attr('string'),
|
||||
// TODO: Figure out whether we need this or not
|
||||
Datacenters: attr(),
|
||||
Hash: attr('string'),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
@ -30,6 +30,7 @@ export default Model.extend({
|
||||
Node: attr(),
|
||||
Service: attr(),
|
||||
Checks: attr(),
|
||||
meta: attr(),
|
||||
passing: computed('ChecksPassing', 'Checks', function() {
|
||||
let num = 0;
|
||||
// TODO: use typeof
|
||||
|
@ -8,6 +8,7 @@ export const SLUG_KEY = 'AccessorID';
|
||||
const model = Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
IDPName: attr('string'),
|
||||
SecretID: attr('string'),
|
||||
// Legacy
|
||||
Type: attr('string'),
|
||||
@ -27,7 +28,18 @@ const model = Model.extend({
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
Roles: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
ServiceIdentities: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
CreateTime: attr('date'),
|
||||
Hash: attr('string'),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
||||
@ -39,6 +51,7 @@ export const ATTRS = writable(model, [
|
||||
'Local',
|
||||
'Description',
|
||||
'Policies',
|
||||
'Roles',
|
||||
// SecretID isn't writable but we need it to identify an
|
||||
// update via the old API, see TokenAdapter dataForRequest
|
||||
'SecretID',
|
||||
|
@ -18,6 +18,9 @@ export const routes = {
|
||||
show: {
|
||||
_options: { path: '/:name' },
|
||||
},
|
||||
instance: {
|
||||
_options: { path: '/:name/:id' },
|
||||
},
|
||||
},
|
||||
// Nodes represent a consul node
|
||||
nodes: {
|
||||
@ -71,6 +74,15 @@ export const routes = {
|
||||
_options: { path: '/create' },
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
_options: { path: '/roles' },
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: { path: '/create' },
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
_options: { path: '/tokens' },
|
||||
edit: {
|
||||
@ -88,9 +100,9 @@ export const routes = {
|
||||
_options: { path: '/' },
|
||||
},
|
||||
// The settings page is global.
|
||||
// settings: {
|
||||
// _options: { path: '/setting' },
|
||||
// },
|
||||
settings: {
|
||||
_options: { path: '/setting' },
|
||||
},
|
||||
notfound: {
|
||||
_options: { path: '/*path' },
|
||||
},
|
||||
|
@ -4,12 +4,14 @@ import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import { next } from '@ember/runloop';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
const $html = document.documentElement;
|
||||
const removeLoading = function() {
|
||||
return $html.classList.remove('ember-loading');
|
||||
|
||||
const removeLoading = function($from) {
|
||||
return $from.classList.remove('ember-loading');
|
||||
};
|
||||
export default Route.extend(WithBlockingActions, {
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
@ -17,20 +19,21 @@ export default Route.extend(WithBlockingActions, {
|
||||
settings: service('settings'),
|
||||
actions: {
|
||||
loading: function(transition, originRoute) {
|
||||
const $root = get(this, 'dom').root();
|
||||
let dc = null;
|
||||
if (originRoute.routeName !== 'dc') {
|
||||
const model = this.modelFor('dc') || { dcs: null, dc: { Name: null } };
|
||||
dc = get(this, 'repo').getActive(model.dc.Name, model.dcs);
|
||||
}
|
||||
hash({
|
||||
loading: !$html.classList.contains('ember-loading'),
|
||||
loading: !$root.classList.contains('ember-loading'),
|
||||
dc: dc,
|
||||
}).then(model => {
|
||||
next(() => {
|
||||
const controller = this.controllerFor('application');
|
||||
controller.setProperties(model);
|
||||
transition.promise.finally(function() {
|
||||
removeLoading();
|
||||
removeLoading($root);
|
||||
controller.setProperties({
|
||||
loading: false,
|
||||
dc: model.dc,
|
||||
@ -74,6 +77,7 @@ export default Route.extend(WithBlockingActions, {
|
||||
if (error.status === '') {
|
||||
error.message = 'Error';
|
||||
}
|
||||
const $root = get(this, 'dom').root();
|
||||
hash({
|
||||
error: error,
|
||||
dc:
|
||||
@ -85,13 +89,13 @@ export default Route.extend(WithBlockingActions, {
|
||||
dcs: model && model.dcs ? model.dcs : [],
|
||||
})
|
||||
.then(model => {
|
||||
removeLoading();
|
||||
removeLoading($root);
|
||||
next(() => {
|
||||
this.controllerFor('error').setProperties(model);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
removeLoading();
|
||||
removeLoading($root);
|
||||
next(() => {
|
||||
this.controllerFor('error').setProperties({ error: error });
|
||||
});
|
||||
|
@ -19,7 +19,6 @@ export default Route.extend({
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
||||
|
@ -22,7 +22,6 @@ export default Route.extend(WithAclActions, {
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
deactivate: function() {
|
||||
|
@ -16,7 +16,6 @@ export default Route.extend(WithAclActions, {
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
||||
|
@ -36,7 +36,6 @@ export default Route.extend(WithAclActions, {
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
|
||||
export default SingleRoute.extend(WithPolicyActions, {
|
||||
repo: service('repository/policy'),
|
||||
tokenRepo: service('repository/token'),
|
||||
datacenterRepo: service('repository/dc'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const tokenRepo = get(this, 'tokenRepo');
|
||||
@ -16,7 +15,6 @@ export default SingleRoute.extend(WithPolicyActions, {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
datacenters: get(this, 'datacenterRepo').findAll(),
|
||||
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
@ -31,7 +29,6 @@ export default SingleRoute.extend(WithPolicyActions, {
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
||||
|
@ -23,7 +23,6 @@ export default Route.extend(WithPolicyActions, {
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
||||
|
6
ui-v2/app/routes/dc/acls/roles/create.js
Normal file
6
ui-v2/app/routes/dc/acls/roles/create.js
Normal file
@ -0,0 +1,6 @@
|
||||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default Route.extend(CreatingRoute, {
|
||||
templateName: 'dc/acls/roles/edit',
|
||||
});
|
34
ui-v2/app/routes/dc/acls/roles/edit.js
Normal file
34
ui-v2/app/routes/dc/acls/roles/edit.js
Normal file
@ -0,0 +1,34 @@
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
|
||||
export default SingleRoute.extend(WithRoleActions, {
|
||||
repo: service('repository/role'),
|
||||
tokenRepo: service('repository/token'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const tokenRepo = get(this, 'tokenRepo');
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: tokenRepo.findByRole(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
28
ui-v2/app/routes/dc/acls/roles/index.js
Normal file
28
ui-v2/app/routes/dc/acls/roles/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
|
||||
export default Route.extend(WithRoleActions, {
|
||||
repo: service('repository/role'),
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = get(this, 'repo');
|
||||
return hash({
|
||||
...repo.status({
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
}),
|
||||
isLoading: false,
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
@ -1,105 +1,24 @@
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { set, get } from '@ember/object';
|
||||
import updateArrayObject from 'consul-ui/utils/update-array-object';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
|
||||
|
||||
const ERROR_PARSE_RULES = 'Failed to parse ACL rules';
|
||||
const ERROR_NAME_EXISTS = 'Invalid Policy: A Policy with Name';
|
||||
export default SingleRoute.extend(WithTokenActions, {
|
||||
repo: service('repository/token'),
|
||||
policyRepo: service('repository/policy'),
|
||||
datacenterRepo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
model: function(params, transition) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const policyRepo = get(this, 'policyRepo');
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
// TODO: I only need these to create a new policy
|
||||
datacenters: get(this, 'datacenterRepo').findAll(),
|
||||
policy: this.getEmptyPolicy(),
|
||||
token: get(this, 'settings').findBySlug('token'),
|
||||
items: policyRepo.findAllByDatacenter(dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
getEmptyPolicy: function() {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
return get(this, 'policyRepo').create({ Datacenter: dc });
|
||||
},
|
||||
actions: {
|
||||
// TODO: Some of this could potentially be moved to the repo services
|
||||
loadPolicy: function(item, items) {
|
||||
const repo = get(this, 'policyRepo');
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const slug = get(item, repo.getSlugKey());
|
||||
repo.findBySlug(slug, dc).then(item => {
|
||||
updateArrayObject(items, item, repo.getSlugKey());
|
||||
});
|
||||
},
|
||||
remove: function(item, items) {
|
||||
return items.removeObject(item);
|
||||
},
|
||||
clearPolicy: function() {
|
||||
// TODO: I should be able to reset the ember-data object
|
||||
// back to it original state?
|
||||
// possibly Forms could know how to create
|
||||
const controller = get(this, 'controller');
|
||||
controller.setProperties({
|
||||
policy: this.getEmptyPolicy(),
|
||||
});
|
||||
},
|
||||
createPolicy: function(item, policies, success) {
|
||||
get(this, 'policyRepo')
|
||||
.persist(item)
|
||||
.then(item => {
|
||||
set(item, 'CreateTime', new Date().getTime());
|
||||
policies.pushObject(item);
|
||||
return item;
|
||||
})
|
||||
.then(function() {
|
||||
success();
|
||||
})
|
||||
.catch(err => {
|
||||
if (typeof err.errors !== 'undefined') {
|
||||
const error = err.errors[0];
|
||||
let prop;
|
||||
let message = error.detail;
|
||||
switch (true) {
|
||||
case message.indexOf(ERROR_PARSE_RULES) === 0:
|
||||
prop = 'Rules';
|
||||
message = error.detail;
|
||||
break;
|
||||
case message.indexOf(ERROR_NAME_EXISTS) === 0:
|
||||
prop = 'Name';
|
||||
message = message.substr(ERROR_NAME_EXISTS.indexOf(':') + 1);
|
||||
break;
|
||||
}
|
||||
if (prop) {
|
||||
item.addError(prop, message);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user