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)
This commit is contained in:
parent
08c5b376e7
commit
81f209d71e
72
ui-v2/app/adapters/role.js
Normal file
72
ui-v2/app/adapters/role.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import Adapter, {
|
||||
REQUEST_CREATE,
|
||||
REQUEST_UPDATE,
|
||||
DATACENTER_QUERY_PARAM as API_DATACENTER_KEY,
|
||||
} from './application';
|
||||
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
import { PUT as HTTP_PUT } from 'consul-ui/utils/http/method';
|
||||
|
||||
import WithPolicies from 'consul-ui/mixins/policy/as-many';
|
||||
|
||||
export default Adapter.extend(WithPolicies, {
|
||||
urlForQuery: function(query, modelName) {
|
||||
return this.appendURL('acl/roles', [], this.cleanQuery(query));
|
||||
},
|
||||
urlForQueryRecord: function(query, modelName) {
|
||||
if (typeof query.id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return this.appendURL('acl/role', [query.id], this.cleanQuery(query));
|
||||
},
|
||||
urlForCreateRecord: function(modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForUpdateRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForDeleteRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('acl/role', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
handleResponse: function(status, headers, payload, requestData) {
|
||||
let response = payload;
|
||||
if (status === HTTP_OK) {
|
||||
const url = this.parseURL(requestData.url);
|
||||
switch (true) {
|
||||
case response === true:
|
||||
response = this.handleBooleanResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
break;
|
||||
case Array.isArray(response):
|
||||
response = this.handleBatchResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
break;
|
||||
default:
|
||||
response = this.handleSingleResponse(url, response, PRIMARY_KEY, SLUG_KEY);
|
||||
}
|
||||
}
|
||||
return this._super(status, headers, response, requestData);
|
||||
},
|
||||
methodForRequest: function(params) {
|
||||
switch (params.requestType) {
|
||||
case REQUEST_CREATE:
|
||||
return HTTP_PUT;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const data = this._super(...arguments);
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
case REQUEST_CREATE:
|
||||
return data.role;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
|
@ -10,12 +10,15 @@ import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
|||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
import { PUT as HTTP_PUT } from 'consul-ui/utils/http/method';
|
||||
|
||||
import WithPolicies from 'consul-ui/mixins/policy/as-many';
|
||||
import WithRoles from 'consul-ui/mixins/role/as-many';
|
||||
|
||||
import { get } from '@ember/object';
|
||||
|
||||
const REQUEST_CLONE = 'cloneRecord';
|
||||
const REQUEST_SELF = 'querySelf';
|
||||
|
||||
export default Adapter.extend({
|
||||
export default Adapter.extend(WithRoles, WithPolicies, {
|
||||
store: service('store'),
|
||||
cleanQuery: function(_query) {
|
||||
const query = this._super(...arguments);
|
||||
|
@ -108,10 +111,6 @@ export default Adapter.extend({
|
|||
return this._makeRequest(request);
|
||||
},
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
// Sometimes we get `Policies: null`, make null equal an empty array
|
||||
if (typeof response.Policies === 'undefined' || response.Policies === null) {
|
||||
response.Policies = [];
|
||||
}
|
||||
// Convert an old style update response to a new style
|
||||
if (typeof response['ID'] !== 'undefined') {
|
||||
const item = get(this, 'store')
|
||||
|
@ -169,19 +168,6 @@ export default Adapter.extend({
|
|||
}
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
if (Array.isArray(data.token.Policies)) {
|
||||
data.token.Policies = data.token.Policies.filter(function(item) {
|
||||
// Just incase, don't save any policies that aren't saved
|
||||
return !get(item, 'isNew');
|
||||
}).map(function(item) {
|
||||
return {
|
||||
ID: get(item, 'ID'),
|
||||
Name: get(item, 'Name'),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
delete data.token.Policies;
|
||||
}
|
||||
data = data.token;
|
||||
break;
|
||||
case REQUEST_SELF:
|
||||
|
|
113
ui-v2/app/components/child-selector.js
Normal file
113
ui-v2/app/components/child-selector.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
import Component from '@ember/component';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
import SlotsMixin from 'block-slots';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
|
||||
export default Component.extend(SlotsMixin, WithListeners, {
|
||||
onchange: function() {},
|
||||
|
||||
error: function() {},
|
||||
type: '',
|
||||
|
||||
dom: service('dom'),
|
||||
container: service('search'),
|
||||
formContainer: service('form'),
|
||||
|
||||
item: alias('form.data'),
|
||||
|
||||
selectedOptions: alias('items'),
|
||||
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.searchable = get(this, 'container').searchable(get(this, 'type'));
|
||||
this.form = get(this, 'formContainer').form(get(this, 'type'));
|
||||
this.form.clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
options: computed('selectedOptions.[]', 'allOptions.[]', function() {
|
||||
// It's not massively important here that we are defaulting `items` and
|
||||
// losing reference as its just to figure out the diff
|
||||
let options = get(this, 'allOptions') || [];
|
||||
const items = get(this, 'selectedOptions') || [];
|
||||
if (get(items, 'length') > 0) {
|
||||
// find a proper ember-data diff
|
||||
options = options.filter(item => !items.findBy('ID', get(item, 'ID')));
|
||||
this.searchable.add(options);
|
||||
}
|
||||
return options;
|
||||
}),
|
||||
actions: {
|
||||
search: function(term) {
|
||||
// TODO: make sure we can either search before things are loaded
|
||||
// or wait until we are loaded, guess power select take care of that
|
||||
return new Promise(resolve => {
|
||||
const remove = this.listen(this.searchable, 'change', function(e) {
|
||||
remove();
|
||||
resolve(e.target.data);
|
||||
});
|
||||
this.searchable.search(term);
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
get(this, 'form').clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
open: function() {
|
||||
if (!get(this, 'allOptions.closed')) {
|
||||
set(this, 'allOptions', get(this, 'repo').findAllByDatacenter(get(this, 'dc')));
|
||||
}
|
||||
},
|
||||
save: function(item, items, success = function() {}) {
|
||||
// Specifically this saves an 'new' option/child
|
||||
// and then adds it to the selectedOptions, not options
|
||||
const repo = get(this, 'repo');
|
||||
set(item, 'CreateTime', new Date().getTime());
|
||||
// TODO: temporary async
|
||||
// this should be `set(this, 'item', repo.persist(item));`
|
||||
// need to be sure that its saved before adding/closing the modal for now
|
||||
// and we don't open the modal on prop change yet
|
||||
item = repo.persist(item);
|
||||
this.listen(item, 'message', e => {
|
||||
this.actions.change.bind(this)(
|
||||
{
|
||||
target: {
|
||||
name: 'items[]',
|
||||
value: items,
|
||||
},
|
||||
},
|
||||
items,
|
||||
e.data
|
||||
);
|
||||
success();
|
||||
});
|
||||
this.listen(item, 'error', this.error.bind(this));
|
||||
},
|
||||
remove: function(item, items) {
|
||||
const prop = get(this, 'repo').getSlugKey();
|
||||
const value = get(item, prop);
|
||||
const pos = items.findIndex(function(item) {
|
||||
return get(item, prop) === value;
|
||||
});
|
||||
if (pos !== -1) {
|
||||
return items.removeAt(pos, 1);
|
||||
}
|
||||
this.onchange({ target: this });
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(...arguments);
|
||||
const items = value;
|
||||
switch (event.target.name) {
|
||||
case 'items[]':
|
||||
set(item, 'CreateTime', new Date().getTime());
|
||||
// this always happens synchronously
|
||||
items.pushObject(item);
|
||||
// TODO: Fire a proper event
|
||||
this.onchange({ target: this });
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -11,31 +11,57 @@ const DEFAULTS = {
|
|||
};
|
||||
export default Component.extend({
|
||||
settings: service('settings'),
|
||||
dom: service('dom'),
|
||||
helper: service('code-mirror/linter'),
|
||||
classNames: ['code-editor'],
|
||||
readonly: false,
|
||||
syntax: '',
|
||||
onchange: function(value) {
|
||||
get(this, 'settings').persist({
|
||||
'code-editor': value,
|
||||
});
|
||||
this.setMode(value);
|
||||
},
|
||||
// TODO: Change this to oninput to be consistent? We'll have to do it throughout the templates
|
||||
onkeyup: function() {},
|
||||
oninput: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'modes', get(this, 'helper').modes());
|
||||
},
|
||||
didReceiveAttrs: function() {
|
||||
this._super(...arguments);
|
||||
const editor = get(this, 'editor');
|
||||
if (editor) {
|
||||
editor.setOption('readOnly', get(this, 'readonly'));
|
||||
}
|
||||
},
|
||||
setMode: function(mode) {
|
||||
set(this, 'options', {
|
||||
...DEFAULTS,
|
||||
mode: mode.mime,
|
||||
readOnly: get(this, 'readonly'),
|
||||
});
|
||||
const editor = get(this, 'editor');
|
||||
editor.setOption('mode', mode.mime);
|
||||
get(this, 'helper').lint(editor, mode.mode);
|
||||
set(this, 'mode', mode);
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
const $code = get(this, 'dom').element('textarea ~ pre code', get(this, 'element'));
|
||||
if ($code.firstChild) {
|
||||
this.observer = new MutationObserver(([e]) => {
|
||||
this.oninput(set(this, 'value', e.target.wholeText));
|
||||
});
|
||||
this.observer.observe($code, {
|
||||
attributes: false,
|
||||
subtree: true,
|
||||
childList: false,
|
||||
characterData: true,
|
||||
});
|
||||
set(this, 'value', $code.firstChild.wholeText);
|
||||
}
|
||||
set(this, 'editor', get(this, 'helper').getEditor(this.element));
|
||||
get(this, 'settings')
|
||||
.findBySlug('code-editor')
|
||||
|
@ -54,4 +80,12 @@ export default Component.extend({
|
|||
didAppear: function() {
|
||||
get(this, 'editor').refresh();
|
||||
},
|
||||
actions: {
|
||||
change: function(value) {
|
||||
get(this, 'settings').persist({
|
||||
'code-editor': value,
|
||||
});
|
||||
this.setMode(value);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
42
ui-v2/app/components/form-component.js
Normal file
42
ui-v2/app/components/form-component.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import Component from '@ember/component';
|
||||
import SlotsMixin from 'block-slots';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import WithListeners from 'consul-ui/mixins/with-listeners';
|
||||
// match anything that isn't a [ or ] into multiple groups
|
||||
const propRe = /([^[\]])+/g;
|
||||
export default Component.extend(WithListeners, SlotsMixin, {
|
||||
onreset: function() {},
|
||||
onchange: function() {},
|
||||
onerror: function() {},
|
||||
onsuccess: function() {},
|
||||
|
||||
data: alias('form.data'),
|
||||
item: alias('form.data'),
|
||||
// TODO: Could probably alias item
|
||||
// or just use data/value instead
|
||||
|
||||
dom: service('dom'),
|
||||
container: service('form'),
|
||||
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
let event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const matches = [...event.target.name.matchAll(propRe)];
|
||||
const prop = matches[matches.length - 1][0];
|
||||
event = get(this, 'dom').normalizeEvent(
|
||||
`${get(this, 'type')}[${prop}]`,
|
||||
event.target.value,
|
||||
event.target
|
||||
);
|
||||
const form = get(this, 'form');
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
this.onchange({ target: this });
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -38,9 +38,11 @@ export default DomBufferComponent.extend(SlotsMixin, WithResizing, {
|
|||
_close: function(e) {
|
||||
set(this, 'checked', false);
|
||||
const dialogPanel = get(this, 'dialog');
|
||||
const overflowing = get(this, 'overflowingClass');
|
||||
if (dialogPanel.classList.contains(overflowing)) {
|
||||
dialogPanel.classList.remove(overflowing);
|
||||
if (dialogPanel) {
|
||||
const overflowing = get(this, 'overflowingClass');
|
||||
if (dialogPanel.classList.contains(overflowing)) {
|
||||
dialogPanel.classList.remove(overflowing);
|
||||
}
|
||||
}
|
||||
// TODO: should we make a didDisappear?
|
||||
get(this, 'dom')
|
||||
|
@ -108,7 +110,7 @@ export default DomBufferComponent.extend(SlotsMixin, WithResizing, {
|
|||
if (get(e, 'target.checked')) {
|
||||
this._open(e);
|
||||
} else {
|
||||
this._close();
|
||||
this._close(e);
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
|
|
53
ui-v2/app/components/policy-form.js
Normal file
53
ui-v2/app/components/policy-form.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import FormComponent from './form-component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
|
||||
export default FormComponent.extend({
|
||||
repo: service('repository/policy/component'),
|
||||
datacenterRepo: service('repository/dc/component'),
|
||||
type: 'policy',
|
||||
name: 'policy',
|
||||
classNames: ['policy-form'],
|
||||
|
||||
isScoped: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'isScoped', get(this, 'item.Datacenters.length') > 0);
|
||||
set(this, 'datacenters', get(this, 'datacenterRepo').findAll());
|
||||
this.templates = [
|
||||
{
|
||||
name: 'Policy',
|
||||
template: '',
|
||||
},
|
||||
{
|
||||
name: 'Service Identity',
|
||||
template: 'service-identity',
|
||||
},
|
||||
];
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
try {
|
||||
this._super(...arguments);
|
||||
} catch (err) {
|
||||
const scoped = get(this, 'isScoped');
|
||||
const name = err.target.name;
|
||||
switch (name) {
|
||||
case 'policy[isScoped]':
|
||||
if (scoped) {
|
||||
set(this, 'previousDatacenters', get(this.item, 'Datacenters'));
|
||||
set(this.item, 'Datacenters', null);
|
||||
} else {
|
||||
set(this.item, 'Datacenters', get(this, 'previousDatacenters'));
|
||||
set(this, 'previousDatacenters', null);
|
||||
}
|
||||
set(this, 'isScoped', !scoped);
|
||||
break;
|
||||
default:
|
||||
this.onerror(err);
|
||||
}
|
||||
this.onchange({ target: get(this, 'form') });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
82
ui-v2/app/components/policy-selector.js
Normal file
82
ui-v2/app/components/policy-selector.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import ChildSelectorComponent from './child-selector';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import updateArrayObject from 'consul-ui/utils/update-array-object';
|
||||
|
||||
const ERROR_PARSE_RULES = 'Failed to parse ACL rules';
|
||||
const ERROR_NAME_EXISTS = 'Invalid Policy: A Policy with Name';
|
||||
|
||||
export default ChildSelectorComponent.extend({
|
||||
repo: service('repository/policy/component'),
|
||||
datacenterRepo: service('repository/dc/component'),
|
||||
name: 'policy',
|
||||
type: 'policy',
|
||||
classNames: ['policy-selector'],
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
const source = get(this, 'source');
|
||||
if (source) {
|
||||
const event = 'save';
|
||||
this.listen(source, event, e => {
|
||||
this.actions[event].bind(this)(...e.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
reset: function(e) {
|
||||
this._super(...arguments);
|
||||
set(this, 'isScoped', false);
|
||||
set(this, 'datacenters', get(this, 'datacenterRepo').findAll());
|
||||
},
|
||||
refreshCodeEditor: function(e, target) {
|
||||
const selector = '.code-editor';
|
||||
get(this, 'dom')
|
||||
.component(selector, target)
|
||||
.didAppear();
|
||||
},
|
||||
error: function(e) {
|
||||
const item = get(this, 'item');
|
||||
const err = e.error;
|
||||
if (typeof err.errors !== 'undefined') {
|
||||
const error = err.errors[0];
|
||||
let prop;
|
||||
let message = error.detail;
|
||||
switch (true) {
|
||||
case message.indexOf(ERROR_PARSE_RULES) === 0:
|
||||
prop = 'Rules';
|
||||
message = error.detail;
|
||||
break;
|
||||
case message.indexOf(ERROR_NAME_EXISTS) === 0:
|
||||
prop = 'Name';
|
||||
message = message.substr(ERROR_NAME_EXISTS.indexOf(':') + 1);
|
||||
break;
|
||||
}
|
||||
if (prop) {
|
||||
item.addError(prop, message);
|
||||
}
|
||||
} else {
|
||||
// TODO: Conponents can't throw, use onerror
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
loadItem: function(e, item, items) {
|
||||
const target = e.target;
|
||||
// the Details expander toggle, only load on opening
|
||||
if (target.checked) {
|
||||
const value = item;
|
||||
this.refreshCodeEditor(e, target.parentNode);
|
||||
if (get(item, 'template') === 'service-identity') {
|
||||
return;
|
||||
}
|
||||
// potentially the item could change between load, so we don't check
|
||||
// anything to see if its already loaded here
|
||||
const repo = get(this, 'repo');
|
||||
// TODO: Temporarily add dc here, will soon be serialized onto the policy itself
|
||||
const dc = get(this, 'dc');
|
||||
const slugKey = repo.getSlugKey();
|
||||
const slug = get(value, slugKey);
|
||||
updateArrayObject(items, repo.findBySlug(slug, dc), slugKey, slug);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
6
ui-v2/app/components/role-form.js
Normal file
6
ui-v2/app/components/role-form.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import FormComponent from './form-component';
|
||||
export default FormComponent.extend({
|
||||
type: 'role',
|
||||
name: 'role',
|
||||
classNames: ['role-form'],
|
||||
});
|
42
ui-v2/app/components/role-selector.js
Normal file
42
ui-v2/app/components/role-selector.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import ChildSelectorComponent from './child-selector';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
import { CallableEventSource as EventSource } from 'consul-ui/utils/dom/event-source';
|
||||
|
||||
export default ChildSelectorComponent.extend({
|
||||
repo: service('repository/role/component'),
|
||||
name: 'role',
|
||||
type: 'role',
|
||||
classNames: ['role-selector'],
|
||||
state: 'role',
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.policyForm = get(this, 'formContainer').form('policy');
|
||||
this.source = new EventSource();
|
||||
},
|
||||
// You have to alias data
|
||||
// is you just set it it loses its reference?
|
||||
policy: alias('policyForm.data'),
|
||||
actions: {
|
||||
reset: function(e) {
|
||||
this._super(...arguments);
|
||||
get(this, 'policyForm').clear({ Datacenter: get(this, 'dc') });
|
||||
},
|
||||
dispatch: function(type, data) {
|
||||
this.source.dispatchEvent({ type: type, data: data });
|
||||
},
|
||||
change: function() {
|
||||
const event = get(this, 'dom').normalizeEvent(...arguments);
|
||||
switch (event.target.name) {
|
||||
case 'role[state]':
|
||||
set(this, 'state', event.target.value);
|
||||
break;
|
||||
default:
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
5
ui-v2/app/components/service-identity.js
Normal file
5
ui-v2/app/components/service-identity.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
|
@ -80,13 +80,16 @@ const change = function(e) {
|
|||
// therefore we don't need to calculate
|
||||
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
|
||||
const dom = get(this, 'dom');
|
||||
|
||||
const $tr = dom.closest('tr', e.currentTarget);
|
||||
const $group = dom.sibling(e.currentTarget, 'ul');
|
||||
const $footer = dom.element('footer[role="contentinfo"]');
|
||||
const groupRect = $group.getBoundingClientRect();
|
||||
const footerRect = $footer.getBoundingClientRect();
|
||||
const groupBottom = groupRect.top + $group.clientHeight;
|
||||
|
||||
const $footer = dom.element('footer[role="contentinfo"]');
|
||||
const footerRect = $footer.getBoundingClientRect();
|
||||
const footerTop = footerRect.top;
|
||||
|
||||
if (groupBottom > footerTop) {
|
||||
$group.classList.add('above');
|
||||
} else {
|
||||
|
@ -111,6 +114,7 @@ const change = function(e) {
|
|||
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
||||
tagName: 'table',
|
||||
classNames: ['dom-recycling'],
|
||||
classNameBindings: ['hasActions'],
|
||||
attributeBindings: ['style'],
|
||||
width: 1150,
|
||||
rowHeight: 50,
|
||||
|
@ -128,13 +132,14 @@ export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
|||
},
|
||||
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
|
||||
const maxRows = get(this, 'rows');
|
||||
let rows = get(this._items || [], 'length');
|
||||
let height = get(this, 'maxHeight');
|
||||
if (maxRows) {
|
||||
let rows = Math.max(3, get(this._items || [], 'length'));
|
||||
rows = Math.min(maxRows, rows);
|
||||
height = get(this, 'rowHeight') * rows + 29;
|
||||
}
|
||||
const height = get(this, 'rowHeight') * rows + 29;
|
||||
return {
|
||||
height: Math.min(get(this, 'maxHeight'), height),
|
||||
height: height,
|
||||
};
|
||||
}),
|
||||
resize: function(e) {
|
||||
|
|
|
@ -19,8 +19,8 @@ export default Component.extend(SlotsMixin, {
|
|||
click: function(e) {
|
||||
get(this, 'dom').clickFirstAnchor(e);
|
||||
},
|
||||
change: function(item, e) {
|
||||
this.onchange(e, item);
|
||||
change: function(item, items, e) {
|
||||
this.onchange(e, item, items);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
dom: service('dom'),
|
||||
isScoped: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('policy');
|
||||
|
@ -21,25 +19,5 @@ export default Controller.extend({
|
|||
return prev;
|
||||
}, model)
|
||||
);
|
||||
set(this, 'isScoped', get(model.item, 'Datacenters.length') > 0);
|
||||
},
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
const 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
2
ui-v2/app/controllers/dc/acls/roles/create.js
Normal file
2
ui-v2/app/controllers/dc/acls/roles/create.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Controller from './edit';
|
||||
export default Controller.extend();
|
23
ui-v2/app/controllers/dc/acls/roles/edit.js
Normal file
23
ui-v2/app/controllers/dc/acls/roles/edit.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = get(this, 'builder').form('role');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
// essentially this replaces the data with changesets
|
||||
this._super(
|
||||
Object.keys(model).reduce((prev, key, i) => {
|
||||
switch (key) {
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
});
|
23
ui-v2/app/controllers/dc/acls/roles/index.js
Normal file
23
ui-v2/app/controllers/dc/acls/roles/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { get, computed } from '@ember/object';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
role: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items', function() {
|
||||
return get(this, 'searchables.role')
|
||||
.add(get(this, 'items'))
|
||||
.search(get(this, this.searchParams.role));
|
||||
}),
|
||||
actions: {},
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
export default Controller.extend({
|
||||
dom: service('dom'),
|
||||
builder: service('form'),
|
||||
|
@ -17,33 +17,12 @@ export default Controller.extend({
|
|||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
case 'policy':
|
||||
prev[key] = this.form
|
||||
.form(key)
|
||||
.setData(prev[key])
|
||||
.getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
actions: {
|
||||
sendClearPolicy: function(item) {
|
||||
set(this, 'isScoped', false);
|
||||
this.send('clearPolicy');
|
||||
},
|
||||
sendCreatePolicy: function(item, policies, success) {
|
||||
this.send('createPolicy', item, policies, success);
|
||||
},
|
||||
refreshCodeEditor: function(selector, parent) {
|
||||
if (parent.target) {
|
||||
parent = undefined;
|
||||
}
|
||||
get(this, 'dom')
|
||||
.component(selector, parent)
|
||||
.didAppear();
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const event = get(this, 'dom').normalizeEvent(e, value);
|
||||
const form = get(this, 'form');
|
||||
|
@ -52,24 +31,6 @@ export default Controller.extend({
|
|||
} catch (err) {
|
||||
const target = event.target;
|
||||
switch (target.name) {
|
||||
case 'policy[isScoped]':
|
||||
set(this, 'isScoped', !get(this, 'isScoped'));
|
||||
set(this.policy, 'Datacenters', null);
|
||||
break;
|
||||
case 'Policy':
|
||||
set(value, 'CreateTime', new Date().getTime());
|
||||
get(this, 'item.Policies').pushObject(value);
|
||||
break;
|
||||
case 'Details':
|
||||
// the Details expander toggle
|
||||
// only load on opening
|
||||
if (target.checked) {
|
||||
this.send('refreshCodeEditor', '.code-editor', target.parentNode);
|
||||
if (!get(value, 'Rules')) {
|
||||
this.send('loadPolicy', value, get(this, 'item.Policies'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import validations from 'consul-ui/validations/acl';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = '', v = validations, form = builder) {
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import validations from 'consul-ui/validations/intention';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = '', v = validations, form = builder) {
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import validations from 'consul-ui/validations/kv';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = '', v = validations, form = builder) {
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import validations from 'consul-ui/validations/policy';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = 'policy', v = validations, form = builder) {
|
||||
export default function(container, name = 'policy', v = validations, form = builder) {
|
||||
return form(name, {
|
||||
Datacenters: {
|
||||
type: 'array',
|
||||
|
|
8
ui-v2/app/forms/role.js
Normal file
8
ui-v2/app/forms/role.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import validations from 'consul-ui/validations/role';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = 'role', v = validations, form = builder) {
|
||||
return form(name, {})
|
||||
.setValidators(v)
|
||||
.add(container.form('policy'));
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import validations from 'consul-ui/validations/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(name = '', v = validations, form = builder) {
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {})
|
||||
.setValidators(v)
|
||||
.add(policy());
|
||||
.add(container.form('policy'))
|
||||
.add(container.form('role'));
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
import { get } from '@ember/object';
|
||||
const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001';
|
||||
export function isManagement(params, hash) {
|
||||
return get(params[0], 'ID') === MANAGEMENT_ID;
|
||||
}
|
||||
|
||||
export default helper(isManagement);
|
18
ui-v2/app/helpers/policy/typeof.js
Normal file
18
ui-v2/app/helpers/policy/typeof.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
import { get } from '@ember/object';
|
||||
const MANAGEMENT_ID = '00000000-0000-0000-0000-000000000001';
|
||||
export function typeOf(params, hash) {
|
||||
const item = params[0];
|
||||
switch (true) {
|
||||
case get(item, 'ID') === MANAGEMENT_ID:
|
||||
return 'policy-management';
|
||||
case typeof get(item, 'template') === 'undefined':
|
||||
return 'role';
|
||||
case get(item, 'template') !== '':
|
||||
return 'policy-service-identity';
|
||||
default:
|
||||
return 'policy';
|
||||
}
|
||||
}
|
||||
|
||||
export default helper(typeOf);
|
|
@ -1,21 +1,40 @@
|
|||
import { get, set } from '@ember/object';
|
||||
|
||||
import kv from 'consul-ui/forms/kv';
|
||||
import acl from 'consul-ui/forms/acl';
|
||||
import token from 'consul-ui/forms/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import role from 'consul-ui/forms/role';
|
||||
import intention from 'consul-ui/forms/intention';
|
||||
|
||||
export function initialize(application) {
|
||||
// Service-less injection using private properties at a per-project level
|
||||
const FormBuilder = application.resolveRegistration('service:form');
|
||||
const forms = {
|
||||
kv: kv(),
|
||||
acl: acl(),
|
||||
token: token(),
|
||||
policy: policy(),
|
||||
intention: intention(),
|
||||
kv: kv,
|
||||
acl: acl,
|
||||
token: token,
|
||||
policy: policy,
|
||||
role: role,
|
||||
intention: intention,
|
||||
};
|
||||
FormBuilder.reopen({
|
||||
form: function(name) {
|
||||
return forms[name];
|
||||
let form = get(this.forms, name);
|
||||
if (!form) {
|
||||
form = set(this.forms, name, forms[name](this));
|
||||
// only do special things for our new things for the moment
|
||||
if (name === 'role' || name === 'policy') {
|
||||
const repo = get(this, name);
|
||||
form.clear(function(obj) {
|
||||
return repo.create(obj);
|
||||
});
|
||||
form.submit(function(obj) {
|
||||
return repo.persist(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
return form;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
15
ui-v2/app/initializers/power-select.js
Normal file
15
ui-v2/app/initializers/power-select.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { get } from '@ember/object';
|
||||
export function initialize(application) {
|
||||
const PowerSelectComponent = application.resolveRegistration('component:power-select');
|
||||
PowerSelectComponent.reopen({
|
||||
updateState: function(changes) {
|
||||
if (!get(this, 'isDestroyed')) {
|
||||
return this._super(changes);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import intention from 'consul-ui/search/filters/intention';
|
||||
import token from 'consul-ui/search/filters/token';
|
||||
import policy from 'consul-ui/search/filters/policy';
|
||||
import role from 'consul-ui/search/filters/role';
|
||||
import kv from 'consul-ui/search/filters/kv';
|
||||
import acl from 'consul-ui/search/filters/acl';
|
||||
import node from 'consul-ui/search/filters/node';
|
||||
|
@ -19,6 +20,7 @@ export function initialize(application) {
|
|||
token: token(filterable),
|
||||
acl: acl(filterable),
|
||||
policy: policy(filterable),
|
||||
role: role(filterable),
|
||||
kv: kv(filterable),
|
||||
healthyNode: 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([
|
||||
// These are the routes where we overwrite the 'default'
|
||||
// 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',
|
||||
},
|
||||
},
|
||||
{
|
||||
service: 'form',
|
||||
services: {
|
||||
role: 'repository/role/component',
|
||||
policy: 'repository/policy/component',
|
||||
},
|
||||
},
|
||||
])
|
||||
.forEach(function(definition) {
|
||||
if (typeof definition.extend !== 'undefined') {
|
||||
|
|
70
ui-v2/app/mixins/policy/as-many.js
Normal file
70
ui-v2/app/mixins/policy/as-many.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { REQUEST_CREATE, REQUEST_UPDATE } from 'consul-ui/adapters/application';
|
||||
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import minimizeModel from 'consul-ui/utils/minimizeModel';
|
||||
|
||||
const normalizeServiceIdentities = function(items) {
|
||||
return (items || []).map(function(item) {
|
||||
const policy = {
|
||||
template: 'service-identity',
|
||||
Name: item.ServiceName,
|
||||
};
|
||||
if (typeof item.Datacenters !== 'undefined') {
|
||||
policy.Datacenters = item.Datacenters;
|
||||
}
|
||||
return policy;
|
||||
});
|
||||
};
|
||||
const normalizePolicies = function(items) {
|
||||
return (items || []).map(function(item) {
|
||||
return {
|
||||
template: '',
|
||||
...item,
|
||||
};
|
||||
});
|
||||
};
|
||||
const serializeServiceIdentities = function(items) {
|
||||
return items
|
||||
.filter(function(item) {
|
||||
return get(item, 'template') === 'service-identity';
|
||||
})
|
||||
.map(function(item) {
|
||||
const identity = {
|
||||
ServiceName: get(item, 'Name'),
|
||||
};
|
||||
if (get(item, 'Datacenters')) {
|
||||
identity.Datacenters = get(item, 'Datacenters');
|
||||
}
|
||||
return identity;
|
||||
});
|
||||
};
|
||||
const serializePolicies = function(items) {
|
||||
return items.filter(function(item) {
|
||||
return get(item, 'template') === '';
|
||||
});
|
||||
};
|
||||
|
||||
export default Mixin.create({
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
response.Policies = normalizePolicies(response.Policies).concat(
|
||||
normalizeServiceIdentities(response.ServiceIdentities)
|
||||
);
|
||||
return this._super(url, response, primary, slug);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const data = this._super(...arguments);
|
||||
const name = params.type.modelName;
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
// ServiceIdentities serialization must happen first, or a copy taken
|
||||
data[name].ServiceIdentities = serializeServiceIdentities(data[name].Policies);
|
||||
data[name].Policies = minimizeModel(serializePolicies(data[name].Policies));
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
28
ui-v2/app/mixins/role/as-many.js
Normal file
28
ui-v2/app/mixins/role/as-many.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { REQUEST_CREATE, REQUEST_UPDATE } from 'consul-ui/adapters/application';
|
||||
|
||||
import Mixin from '@ember/object/mixin';
|
||||
|
||||
import minimizeModel from 'consul-ui/utils/minimizeModel';
|
||||
|
||||
export default Mixin.create({
|
||||
handleSingleResponse: function(url, response, primary, slug) {
|
||||
['Roles'].forEach(function(prop) {
|
||||
if (typeof response[prop] === 'undefined' || response[prop] === null) {
|
||||
response[prop] = [];
|
||||
}
|
||||
});
|
||||
return this._super(url, response, primary, slug);
|
||||
},
|
||||
dataForRequest: function(params) {
|
||||
const name = params.type.modelName;
|
||||
const data = this._super(...arguments);
|
||||
switch (params.requestType) {
|
||||
case REQUEST_UPDATE:
|
||||
// falls through
|
||||
case REQUEST_CREATE:
|
||||
data[name].Roles = minimizeModel(data[name].Roles);
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
4
ui-v2/app/mixins/role/with-actions.js
Normal file
4
ui-v2/app/mixins/role/with-actions.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
|
@ -24,6 +24,10 @@ const model = Model.extend({
|
|||
Datacenters: attr(),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
|
||||
template: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
});
|
||||
export const ATTRS = writable(model, ['Name', 'Description', 'Rules', 'Datacenters']);
|
||||
export default model;
|
||||
|
|
34
ui-v2/app/models/role.js
Normal file
34
ui-v2/app/models/role.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
Name: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
Description: attr('string', {
|
||||
defaultValue: '',
|
||||
}),
|
||||
Policies: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
ServiceIdentities: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
// frontend only for ordering where CreateIndex can't be used
|
||||
CreateTime: attr('date'),
|
||||
//
|
||||
Datacenter: attr('string'),
|
||||
// TODO: Figure out whether we need this or not
|
||||
Datacenters: attr(),
|
||||
Hash: attr('string'),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
|
@ -8,6 +8,7 @@ export const SLUG_KEY = 'AccessorID';
|
|||
const model = Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
IDPName: attr('string'),
|
||||
SecretID: attr('string'),
|
||||
// Legacy
|
||||
Type: attr('string'),
|
||||
|
@ -27,7 +28,18 @@ const model = Model.extend({
|
|||
return [];
|
||||
},
|
||||
}),
|
||||
Roles: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
ServiceIdentities: attr({
|
||||
defaultValue: function() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
CreateTime: attr('date'),
|
||||
Hash: attr('string'),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
||||
|
@ -39,6 +51,7 @@ export const ATTRS = writable(model, [
|
|||
'Local',
|
||||
'Description',
|
||||
'Policies',
|
||||
'Roles',
|
||||
// SecretID isn't writable but we need it to identify an
|
||||
// update via the old API, see TokenAdapter dataForRequest
|
||||
'SecretID',
|
||||
|
|
|
@ -74,6 +74,15 @@ export const routes = {
|
|||
_options: { path: '/create' },
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
_options: { path: '/roles' },
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: { path: '/create' },
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
_options: { path: '/tokens' },
|
||||
edit: {
|
||||
|
|
|
@ -8,7 +8,6 @@ import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
|
|||
export default SingleRoute.extend(WithPolicyActions, {
|
||||
repo: service('repository/policy'),
|
||||
tokenRepo: service('repository/token'),
|
||||
datacenterRepo: service('repository/dc'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const tokenRepo = get(this, 'tokenRepo');
|
||||
|
@ -16,7 +15,6 @@ export default SingleRoute.extend(WithPolicyActions, {
|
|||
return hash({
|
||||
...model,
|
||||
...{
|
||||
datacenters: get(this, 'datacenterRepo').findAll(),
|
||||
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
|
|
6
ui-v2/app/routes/dc/acls/roles/create.js
Normal file
6
ui-v2/app/routes/dc/acls/roles/create.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default Route.extend(CreatingRoute, {
|
||||
templateName: 'dc/acls/roles/edit',
|
||||
});
|
34
ui-v2/app/routes/dc/acls/roles/edit.js
Normal file
34
ui-v2/app/routes/dc/acls/roles/edit.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
|
||||
export default SingleRoute.extend(WithRoleActions, {
|
||||
repo: service('repository/role'),
|
||||
tokenRepo: service('repository/token'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const tokenRepo = get(this, 'tokenRepo');
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: tokenRepo.findByRole(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
28
ui-v2/app/routes/dc/acls/roles/index.js
Normal file
28
ui-v2/app/routes/dc/acls/roles/index.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
|
||||
export default Route.extend(WithRoleActions, {
|
||||
repo: service('repository/role'),
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = get(this, 'repo');
|
||||
return hash({
|
||||
...repo.status({
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
}),
|
||||
isLoading: false,
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
|
@ -1,38 +1,19 @@
|
|||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { set, get } from '@ember/object';
|
||||
import updateArrayObject from 'consul-ui/utils/update-array-object';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
|
||||
|
||||
const ERROR_PARSE_RULES = 'Failed to parse ACL rules';
|
||||
const ERROR_NAME_EXISTS = 'Invalid Policy: A Policy with Name';
|
||||
export default SingleRoute.extend(WithTokenActions, {
|
||||
repo: service('repository/token'),
|
||||
policyRepo: service('repository/policy'),
|
||||
datacenterRepo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
model: function(params, transition) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const policyRepo = get(this, 'policyRepo');
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
// TODO: I only need these to create a new policy
|
||||
datacenters: get(this, 'datacenterRepo').findAll(),
|
||||
policy: this.getEmptyPolicy(),
|
||||
token: get(this, 'settings').findBySlug('token'),
|
||||
items: policyRepo.findAllByDatacenter(dc).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -40,65 +21,4 @@ export default SingleRoute.extend(WithTokenActions, {
|
|||
setupController: function(controller, 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);
|
||||
return hash({
|
||||
isLoading: false,
|
||||
dc: dc,
|
||||
create: create,
|
||||
...repo.status({
|
||||
item: create
|
||||
|
|
14
ui-v2/app/search/filters/role.js
Normal file
14
ui-v2/app/search/filters/role.js
Normal file
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
5
ui-v2/app/serializers/role.js
Normal file
5
ui-v2/app/serializers/role.js
Normal file
|
@ -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';
|
||||
const builder = builderFactory();
|
||||
export default Service.extend({
|
||||
// a `get` method is added via the form initializer
|
||||
// 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) {
|
||||
return builder(...arguments);
|
||||
},
|
||||
form: function() {},
|
||||
});
|
||||
|
|
|
@ -12,9 +12,14 @@ export default Service.extend({
|
|||
if (typeof content[prop] === 'function') {
|
||||
if (this.shouldProxy(content, prop)) {
|
||||
this[prop] = function() {
|
||||
return this.execute(content, prop).then(method => {
|
||||
return method.apply(this, arguments);
|
||||
});
|
||||
const cb = this.execute(content, prop);
|
||||
if (typeof cb.then !== 'undefined') {
|
||||
return cb.then(method => {
|
||||
return method.apply(this, arguments);
|
||||
});
|
||||
} else {
|
||||
return cb.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
} else if (typeof this[prop] !== 'function') {
|
||||
this[prop] = function() {
|
||||
|
|
|
@ -22,6 +22,16 @@ export default RepositoryService.extend({
|
|||
status: function(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) {
|
||||
return get(this, 'store').translate('policy', get(item, 'Rules'));
|
||||
},
|
||||
|
|
24
ui-v2/app/services/repository/role.js
Normal file
24
ui-v2/app/services/repository/role.js
Normal file
|
@ -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,
|
||||
});
|
||||
},
|
||||
findByRole: function(id, dc) {
|
||||
return get(this, 'store').query(this.getModelName(), {
|
||||
role: id,
|
||||
dc: dc,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
16
ui-v2/app/services/repository/type/component.js
Normal file
16
ui-v2/app/services/repository/type/component.js
Normal file
|
@ -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';
|
||||
export default Service.extend({});
|
||||
export default Service.extend({
|
||||
searchable: function() {
|
||||
return {
|
||||
addEventListener: function() {},
|
||||
removeEventListener: function() {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,7 +4,13 @@
|
|||
@import 'base/reset/index';
|
||||
@import 'variables/index';
|
||||
|
||||
/*TODO: Move this to its own local component*/
|
||||
@import 'ember-power-select';
|
||||
#ember-basic-dropdown-wormhole {
|
||||
z-index: 510;
|
||||
position: relative;
|
||||
}
|
||||
/**/
|
||||
|
||||
@import 'components/index';
|
||||
@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 {
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
content: '';
|
||||
visibility: visible;
|
||||
background-size: contain;
|
||||
}
|
||||
%with-cancel-plain-icon {
|
||||
@extend %with-icon;
|
||||
background-image: $cancel-plain-svg;
|
||||
}
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: text-top;
|
||||
}
|
|
@ -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>');
|
||||
$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>');
|
||||
$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>');
|
||||
$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-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-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>');
|
||||
|
|
494
ui-v2/app/styles/base/icons/icon-placeholders.scss
Normal file
494
ui-v2/app/styles/base/icons/icon-placeholders.scss
Normal file
|
@ -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-placeholders';
|
||||
@import './icon-placeholders';
|
||||
|
|
|
@ -9,21 +9,12 @@
|
|||
%action-group li > * {
|
||||
@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 {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 15px;
|
||||
right: 3px;
|
||||
}
|
||||
%action-group label {
|
||||
display: block;
|
||||
|
@ -38,12 +29,12 @@
|
|||
/* this is actually the group */
|
||||
%action-group ul {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
right: 0px;
|
||||
padding: 1px;
|
||||
}
|
||||
%action-group ul::before {
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
right: 9px;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 10px;
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
%action-group-action {
|
||||
cursor: pointer;
|
||||
}
|
||||
%action-group label::after,
|
||||
%action-group label::before,
|
||||
%action-group::before {
|
||||
@extend %with-dot;
|
||||
%action-group label:first-of-type::after {
|
||||
@extend %with-more-horizontal-icon, %as-pseudo;
|
||||
opacity: 0.7;
|
||||
}
|
||||
%action-group ul {
|
||||
border: $decor-border-100;
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
%main-content a {
|
||||
color: $gray-900;
|
||||
}
|
||||
%main-content a[rel*='help'] {
|
||||
@extend %with-info;
|
||||
}
|
||||
%main-content label a[rel*='help'] {
|
||||
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 {
|
||||
content: none;
|
||||
|
|
|
@ -29,3 +29,6 @@
|
|||
content: '';
|
||||
display: block;
|
||||
}
|
||||
%code-editor > pre {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ form table,
|
|||
%app-content form dl {
|
||||
@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;
|
||||
}
|
||||
%radio-group label {
|
||||
|
@ -33,6 +34,9 @@ form table,
|
|||
.checkbox-group {
|
||||
@extend %checkbox-group;
|
||||
}
|
||||
fieldset > p {
|
||||
color: $gray-400;
|
||||
}
|
||||
%toggle + .checkbox-group {
|
||||
margin-top: -1em;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@import './healthcheck-info/index';
|
||||
@import './icons/index';
|
||||
tr dl {
|
||||
tr .healthcheck-info {
|
||||
@extend %healthcheck-info;
|
||||
}
|
||||
td span.zero {
|
||||
@extend %with-no-healthchecks;
|
||||
// TODO: Why isn't this is layout?
|
||||
// TODO: Why isn't this in layout?
|
||||
display: block;
|
||||
text-indent: 20px;
|
||||
color: $gray-400;
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
%healthcheck-info {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
float: left;
|
||||
display: inline-flex;
|
||||
}
|
||||
%healthcheck-info > * {
|
||||
display: block;
|
||||
}
|
||||
%healthcheck-info dt {
|
||||
text-indent: -9000px;
|
||||
}
|
||||
%healthcheck-info dt.zero {
|
||||
display: none;
|
||||
}
|
||||
%healthcheck-info dd.zero {
|
||||
visibility: hidden;
|
||||
}
|
||||
%healthcheck-info dt {
|
||||
text-indent: -9000px;
|
||||
}
|
||||
%healthcheck-info dt.warning {
|
||||
overflow: visible;
|
||||
}
|
||||
%healthcheck-info dt.warning::before {
|
||||
top: 7px;
|
||||
}
|
||||
|
|
|
@ -149,18 +149,6 @@
|
|||
height: 0.05em;
|
||||
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 */
|
||||
%with-chevron-down::before {
|
||||
@extend %pseudo-icon-bg-img;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
%modal-dialog {
|
||||
z-index: 10000;
|
||||
z-index: 500;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
|
|
@ -2,4 +2,44 @@
|
|||
td strong,
|
||||
%tag-list span {
|
||||
@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 {
|
||||
@extend %frame-gray-900;
|
||||
border-radius: $radius-small;
|
||||
}
|
||||
%pill button {
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
%header-nav-panel {
|
||||
box-sizing: border-box;
|
||||
padding: 15px 35px;
|
||||
z-index: 10000;
|
||||
z-index: 499;
|
||||
text-align: right;
|
||||
}
|
||||
%header-nav-toggle-button {
|
||||
|
@ -79,7 +79,7 @@
|
|||
right: 0px;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
z-index: 2;
|
||||
z-index: 200;
|
||||
cursor: pointer;
|
||||
}
|
||||
%header-nav-panel {
|
||||
|
@ -88,7 +88,7 @@
|
|||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
z-index: 3;
|
||||
z-index: 300;
|
||||
padding: 0;
|
||||
padding-top: 15px;
|
||||
right: -100%;
|
||||
|
@ -167,7 +167,7 @@
|
|||
%header-drop-nav {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
z-index: 400;
|
||||
}
|
||||
%header-drop-nav a {
|
||||
text-align: left;
|
||||
|
|
|
@ -24,10 +24,9 @@ td .kind-proxy {
|
|||
@extend %type-icon, %with-proxy;
|
||||
text-indent: -9000px !important;
|
||||
width: 24px;
|
||||
margin-top: -8px;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
table:not(.sessions) tr {
|
||||
table:not(.sessions) tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
table:not(.sessions) td:first-child {
|
||||
|
@ -39,14 +38,12 @@ th {
|
|||
}
|
||||
th span {
|
||||
@extend %tooltip;
|
||||
@extend %with-info;
|
||||
margin-left: 12px;
|
||||
top: 3px;
|
||||
width: 23px;
|
||||
height: 15px;
|
||||
margin-left: 2px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
th span:after {
|
||||
left: -8px;
|
||||
th span::after {
|
||||
@extend %with-info-circle-outline-icon, %as-pseudo;
|
||||
opacity: 0.6;
|
||||
}
|
||||
th span em::after {
|
||||
@extend %tooltip-tail;
|
||||
|
@ -66,3 +63,31 @@ th span:hover em::after,
|
|||
th span:hover em {
|
||||
@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%;
|
||||
}
|
||||
%table-actions {
|
||||
width: 60px;
|
||||
width: 60px !important;
|
||||
}
|
||||
th.actions input {
|
||||
display: none;
|
||||
|
@ -10,38 +10,29 @@ th.actions input {
|
|||
th.actions {
|
||||
text-align: right;
|
||||
}
|
||||
td.actions .with-confirmation.confirming {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 1px;
|
||||
table tr {
|
||||
display: flex;
|
||||
}
|
||||
td.actions .with-confirmation.confirming p {
|
||||
margin-bottom: 1em;
|
||||
table td {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
}
|
||||
table td a {
|
||||
display: block;
|
||||
}
|
||||
table caption {
|
||||
text-align: left;
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
td > button,
|
||||
td > .with-confirmation > button {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
table th {
|
||||
padding-bottom: 0.6em;
|
||||
}
|
||||
table td,
|
||||
table td:first-child a {
|
||||
padding: 0.9em 0;
|
||||
}
|
||||
table th,
|
||||
table th:not(.actions),
|
||||
table td:not(.actions),
|
||||
table td a {
|
||||
padding-right: 0.9em;
|
||||
}
|
||||
table td a {
|
||||
display: block;
|
||||
}
|
||||
th,
|
||||
td:not(.actions),
|
||||
td:not(.actions) a {
|
||||
|
@ -49,43 +40,9 @@ td:not(.actions) a {
|
|||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* hide actions on narrow screens, you can always click in do everything from there */
|
||||
@media #{$--lt-wide-table} {
|
||||
tr > .actions {
|
||||
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;
|
||||
}
|
||||
th {
|
||||
color: $gray-400 !important;
|
||||
}
|
||||
th {
|
||||
border-color: $keyline-dark;
|
||||
border-color: $gray-300;
|
||||
}
|
||||
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: */
|
||||
/* 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 */
|
||||
html.template-service.template-list main table tr {
|
||||
@extend %services-row;
|
||||
|
@ -38,26 +75,16 @@ html.template-service.template-list main table tr {
|
|||
html.template-service.template-show #instances table tr {
|
||||
@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 {
|
||||
@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 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;
|
||||
}
|
||||
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 {
|
||||
@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 {
|
||||
@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} {
|
||||
%node-sessions-row > * {
|
||||
// (100% / 7) - (300px / 6) - (120px / 6)
|
||||
|
@ -101,37 +170,6 @@ html.template-node.template-show main table.sessions tr {
|
|||
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} {
|
||||
/* Token > Policies */
|
||||
/* Token > Your Token */
|
||||
|
@ -148,37 +186,3 @@ html.template-node.template-show main table.sessions tr {
|
|||
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 */
|
||||
%tabular-details {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
%tabular-details tr > .actions {
|
||||
@extend %table-actions;
|
||||
position: relative;
|
||||
|
@ -14,54 +10,48 @@
|
|||
@extend %toggle-button;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
}
|
||||
%tabular-details td > label {
|
||||
@extend %tabular-details-toggle-button;
|
||||
/*TODO: This needs to be figured out with %toggle-button/%action-group */
|
||||
top: 8px;
|
||||
right: 15px;
|
||||
right: 2px;
|
||||
}
|
||||
%tabular-details tr:nth-child(even) td {
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
}
|
||||
%tabular-details tr:nth-child(even) td > * {
|
||||
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 {
|
||||
@extend %tabular-details-toggle-button;
|
||||
top: 8px;
|
||||
right: 24px;
|
||||
right: 11px;
|
||||
}
|
||||
%tabular-details tr:nth-child(even) td > input:checked + * {
|
||||
display: block;
|
||||
}
|
||||
%tabular-details td:only-child {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// detail
|
||||
%tabular-detail {
|
||||
position: relative;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
width: calc(100% + 20px);
|
||||
margin-top: -48px;
|
||||
margin-top: -51px;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
%tabular-detail {
|
||||
padding: 10px;
|
||||
}
|
||||
%tabular-detail::before {
|
||||
%tabular-detail::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
%tabular-detail > div {
|
||||
pointer-events: auto;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
}
|
||||
%tabular-details td:only-child {
|
||||
cursor: default;
|
||||
border: 0;
|
||||
}
|
||||
%tabular-detail {
|
||||
border: 1px solid $gray-300;
|
||||
|
@ -18,3 +19,14 @@
|
|||
%tabular-detail > label::before {
|
||||
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
|
||||
// only add padding to dl's in edit pages
|
||||
%tag-list dd {
|
||||
display: inline-flex;
|
||||
padding-left: 0;
|
||||
}
|
||||
%tag-list dd > * {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
%tooltip-bubble,
|
||||
%tooltip-tail {
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
%button {
|
||||
font-family: $typo-family-sans;
|
||||
}
|
||||
main p,
|
||||
%modal-window p {
|
||||
margin-bottom: 1em;
|
||||
|
@ -24,33 +21,42 @@ main p,
|
|||
%footer {
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
th,
|
||||
button,
|
||||
td strong,
|
||||
td:first-child,
|
||||
|
||||
%button {
|
||||
font-family: $typo-family-sans;
|
||||
}
|
||||
/* Weighting */
|
||||
h1,
|
||||
%app-content div > dt,
|
||||
%header-drop-nav .is-active {
|
||||
font-weight: $typo-weight-bold;
|
||||
}
|
||||
h2,
|
||||
fieldset > header,
|
||||
caption,
|
||||
%header-nav,
|
||||
%healthchecked-resource header span,
|
||||
%healthcheck-output dt,
|
||||
%copy-button,
|
||||
%app-content div > dl > dt,
|
||||
td:first-child a {
|
||||
font-weight: $typo-weight-semibold;
|
||||
}
|
||||
%tbody-th,
|
||||
%form-element > span,
|
||||
%toggle label span,
|
||||
caption {
|
||||
%toggle label span {
|
||||
font-weight: $typo-weight-semibold;
|
||||
}
|
||||
%button {
|
||||
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,
|
||||
td strong,
|
||||
%breadcrumbs li > *,
|
||||
%action-group-action,
|
||||
%tab-nav,
|
||||
|
@ -58,20 +64,16 @@ th,
|
|||
%type-icon {
|
||||
font-weight: $typo-weight-medium;
|
||||
}
|
||||
main label a[rel*='help'],
|
||||
td:first-child em,
|
||||
%pill,
|
||||
%form-element > strong,
|
||||
%healthchecked-resource strong,
|
||||
%app-view h1 em {
|
||||
font-weight: $typo-weight-normal;
|
||||
}
|
||||
|
||||
/* Styling */
|
||||
%form-element > em,
|
||||
td:first-child em,
|
||||
%tbody-th em,
|
||||
%healthchecked-resource header em,
|
||||
%app-view h1 em {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Sizing */
|
||||
%footer > * {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
@ -79,20 +81,23 @@ h1 {
|
|||
font-size: $typo-header-100;
|
||||
}
|
||||
h2,
|
||||
%healthcheck-info dt,
|
||||
%header-drop-nav .is-active,
|
||||
%app-view h1 em {
|
||||
font-size: $typo-size-500;
|
||||
}
|
||||
body,
|
||||
%action-group-action,
|
||||
fieldset h2,
|
||||
fieldset > header,
|
||||
pre code,
|
||||
input,
|
||||
textarea,
|
||||
td {
|
||||
%action-group-action,
|
||||
%tbody-th {
|
||||
font-size: $typo-size-600;
|
||||
}
|
||||
th,
|
||||
td,
|
||||
caption,
|
||||
.type-dialog,
|
||||
%form-element > span,
|
||||
|
@ -105,14 +110,15 @@ caption,
|
|||
%toggle label span {
|
||||
font-size: $typo-size-700 !important;
|
||||
}
|
||||
%app-content > p:only-child,
|
||||
[role='tabpanel'] > p:only-child,
|
||||
%app-view > div.disabled > div,
|
||||
fieldset > p,
|
||||
.template-error > div,
|
||||
[role='tabpanel'] > p:only-child,
|
||||
.with-confirmation p,
|
||||
%app-content > p:only-child,
|
||||
%app-view > div.disabled > div,
|
||||
%button,
|
||||
%form-element > em,
|
||||
%form-element > strong,
|
||||
.with-confirmation p,
|
||||
%feedback-dialog-inline p {
|
||||
font-size: $typo-size-800;
|
||||
}
|
||||
|
|
|
@ -29,3 +29,10 @@ td a.is-management::after {
|
|||
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;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
%pill.policy-management {
|
||||
@extend %with-star;
|
||||
}
|
||||
%token-yours {
|
||||
text-indent: 20px;
|
||||
color: $blue-500;
|
||||
|
@ -28,6 +26,3 @@
|
|||
.template-token.template-edit dl {
|
||||
@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;
|
||||
}
|
||||
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
|
||||
@import '../../../components/pill/index';
|
||||
html.template-node.template-show td.tags span {
|
||||
@extend %pill;
|
||||
html.template-node.template-show .sessions td:last-child {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
html.template-node.template-show .sessions td:first-child {
|
||||
@extend %tbody-th;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,6 @@ $gray-025: #fafbfc;
|
|||
|
||||
$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
|
||||
// undecided
|
||||
$radius-small: $decor-radius-100;
|
||||
|
|
21
ui-v2/app/templates/components/child-selector.hbs
Normal file
21
ui-v2/app/templates/components/child-selector.hbs
Normal file
|
@ -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,18 +1,18 @@
|
|||
{{ivy-codemirror
|
||||
value=value
|
||||
readonly=readonly
|
||||
name=name
|
||||
class=class
|
||||
options=options
|
||||
valueUpdated=(action onkeyup)
|
||||
}}
|
||||
{{#if (not syntax)}}
|
||||
{{#power-select
|
||||
onchange=(action onchange)
|
||||
selected=mode
|
||||
searchEnabled=false
|
||||
options=modes as |mode|
|
||||
}}
|
||||
{{mode.name}}
|
||||
{{/power-select}}
|
||||
<pre><code>{{yield}}</code></pre>
|
||||
{{#if (and (not readonly) (not syntax))}}
|
||||
{{#power-select
|
||||
onchange=(action 'change')
|
||||
selected=mode
|
||||
searchEnabled=false
|
||||
options=modes as |mode|
|
||||
}}
|
||||
{{mode.name}}
|
||||
{{/power-select}}
|
||||
{{/if}}
|
||||
|
|
1
ui-v2/app/templates/components/form-component.hbs
Normal file
1
ui-v2/app/templates/components/form-component.hbs
Normal file
|
@ -0,0 +1 @@
|
|||
{{yield}}
|
|
@ -1,7 +1,7 @@
|
|||
{{#if (and (lt passing 1) (lt warning 1) (lt critical 1) )}}
|
||||
<span title="No Healthchecks" class="zero">0</span>
|
||||
{{else}}
|
||||
<dl>
|
||||
<dl class="healthcheck-info">
|
||||
{{healthcheck-status width=passingWidth name='passing' value=passing}}
|
||||
{{healthcheck-status width=warningWidth name='warning' value=warning}}
|
||||
{{healthcheck-status width=criticalWidth name='critical' value=critical}}
|
||||
|
|
76
ui-v2/app/templates/components/policy-form.hbs
Normal file
76
ui-v2/app/templates/components/policy-form.hbs
Normal file
|
@ -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>
|
||||
|
89
ui-v2/app/templates/components/policy-selector.hbs
Normal file
89
ui-v2/app/templates/components/policy-selector.hbs
Normal file
|
@ -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}}
|
26
ui-v2/app/templates/components/role-form.hbs
Normal file
26
ui-v2/app/templates/components/role-form.hbs
Normal file
|
@ -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>
|
106
ui-v2/app/templates/components/role-selector.hbs
Normal file
106
ui-v2/app/templates/components/role-selector.hbs
Normal file
|
@ -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}}
|
12
ui-v2/app/templates/components/service-identity.hbs
Normal file
12
ui-v2/app/templates/components/service-identity.hbs
Normal file
|
@ -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">}}
|
||||
<ul>
|
||||
{{#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">
|
||||
{{#if item.href }}
|
||||
<a href="{{ item.href }}">{{ item.label }}</a>
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
</thead>
|
||||
{{#ember-native-scrollable tagName='tbody' content-size=_contentSize scroll-left=_scrollLeft scroll-top=_scrollTop scrollChange=(action "scrollChange") clientSizeChange=(action "clientSizeChange")}}
|
||||
<tr></tr>
|
||||
{{~#each _cells as |cell|~}}
|
||||
{{~#each _cells as |cell index|~}}
|
||||
<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 }}
|
||||
<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>
|
||||
{{/if}}
|
||||
</tr>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{yield}}
|
||||
<table class="with-details">
|
||||
<table class="with-details has-actions">
|
||||
<thead>
|
||||
<tr>
|
||||
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
|
||||
|
@ -16,7 +16,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<label for={{concat inputId index}}><span>Hide details</span></label>
|
||||
<div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
{{#if (gt items.length 0)}}
|
||||
{{#tabular-collection
|
||||
data-test-tokens
|
||||
class='token-list'
|
||||
items=(sort-by 'AccessorID:asc' items) as |item index|
|
||||
}}
|
||||
{{#if caption}}
|
||||
|
|
|
@ -3,11 +3,17 @@
|
|||
(hash
|
||||
label='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
|
||||
label='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>
|
||||
{{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) }}
|
||||
{{token-list caption="Applied to the following tokens:" items=items}}
|
||||
{{/if}}
|
||||
|
@ -18,13 +21,13 @@
|
|||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
{{#if (gt items.length 0)}}
|
||||
{{#modal-dialog onclose=(action cancel)}}
|
||||
{{#modal-dialog data-test-delete-modal onclose=(action cancel)}}
|
||||
{{#block-slot 'header'}}
|
||||
<h2>Policy in Use</h2>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'body'}}
|
||||
<p>
|
||||
This Policy is currently in use. If you choose to delete this Policy, it will be removed from the following <strong>{{items.length}} Tokens</strong>:
|
||||
This Policy is currently in use. If you choose to delete this Policy, it will be removed from the following <strong>{{items.length}} Tokens</strong>:
|
||||
</p>
|
||||
{{token-list items=items target='_blank'}}
|
||||
<p>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue