mirror of https://github.com/hashicorp/consul
UI: ACL Roles (#5635)
Adds support for ACL Roles and Service Identities CRUD, along with necessary changes to Tokens, and the CSS improvements required. Also includes refinements/improvements for easier testing of deeply nested components. 1. ember-data adapter/serializer/model triplet for Roles 2. repository, form/validations and searching filter for Roles 3. Moves potentially, repeated, or soon to to repeated functionality into a mixin (mainly for 'many policy' relationships) 4. A few styling tweaks for little edge cases around roles 5. Router additions, Route, Controller and templates for Roles Also see: * UI: ACL Roles cont. plus Service Identities (#5661 and #5720)pull/5729/head
parent
eeb7a858e2
commit
482426b13e
|
@ -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 { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||||
import { PUT as HTTP_PUT } from 'consul-ui/utils/http/method';
|
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';
|
import { get } from '@ember/object';
|
||||||
|
|
||||||
const REQUEST_CLONE = 'cloneRecord';
|
const REQUEST_CLONE = 'cloneRecord';
|
||||||
const REQUEST_SELF = 'querySelf';
|
const REQUEST_SELF = 'querySelf';
|
||||||
|
|
||||||
export default Adapter.extend({
|
export default Adapter.extend(WithRoles, WithPolicies, {
|
||||||
store: service('store'),
|
store: service('store'),
|
||||||
cleanQuery: function(_query) {
|
cleanQuery: function(_query) {
|
||||||
const query = this._super(...arguments);
|
const query = this._super(...arguments);
|
||||||
|
@ -108,10 +111,6 @@ export default Adapter.extend({
|
||||||
return this._makeRequest(request);
|
return this._makeRequest(request);
|
||||||
},
|
},
|
||||||
handleSingleResponse: function(url, response, primary, slug) {
|
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
|
// Convert an old style update response to a new style
|
||||||
if (typeof response['ID'] !== 'undefined') {
|
if (typeof response['ID'] !== 'undefined') {
|
||||||
const item = get(this, 'store')
|
const item = get(this, 'store')
|
||||||
|
@ -169,19 +168,6 @@ export default Adapter.extend({
|
||||||
}
|
}
|
||||||
// falls through
|
// falls through
|
||||||
case REQUEST_CREATE:
|
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;
|
data = data.token;
|
||||||
break;
|
break;
|
||||||
case REQUEST_SELF:
|
case REQUEST_SELF:
|
||||||
|
|
|
@ -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({
|
export default Component.extend({
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
|
dom: service('dom'),
|
||||||
helper: service('code-mirror/linter'),
|
helper: service('code-mirror/linter'),
|
||||||
classNames: ['code-editor'],
|
classNames: ['code-editor'],
|
||||||
|
readonly: false,
|
||||||
syntax: '',
|
syntax: '',
|
||||||
onchange: function(value) {
|
// TODO: Change this to oninput to be consistent? We'll have to do it throughout the templates
|
||||||
get(this, 'settings').persist({
|
|
||||||
'code-editor': value,
|
|
||||||
});
|
|
||||||
this.setMode(value);
|
|
||||||
},
|
|
||||||
onkeyup: function() {},
|
onkeyup: function() {},
|
||||||
|
oninput: function() {},
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
set(this, 'modes', get(this, 'helper').modes());
|
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) {
|
setMode: function(mode) {
|
||||||
set(this, 'options', {
|
set(this, 'options', {
|
||||||
...DEFAULTS,
|
...DEFAULTS,
|
||||||
mode: mode.mime,
|
mode: mode.mime,
|
||||||
|
readOnly: get(this, 'readonly'),
|
||||||
});
|
});
|
||||||
const editor = get(this, 'editor');
|
const editor = get(this, 'editor');
|
||||||
editor.setOption('mode', mode.mime);
|
editor.setOption('mode', mode.mime);
|
||||||
get(this, 'helper').lint(editor, mode.mode);
|
get(this, 'helper').lint(editor, mode.mode);
|
||||||
set(this, 'mode', mode);
|
set(this, 'mode', mode);
|
||||||
},
|
},
|
||||||
|
willDestroyElement: function() {
|
||||||
|
this._super(...arguments);
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
didInsertElement: function() {
|
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));
|
set(this, 'editor', get(this, 'helper').getEditor(this.element));
|
||||||
get(this, 'settings')
|
get(this, 'settings')
|
||||||
.findBySlug('code-editor')
|
.findBySlug('code-editor')
|
||||||
|
@ -54,4 +80,12 @@ export default Component.extend({
|
||||||
didAppear: function() {
|
didAppear: function() {
|
||||||
get(this, 'editor').refresh();
|
get(this, 'editor').refresh();
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
change: function(value) {
|
||||||
|
get(this, 'settings').persist({
|
||||||
|
'code-editor': value,
|
||||||
|
});
|
||||||
|
this.setMode(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -38,10 +38,12 @@ export default DomBufferComponent.extend(SlotsMixin, WithResizing, {
|
||||||
_close: function(e) {
|
_close: function(e) {
|
||||||
set(this, 'checked', false);
|
set(this, 'checked', false);
|
||||||
const dialogPanel = get(this, 'dialog');
|
const dialogPanel = get(this, 'dialog');
|
||||||
|
if (dialogPanel) {
|
||||||
const overflowing = get(this, 'overflowingClass');
|
const overflowing = get(this, 'overflowingClass');
|
||||||
if (dialogPanel.classList.contains(overflowing)) {
|
if (dialogPanel.classList.contains(overflowing)) {
|
||||||
dialogPanel.classList.remove(overflowing);
|
dialogPanel.classList.remove(overflowing);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO: should we make a didDisappear?
|
// TODO: should we make a didDisappear?
|
||||||
get(this, 'dom')
|
get(this, 'dom')
|
||||||
.root()
|
.root()
|
||||||
|
@ -108,7 +110,7 @@ export default DomBufferComponent.extend(SlotsMixin, WithResizing, {
|
||||||
if (get(e, 'target.checked')) {
|
if (get(e, 'target.checked')) {
|
||||||
this._open(e);
|
this._open(e);
|
||||||
} else {
|
} else {
|
||||||
this._close();
|
this._close(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close: function() {
|
close: function() {
|
||||||
|
|
|
@ -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') });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
import FormComponent from './form-component';
|
||||||
|
export default FormComponent.extend({
|
||||||
|
type: 'role',
|
||||||
|
name: 'role',
|
||||||
|
classNames: ['role-form'],
|
||||||
|
});
|
|
@ -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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
});
|
|
@ -80,13 +80,16 @@ const change = function(e) {
|
||||||
// therefore we don't need to calculate
|
// therefore we don't need to calculate
|
||||||
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
|
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
|
||||||
const dom = get(this, 'dom');
|
const dom = get(this, 'dom');
|
||||||
|
|
||||||
const $tr = dom.closest('tr', e.currentTarget);
|
const $tr = dom.closest('tr', e.currentTarget);
|
||||||
const $group = dom.sibling(e.currentTarget, 'ul');
|
const $group = dom.sibling(e.currentTarget, 'ul');
|
||||||
const $footer = dom.element('footer[role="contentinfo"]');
|
|
||||||
const groupRect = $group.getBoundingClientRect();
|
const groupRect = $group.getBoundingClientRect();
|
||||||
const footerRect = $footer.getBoundingClientRect();
|
|
||||||
const groupBottom = groupRect.top + $group.clientHeight;
|
const groupBottom = groupRect.top + $group.clientHeight;
|
||||||
|
|
||||||
|
const $footer = dom.element('footer[role="contentinfo"]');
|
||||||
|
const footerRect = $footer.getBoundingClientRect();
|
||||||
const footerTop = footerRect.top;
|
const footerTop = footerRect.top;
|
||||||
|
|
||||||
if (groupBottom > footerTop) {
|
if (groupBottom > footerTop) {
|
||||||
$group.classList.add('above');
|
$group.classList.add('above');
|
||||||
} else {
|
} else {
|
||||||
|
@ -111,6 +114,7 @@ const change = function(e) {
|
||||||
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
||||||
tagName: 'table',
|
tagName: 'table',
|
||||||
classNames: ['dom-recycling'],
|
classNames: ['dom-recycling'],
|
||||||
|
classNameBindings: ['hasActions'],
|
||||||
attributeBindings: ['style'],
|
attributeBindings: ['style'],
|
||||||
width: 1150,
|
width: 1150,
|
||||||
rowHeight: 50,
|
rowHeight: 50,
|
||||||
|
@ -128,13 +132,14 @@ export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
||||||
},
|
},
|
||||||
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
|
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
|
||||||
const maxRows = get(this, 'rows');
|
const maxRows = get(this, 'rows');
|
||||||
let rows = get(this._items || [], 'length');
|
let height = get(this, 'maxHeight');
|
||||||
if (maxRows) {
|
if (maxRows) {
|
||||||
|
let rows = Math.max(3, get(this._items || [], 'length'));
|
||||||
rows = Math.min(maxRows, rows);
|
rows = Math.min(maxRows, rows);
|
||||||
|
height = get(this, 'rowHeight') * rows + 29;
|
||||||
}
|
}
|
||||||
const height = get(this, 'rowHeight') * rows + 29;
|
|
||||||
return {
|
return {
|
||||||
height: Math.min(get(this, 'maxHeight'), height),
|
height: height,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
resize: function(e) {
|
resize: function(e) {
|
||||||
|
|
|
@ -19,8 +19,8 @@ export default Component.extend(SlotsMixin, {
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
get(this, 'dom').clickFirstAnchor(e);
|
get(this, 'dom').clickFirstAnchor(e);
|
||||||
},
|
},
|
||||||
change: function(item, e) {
|
change: function(item, items, e) {
|
||||||
this.onchange(e, item);
|
this.onchange(e, item, items);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { get, set } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
builder: service('form'),
|
builder: service('form'),
|
||||||
dom: service('dom'),
|
|
||||||
isScoped: false,
|
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.form = get(this, 'builder').form('policy');
|
this.form = get(this, 'builder').form('policy');
|
||||||
|
@ -21,25 +19,5 @@ export default Controller.extend({
|
||||||
return prev;
|
return prev;
|
||||||
}, model)
|
}, model)
|
||||||
);
|
);
|
||||||
set(this, 'isScoped', get(model.item, 'Datacenters.length') > 0);
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
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;
|
|
||||||
switch (target.name) {
|
|
||||||
case 'policy[isScoped]':
|
|
||||||
set(this, 'isScoped', !get(this, 'isScoped'));
|
|
||||||
set(this.item, 'Datacenters', null);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
import Controller from './edit';
|
||||||
|
export default Controller.extend();
|
|
@ -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)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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 Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { get, set } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
builder: service('form'),
|
builder: service('form'),
|
||||||
|
@ -17,33 +17,12 @@ export default Controller.extend({
|
||||||
case 'item':
|
case 'item':
|
||||||
prev[key] = this.form.setData(prev[key]).getData();
|
prev[key] = this.form.setData(prev[key]).getData();
|
||||||
break;
|
break;
|
||||||
case 'policy':
|
|
||||||
prev[key] = this.form
|
|
||||||
.form(key)
|
|
||||||
.setData(prev[key])
|
|
||||||
.getData();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}, model)
|
}, model)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
actions: {
|
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) {
|
change: function(e, value, item) {
|
||||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||||
const form = get(this, 'form');
|
const form = get(this, 'form');
|
||||||
|
@ -52,24 +31,6 @@ export default Controller.extend({
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
switch (target.name) {
|
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:
|
default:
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import validations from 'consul-ui/validations/acl';
|
import validations from 'consul-ui/validations/acl';
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
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);
|
return form(name, {}).setValidators(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import validations from 'consul-ui/validations/intention';
|
import validations from 'consul-ui/validations/intention';
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
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);
|
return form(name, {}).setValidators(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import validations from 'consul-ui/validations/kv';
|
import validations from 'consul-ui/validations/kv';
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
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);
|
return form(name, {}).setValidators(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import validations from 'consul-ui/validations/policy';
|
import validations from 'consul-ui/validations/policy';
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
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, {
|
return form(name, {
|
||||||
Datacenters: {
|
Datacenters: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -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 validations from 'consul-ui/validations/token';
|
import validations from 'consul-ui/validations/token';
|
||||||
import policy from 'consul-ui/forms/policy';
|
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
const builder = builderFactory();
|
||||||
export default function(name = '', v = validations, form = builder) {
|
export default function(container, name = '', v = validations, form = builder) {
|
||||||
return form(name, {})
|
return form(name, {})
|
||||||
.setValidators(v)
|
.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);
|
|
|
@ -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);
|
|
@ -1,21 +1,40 @@
|
||||||
|
import { get, set } from '@ember/object';
|
||||||
|
|
||||||
import kv from 'consul-ui/forms/kv';
|
import kv from 'consul-ui/forms/kv';
|
||||||
import acl from 'consul-ui/forms/acl';
|
import acl from 'consul-ui/forms/acl';
|
||||||
import token from 'consul-ui/forms/token';
|
import token from 'consul-ui/forms/token';
|
||||||
import policy from 'consul-ui/forms/policy';
|
import policy from 'consul-ui/forms/policy';
|
||||||
|
import role from 'consul-ui/forms/role';
|
||||||
import intention from 'consul-ui/forms/intention';
|
import intention from 'consul-ui/forms/intention';
|
||||||
|
|
||||||
export function initialize(application) {
|
export function initialize(application) {
|
||||||
// Service-less injection using private properties at a per-project level
|
// Service-less injection using private properties at a per-project level
|
||||||
const FormBuilder = application.resolveRegistration('service:form');
|
const FormBuilder = application.resolveRegistration('service:form');
|
||||||
const forms = {
|
const forms = {
|
||||||
kv: kv(),
|
kv: kv,
|
||||||
acl: acl(),
|
acl: acl,
|
||||||
token: token(),
|
token: token,
|
||||||
policy: policy(),
|
policy: policy,
|
||||||
intention: intention(),
|
role: role,
|
||||||
|
intention: intention,
|
||||||
};
|
};
|
||||||
FormBuilder.reopen({
|
FormBuilder.reopen({
|
||||||
form: function(name) {
|
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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import intention from 'consul-ui/search/filters/intention';
|
import intention from 'consul-ui/search/filters/intention';
|
||||||
import token from 'consul-ui/search/filters/token';
|
import token from 'consul-ui/search/filters/token';
|
||||||
import policy from 'consul-ui/search/filters/policy';
|
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 kv from 'consul-ui/search/filters/kv';
|
||||||
import acl from 'consul-ui/search/filters/acl';
|
import acl from 'consul-ui/search/filters/acl';
|
||||||
import node from 'consul-ui/search/filters/node';
|
import node from 'consul-ui/search/filters/node';
|
||||||
|
@ -19,6 +20,7 @@ export function initialize(application) {
|
||||||
token: token(filterable),
|
token: token(filterable),
|
||||||
acl: acl(filterable),
|
acl: acl(filterable),
|
||||||
policy: policy(filterable),
|
policy: policy(filterable),
|
||||||
|
role: role(filterable),
|
||||||
kv: kv(filterable),
|
kv: kv(filterable),
|
||||||
healthyNode: node(filterable),
|
healthyNode: node(filterable),
|
||||||
unhealthyNode: node(filterable),
|
unhealthyNode: node(filterable),
|
||||||
|
|
|
@ -17,6 +17,20 @@ export function initialize(container) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.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([
|
.concat([
|
||||||
// These are the routes where we overwrite the 'default'
|
// These are the routes where we overwrite the 'default'
|
||||||
// repo service. Default repos are repos that return a promise resolving to
|
// repo service. Default repos are repos that return a promise resolving to
|
||||||
|
@ -54,6 +68,13 @@ export function initialize(container) {
|
||||||
proxyRepo: 'repository/proxy/event-source',
|
proxyRepo: 'repository/proxy/event-source',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
service: 'form',
|
||||||
|
services: {
|
||||||
|
role: 'repository/role/component',
|
||||||
|
policy: 'repository/policy/component',
|
||||||
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.forEach(function(definition) {
|
.forEach(function(definition) {
|
||||||
if (typeof definition.extend !== 'undefined') {
|
if (typeof definition.extend !== 'undefined') {
|
||||||
|
|
|
@ -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;
|
||||||
|
},
|
||||||
|
});
|
|
@ -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;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Mixin from '@ember/object/mixin';
|
||||||
|
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||||
|
|
||||||
|
export default Mixin.create(WithBlockingActions, {});
|
|
@ -24,6 +24,10 @@ const model = Model.extend({
|
||||||
Datacenters: attr(),
|
Datacenters: attr(),
|
||||||
CreateIndex: attr('number'),
|
CreateIndex: attr('number'),
|
||||||
ModifyIndex: attr('number'),
|
ModifyIndex: attr('number'),
|
||||||
|
|
||||||
|
template: attr('string', {
|
||||||
|
defaultValue: '',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
export const ATTRS = writable(model, ['Name', 'Description', 'Rules', 'Datacenters']);
|
export const ATTRS = writable(model, ['Name', 'Description', 'Rules', 'Datacenters']);
|
||||||
export default model;
|
export default model;
|
||||||
|
|
|
@ -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'),
|
||||||
|
});
|
|
@ -8,6 +8,7 @@ export const SLUG_KEY = 'AccessorID';
|
||||||
const model = Model.extend({
|
const model = Model.extend({
|
||||||
[PRIMARY_KEY]: attr('string'),
|
[PRIMARY_KEY]: attr('string'),
|
||||||
[SLUG_KEY]: attr('string'),
|
[SLUG_KEY]: attr('string'),
|
||||||
|
IDPName: attr('string'),
|
||||||
SecretID: attr('string'),
|
SecretID: attr('string'),
|
||||||
// Legacy
|
// Legacy
|
||||||
Type: attr('string'),
|
Type: attr('string'),
|
||||||
|
@ -27,7 +28,18 @@ const model = Model.extend({
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Roles: attr({
|
||||||
|
defaultValue: function() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ServiceIdentities: attr({
|
||||||
|
defaultValue: function() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
}),
|
||||||
CreateTime: attr('date'),
|
CreateTime: attr('date'),
|
||||||
|
Hash: attr('string'),
|
||||||
CreateIndex: attr('number'),
|
CreateIndex: attr('number'),
|
||||||
ModifyIndex: attr('number'),
|
ModifyIndex: attr('number'),
|
||||||
});
|
});
|
||||||
|
@ -39,6 +51,7 @@ export const ATTRS = writable(model, [
|
||||||
'Local',
|
'Local',
|
||||||
'Description',
|
'Description',
|
||||||
'Policies',
|
'Policies',
|
||||||
|
'Roles',
|
||||||
// SecretID isn't writable but we need it to identify an
|
// SecretID isn't writable but we need it to identify an
|
||||||
// update via the old API, see TokenAdapter dataForRequest
|
// update via the old API, see TokenAdapter dataForRequest
|
||||||
'SecretID',
|
'SecretID',
|
||||||
|
|
|
@ -74,6 +74,15 @@ export const routes = {
|
||||||
_options: { path: '/create' },
|
_options: { path: '/create' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
roles: {
|
||||||
|
_options: { path: '/roles' },
|
||||||
|
edit: {
|
||||||
|
_options: { path: '/:id' },
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
_options: { path: '/create' },
|
||||||
|
},
|
||||||
|
},
|
||||||
tokens: {
|
tokens: {
|
||||||
_options: { path: '/tokens' },
|
_options: { path: '/tokens' },
|
||||||
edit: {
|
edit: {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
|
||||||
export default SingleRoute.extend(WithPolicyActions, {
|
export default SingleRoute.extend(WithPolicyActions, {
|
||||||
repo: service('repository/policy'),
|
repo: service('repository/policy'),
|
||||||
tokenRepo: service('repository/token'),
|
tokenRepo: service('repository/token'),
|
||||||
datacenterRepo: service('repository/dc'),
|
|
||||||
model: function(params) {
|
model: function(params) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
const tokenRepo = get(this, 'tokenRepo');
|
const tokenRepo = get(this, 'tokenRepo');
|
||||||
|
@ -16,7 +15,6 @@ export default SingleRoute.extend(WithPolicyActions, {
|
||||||
return hash({
|
return hash({
|
||||||
...model,
|
...model,
|
||||||
...{
|
...{
|
||||||
datacenters: get(this, 'datacenterRepo').findAll(),
|
|
||||||
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) {
|
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) {
|
||||||
switch (get(e, 'errors.firstObject.status')) {
|
switch (get(e, 'errors.firstObject.status')) {
|
||||||
case '403':
|
case '403':
|
||||||
|
|
|
@ -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',
|
||||||
|
});
|
|
@ -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);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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,38 +1,19 @@
|
||||||
import SingleRoute from 'consul-ui/routing/single';
|
import SingleRoute from 'consul-ui/routing/single';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { hash } from 'rsvp';
|
import { hash } from 'rsvp';
|
||||||
import { set, get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
import updateArrayObject from 'consul-ui/utils/update-array-object';
|
|
||||||
|
|
||||||
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
|
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, {
|
export default SingleRoute.extend(WithTokenActions, {
|
||||||
repo: service('repository/token'),
|
repo: service('repository/token'),
|
||||||
policyRepo: service('repository/policy'),
|
|
||||||
datacenterRepo: service('repository/dc'),
|
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
model: function(params, transition) {
|
model: function(params, transition) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
|
||||||
const policyRepo = get(this, 'policyRepo');
|
|
||||||
return this._super(...arguments).then(model => {
|
return this._super(...arguments).then(model => {
|
||||||
return hash({
|
return hash({
|
||||||
...model,
|
...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'),
|
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;
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,65 +21,4 @@ export default SingleRoute.extend(WithTokenActions, {
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
controller.setProperties(model);
|
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,7 @@ export default Route.extend({
|
||||||
const create = this.isCreate(...arguments);
|
const create = this.isCreate(...arguments);
|
||||||
return hash({
|
return hash({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
dc: dc,
|
||||||
create: create,
|
create: create,
|
||||||
...repo.status({
|
...repo.status({
|
||||||
item: create
|
item: create
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { get } from '@ember/object';
|
||||||
|
export default function(filterable) {
|
||||||
|
return filterable(function(item, { s = '' }) {
|
||||||
|
const sLower = s.toLowerCase();
|
||||||
|
return (
|
||||||
|
get(item, 'Name')
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(sLower) !== -1 ||
|
||||||
|
get(item, 'Description')
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(sLower) !== -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Serializer from './application';
|
||||||
|
import { PRIMARY_KEY } from 'consul-ui/models/role';
|
||||||
|
export default Serializer.extend({
|
||||||
|
primaryKey: PRIMARY_KEY,
|
||||||
|
});
|
|
@ -1,10 +1,21 @@
|
||||||
import Service from '@ember/service';
|
import Service, { inject as service } from '@ember/service';
|
||||||
import builderFactory from 'consul-ui/utils/form/builder';
|
import builderFactory from 'consul-ui/utils/form/builder';
|
||||||
const builder = builderFactory();
|
const builder = builderFactory();
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
// a `get` method is added via the form initializer
|
// a `get` method is added via the form initializer
|
||||||
// see initializers/form.js
|
// see initializers/form.js
|
||||||
|
|
||||||
|
// TODO: Temporarily add these here until something else needs
|
||||||
|
// dynamic repos
|
||||||
|
role: service('repository/role'),
|
||||||
|
policy: service('repository/policy'),
|
||||||
|
//
|
||||||
|
init: function() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.forms = [];
|
||||||
|
},
|
||||||
build: function(obj, name) {
|
build: function(obj, name) {
|
||||||
return builder(...arguments);
|
return builder(...arguments);
|
||||||
},
|
},
|
||||||
|
form: function() {},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,9 +12,14 @@ export default Service.extend({
|
||||||
if (typeof content[prop] === 'function') {
|
if (typeof content[prop] === 'function') {
|
||||||
if (this.shouldProxy(content, prop)) {
|
if (this.shouldProxy(content, prop)) {
|
||||||
this[prop] = function() {
|
this[prop] = function() {
|
||||||
return this.execute(content, prop).then(method => {
|
const cb = this.execute(content, prop);
|
||||||
|
if (typeof cb.then !== 'undefined') {
|
||||||
|
return cb.then(method => {
|
||||||
return method.apply(this, arguments);
|
return method.apply(this, arguments);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return cb.apply(this, arguments);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else if (typeof this[prop] !== 'function') {
|
} else if (typeof this[prop] !== 'function') {
|
||||||
this[prop] = function() {
|
this[prop] = function() {
|
||||||
|
|
|
@ -22,6 +22,16 @@ export default RepositoryService.extend({
|
||||||
status: function(obj) {
|
status: function(obj) {
|
||||||
return status(obj);
|
return status(obj);
|
||||||
},
|
},
|
||||||
|
persist: function(item) {
|
||||||
|
// only if a policy doesn't have a template, save it
|
||||||
|
// right now only ServiceIdentities have templates and
|
||||||
|
// are not saveable themselves (but can be saved to a Role/Token)
|
||||||
|
switch (get(item, 'template')) {
|
||||||
|
case '':
|
||||||
|
return item.save();
|
||||||
|
}
|
||||||
|
return Promise.resolve(item);
|
||||||
|
},
|
||||||
translate: function(item) {
|
translate: function(item) {
|
||||||
return get(this, 'store').translate('policy', get(item, 'Rules'));
|
return get(this, 'store').translate('policy', get(item, 'Rules'));
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
|
import { Promise } from 'rsvp';
|
||||||
|
import statusFactory from 'consul-ui/utils/acls-status';
|
||||||
|
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||||
|
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role';
|
||||||
|
|
||||||
|
const isValidServerError = isValidServerErrorFactory();
|
||||||
|
const status = statusFactory(isValidServerError, Promise);
|
||||||
|
const MODEL_NAME = 'role';
|
||||||
|
|
||||||
|
export default RepositoryService.extend({
|
||||||
|
getModelName: function() {
|
||||||
|
return MODEL_NAME;
|
||||||
|
},
|
||||||
|
getPrimaryKey: function() {
|
||||||
|
return PRIMARY_KEY;
|
||||||
|
},
|
||||||
|
getSlugKey: function() {
|
||||||
|
return SLUG_KEY;
|
||||||
|
},
|
||||||
|
status: function(obj) {
|
||||||
|
return status(obj);
|
||||||
|
},
|
||||||
|
});
|
|
@ -49,4 +49,10 @@ export default RepositoryService.extend({
|
||||||
dc: dc,
|
dc: dc,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
findByRole: function(id, dc) {
|
||||||
|
return get(this, 'store').query(this.getModelName(), {
|
||||||
|
role: id,
|
||||||
|
dc: dc,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import LazyProxyService from 'consul-ui/services/lazy-proxy';
|
||||||
|
|
||||||
|
import { fromPromise, proxy } from 'consul-ui/utils/dom/event-source';
|
||||||
|
export default LazyProxyService.extend({
|
||||||
|
shouldProxy: function(content, method) {
|
||||||
|
return method.indexOf('find') === 0 || method === 'persist';
|
||||||
|
},
|
||||||
|
execute: function(repo, findOrPersist) {
|
||||||
|
return function() {
|
||||||
|
return proxy(
|
||||||
|
fromPromise(repo[findOrPersist](...arguments)),
|
||||||
|
findOrPersist.indexOf('All') === -1 ? {} : []
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,2 +1,9 @@
|
||||||
import Service from '@ember/service';
|
import Service from '@ember/service';
|
||||||
export default Service.extend({});
|
export default Service.extend({
|
||||||
|
searchable: function() {
|
||||||
|
return {
|
||||||
|
addEventListener: function() {},
|
||||||
|
removeEventListener: function() {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -4,7 +4,13 @@
|
||||||
@import 'base/reset/index';
|
@import 'base/reset/index';
|
||||||
@import 'variables/index';
|
@import 'variables/index';
|
||||||
|
|
||||||
|
/*TODO: Move this to its own local component*/
|
||||||
@import 'ember-power-select';
|
@import 'ember-power-select';
|
||||||
|
#ember-basic-dropdown-wormhole {
|
||||||
|
z-index: 510;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/**/
|
||||||
|
|
||||||
@import 'components/index';
|
@import 'components/index';
|
||||||
@import 'core/typography';
|
@import 'core/typography';
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
%visually-hidden {
|
||||||
|
position: absolute !important;
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
|
}
|
||||||
|
%visually-hidden-text {
|
||||||
|
text-indent: -9000px;
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
%decor-border-000 {
|
%decor-border-000 {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
content: '';
|
content: '';
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
width: 1.2em;
|
||||||
%with-cancel-plain-icon {
|
height: 1.2em;
|
||||||
@extend %with-icon;
|
vertical-align: text-top;
|
||||||
background-image: $cancel-plain-svg;
|
|
||||||
}
|
}
|
|
@ -73,10 +73,11 @@ $queue-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xm
|
||||||
$refresh-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="%23000"/></svg>');
|
$refresh-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="%23000"/></svg>');
|
||||||
$run-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="structure-icon-run"><style>.structure-icon-run {animation: structure-icon-run-simple-spin 1s infinite linear;}.structure-icon-run-progress {animation: structure-icon-run-fancy-spin 3s infinite linear;fill: transparent;opacity: 0.66;stroke-dasharray: 16 16;transform-origin: 50% 50%;}@keyframes structure-icon-run-fancy-spin {0% {stroke-dasharray: 4 32;}50% {stroke-dasharray: 24 8;}50% {stroke-dasharray: 4 32;}50% {stroke-dasharray: 24 8;}100% {stroke-dasharray: 4 32;}}@keyframes structure-icon-run-simple-spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}</style><g fill="none" fill-rule="evenodd"><circle cx="12" cy="12" r="8" stroke="%23000" stroke-width="4"/><circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2" class="structure-icon-run-progress"/><circle cx="12" cy="12" r="4" fill="currentColor"/></g></svg>');
|
$run-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="structure-icon-run"><style>.structure-icon-run {animation: structure-icon-run-simple-spin 1s infinite linear;}.structure-icon-run-progress {animation: structure-icon-run-fancy-spin 3s infinite linear;fill: transparent;opacity: 0.66;stroke-dasharray: 16 16;transform-origin: 50% 50%;}@keyframes structure-icon-run-fancy-spin {0% {stroke-dasharray: 4 32;}50% {stroke-dasharray: 24 8;}50% {stroke-dasharray: 4 32;}50% {stroke-dasharray: 24 8;}100% {stroke-dasharray: 4 32;}}@keyframes structure-icon-run-simple-spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}</style><g fill="none" fill-rule="evenodd"><circle cx="12" cy="12" r="8" stroke="%23000" stroke-width="4"/><circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2" class="structure-icon-run-progress"/><circle cx="12" cy="12" r="4" fill="currentColor"/></g></svg>');
|
||||||
$search-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="%23000"/></svg>');
|
$search-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="%23000"/></svg>');
|
||||||
|
$service-identity-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M6.5 13a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm11-3a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7zm-4 11a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7z" id="a"/></defs><use fill="%239E2159" xlink:href="%23a" fill-rule="evenodd"/></svg>');
|
||||||
$settings-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.488.488 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" fill="%23000"/></svg>');
|
$settings-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.488.488 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" fill="%23000"/></svg>');
|
||||||
$star-fill-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" fill="%23000"/></svg>');
|
$star-fill-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" fill="%23000"/></svg>');
|
||||||
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="%23000"/></svg>');
|
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="%23000"/></svg>');
|
||||||
$star-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="10" height="9" viewBox="0 0 10 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M5 7.196L7.575 8.75l-.683-2.93 2.275-1.97-2.996-.254L5 .833 3.83 3.596.832 3.85l2.275 1.97-.683 2.93z"/></defs><use fill="%23FAC402" xlink:href="%23a" fill-rule="evenodd"/></svg>');
|
$star-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="10" height="9" viewBox="0 0 10 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M5 7.196L7.575 8.75l-.683-2.93 2.275-1.97-2.996-.254L5 .833 3.83 3.596.832 3.85l2.275 1.97-.683 2.93z"/></defs><use fill="%239E2159" xlink:href="%23a" fill-rule="evenodd"/></svg>');
|
||||||
$sub-arrow-left-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M11 9l1.42 1.42L8.83 14H18V4h2v12H8.83l3.59 3.58L11 21l-6-6z" fill="%23000"/></svg>');
|
$sub-arrow-left-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M11 9l1.42 1.42L8.83 14H18V4h2v12H8.83l3.59 3.58L11 21l-6-6z" fill="%23000"/></svg>');
|
||||||
$sub-arrow-right-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9z" fill="%23000"/></svg>');
|
$sub-arrow-right-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9z" fill="%23000"/></svg>');
|
||||||
$swap-horizontal-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z" fill="%23000"/></svg>');
|
$swap-horizontal-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z" fill="%23000"/></svg>');
|
||||||
|
|
|
@ -0,0 +1,494 @@
|
||||||
|
%with-alert-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $alert-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-alert-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $alert-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-alert-triangle-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $alert-triangle-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-arrow-down-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $arrow-down-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-arrow-left-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $arrow-left-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-arrow-right-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $arrow-right-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-arrow-up-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $arrow-up-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-calendar-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $calendar-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-cancel-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $cancel-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-cancel-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $cancel-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-cancel-plain-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $cancel-plain-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-cancel-square-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $cancel-square-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-cancel-square-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $cancel-square-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-caret-down-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $caret-down-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-caret-up-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $caret-up-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-check-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $check-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-check-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $check-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-check-plain-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $check-plain-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-down-2-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-down-2-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-down-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-down-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-left-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-left-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-right-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-right-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-up-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-up-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-chevron-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $chevron-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-clock-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $clock-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-clock-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $clock-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-code-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $code-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-consul-logo-color-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $consul-logo-color-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-copy-action-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $copy-action-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-copy-success-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $copy-success-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-disabled-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $disabled-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-download-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $download-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-edit-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $edit-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-exit-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $exit-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-expand-less-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $expand-less-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-expand-more-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $expand-more-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-eye-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $eye-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-file-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $file-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-file-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $file-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-filter-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $filter-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-flag-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $flag-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-folder-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $folder-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-folder-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $folder-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-git-branch-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $git-branch-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-git-commit-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $git-commit-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-git-pull-request-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $git-pull-request-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-hashicorp-logo-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $hashicorp-logo-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-help-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $help-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-help-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $help-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-history-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $history-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-info-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $info-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-info-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $info-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-kubernetes-logo-color-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $kubernetes-logo-color-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-link-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $link-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-loading-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $loading-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-lock-closed-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $lock-closed-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-lock-disabled-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $lock-disabled-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-lock-open-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $lock-open-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-menu-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $menu-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-minus-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $minus-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-minus-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $minus-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-minus-plain-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $minus-plain-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-minus-square-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $minus-square-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-minus-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $minus-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-more-horizontal-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $more-horizontal-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-more-vertical-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $more-vertical-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-nomad-logo-color-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $nomad-logo-color-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-plus-circle-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $plus-circle-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-plus-circle-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $plus-circle-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-plus-plain-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $plus-plain-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-plus-square-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $plus-square-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-queue-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $queue-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-refresh-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $refresh-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-run-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $run-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-search-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $search-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-service-identity-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $service-identity-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-settings-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $settings-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-star-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $star-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-star-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $star-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-star-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $star-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-sub-arrow-left-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $sub-arrow-left-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-sub-arrow-right-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $sub-arrow-right-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-swap-horizontal-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $swap-horizontal-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-swap-vertical-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $swap-vertical-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-terraform-logo-color-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $terraform-logo-color-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-tier-enterprise-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $tier-enterprise-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-tier-oss-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $tier-oss-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-trash-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $trash-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-tune-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $tune-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-unfold-less-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $unfold-less-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-unfold-more-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $unfold-more-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-upload-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $upload-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-user-organization-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $user-organization-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-user-plain-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $user-plain-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-user-square-fill-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $user-square-fill-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-user-square-outline-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $user-square-outline-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-user-team-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $user-team-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-visibility-hide-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $visibility-hide-svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
%with-visibility-show-icon {
|
||||||
|
@extend %with-icon;
|
||||||
|
background-image: $visibility-show-svg;
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
@import './base-variables';
|
@import './base-variables';
|
||||||
@import './base-placeholders';
|
@import './base-placeholders';
|
||||||
|
@import './icon-placeholders';
|
||||||
|
|
|
@ -9,21 +9,12 @@
|
||||||
%action-group li > * {
|
%action-group li > * {
|
||||||
@extend %action-group-action;
|
@extend %action-group-action;
|
||||||
}
|
}
|
||||||
%action-group::before {
|
|
||||||
margin-left: -1px;
|
|
||||||
}
|
|
||||||
%action-group label::after {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
%action-group label::before {
|
|
||||||
margin-left: -7px;
|
|
||||||
}
|
|
||||||
%action-group {
|
%action-group {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 15px;
|
right: 3px;
|
||||||
}
|
}
|
||||||
%action-group label {
|
%action-group label {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -38,12 +29,12 @@
|
||||||
/* this is actually the group */
|
/* this is actually the group */
|
||||||
%action-group ul {
|
%action-group ul {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -10px;
|
right: 0px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
%action-group ul::before {
|
%action-group ul::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 18px;
|
right: 9px;
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
|
|
|
@ -9,10 +9,9 @@
|
||||||
%action-group-action {
|
%action-group-action {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
%action-group label::after,
|
%action-group label:first-of-type::after {
|
||||||
%action-group label::before,
|
@extend %with-more-horizontal-icon, %as-pseudo;
|
||||||
%action-group::before {
|
opacity: 0.7;
|
||||||
@extend %with-dot;
|
|
||||||
}
|
}
|
||||||
%action-group ul {
|
%action-group ul {
|
||||||
border: $decor-border-100;
|
border: $decor-border-100;
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
%main-content a {
|
%main-content a {
|
||||||
color: $gray-900;
|
color: $gray-900;
|
||||||
}
|
}
|
||||||
%main-content a[rel*='help'] {
|
|
||||||
@extend %with-info;
|
|
||||||
}
|
|
||||||
%main-content label a[rel*='help'] {
|
%main-content label a[rel*='help'] {
|
||||||
color: $gray-400;
|
color: $gray-400;
|
||||||
}
|
}
|
||||||
|
%main-content a[rel*='help']::after {
|
||||||
|
@extend %with-info-circle-outline-icon, %as-pseudo;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
[role='tabpanel'] > p:only-child [rel*='help']::after {
|
[role='tabpanel'] > p:only-child [rel*='help']::after {
|
||||||
content: none;
|
content: none;
|
||||||
|
|
|
@ -29,3 +29,6 @@
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
%code-editor > pre {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ form table,
|
||||||
%app-content form dl {
|
%app-content form dl {
|
||||||
@extend %form-row;
|
@extend %form-row;
|
||||||
}
|
}
|
||||||
%app-content form:not(.filter-bar) [role='radiogroup'] {
|
%app-content form:not(.filter-bar) [role='radiogroup'],
|
||||||
|
%modal-window [role='radiogroup'] {
|
||||||
@extend %radio-group;
|
@extend %radio-group;
|
||||||
}
|
}
|
||||||
%radio-group label {
|
%radio-group label {
|
||||||
|
@ -33,6 +34,9 @@ form table,
|
||||||
.checkbox-group {
|
.checkbox-group {
|
||||||
@extend %checkbox-group;
|
@extend %checkbox-group;
|
||||||
}
|
}
|
||||||
|
fieldset > p {
|
||||||
|
color: $gray-400;
|
||||||
|
}
|
||||||
%toggle + .checkbox-group {
|
%toggle + .checkbox-group {
|
||||||
margin-top: -1em;
|
margin-top: -1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
@import './healthcheck-info/index';
|
@import './healthcheck-info/index';
|
||||||
@import './icons/index';
|
@import './icons/index';
|
||||||
tr dl {
|
tr .healthcheck-info {
|
||||||
@extend %healthcheck-info;
|
@extend %healthcheck-info;
|
||||||
}
|
}
|
||||||
td span.zero {
|
td span.zero {
|
||||||
@extend %with-no-healthchecks;
|
@extend %with-no-healthchecks;
|
||||||
// TODO: Why isn't this is layout?
|
// TODO: Why isn't this in layout?
|
||||||
display: block;
|
display: block;
|
||||||
text-indent: 20px;
|
text-indent: 20px;
|
||||||
color: $gray-400;
|
color: $gray-400;
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
%healthcheck-info {
|
%healthcheck-info {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
height: 100%;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
%healthcheck-info > * {
|
%healthcheck-info > * {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
%healthcheck-info dt {
|
||||||
|
text-indent: -9000px;
|
||||||
|
}
|
||||||
%healthcheck-info dt.zero {
|
%healthcheck-info dt.zero {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
%healthcheck-info dd.zero {
|
%healthcheck-info dd.zero {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
%healthcheck-info dt {
|
|
||||||
text-indent: -9000px;
|
|
||||||
}
|
|
||||||
%healthcheck-info dt.warning {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
%healthcheck-info dt.warning::before {
|
%healthcheck-info dt.warning::before {
|
||||||
top: 7px;
|
top: 7px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,18 +149,6 @@
|
||||||
height: 0.05em;
|
height: 0.05em;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
%with-info {
|
|
||||||
position: relative;
|
|
||||||
padding-right: 23px;
|
|
||||||
}
|
|
||||||
%with-info::after {
|
|
||||||
@extend %pseudo-icon;
|
|
||||||
right: 0;
|
|
||||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><circle stroke="%23BBC4D1" fill="%23FFF" cx="7" cy="7" r="7"/><path fill="%236A7786" d="M6.15 4.677V3.233h1.56v1.444zM6.15 11.374V6.35h1.56v5.023z"/></svg>');
|
|
||||||
background-color: $color-transparent;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
/*TODO: All chevrons need merging */
|
/*TODO: All chevrons need merging */
|
||||||
%with-chevron-down::before {
|
%with-chevron-down::before {
|
||||||
@extend %pseudo-icon-bg-img;
|
@extend %pseudo-icon-bg-img;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
%modal-dialog {
|
%modal-dialog {
|
||||||
z-index: 10000;
|
z-index: 500;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -2,4 +2,44 @@
|
||||||
td strong,
|
td strong,
|
||||||
%tag-list span {
|
%tag-list span {
|
||||||
@extend %pill;
|
@extend %pill;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
// For the moment pills with classes are iconed ones
|
||||||
|
%pill:not([class]) {
|
||||||
|
@extend %frame-gray-900;
|
||||||
|
}
|
||||||
|
%pill[class] {
|
||||||
|
padding-left: 0;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
%pill[class]::before {
|
||||||
|
@extend %as-pseudo;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
%pill.policy::before {
|
||||||
|
@extend %with-file-fill-icon;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
%pill.policy-management::before {
|
||||||
|
@extend %with-star-icon;
|
||||||
|
}
|
||||||
|
%pill.policy-service-identity::before {
|
||||||
|
@extend %with-service-identity-icon;
|
||||||
|
}
|
||||||
|
%pill.role::before {
|
||||||
|
@extend %with-user-plain-icon;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: These are related to the pill icons, but also to the tables
|
||||||
|
// All of this icon assigning stuff should probably go in the eventual
|
||||||
|
// refactored /components/icons.scss file
|
||||||
|
|
||||||
|
td.policy-service-identity a::after {
|
||||||
|
@extend %with-service-identity-icon, %as-pseudo;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
td.policy-management a::after {
|
||||||
|
@extend %with-star-icon, %as-pseudo;
|
||||||
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
%pill {
|
%pill {
|
||||||
@extend %frame-gray-900;
|
|
||||||
border-radius: $radius-small;
|
border-radius: $radius-small;
|
||||||
}
|
}
|
||||||
%pill button {
|
%pill button {
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
%header-nav-panel {
|
%header-nav-panel {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 15px 35px;
|
padding: 15px 35px;
|
||||||
z-index: 10000;
|
z-index: 499;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
%header-nav-toggle-button {
|
%header-nav-toggle-button {
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
right: 0px;
|
right: 0px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
z-index: 2;
|
z-index: 200;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
%header-nav-panel {
|
%header-nav-panel {
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
z-index: 3;
|
z-index: 300;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
right: -100%;
|
right: -100%;
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
%header-drop-nav {
|
%header-drop-nav {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 400;
|
||||||
}
|
}
|
||||||
%header-drop-nav a {
|
%header-drop-nav a {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
|
@ -24,10 +24,9 @@ td .kind-proxy {
|
||||||
@extend %type-icon, %with-proxy;
|
@extend %type-icon, %with-proxy;
|
||||||
text-indent: -9000px !important;
|
text-indent: -9000px !important;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
margin-top: -8px;
|
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
}
|
}
|
||||||
table:not(.sessions) tr {
|
table:not(.sessions) tbody tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
table:not(.sessions) td:first-child {
|
table:not(.sessions) td:first-child {
|
||||||
|
@ -39,14 +38,12 @@ th {
|
||||||
}
|
}
|
||||||
th span {
|
th span {
|
||||||
@extend %tooltip;
|
@extend %tooltip;
|
||||||
@extend %with-info;
|
margin-left: 2px;
|
||||||
margin-left: 12px;
|
vertical-align: text-top;
|
||||||
top: 3px;
|
|
||||||
width: 23px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
}
|
||||||
th span:after {
|
th span::after {
|
||||||
left: -8px;
|
@extend %with-info-circle-outline-icon, %as-pseudo;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
th span em::after {
|
th span em::after {
|
||||||
@extend %tooltip-tail;
|
@extend %tooltip-tail;
|
||||||
|
@ -66,3 +63,31 @@ th span:hover em::after,
|
||||||
th span:hover em {
|
th span:hover em {
|
||||||
@extend %blink-in-fade-out-active;
|
@extend %blink-in-fade-out-active;
|
||||||
}
|
}
|
||||||
|
/* ideally these would be in route css files, but left here as they */
|
||||||
|
/* accomplish the same thing (hide non-essential columns for tables) */
|
||||||
|
@media #{$--lt-medium-table} {
|
||||||
|
/* Policy > Datacenters */
|
||||||
|
html.template-policy.template-list tr > :nth-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html.template-service.template-list tr > :nth-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media #{$--lt-wide-table} {
|
||||||
|
html.template-intention.template-list tr > :nth-last-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html.template-service.template-list tr > :last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html.template-node.template-show #services tr > :last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html.template-node.template-show #lock-sessions tr > :not(:first-child):not(:last-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html.template-node.template-show #lock-sessions td:last-child {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
%table-actions {
|
%table-actions {
|
||||||
width: 60px;
|
width: 60px !important;
|
||||||
}
|
}
|
||||||
th.actions input {
|
th.actions input {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -10,38 +10,29 @@ th.actions input {
|
||||||
th.actions {
|
th.actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
td.actions .with-confirmation.confirming {
|
table tr {
|
||||||
position: absolute;
|
display: flex;
|
||||||
bottom: 4px;
|
|
||||||
right: 1px;
|
|
||||||
}
|
}
|
||||||
td.actions .with-confirmation.confirming p {
|
table td {
|
||||||
margin-bottom: 1em;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
table td a {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
table caption {
|
table caption {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin-bottom: 0.8em;
|
margin-bottom: 0.8em;
|
||||||
}
|
}
|
||||||
td > button,
|
|
||||||
td > .with-confirmation > button {
|
|
||||||
position: relative;
|
|
||||||
top: -6px;
|
|
||||||
}
|
|
||||||
table th {
|
table th {
|
||||||
padding-bottom: 0.6em;
|
padding-bottom: 0.6em;
|
||||||
}
|
}
|
||||||
table td,
|
table th:not(.actions),
|
||||||
table td:first-child a {
|
|
||||||
padding: 0.9em 0;
|
|
||||||
}
|
|
||||||
table th,
|
|
||||||
table td:not(.actions),
|
table td:not(.actions),
|
||||||
table td a {
|
table td a {
|
||||||
padding-right: 0.9em;
|
padding-right: 0.9em;
|
||||||
}
|
}
|
||||||
table td a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
th,
|
th,
|
||||||
td:not(.actions),
|
td:not(.actions),
|
||||||
td:not(.actions) a {
|
td:not(.actions) a {
|
||||||
|
@ -49,43 +40,9 @@ td:not(.actions) a {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hide actions on narrow screens, you can always click in do everything from there */
|
/* hide actions on narrow screens, you can always click in do everything from there */
|
||||||
@media #{$--lt-wide-table} {
|
@media #{$--lt-wide-table} {
|
||||||
tr > .actions {
|
tr > .actions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* ideally these would be in route css files, but left here as they */
|
|
||||||
/* accomplish the same thing (hide non-essential columns for tables) */
|
|
||||||
/* TODO: Move these to component/table.scss for the moment */
|
|
||||||
/* Also mixed with things in component/tabular-collection.scss move those also */
|
|
||||||
@media #{$--lt-medium-table} {
|
|
||||||
/* Policy > Datacenters */
|
|
||||||
html.template-policy.template-list tr > :nth-child(2) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html.template-service.template-list tr > :nth-child(2) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media #{$--lt-wide-table} {
|
|
||||||
html.template-intention.template-list tr > :nth-last-child(2) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html.template-service.template-list tr > :last-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html.template-node.template-show #services tr > :last-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html.template-node.template-show #lock-sessions tr > :not(:first-child):not(:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html.template-node.template-show #lock-sessions td:last-child {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
html.template-node.template-show #lock-sessions td:last-child button {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,11 +3,21 @@ td {
|
||||||
border-bottom: $decor-border-100;
|
border-bottom: $decor-border-100;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
color: $gray-400 !important;
|
border-color: $gray-300;
|
||||||
}
|
|
||||||
th {
|
|
||||||
border-color: $keyline-dark;
|
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
border-color: $keyline-mid;
|
border-color: $gray-200;
|
||||||
|
color: $gray-500;
|
||||||
|
}
|
||||||
|
th,
|
||||||
|
td strong {
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
// TODO: Add to native selector `tbody th` - will involve moving all
|
||||||
|
// current th's to `thead th` and changing the templates
|
||||||
|
%tbody-th {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
td:first-child {
|
||||||
|
@extend %tbody-th;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,43 @@ table.dom-recycling {
|
||||||
/* using: */
|
/* using: */
|
||||||
/* calc(<100% divided by number of non-fixed width cells> - <sum of widths of fixed cells divided by number of non-fixed width cells>) */
|
/* calc(<100% divided by number of non-fixed width cells> - <sum of widths of fixed cells divided by number of non-fixed width cells>) */
|
||||||
|
|
||||||
|
table tr > *:nth-last-child(2):first-child,
|
||||||
|
table tr > *:nth-last-child(2):first-child ~ * {
|
||||||
|
width: calc(100% / 2);
|
||||||
|
}
|
||||||
|
table tr > *:nth-last-child(3):first-child,
|
||||||
|
table tr > *:nth-last-child(3):first-child ~ * {
|
||||||
|
width: calc(100% / 3);
|
||||||
|
}
|
||||||
|
table tr > *:nth-last-child(4):first-child,
|
||||||
|
table tr > *:nth-last-child(4):first-child ~ * {
|
||||||
|
width: calc(100% / 4);
|
||||||
|
}
|
||||||
|
table tr > *:nth-last-child(5):first-child,
|
||||||
|
table tr > *:nth-last-child(5):first-child ~ * {
|
||||||
|
width: calc(100% / 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.has-actions tr > .actions {
|
||||||
|
@extend %table-actions;
|
||||||
|
}
|
||||||
|
table.has-actions tr > *:nth-last-child(2):first-child,
|
||||||
|
table.has-actions tr > *:nth-last-child(2):first-child ~ * {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
}
|
||||||
|
table.has-actions tr > *:nth-last-child(3):first-child,
|
||||||
|
table.has-actions tr > *:nth-last-child(3):first-child ~ * {
|
||||||
|
width: calc(50% - 30px);
|
||||||
|
}
|
||||||
|
table.has-actions tr > *:nth-last-child(4):first-child,
|
||||||
|
table.has-actions tr > *:nth-last-child(4):first-child ~ * {
|
||||||
|
width: calc(33% - 20px);
|
||||||
|
}
|
||||||
|
table.has-actions tr > *:nth-last-child(5):first-child,
|
||||||
|
table.has-actions tr > *:nth-last-child(5):first-child ~ * {
|
||||||
|
width: calc(25% - 15px);
|
||||||
|
}
|
||||||
|
|
||||||
/*TODO: trs only live in tables, get rid of table */
|
/*TODO: trs only live in tables, get rid of table */
|
||||||
html.template-service.template-list main table tr {
|
html.template-service.template-list main table tr {
|
||||||
@extend %services-row;
|
@extend %services-row;
|
||||||
|
@ -38,26 +75,16 @@ html.template-service.template-list main table tr {
|
||||||
html.template-service.template-show #instances table tr {
|
html.template-service.template-show #instances table tr {
|
||||||
@extend %instances-row;
|
@extend %instances-row;
|
||||||
}
|
}
|
||||||
html.template-instance.template-show #upstreams table tr {
|
|
||||||
@extend %upstreams-row;
|
|
||||||
}
|
|
||||||
html.template-intention.template-list main table tr {
|
|
||||||
@extend %intentions-row;
|
|
||||||
}
|
|
||||||
html.template-kv.template-list main table tr {
|
|
||||||
@extend %kvs-row;
|
|
||||||
}
|
|
||||||
html.template-acl.template-list main table tr {
|
|
||||||
@extend %acls-row;
|
|
||||||
}
|
|
||||||
html.template-policy.template-list main table tr {
|
|
||||||
@extend %policies-row;
|
|
||||||
}
|
|
||||||
html.template-token.template-list main table tr {
|
html.template-token.template-list main table tr {
|
||||||
@extend %tokens-row;
|
@extend %tokens-row;
|
||||||
}
|
}
|
||||||
|
html.template-role.template-list main table tr {
|
||||||
|
@extend %roles-row;
|
||||||
|
}
|
||||||
html.template-policy.template-edit [role='dialog'] table tr,
|
html.template-policy.template-edit [role='dialog'] table tr,
|
||||||
html.template-policy.template-edit main table tr {
|
html.template-policy.template-edit main table tr,
|
||||||
|
html.template-role.template-edit [role='dialog'] table tr,
|
||||||
|
html.template-role.template-edit main table.token-list tr {
|
||||||
@extend %tokens-minimal-row;
|
@extend %tokens-minimal-row;
|
||||||
}
|
}
|
||||||
html.template-token.template-list main table tr td.me,
|
html.template-token.template-list main table tr td.me,
|
||||||
|
@ -65,12 +92,54 @@ html.template-token.template-list main table tr td.me ~ td,
|
||||||
html.template-token.template-list main table tr th {
|
html.template-token.template-list main table tr th {
|
||||||
@extend %tokens-your-row;
|
@extend %tokens-your-row;
|
||||||
}
|
}
|
||||||
html.template-node.template-show main table tr {
|
|
||||||
@extend %node-services-row;
|
|
||||||
}
|
|
||||||
html.template-node.template-show main table.sessions tr {
|
html.template-node.template-show main table.sessions tr {
|
||||||
@extend %node-sessions-row;
|
@extend %node-sessions-row;
|
||||||
}
|
}
|
||||||
|
// this will get auto calculated later in tabular-collection.js
|
||||||
|
// keeping this here for reference
|
||||||
|
// %services-row > * {
|
||||||
|
// (100% / 2) - (160px / 2)
|
||||||
|
// width: calc(50% - 160px);
|
||||||
|
// }
|
||||||
|
%services-row > *:nth-child(2) {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
%services-row > * {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
%instances-row > * {
|
||||||
|
width: calc(100% / 5);
|
||||||
|
}
|
||||||
|
%tokens-row > *:first-child,
|
||||||
|
%tokens-minimal-row > *:not(last-child),
|
||||||
|
%tokens-row > *:nth-child(2),
|
||||||
|
%tokens-your-row:nth-last-child(2) {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
%tokens-row > *:nth-child(3) {
|
||||||
|
width: calc(30% - 150px);
|
||||||
|
}
|
||||||
|
%tokens-row > *:nth-child(4) {
|
||||||
|
width: calc(70% - 150px);
|
||||||
|
}
|
||||||
|
%tokens-your-row:nth-child(4) {
|
||||||
|
width: calc(70% - 270px) !important;
|
||||||
|
}
|
||||||
|
%tokens-row > *:last-child {
|
||||||
|
@extend %table-actions;
|
||||||
|
}
|
||||||
|
%tokens-minimal-row > *:last-child {
|
||||||
|
width: calc(100% - 240px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
%roles-row > *:nth-child(1),
|
||||||
|
%roles-row > *:nth-child(2) {
|
||||||
|
width: calc(22% - 20px) !important;
|
||||||
|
}
|
||||||
|
%roles-row > *:nth-child(3) {
|
||||||
|
width: calc(56% - 20px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media #{$--horizontal-session-list} {
|
@media #{$--horizontal-session-list} {
|
||||||
%node-sessions-row > * {
|
%node-sessions-row > * {
|
||||||
// (100% / 7) - (300px / 6) - (120px / 6)
|
// (100% / 7) - (300px / 6) - (120px / 6)
|
||||||
|
@ -101,37 +170,6 @@ html.template-node.template-show main table.sessions tr {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
%intentions-row > * {
|
|
||||||
width: calc(25% - 15px);
|
|
||||||
}
|
|
||||||
%intentions-row > *:last-child {
|
|
||||||
@extend %table-actions;
|
|
||||||
}
|
|
||||||
%acls-row > * {
|
|
||||||
width: calc(50% - 30px);
|
|
||||||
}
|
|
||||||
%acls-row > *:last-child {
|
|
||||||
@extend %table-actions;
|
|
||||||
}
|
|
||||||
%tokens-row > *:first-child,
|
|
||||||
%tokens-minimal-row > *:not(last-child),
|
|
||||||
%tokens-row > *:nth-child(2),
|
|
||||||
%tokens-your-row:nth-last-child(2) {
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
%tokens-row > *:nth-child(3),
|
|
||||||
%tokens-row > *:nth-child(4) {
|
|
||||||
width: calc(50% - 150px);
|
|
||||||
}
|
|
||||||
%tokens-your-row:nth-child(4) {
|
|
||||||
width: calc(50% - 270px) !important;
|
|
||||||
}
|
|
||||||
%tokens-row > *:last-child {
|
|
||||||
@extend %table-actions;
|
|
||||||
}
|
|
||||||
%tokens-minimal-row > *:last-child {
|
|
||||||
width: calc(100% - 240px);
|
|
||||||
}
|
|
||||||
@media #{$--lt-medium-table} {
|
@media #{$--lt-medium-table} {
|
||||||
/* Token > Policies */
|
/* Token > Policies */
|
||||||
/* Token > Your Token */
|
/* Token > Your Token */
|
||||||
|
@ -148,37 +186,3 @@ html.template-node.template-show main table.sessions tr {
|
||||||
width: calc(100% / 4);
|
width: calc(100% / 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
%kvs-row > *:first-child {
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
}
|
|
||||||
%kvs-row > *:last-child {
|
|
||||||
@extend %table-actions;
|
|
||||||
}
|
|
||||||
%node-services-row > * {
|
|
||||||
width: calc(100% / 3);
|
|
||||||
}
|
|
||||||
%policies-row > * {
|
|
||||||
width: calc(33% - 20px);
|
|
||||||
}
|
|
||||||
%policies-row > *:last-child {
|
|
||||||
@extend %table-actions;
|
|
||||||
}
|
|
||||||
// this will get auto calculated later in tabular-collection.js
|
|
||||||
// keeping this here for reference
|
|
||||||
// %services-row > * {
|
|
||||||
// (100% / 2) - (160px / 2)
|
|
||||||
// width: calc(50% - 160px);
|
|
||||||
// }
|
|
||||||
%services-row > *:nth-child(2) {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
%services-row > * {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
%instances-row > * {
|
|
||||||
width: calc(100% / 5);
|
|
||||||
}
|
|
||||||
%upstreams-row > * {
|
|
||||||
width: calc(100% / 4);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
/* TODO: rename: %details-table */
|
/* TODO: rename: %details-table */
|
||||||
%tabular-details {
|
|
||||||
width: 100%;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
%tabular-details tr > .actions {
|
%tabular-details tr > .actions {
|
||||||
@extend %table-actions;
|
@extend %table-actions;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -14,54 +10,48 @@
|
||||||
@extend %toggle-button;
|
@extend %toggle-button;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
}
|
}
|
||||||
%tabular-details td > label {
|
%tabular-details td > label {
|
||||||
@extend %tabular-details-toggle-button;
|
@extend %tabular-details-toggle-button;
|
||||||
/*TODO: This needs to be figured out with %toggle-button/%action-group */
|
right: 2px;
|
||||||
top: 8px;
|
}
|
||||||
right: 15px;
|
%tabular-details tr:nth-child(even) td {
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
display: table-cell;
|
||||||
}
|
}
|
||||||
%tabular-details tr:nth-child(even) td > * {
|
%tabular-details tr:nth-child(even) td > * {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
%tabular-details tr:nth-child(odd) td {
|
|
||||||
width: calc(50% - 30px);
|
|
||||||
}
|
|
||||||
%tabular-details tr:nth-child(odd) td:last-child {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
%tabular-detail > label {
|
%tabular-detail > label {
|
||||||
@extend %tabular-details-toggle-button;
|
@extend %tabular-details-toggle-button;
|
||||||
top: 8px;
|
right: 11px;
|
||||||
right: 24px;
|
|
||||||
}
|
}
|
||||||
%tabular-details tr:nth-child(even) td > input:checked + * {
|
%tabular-details tr:nth-child(even) td > input:checked + * {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
%tabular-details td:only-child {
|
%tabular-details td:only-child {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
position: relative;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detail
|
||||||
%tabular-detail {
|
%tabular-detail {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -10px;
|
left: -10px;
|
||||||
right: -10px;
|
right: -10px;
|
||||||
width: calc(100% + 20px);
|
width: calc(100% + 20px);
|
||||||
margin-top: -48px;
|
margin-top: -51px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
%tabular-detail {
|
%tabular-detail {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
%tabular-detail::before {
|
%tabular-detail::after {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
clear: both;
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
%tabular-detail > div {
|
%tabular-detail > div {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
}
|
}
|
||||||
%tabular-details td:only-child {
|
%tabular-details td:only-child {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
%tabular-detail {
|
%tabular-detail {
|
||||||
border: 1px solid $gray-300;
|
border: 1px solid $gray-300;
|
||||||
|
@ -18,3 +19,14 @@
|
||||||
%tabular-detail > label::before {
|
%tabular-detail > label::before {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
// this is here as its a fake border
|
||||||
|
%tabular-detail::before {
|
||||||
|
background: $gray-200;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 10px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
|
@ -6,5 +6,9 @@
|
||||||
// ideally we'd be more specific with those to say
|
// ideally we'd be more specific with those to say
|
||||||
// only add padding to dl's in edit pages
|
// only add padding to dl's in edit pages
|
||||||
%tag-list dd {
|
%tag-list dd {
|
||||||
|
display: inline-flex;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
%tag-list dd > * {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
%tooltip-bubble,
|
%tooltip-bubble,
|
||||||
%tooltip-tail {
|
%tooltip-tail {
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
%button {
|
|
||||||
font-family: $typo-family-sans;
|
|
||||||
}
|
|
||||||
main p,
|
main p,
|
||||||
%modal-window p {
|
%modal-window p {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
@ -24,33 +21,42 @@ main p,
|
||||||
%footer {
|
%footer {
|
||||||
letter-spacing: -0.05em;
|
letter-spacing: -0.05em;
|
||||||
}
|
}
|
||||||
th,
|
|
||||||
button,
|
%button {
|
||||||
td strong,
|
font-family: $typo-family-sans;
|
||||||
td:first-child,
|
}
|
||||||
|
/* Weighting */
|
||||||
h1,
|
h1,
|
||||||
%app-content div > dt,
|
%app-content div > dt,
|
||||||
%header-drop-nav .is-active {
|
%header-drop-nav .is-active {
|
||||||
font-weight: $typo-weight-bold;
|
font-weight: $typo-weight-bold;
|
||||||
}
|
}
|
||||||
h2,
|
h2,
|
||||||
|
fieldset > header,
|
||||||
|
caption,
|
||||||
%header-nav,
|
%header-nav,
|
||||||
%healthchecked-resource header span,
|
%healthchecked-resource header span,
|
||||||
%healthcheck-output dt,
|
%healthcheck-output dt,
|
||||||
%copy-button,
|
%copy-button,
|
||||||
%app-content div > dl > dt,
|
%app-content div > dl > dt,
|
||||||
td:first-child a {
|
%tbody-th,
|
||||||
font-weight: $typo-weight-semibold;
|
|
||||||
}
|
|
||||||
%form-element > span,
|
%form-element > span,
|
||||||
%toggle label span,
|
%toggle label span {
|
||||||
caption {
|
|
||||||
font-weight: $typo-weight-semibold;
|
font-weight: $typo-weight-semibold;
|
||||||
}
|
}
|
||||||
%button {
|
%button {
|
||||||
font-weight: $typo-weight-semibold !important;
|
font-weight: $typo-weight-semibold !important;
|
||||||
}
|
}
|
||||||
|
main label a[rel*='help'],
|
||||||
|
%pill,
|
||||||
|
%tbody-th em,
|
||||||
|
%form-element > strong,
|
||||||
|
%healthchecked-resource strong,
|
||||||
|
%app-view h1 em {
|
||||||
|
font-weight: $typo-weight-normal;
|
||||||
|
}
|
||||||
th,
|
th,
|
||||||
|
td strong,
|
||||||
%breadcrumbs li > *,
|
%breadcrumbs li > *,
|
||||||
%action-group-action,
|
%action-group-action,
|
||||||
%tab-nav,
|
%tab-nav,
|
||||||
|
@ -58,20 +64,16 @@ th,
|
||||||
%type-icon {
|
%type-icon {
|
||||||
font-weight: $typo-weight-medium;
|
font-weight: $typo-weight-medium;
|
||||||
}
|
}
|
||||||
main label a[rel*='help'],
|
|
||||||
td:first-child em,
|
/* Styling */
|
||||||
%pill,
|
|
||||||
%form-element > strong,
|
|
||||||
%healthchecked-resource strong,
|
|
||||||
%app-view h1 em {
|
|
||||||
font-weight: $typo-weight-normal;
|
|
||||||
}
|
|
||||||
%form-element > em,
|
%form-element > em,
|
||||||
td:first-child em,
|
%tbody-th em,
|
||||||
%healthchecked-resource header em,
|
%healthchecked-resource header em,
|
||||||
%app-view h1 em {
|
%app-view h1 em {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sizing */
|
||||||
%footer > * {
|
%footer > * {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
@ -79,20 +81,23 @@ h1 {
|
||||||
font-size: $typo-header-100;
|
font-size: $typo-header-100;
|
||||||
}
|
}
|
||||||
h2,
|
h2,
|
||||||
|
%healthcheck-info dt,
|
||||||
%header-drop-nav .is-active,
|
%header-drop-nav .is-active,
|
||||||
%app-view h1 em {
|
%app-view h1 em {
|
||||||
font-size: $typo-size-500;
|
font-size: $typo-size-500;
|
||||||
}
|
}
|
||||||
body,
|
body,
|
||||||
%action-group-action,
|
|
||||||
fieldset h2,
|
fieldset h2,
|
||||||
|
fieldset > header,
|
||||||
pre code,
|
pre code,
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
td {
|
%action-group-action,
|
||||||
|
%tbody-th {
|
||||||
font-size: $typo-size-600;
|
font-size: $typo-size-600;
|
||||||
}
|
}
|
||||||
th,
|
th,
|
||||||
|
td,
|
||||||
caption,
|
caption,
|
||||||
.type-dialog,
|
.type-dialog,
|
||||||
%form-element > span,
|
%form-element > span,
|
||||||
|
@ -105,14 +110,15 @@ caption,
|
||||||
%toggle label span {
|
%toggle label span {
|
||||||
font-size: $typo-size-700 !important;
|
font-size: $typo-size-700 !important;
|
||||||
}
|
}
|
||||||
%app-content > p:only-child,
|
fieldset > p,
|
||||||
[role='tabpanel'] > p:only-child,
|
|
||||||
%app-view > div.disabled > div,
|
|
||||||
.template-error > div,
|
.template-error > div,
|
||||||
|
[role='tabpanel'] > p:only-child,
|
||||||
|
.with-confirmation p,
|
||||||
|
%app-content > p:only-child,
|
||||||
|
%app-view > div.disabled > div,
|
||||||
%button,
|
%button,
|
||||||
%form-element > em,
|
%form-element > em,
|
||||||
%form-element > strong,
|
%form-element > strong,
|
||||||
.with-confirmation p,
|
|
||||||
%feedback-dialog-inline p {
|
%feedback-dialog-inline p {
|
||||||
font-size: $typo-size-800;
|
font-size: $typo-size-800;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,3 +29,10 @@ td a.is-management::after {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[name='role[state]'],
|
||||||
|
[name='role[state]'] + * {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[name='role[state]']:checked + * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
.template-token.template-edit [for='new-policy-toggle'] {
|
// TODO: Move this out of here and into probably modal
|
||||||
|
.type-dialog {
|
||||||
@extend %anchor;
|
@extend %anchor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
%pill.policy-management {
|
|
||||||
@extend %with-star;
|
|
||||||
}
|
|
||||||
%token-yours {
|
%token-yours {
|
||||||
text-indent: 20px;
|
text-indent: 20px;
|
||||||
color: $blue-500;
|
color: $blue-500;
|
||||||
|
@ -28,6 +26,3 @@
|
||||||
.template-token.template-edit dl {
|
.template-token.template-edit dl {
|
||||||
@extend %form-row;
|
@extend %form-row;
|
||||||
}
|
}
|
||||||
.template-token.template-edit dd .with-feedback {
|
|
||||||
top: -5px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,5 +7,5 @@ html.template-intention.template-list td.intent-deny strong {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
html.template-intention.template-list td.destination {
|
html.template-intention.template-list td.destination {
|
||||||
font-weight: $typo-weight-semibold;
|
@extend %tbody-th;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// TODO: Generalize this, also see services/index
|
html.template-node.template-show .sessions td:last-child {
|
||||||
@import '../../../components/pill/index';
|
justify-content: flex-end;
|
||||||
html.template-node.template-show td.tags span {
|
}
|
||||||
@extend %pill;
|
html.template-node.template-show .sessions td:first-child {
|
||||||
|
@extend %tbody-th;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,6 @@ $gray-025: #fafbfc;
|
||||||
|
|
||||||
$magenta-800-no-hash: 5a1434;
|
$magenta-800-no-hash: 5a1434;
|
||||||
|
|
||||||
$keyline-light: $gray-100; // h1
|
|
||||||
$keyline-mid: $gray-200; // td, footer
|
|
||||||
$keyline-dark: $gray-300; // th
|
|
||||||
$keyline-darker: $gray-400;
|
|
||||||
|
|
||||||
// decoration
|
// decoration
|
||||||
// undecided
|
// undecided
|
||||||
$radius-small: $decor-radius-100;
|
$radius-small: $decor-radius-100;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{{yield}}
|
||||||
|
{{#yield-slot 'create'}}{{yield}}{{/yield-slot}}
|
||||||
|
<label class="type-text">
|
||||||
|
<span>{{#yield-slot 'label'}}{{yield}}{{/yield-slot}}</span>
|
||||||
|
{{#power-select
|
||||||
|
onopen=(action 'open')
|
||||||
|
search=(action 'search')
|
||||||
|
options=options
|
||||||
|
loadingMessage="Loading..."
|
||||||
|
searchMessage="No possible options"
|
||||||
|
searchPlaceholder=placeholder
|
||||||
|
onchange=(action 'change' 'items[]' items) as |item|
|
||||||
|
}}
|
||||||
|
{{#yield-slot 'option' (block-params item)}}{{yield}}{{/yield-slot}}
|
||||||
|
{{/power-select}}
|
||||||
|
</label>
|
||||||
|
{{#if (gt items.length 0)}}
|
||||||
|
{{#yield-slot 'set'}}{{yield}}{{/yield-slot}}
|
||||||
|
{{else}}
|
||||||
|
|
||||||
|
{{/if}}
|
|
@ -1,14 +1,14 @@
|
||||||
{{ivy-codemirror
|
{{ivy-codemirror
|
||||||
value=value
|
value=value
|
||||||
readonly=readonly
|
|
||||||
name=name
|
name=name
|
||||||
class=class
|
class=class
|
||||||
options=options
|
options=options
|
||||||
valueUpdated=(action onkeyup)
|
valueUpdated=(action onkeyup)
|
||||||
}}
|
}}
|
||||||
{{#if (not syntax)}}
|
<pre><code>{{yield}}</code></pre>
|
||||||
|
{{#if (and (not readonly) (not syntax))}}
|
||||||
{{#power-select
|
{{#power-select
|
||||||
onchange=(action onchange)
|
onchange=(action 'change')
|
||||||
selected=mode
|
selected=mode
|
||||||
searchEnabled=false
|
searchEnabled=false
|
||||||
options=modes as |mode|
|
options=modes as |mode|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
|
@ -1,7 +1,7 @@
|
||||||
{{#if (and (lt passing 1) (lt warning 1) (lt critical 1) )}}
|
{{#if (and (lt passing 1) (lt warning 1) (lt critical 1) )}}
|
||||||
<span title="No Healthchecks" class="zero">0</span>
|
<span title="No Healthchecks" class="zero">0</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<dl>
|
<dl class="healthcheck-info">
|
||||||
{{healthcheck-status width=passingWidth name='passing' value=passing}}
|
{{healthcheck-status width=passingWidth name='passing' value=passing}}
|
||||||
{{healthcheck-status width=warningWidth name='warning' value=warning}}
|
{{healthcheck-status width=warningWidth name='warning' value=warning}}
|
||||||
{{healthcheck-status width=criticalWidth name='critical' value=critical}}
|
{{healthcheck-status width=criticalWidth name='critical' value=critical}}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{{yield}}
|
||||||
|
<fieldset>
|
||||||
|
{{#yield-slot 'template'}}
|
||||||
|
{{else}}
|
||||||
|
<header>
|
||||||
|
Policy or service identity?
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
A Service Identity is default policy with a configurable service name. This saves you some time and effort you're using Consul for Connect features.
|
||||||
|
</p>
|
||||||
|
{{! this should use radio-group }}
|
||||||
|
<div role="radiogroup" class={{if item.error.Type ' has-error'}}>
|
||||||
|
{{#each templates as |template|}}
|
||||||
|
<label>
|
||||||
|
<span>{{template.name}}</span>
|
||||||
|
<input data-test-radiobutton={{concat 'template_' template.template}} type="radio" name="{{name}}[template]" value={{template.template}} checked={{eq item.template template.template}} onchange={{action (changeset-set item 'template') value='target.value'}}/>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/yield-slot}}
|
||||||
|
<label class="type-text{{if item.error.Name ' has-error'}}">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" value={{item.Name}} name="{{name}}[Name]" autofocus="autofocus" oninput={{action 'change'}} />
|
||||||
|
<em>
|
||||||
|
Maximum 128 characters. May only include letters (uppercase and/or lowercase) and/or numbers. Must be unique.
|
||||||
|
</em>
|
||||||
|
{{#if item.error.Name}}
|
||||||
|
<strong>{{item.error.Name.validation}}</strong>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<label class="type-text">
|
||||||
|
<span>Rules <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
|
||||||
|
{{#if (eq item.template '') }}
|
||||||
|
{{code-editor syntax='hcl' class=(if item.error.Rules 'error') name=(concat name '[Rules]') value=item.Rules onkeyup=(action 'change' (concat name '[Rules]'))}}
|
||||||
|
{{#if item.error.Rules}}
|
||||||
|
<strong>{{item.error.Rules.validation}}</strong>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#code-editor name=(concat name '[Rules]') syntax='hcl' oninput=(action 'change' (concat name '[Rules]'))}}
|
||||||
|
{{~component 'service-identity' name=item.Name~}}
|
||||||
|
{{/code-editor}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<div class="type-toggle">
|
||||||
|
<span>Valid datacenters</span>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="{{name}}[isScoped]" checked={{if (not isScoped) 'checked' }} onchange={{action 'change'}} />
|
||||||
|
<span>All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{{#if isScoped }}
|
||||||
|
<div class="checkbox-group" role="group">
|
||||||
|
{{#each datacenters as |dc| }}
|
||||||
|
<label class="type-checkbox">
|
||||||
|
<input type="checkbox" name="{{name}}[Datacenters]" value={{dc.Name}} checked={{if (contains dc.Name item.Datacenters) 'checked' }} onchange={{action 'change'}} />
|
||||||
|
<span>{{dc.Name}}</span>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
{{#each item.Datacenters as |dc| }}
|
||||||
|
{{#if (not (find-by 'Name' dc datacenters))}}
|
||||||
|
<label class="type-checkbox">
|
||||||
|
<input type="checkbox" name="{{name}}[Datacenters]" value={{dc}} checked="checked" onchange={{action 'change'}} />
|
||||||
|
<span>{{dc}}</span>
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (eq item.template '') }}
|
||||||
|
<label class="type-text">
|
||||||
|
<span>Description (Optional)</span>
|
||||||
|
<textarea name="{{name}}[Description]" value={{item.Description}} oninput={{action 'change'}}></textarea>
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
{{#child-selector repo=repo dc=dc type="policy" placeholder="Search for policy" items=items}}
|
||||||
|
{{yield}}
|
||||||
|
{{#block-slot 'label'}}
|
||||||
|
Apply an existing policy
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'create'}}
|
||||||
|
{{#yield-slot 'trigger'}}
|
||||||
|
{{yield}}
|
||||||
|
{{else}}
|
||||||
|
<label class="type-dialog" for="new-policy-toggle">
|
||||||
|
<span>Create new policy</span>
|
||||||
|
</label>
|
||||||
|
{{!TODO: potentially call trigger something else}}
|
||||||
|
{{!the modal has to go here so that if you provide a slot to trigger it doesn't get rendered}}
|
||||||
|
{{#modal-dialog data-test-policy-form name="new-policy-toggle"}}
|
||||||
|
{{#block-slot 'header'}}
|
||||||
|
<h2>New Policy</h2>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'body'}}
|
||||||
|
{{policy-form form=form dc=dc}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'actions' as |close|}}
|
||||||
|
<button type="submit" {{action 'save' item items (queue (action close) (action 'reset'))}} disabled={{if (or item.isSaving item.isPristine item.isInvalid) 'disabled'}}>
|
||||||
|
{{#if item.isSaving }}
|
||||||
|
<div class="progress indeterminate"></div>
|
||||||
|
{{/if}}
|
||||||
|
<span>Create and apply</span>
|
||||||
|
</button>
|
||||||
|
<button type="reset" disabled={{if item.isSaving 'disabled'}} {{action (queue (action close) (action 'reset'))}}>Cancel</button>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/modal-dialog}}
|
||||||
|
{{/yield-slot}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'option' as |option|}}
|
||||||
|
{{option.Name}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'set'}}
|
||||||
|
{{#tabular-details
|
||||||
|
data-test-policies
|
||||||
|
onchange=(action 'loadItem')
|
||||||
|
items=(sort-by 'CreateTime:desc' 'Name:asc' items) as |item index|
|
||||||
|
}}
|
||||||
|
{{#block-slot 'header'}}
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Datacenters</th>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'row'}}
|
||||||
|
<td class={{policy/typeof item}}>
|
||||||
|
{{#if item.ID }}
|
||||||
|
<a href={{href-to 'dc.acls.policies.edit' item.ID}}>{{item.Name}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a name={{item.Name}}>{{item.Name}}</a>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{if (not item.isSaving) (join ', ' (policy/datacenters item)) 'Saving...'}}
|
||||||
|
</td>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'details'}}
|
||||||
|
<label class="type-text">
|
||||||
|
<span>Rules <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
|
||||||
|
{{#if (eq item.template 'default')}}
|
||||||
|
{{code-editor syntax='hcl' readonly=true value=item.Rules}}
|
||||||
|
{{else}}
|
||||||
|
{{#code-editor syntax='hcl' readonly=true}}
|
||||||
|
{{~component 'service-identity' name=item.Name~}}
|
||||||
|
{{/code-editor}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
{{#confirmation-dialog message='Are you sure you want to remove this policy from this token?'}}
|
||||||
|
{{#block-slot 'action' as |confirm|}}
|
||||||
|
<button data-test-delete type="button" class="type-delete" {{action confirm 'remove' item items}}>Remove</button>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||||
|
<p>
|
||||||
|
{{message}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button type="button" class="type-delete" {{action execute}}>Confirm remove</button>
|
||||||
|
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/confirmation-dialog}}
|
||||||
|
</div>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/tabular-details}}
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/child-selector}}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{{yield}}
|
||||||
|
<fieldset>
|
||||||
|
<label class="type-text{{if item.error.Name ' has-error'}}">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" value={{item.Name}} name="role[Name]" autofocus="autofocus" oninput={{action 'change'}} />
|
||||||
|
<em>
|
||||||
|
Maximum 256 characters. May only include letters (uppercase and/or lowercase) and/or numbers. Must be unique.
|
||||||
|
</em>
|
||||||
|
{{#if item.error.Name}}
|
||||||
|
<strong>{{item.error.Name.validation}}</strong>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<label class="type-text">
|
||||||
|
<span>Description (Optional)</span>
|
||||||
|
<textarea name="role[Description]" value={{item.Description}} oninput={{action 'change'}}></textarea>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
{{!TODO: temporary policies id, look at the inception token modals and get rid of id="policies" and use something else}}
|
||||||
|
<fieldset id="policies" class="policies">
|
||||||
|
<h2>Policies</h2>
|
||||||
|
{{#yield-slot 'policy' (block-params item)}}
|
||||||
|
{{yield}}
|
||||||
|
{{else}}
|
||||||
|
{{policy-selector dc=dc items=item.Policies}}
|
||||||
|
{{/yield-slot}}
|
||||||
|
</fieldset>
|
|
@ -0,0 +1,106 @@
|
||||||
|
{{#modal-dialog data-test-role-form onclose=(action (mut state) 'role') name="new-role-toggle"}}
|
||||||
|
{{#block-slot 'header'}}
|
||||||
|
{{#if (eq state 'role')}}
|
||||||
|
<h2>New Role</h2>
|
||||||
|
{{else}}
|
||||||
|
<h2>New Policy</h2>
|
||||||
|
{{/if}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'body'}}
|
||||||
|
|
||||||
|
<input id="{{name}}_state_role" type="radio" name="{{name}}[state]" value="role" checked={{eq state 'role'}} onchange={{action 'change'}} />
|
||||||
|
{{#role-form form=form dc=dc}}
|
||||||
|
{{#block-slot 'policy'}}
|
||||||
|
|
||||||
|
{{#policy-selector source=source dc=dc items=item.Policies}}
|
||||||
|
{{#block-slot 'trigger'}}
|
||||||
|
<label for="{{name}}_state_policy" data-test-create-policy class="type-dialog">
|
||||||
|
<span>Create new policy</span>
|
||||||
|
</label>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/policy-selector}}
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/role-form}}
|
||||||
|
|
||||||
|
<input id="{{name}}_state_policy" type="radio" name="{{name}}[state]" value="policy" checked={{eq state 'policy'}} onchange={{action 'change'}} />
|
||||||
|
{{policy-form data-test-policy-form name="role[policy]" form=policyForm dc=dc}}
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'actions' as |close|}}
|
||||||
|
|
||||||
|
{{#if (eq state 'role')}}
|
||||||
|
<button type="submit" {{action 'save' item items (queue (action close) (action 'reset'))}} disabled={{if (or item.isSaving item.isPristine item.isInvalid) 'disabled'}}>
|
||||||
|
{{#if item.isSaving }}
|
||||||
|
<div class="progress indeterminate"></div>
|
||||||
|
{{/if}}
|
||||||
|
<span>Create and apply</span>
|
||||||
|
</button>
|
||||||
|
<button type="reset" disabled={{if item.isSaving 'disabled'}} {{action (queue (action close) (action 'reset'))}}>Cancel</button>
|
||||||
|
{{else}}
|
||||||
|
<button type="submit" {{action 'dispatch' 'save' (array policy item.Policies (action (mut state) 'role'))}} disabled={{if (or policy.isSaving policy.isPristine policy.isInvalid) 'disabled'}}>
|
||||||
|
{{#if policy.isSaving }}
|
||||||
|
<div class="progress indeterminate"></div>
|
||||||
|
{{/if}}
|
||||||
|
<span>Create and apply</span>
|
||||||
|
</button>
|
||||||
|
<button type="reset" disabled={{if policy.isSaving 'disabled'}} {{action (mut state) 'role'}}>Cancel</button>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/modal-dialog}}
|
||||||
|
|
||||||
|
{{#child-selector repo=repo dc=dc type="role" placeholder="Search for role" items=items}}
|
||||||
|
{{#block-slot 'label'}}
|
||||||
|
Apply an existing role
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'create'}}
|
||||||
|
<label class="type-dialog" for="new-role-toggle">
|
||||||
|
<span>Create new role</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'option' as |option|}}
|
||||||
|
{{option.Name}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'set'}}
|
||||||
|
{{#tabular-collection
|
||||||
|
data-test-roles
|
||||||
|
rows=5
|
||||||
|
items=(sort-by 'CreateTime:desc' 'Name:asc' items) as |item index|
|
||||||
|
}}
|
||||||
|
{{#block-slot 'header'}}
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'row'}}
|
||||||
|
<td>
|
||||||
|
<a href={{href-to 'dc.acls.roles.edit' item.ID}}>{{item.Name}}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{item.Description}}
|
||||||
|
</td>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'actions' as |index change checked|}}
|
||||||
|
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to remove this Role?"}}
|
||||||
|
{{#block-slot 'action' as |confirm|}}
|
||||||
|
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a data-test-edit href={{href-to 'dc.acls.roles.edit' item.ID}}>Edit</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="type-delete" data-test-delete {{action confirm 'remove' item items}}>Remove</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{/action-group}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 'dialog' as |execute cancel message name|}}
|
||||||
|
{{delete-confirmation message=message execute=execute cancel=cancel}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/confirmation-dialog}}
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/tabular-collection}}
|
||||||
|
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/child-selector}}
|
|
@ -0,0 +1,12 @@
|
||||||
|
service "{{name}}" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "{{name}}-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{{!<nav role="tablist">}}
|
{{!<nav role="tablist">}}
|
||||||
<ul>
|
<ul>
|
||||||
{{#each items as |item|}}
|
{{#each items as |item|}}
|
||||||
<li class={{if (eq selected (if item.label (slugify item.label) (slugify item))) 'selected'}}>
|
<li class={{if (or item.selected (eq selected (if item.label (slugify item.label) (slugify item)))) 'selected'}}>
|
||||||
<label for="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}" data-test-radiobutton="{{name}}_{{if item.label (slugify item.label) (slugify item)}}" role="tab" aria-controls="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}_panel">
|
<label for="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}" data-test-radiobutton="{{name}}_{{if item.label (slugify item.label) (slugify item)}}" role="tab" aria-controls="radiogroup_{{name}}_{{if item.label (slugify item.label) (slugify item)}}_panel">
|
||||||
{{#if item.href }}
|
{{#if item.href }}
|
||||||
<a href="{{ item.href }}">{{ item.label }}</a>
|
<a href="{{ item.href }}">{{ item.label }}</a>
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
</thead>
|
</thead>
|
||||||
{{#ember-native-scrollable tagName='tbody' content-size=_contentSize scroll-left=_scrollLeft scroll-top=_scrollTop scrollChange=(action "scrollChange") clientSizeChange=(action "clientSizeChange")}}
|
{{#ember-native-scrollable tagName='tbody' content-size=_contentSize scroll-left=_scrollLeft scroll-top=_scrollTop scrollChange=(action "scrollChange") clientSizeChange=(action "clientSizeChange")}}
|
||||||
<tr></tr>
|
<tr></tr>
|
||||||
{{~#each _cells as |cell|~}}
|
{{~#each _cells as |cell index|~}}
|
||||||
<tr data-test-tabular-row style={{{cell.style}}} onclick={{action 'click'}}>
|
<tr data-test-tabular-row style={{{cell.style}}} onclick={{action 'click'}}>
|
||||||
{{#yield-slot 'row'}}{{yield cell.item cell.index}}{{/yield-slot}}
|
{{#yield-slot 'row'}}{{yield cell.item index}}{{/yield-slot}}
|
||||||
{{#if hasActions }}
|
{{#if hasActions }}
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
{{#yield-slot 'actions' (block-params (concat cell.index) change checked)}}{{yield cell.item cell.index}}{{/yield-slot}}
|
{{#yield-slot 'actions' (block-params (concat index) change checked)}}{{yield cell.item index}}{{/yield-slot}}
|
||||||
</td>
|
</td>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{yield}}
|
{{yield}}
|
||||||
<table class="with-details">
|
<table class="with-details has-actions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
|
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<input type="checkbox" value={{index}} name={{name}} id={{concat inputId index}} onchange={{action 'change' item}} />
|
<input type="checkbox" checked={{ not (is-empty item.closed) }} value={{index}} name={{name}} id={{concat inputId index}} onchange={{action 'change' item items}} />
|
||||||
<div>
|
<div>
|
||||||
<label for={{concat inputId index}}><span>Hide details</span></label>
|
<label for={{concat inputId index}}><span>Hide details</span></label>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{{#if (gt items.length 0)}}
|
{{#if (gt items.length 0)}}
|
||||||
{{#tabular-collection
|
{{#tabular-collection
|
||||||
data-test-tokens
|
data-test-tokens
|
||||||
|
class='token-list'
|
||||||
items=(sort-by 'AccessorID:asc' items) as |item index|
|
items=(sort-by 'AccessorID:asc' items) as |item index|
|
||||||
}}
|
}}
|
||||||
{{#if caption}}
|
{{#if caption}}
|
||||||
|
|
|
@ -3,11 +3,17 @@
|
||||||
(hash
|
(hash
|
||||||
label='Tokens'
|
label='Tokens'
|
||||||
href=(href-to 'dc.acls.tokens')
|
href=(href-to 'dc.acls.tokens')
|
||||||
|
selected=(is-href 'dc.acls.tokens')
|
||||||
|
)
|
||||||
|
(hash
|
||||||
|
label='Roles'
|
||||||
|
href=(href-to 'dc.acls.roles')
|
||||||
|
selected=(is-href 'dc.acls.roles')
|
||||||
)
|
)
|
||||||
(hash
|
(hash
|
||||||
label='Policies'
|
label='Policies'
|
||||||
href=(href-to 'dc.acls.policies')
|
href=(href-to 'dc.acls.policies')
|
||||||
|
selected=(is-href 'dc.acls.policies')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
selected=(if (is-href 'dc.acls.policies') 'policies' 'tokens')
|
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<fieldset>
|
|
||||||
<label class="type-text{{if item.error.Name ' has-error'}}">
|
|
||||||
<span>Name</span>
|
|
||||||
{{input value=item.Name name='policy[Name]' autofocus='autofocus'}}
|
|
||||||
<em>
|
|
||||||
Maximum 128 characters. May only include letters (uppercase and/or lowercase) and/or numbers. Must be unique.
|
|
||||||
</em>
|
|
||||||
{{#if item.error.Name}}
|
|
||||||
<strong>{{item.error.Name.validation}}</strong>
|
|
||||||
{{/if}}
|
|
||||||
</label>
|
|
||||||
<label class="type-text">
|
|
||||||
<span>Rules <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
|
|
||||||
{{code-editor id="policy_rules" syntax='hcl' class=(if item.error.Rules 'error') name='policy[Rules]' value=item.Rules onkeyup=(action 'change' 'policy[Rules]')}}
|
|
||||||
{{#if item.error.Rules}}
|
|
||||||
<strong>{{item.error.Rules.validation}}</strong>
|
|
||||||
{{/if}}
|
|
||||||
</label>
|
|
||||||
<div class="type-toggle">
|
|
||||||
<span>Valid datacenters</span>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="policy[isScoped]" checked={{if (not isScoped) 'checked' }} onchange={{action 'change'}} />
|
|
||||||
<span>All</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{{#if isScoped }}
|
|
||||||
<div class="checkbox-group" role="group">
|
|
||||||
{{#each datacenters as |dc| }}
|
|
||||||
<label class="type-checkbox">
|
|
||||||
<input type="checkbox" name="policy[Datacenters]" value={{dc.Name}} checked={{if (contains dc.Name item.Datacenters) 'checked' }} onchange={{action 'change'}} />
|
|
||||||
<span>{{dc.Name}}</span>
|
|
||||||
</label>
|
|
||||||
{{/each}}
|
|
||||||
{{#each item.Datacenters as |dc| }}
|
|
||||||
{{#if (not (find-by 'Name' dc datacenters))}}
|
|
||||||
<label class="type-checkbox">
|
|
||||||
<input type="checkbox" name="policy[Datacenters]" value={{dc}} checked="checked" onchange={{action 'change'}} />
|
|
||||||
<span>{{dc}}</span>
|
|
||||||
</label>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
<label class="type-text">
|
|
||||||
<span>Description (Optional)</span>
|
|
||||||
<textarea name="policy[Description]" value={{item.Description}} oninput={{action 'change'}}></textarea>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<form>
|
<form>
|
||||||
{{partial 'dc/acls/policies/fieldsets'}}
|
{{#policy-form form=form item=item}}
|
||||||
|
{{!don't show template selection here, i.e. Service Identity}}
|
||||||
|
{{block-slot 'template'}}
|
||||||
|
{{/policy-form}}
|
||||||
{{#if (not create) }}
|
{{#if (not create) }}
|
||||||
{{token-list caption="Applied to the following tokens:" items=items}}
|
{{token-list caption="Applied to the following tokens:" items=items}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -18,7 +21,7 @@
|
||||||
{{/block-slot}}
|
{{/block-slot}}
|
||||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||||
{{#if (gt items.length 0)}}
|
{{#if (gt items.length 0)}}
|
||||||
{{#modal-dialog onclose=(action cancel)}}
|
{{#modal-dialog data-test-delete-modal onclose=(action cancel)}}
|
||||||
{{#block-slot 'header'}}
|
{{#block-slot 'header'}}
|
||||||
<h2>Policy in Use</h2>
|
<h2>Policy in Use</h2>
|
||||||
{{/block-slot}}
|
{{/block-slot}}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue