From a2ee02fdf7c3412fea628e0a6bc3bcaa24307b71 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Wed, 1 May 2019 19:01:14 +0100 Subject: [PATCH] UI: ACL Roles cont. plus Service Identities (#5661) 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. --- ui-v2/app/adapters/role.js | 20 +- ui-v2/app/adapters/token.js | 14 +- ui-v2/app/components/child-selector.js | 41 +- ui-v2/app/components/code-editor.js | 46 +- ui-v2/app/components/form-component.js | 18 +- ui-v2/app/components/policy-form.js | 20 +- ui-v2/app/components/policy-selector.js | 67 ++- ui-v2/app/components/role-form.js | 5 +- ui-v2/app/components/role-selector.js | 20 +- ui-v2/app/components/service-identity.js | 5 + ui-v2/app/components/tabular-collection.js | 15 +- ui-v2/app/helpers/policy/is-management.js | 8 - ui-v2/app/helpers/policy/typeof.js | 18 + ui-v2/app/initializers/power-select.js | 15 + ui-v2/app/mixins/policy/as-many.js | 70 +++ ui-v2/app/mixins/role/as-many.js | 28 + ui-v2/app/models/policy.js | 4 + ui-v2/app/serializers/role.js | 1 - ui-v2/app/services/repository/policy.js | 10 + .../base/decoration/base-placeholders.scss | 11 + .../styles/base/icons/base-placeholders.scss | 13 + .../app/styles/base/icons/base-variables.scss | 3 +- .../styles/base/icons/icon-placeholders.scss | 494 ++++++++++++++++++ ui-v2/app/styles/base/icons/index.scss | 2 + .../components/action-group/layout.scss | 15 +- .../styles/components/action-group/skin.scss | 7 +- ui-v2/app/styles/components/anchors.scss | 7 +- .../styles/components/code-editor/layout.scss | 3 + .../app/styles/components/form-elements.scss | 6 +- .../styles/components/healthcheck-info.scss | 4 +- .../components/healthcheck-info/layout.scss | 13 +- ui-v2/app/styles/components/icons/index.scss | 12 - ui-v2/app/styles/components/pill.scss | 40 ++ ui-v2/app/styles/components/pill/skin.scss | 1 - ui-v2/app/styles/components/table.scss | 43 +- ui-v2/app/styles/components/table/layout.scss | 65 +-- ui-v2/app/styles/components/table/skin.scss | 20 +- .../styles/components/tabular-collection.scss | 177 +++---- .../components/tabular-details/layout.scss | 38 +- .../components/tabular-details/skin.scss | 12 + .../styles/components/tag-list/layout.scss | 4 + .../components/with-tooltip/layout.scss | 1 + ui-v2/app/styles/core/typography.scss | 58 +- ui-v2/app/styles/routes/dc/acls/index.scss | 7 + .../styles/routes/dc/acls/tokens/index.scss | 6 - .../app/styles/routes/dc/intention/index.scss | 2 +- ui-v2/app/styles/routes/dc/nodes/index.scss | 9 +- ui-v2/app/styles/variables/index.scss | 5 - .../app/templates/components/code-editor.hbs | 20 +- .../templates/components/healthcheck-info.hbs | 2 +- .../app/templates/components/policy-form.hbs | 39 +- .../templates/components/policy-selector.hbs | 53 +- ui-v2/app/templates/components/role-form.hbs | 3 +- .../templates/components/role-selector.hbs | 20 +- .../templates/components/service-identity.hbs | 12 + .../components/tabular-collection.hbs | 6 +- .../templates/components/tabular-details.hbs | 2 +- ui-v2/app/templates/components/token-list.hbs | 1 + .../app/templates/dc/acls/policies/-form.hbs | 9 +- ui-v2/app/templates/dc/acls/policies/edit.hbs | 4 +- .../app/templates/dc/acls/policies/index.hbs | 4 +- ui-v2/app/templates/dc/acls/roles/index.hbs | 9 +- ui-v2/app/templates/dc/acls/tokens/index.hbs | 2 +- ui-v2/app/templates/dc/nodes/-services.hbs | 8 +- ui-v2/app/templates/dc/services/index.hbs | 8 +- ui-v2/package.json | 2 +- .../as-many}/add-existing.feature | 34 +- .../dc/acls/policies/as-many/add-new.feature | 79 +++ .../dc/acls/policies/as-many/list.feature | 32 ++ .../dc/acls/policies/as-many/remove.feature | 35 ++ .../dc/acls/policies/delete.feature | 46 ++ .../as-many}/add-existing.feature | 6 +- .../roles => roles/as-many}/add-new.feature | 50 +- .../roles => roles/as-many}/list.feature | 4 +- .../roles => roles/as-many}/remove.feature | 12 +- .../dc/acls/tokens/policies/add-new.feature | 45 -- .../dc/acls/tokens/policies/list.feature | 22 - .../dc/acls/tokens/policies/remove.feature | 29 - ui-v2/tests/acceptance/deleting.feature | 1 - .../as-many}/add-existing-steps.js | 0 .../as-many}/add-new-steps.js | 0 .../roles => policies/as-many}/list-steps.js | 0 .../as-many}/remove-steps.js | 0 .../steps/dc/acls/policies/delete-steps.js | 10 + .../as-many/add-existing-steps.js} | 0 .../as-many/add-new-steps.js} | 7 +- .../policies => roles/as-many}/list-steps.js | 7 +- .../as-many}/remove-steps.js | 7 +- ui-v2/tests/helpers/normalizers.js | 19 + .../adapters/role/response-test.js | 4 + .../adapters/token/response-test.js | 5 + .../components/service-identity-test.js | 34 ++ .../{is-management-test.js => typeof-test.js} | 8 +- .../services/repository/policy-test.js | 2 +- .../services/repository/role-test.js | 6 +- .../services/repository/token-test.js | 4 + .../tests/lib/page-object/createDeletable.js | 2 +- .../tests/lib/page-object/createSubmitable.js | 2 +- ui-v2/tests/lib/page-object/radiogroup.js | 4 +- ui-v2/tests/pages.js | 26 +- ui-v2/tests/pages/components/policy-form.js | 11 + .../tests/pages/components/policy-selector.js | 20 + ui-v2/tests/pages/components/role-form.js | 11 + ui-v2/tests/pages/components/role-selector.js | 14 + ui-v2/tests/pages/components/token-list.js | 7 + ui-v2/tests/pages/dc/acls/policies/edit.js | 28 +- ui-v2/tests/pages/dc/acls/roles/edit.js | 47 +- ui-v2/tests/pages/dc/acls/tokens/edit.js | 58 +- ui-v2/tests/steps.js | 40 +- ui-v2/tests/steps/assertions/model.js | 14 +- ui-v2/tests/steps/assertions/page.js | 8 +- ui-v2/tests/steps/interactions/click.js | 34 +- ui-v2/tests/steps/interactions/form.js | 56 +- .../unit/helpers/policy/is-management-test.js | 13 - .../tests/unit/mixins/policy/as-many-test.js | 12 + ui-v2/tests/unit/mixins/role/as-many-test.js | 12 + ui-v2/yarn.lock | 428 +++++++++++++-- 117 files changed, 2238 insertions(+), 824 deletions(-) create mode 100644 ui-v2/app/components/service-identity.js delete mode 100644 ui-v2/app/helpers/policy/is-management.js create mode 100644 ui-v2/app/helpers/policy/typeof.js create mode 100644 ui-v2/app/initializers/power-select.js create mode 100644 ui-v2/app/mixins/policy/as-many.js create mode 100644 ui-v2/app/mixins/role/as-many.js create mode 100644 ui-v2/app/styles/base/icons/base-placeholders.scss create mode 100644 ui-v2/app/styles/base/icons/icon-placeholders.scss create mode 100644 ui-v2/app/templates/components/service-identity.hbs rename ui-v2/tests/acceptance/dc/acls/{tokens/policies => policies/as-many}/add-existing.feature (53%) create mode 100644 ui-v2/tests/acceptance/dc/acls/policies/as-many/add-new.feature create mode 100644 ui-v2/tests/acceptance/dc/acls/policies/as-many/list.feature create mode 100644 ui-v2/tests/acceptance/dc/acls/policies/as-many/remove.feature create mode 100644 ui-v2/tests/acceptance/dc/acls/policies/delete.feature rename ui-v2/tests/acceptance/dc/acls/{tokens/roles => roles/as-many}/add-existing.feature (87%) rename ui-v2/tests/acceptance/dc/acls/{tokens/roles => roles/as-many}/add-new.feature (68%) rename ui-v2/tests/acceptance/dc/acls/{tokens/roles => roles/as-many}/list.feature (81%) rename ui-v2/tests/acceptance/dc/acls/{tokens/roles => roles/as-many}/remove.feature (64%) delete mode 100644 ui-v2/tests/acceptance/dc/acls/tokens/policies/add-new.feature delete mode 100644 ui-v2/tests/acceptance/dc/acls/tokens/policies/list.feature delete mode 100644 ui-v2/tests/acceptance/dc/acls/tokens/policies/remove.feature rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/roles => policies/as-many}/add-existing-steps.js (100%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/policies => policies/as-many}/add-new-steps.js (100%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/roles => policies/as-many}/list-steps.js (100%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/roles => policies/as-many}/remove-steps.js (100%) create mode 100644 ui-v2/tests/acceptance/steps/dc/acls/policies/delete-steps.js rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/roles/add-new-steps.js => roles/as-many/add-existing-steps.js} (100%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/policies/add-existing-steps.js => roles/as-many/add-new-steps.js} (61%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/policies => roles/as-many}/list-steps.js (61%) rename ui-v2/tests/acceptance/steps/dc/acls/{tokens/policies => roles/as-many}/remove-steps.js (61%) create mode 100644 ui-v2/tests/helpers/normalizers.js create mode 100644 ui-v2/tests/integration/components/service-identity-test.js rename ui-v2/tests/integration/helpers/policy/{is-management-test.js => typeof-test.js} (61%) create mode 100644 ui-v2/tests/pages/components/policy-form.js create mode 100644 ui-v2/tests/pages/components/policy-selector.js create mode 100644 ui-v2/tests/pages/components/role-form.js create mode 100644 ui-v2/tests/pages/components/role-selector.js create mode 100644 ui-v2/tests/pages/components/token-list.js delete mode 100644 ui-v2/tests/unit/helpers/policy/is-management-test.js create mode 100644 ui-v2/tests/unit/mixins/policy/as-many-test.js create mode 100644 ui-v2/tests/unit/mixins/role/as-many-test.js diff --git a/ui-v2/app/adapters/role.js b/ui-v2/app/adapters/role.js index 9d54d6c72288..81128125927f 100644 --- a/ui-v2/app/adapters/role.js +++ b/ui-v2/app/adapters/role.js @@ -8,9 +8,10 @@ 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 minimizeModel from 'consul-ui/utils/minimizeModel'; -export default Adapter.extend({ +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)); }, @@ -52,15 +53,6 @@ export default Adapter.extend({ } return this._super(status, headers, response, requestData); }, - handleSingleResponse: function(url, response, primary, slug) { - // Sometimes we get `Policies: null`, make null equal an empty array - ['Policies'].forEach(function(prop) { - if (typeof response[prop] === 'undefined' || response[prop] === null) { - response[prop] = []; - } - }); - return this._super(url, response, primary, slug); - }, methodForRequest: function(params) { switch (params.requestType) { case REQUEST_CREATE: @@ -69,13 +61,11 @@ export default Adapter.extend({ return this._super(...arguments); }, dataForRequest: function(params) { - let data = this._super(...arguments); + const data = this._super(...arguments); switch (params.requestType) { case REQUEST_UPDATE: case REQUEST_CREATE: - data.role.Policies = minimizeModel(data.role.Policies); - data = data.role; - break; + return data.role; } return data; }, diff --git a/ui-v2/app/adapters/token.js b/ui-v2/app/adapters/token.js index 5ece2577ed73..1d34970f582a 100644 --- a/ui-v2/app/adapters/token.js +++ b/ui-v2/app/adapters/token.js @@ -9,14 +9,16 @@ import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/token'; 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 minimizeModel from 'consul-ui/utils/minimizeModel'; + +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); @@ -109,12 +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 - ['Policies', 'Roles'].forEach(function(prop) { - if (typeof response[prop] === 'undefined' || response[prop] === null) { - response[prop] = []; - } - }); // Convert an old style update response to a new style if (typeof response['ID'] !== 'undefined') { const item = get(this, 'store') @@ -172,8 +168,6 @@ export default Adapter.extend({ } // falls through case REQUEST_CREATE: - data.token.Policies = minimizeModel(data.token.Policies); - data.token.Roles = minimizeModel(data.token.Roles); data = data.token; break; case REQUEST_SELF: diff --git a/ui-v2/app/components/child-selector.js b/ui-v2/app/components/child-selector.js index f81080f429e3..5a471b9caa02 100644 --- a/ui-v2/app/components/child-selector.js +++ b/ui-v2/app/components/child-selector.js @@ -1,31 +1,37 @@ import Component from '@ember/component'; -import SlotsMixin from 'block-slots'; 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'; -import { alias } from '@ember/object/computed'; 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, 'name')); - this.form = get(this, 'formContainer').form(get(this, 'name')); + 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('items.[]', 'allOptions.[]', function() { + 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, 'items') || []; + 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'))); @@ -54,21 +60,29 @@ export default Component.extend(SlotsMixin, WithListeners, { } }, 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 - this.listen(repo.persist(item), 'message', e => { - const item = e.data; - set(item, 'CreateTime', new Date().getTime()); - items.pushObject(item); - this.onchange({ target: this }); - // It looks like success is the only potentially unsafe - // operation here + 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(); @@ -84,7 +98,6 @@ export default Component.extend(SlotsMixin, WithListeners, { change: function(e, value, item) { const event = get(this, 'dom').normalizeEvent(...arguments); const items = value; - // const item = event.target.value; switch (event.target.name) { case 'items[]': set(item, 'CreateTime', new Date().getTime()); diff --git a/ui-v2/app/components/code-editor.js b/ui-v2/app/components/code-editor.js index fbf87a180dd9..b697657e8978 100644 --- a/ui-v2/app/components/code-editor.js +++ b/ui-v2/app/components/code-editor.js @@ -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); + }, + }, }); diff --git a/ui-v2/app/components/form-component.js b/ui-v2/app/components/form-component.js index 79a768afae7d..5a6e5f393a81 100644 --- a/ui-v2/app/components/form-component.js +++ b/ui-v2/app/components/form-component.js @@ -1,10 +1,12 @@ 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'; - -export default Component.extend(WithListeners, { +// 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() {}, @@ -17,13 +19,17 @@ export default Component.extend(WithListeners, { dom: service('dom'), container: service('form'), - init: function() { - this._super(...arguments); - }, actions: { change: function(e, value, item) { - const event = get(this, 'dom').normalizeEvent(e, value); + 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); diff --git a/ui-v2/app/components/policy-form.js b/ui-v2/app/components/policy-form.js index a5054f4dcff9..f178e64bbc45 100644 --- a/ui-v2/app/components/policy-form.js +++ b/ui-v2/app/components/policy-form.js @@ -5,22 +5,33 @@ 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, - type: 'policy', 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() { + change: function(e) { try { this._super(...arguments); } catch (err) { const scoped = get(this, 'isScoped'); const name = err.target.name; - const value = err.target.value; switch (name) { case 'policy[isScoped]': if (scoped) { @@ -32,9 +43,6 @@ export default FormComponent.extend({ } set(this, 'isScoped', !scoped); break; - case 'policy[type]': - set(this, 'type', value); - break; default: this.onerror(err); } diff --git a/ui-v2/app/components/policy-selector.js b/ui-v2/app/components/policy-selector.js index 37046c2cab68..778c7296ebcb 100644 --- a/ui-v2/app/components/policy-selector.js +++ b/ui-v2/app/components/policy-selector.js @@ -10,6 +10,18 @@ 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); @@ -21,6 +33,31 @@ export default ChildSelectorComponent.extend({ .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; @@ -28,6 +65,9 @@ export default ChildSelectorComponent.extend({ 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'); @@ -38,32 +78,5 @@ export default ChildSelectorComponent.extend({ updateArrayObject(items, repo.findBySlug(slug, dc), slugKey, slug); } }, - save: function(item, items) { - try { - this._super(...arguments); - } 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 { - // TODO: Conponents can't throw, use onerror - throw err; - } - } - }, }, }); diff --git a/ui-v2/app/components/role-form.js b/ui-v2/app/components/role-form.js index 01a9a6a65446..5ebe540c9981 100644 --- a/ui-v2/app/components/role-form.js +++ b/ui-v2/app/components/role-form.js @@ -1,5 +1,6 @@ import FormComponent from './form-component'; -import SlotsMixin from 'block-slots'; -export default FormComponent.extend(SlotsMixin, { +export default FormComponent.extend({ + type: 'role', name: 'role', + classNames: ['role-form'], }); diff --git a/ui-v2/app/components/role-selector.js b/ui-v2/app/components/role-selector.js index ae7d7853c3c3..f1243ad17850 100644 --- a/ui-v2/app/components/role-selector.js +++ b/ui-v2/app/components/role-selector.js @@ -1,16 +1,21 @@ import ChildSelectorComponent from './child-selector'; import { inject as service } from '@ember/service'; -import { get } from '@ember/object'; +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? @@ -20,5 +25,18 @@ export default ChildSelectorComponent.extend({ 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); + } + }, }, }); diff --git a/ui-v2/app/components/service-identity.js b/ui-v2/app/components/service-identity.js new file mode 100644 index 000000000000..4798652642ba --- /dev/null +++ b/ui-v2/app/components/service-identity.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/tabular-collection.js b/ui-v2/app/components/tabular-collection.js index 286f12599f35..c4d2e1c7a87d 100644 --- a/ui-v2/app/components/tabular-collection.js +++ b/ui-v2/app/components/tabular-collection.js @@ -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) { diff --git a/ui-v2/app/helpers/policy/is-management.js b/ui-v2/app/helpers/policy/is-management.js deleted file mode 100644 index d35093f22712..000000000000 --- a/ui-v2/app/helpers/policy/is-management.js +++ /dev/null @@ -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); diff --git a/ui-v2/app/helpers/policy/typeof.js b/ui-v2/app/helpers/policy/typeof.js new file mode 100644 index 000000000000..ec4b5e441ad9 --- /dev/null +++ b/ui-v2/app/helpers/policy/typeof.js @@ -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); diff --git a/ui-v2/app/initializers/power-select.js b/ui-v2/app/initializers/power-select.js new file mode 100644 index 000000000000..30ae44dd6cfb --- /dev/null +++ b/ui-v2/app/initializers/power-select.js @@ -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, +}; diff --git a/ui-v2/app/mixins/policy/as-many.js b/ui-v2/app/mixins/policy/as-many.js new file mode 100644 index 000000000000..37d07a494b62 --- /dev/null +++ b/ui-v2/app/mixins/policy/as-many.js @@ -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; + }, +}); diff --git a/ui-v2/app/mixins/role/as-many.js b/ui-v2/app/mixins/role/as-many.js new file mode 100644 index 000000000000..8fce74eb4180 --- /dev/null +++ b/ui-v2/app/mixins/role/as-many.js @@ -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; + }, +}); diff --git a/ui-v2/app/models/policy.js b/ui-v2/app/models/policy.js index 0bbf4982781d..d002d57cb869 100644 --- a/ui-v2/app/models/policy.js +++ b/ui-v2/app/models/policy.js @@ -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; diff --git a/ui-v2/app/serializers/role.js b/ui-v2/app/serializers/role.js index acc3985a5b73..4a43dd24f77b 100644 --- a/ui-v2/app/serializers/role.js +++ b/ui-v2/app/serializers/role.js @@ -1,6 +1,5 @@ import Serializer from './application'; import { PRIMARY_KEY } from 'consul-ui/models/role'; - export default Serializer.extend({ primaryKey: PRIMARY_KEY, }); diff --git a/ui-v2/app/services/repository/policy.js b/ui-v2/app/services/repository/policy.js index 74787266a951..f9a506d023a8 100644 --- a/ui-v2/app/services/repository/policy.js +++ b/ui-v2/app/services/repository/policy.js @@ -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')); }, diff --git a/ui-v2/app/styles/base/decoration/base-placeholders.scss b/ui-v2/app/styles/base/decoration/base-placeholders.scss index 42a6acd54ced..69bc7f7f7152 100644 --- a/ui-v2/app/styles/base/decoration/base-placeholders.scss +++ b/ui-v2/app/styles/base/decoration/base-placeholders.scss @@ -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; diff --git a/ui-v2/app/styles/base/icons/base-placeholders.scss b/ui-v2/app/styles/base/icons/base-placeholders.scss new file mode 100644 index 000000000000..0d873f2e5932 --- /dev/null +++ b/ui-v2/app/styles/base/icons/base-placeholders.scss @@ -0,0 +1,13 @@ +%with-icon { + background-repeat: no-repeat; + background-position: center; +} +%as-pseudo { + display: inline-block; + content: ''; + visibility: visible; + background-size: contain; + width: 1.2em; + height: 1.2em; + vertical-align: text-top; +} diff --git a/ui-v2/app/styles/base/icons/base-variables.scss b/ui-v2/app/styles/base/icons/base-variables.scss index e63eeb064212..1be5ffae1b19 100644 --- a/ui-v2/app/styles/base/icons/base-variables.scss +++ b/ui-v2/app/styles/base/icons/base-variables.scss @@ -73,10 +73,11 @@ $queue-svg: url('data:image/svg+xml;charset=UTF-8,'); $run-svg: url('data:image/svg+xml;charset=UTF-8,'); $search-svg: url('data:image/svg+xml;charset=UTF-8,'); +$service-identity-svg: url('data:image/svg+xml;charset=UTF-8,'); $settings-svg: url('data:image/svg+xml;charset=UTF-8,'); $star-fill-svg: url('data:image/svg+xml;charset=UTF-8,'); $star-outline-svg: url('data:image/svg+xml;charset=UTF-8,'); -$star-svg: url('data:image/svg+xml;charset=UTF-8,'); +$star-svg: url('data:image/svg+xml;charset=UTF-8,'); $sub-arrow-left-svg: url('data:image/svg+xml;charset=UTF-8,'); $sub-arrow-right-svg: url('data:image/svg+xml;charset=UTF-8,'); $swap-horizontal-svg: url('data:image/svg+xml;charset=UTF-8,'); diff --git a/ui-v2/app/styles/base/icons/icon-placeholders.scss b/ui-v2/app/styles/base/icons/icon-placeholders.scss new file mode 100644 index 000000000000..f6d41b930850 --- /dev/null +++ b/ui-v2/app/styles/base/icons/icon-placeholders.scss @@ -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; +} diff --git a/ui-v2/app/styles/base/icons/index.scss b/ui-v2/app/styles/base/icons/index.scss index bb221c7e8f97..17b18ac3c64d 100644 --- a/ui-v2/app/styles/base/icons/index.scss +++ b/ui-v2/app/styles/base/icons/index.scss @@ -1 +1,3 @@ @import './base-variables'; +@import './base-placeholders'; +@import './icon-placeholders'; diff --git a/ui-v2/app/styles/components/action-group/layout.scss b/ui-v2/app/styles/components/action-group/layout.scss index b5f054932152..b3a6c4965430 100644 --- a/ui-v2/app/styles/components/action-group/layout.scss +++ b/ui-v2/app/styles/components/action-group/layout.scss @@ -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; diff --git a/ui-v2/app/styles/components/action-group/skin.scss b/ui-v2/app/styles/components/action-group/skin.scss index 7ef8d43773e4..344fa9319c5c 100644 --- a/ui-v2/app/styles/components/action-group/skin.scss +++ b/ui-v2/app/styles/components/action-group/skin.scss @@ -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; diff --git a/ui-v2/app/styles/components/anchors.scss b/ui-v2/app/styles/components/anchors.scss index a4ac20ee7b4b..af16dbd1b63b 100644 --- a/ui-v2/app/styles/components/anchors.scss +++ b/ui-v2/app/styles/components/anchors.scss @@ -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; diff --git a/ui-v2/app/styles/components/code-editor/layout.scss b/ui-v2/app/styles/components/code-editor/layout.scss index d2dbfac8457e..a4f1865e590a 100644 --- a/ui-v2/app/styles/components/code-editor/layout.scss +++ b/ui-v2/app/styles/components/code-editor/layout.scss @@ -29,3 +29,6 @@ content: ''; display: block; } +%code-editor > pre { + display: none; +} diff --git a/ui-v2/app/styles/components/form-elements.scss b/ui-v2/app/styles/components/form-elements.scss index c0bd6fa6e0a6..7717f9baa83f 100644 --- a/ui-v2/app/styles/components/form-elements.scss +++ b/ui-v2/app/styles/components/form-elements.scss @@ -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; } diff --git a/ui-v2/app/styles/components/healthcheck-info.scss b/ui-v2/app/styles/components/healthcheck-info.scss index a249d32c4929..ff44885a301a 100644 --- a/ui-v2/app/styles/components/healthcheck-info.scss +++ b/ui-v2/app/styles/components/healthcheck-info.scss @@ -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; diff --git a/ui-v2/app/styles/components/healthcheck-info/layout.scss b/ui-v2/app/styles/components/healthcheck-info/layout.scss index 0f084db303a5..06b64a930aa0 100644 --- a/ui-v2/app/styles/components/healthcheck-info/layout.scss +++ b/ui-v2/app/styles/components/healthcheck-info/layout.scss @@ -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; } diff --git a/ui-v2/app/styles/components/icons/index.scss b/ui-v2/app/styles/components/icons/index.scss index 13e97516725c..5f3dfdb460f7 100644 --- a/ui-v2/app/styles/components/icons/index.scss +++ b/ui-v2/app/styles/components/icons/index.scss @@ -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,'); - background-color: $color-transparent; - width: 16px; - height: 16px; -} /*TODO: All chevrons need merging */ %with-chevron-down::before { @extend %pseudo-icon-bg-img; diff --git a/ui-v2/app/styles/components/pill.scss b/ui-v2/app/styles/components/pill.scss index af1809c33957..8b2771a6dd1b 100644 --- a/ui-v2/app/styles/components/pill.scss +++ b/ui-v2/app/styles/components/pill.scss @@ -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; } diff --git a/ui-v2/app/styles/components/pill/skin.scss b/ui-v2/app/styles/components/pill/skin.scss index b234ac46dff3..f1cf7bdc77a9 100644 --- a/ui-v2/app/styles/components/pill/skin.scss +++ b/ui-v2/app/styles/components/pill/skin.scss @@ -1,4 +1,3 @@ %pill { - @extend %frame-gray-900; border-radius: $radius-small; } diff --git a/ui-v2/app/styles/components/table.scss b/ui-v2/app/styles/components/table.scss index 1a98bbc9983a..4d3fd154a640 100644 --- a/ui-v2/app/styles/components/table.scss +++ b/ui-v2/app/styles/components/table.scss @@ -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; + } +} diff --git a/ui-v2/app/styles/components/table/layout.scss b/ui-v2/app/styles/components/table/layout.scss index 6cf8ef1ccd2f..da345c615300 100644 --- a/ui-v2/app/styles/components/table/layout.scss +++ b/ui-v2/app/styles/components/table/layout.scss @@ -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; - } -} diff --git a/ui-v2/app/styles/components/table/skin.scss b/ui-v2/app/styles/components/table/skin.scss index 9ab4efc978f9..ceac149fbc01 100644 --- a/ui-v2/app/styles/components/table/skin.scss +++ b/ui-v2/app/styles/components/table/skin.scss @@ -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; } diff --git a/ui-v2/app/styles/components/tabular-collection.scss b/ui-v2/app/styles/components/tabular-collection.scss index ae890282a129..da5a0345067e 100644 --- a/ui-v2/app/styles/components/tabular-collection.scss +++ b/ui-v2/app/styles/components/tabular-collection.scss @@ -31,38 +31,60 @@ table.dom-recycling { /* using: */ /* calc(<100% divided by number of non-fixed width cells> - ) */ -/*TODO: trs only live in tables, get rid of table */ -html.template-service.template-list main table tr { - @extend %services-row; +table tr > *:nth-last-child(2):first-child, +table tr > *:nth-last-child(2):first-child ~ * { + width: calc(100% / 2); } -html.template-service.template-show #instances table tr { - @extend %instances-row; +table tr > *:nth-last-child(3):first-child, +table tr > *:nth-last-child(3):first-child ~ * { + width: calc(100% / 3); } -html.template-instance.template-show #upstreams table tr { - @extend %upstreams-row; +table tr > *:nth-last-child(4):first-child, +table tr > *:nth-last-child(4):first-child ~ * { + width: calc(100% / 4); } -html.template-intention.template-list main table tr { - @extend %intentions-row; +table tr > *:nth-last-child(5):first-child, +table tr > *:nth-last-child(5):first-child ~ * { + width: calc(100% / 5); } -html.template-kv.template-list main table tr { - @extend %kvs-row; + +table.has-actions tr > .actions { + @extend %table-actions; } -html.template-acl.template-list main table tr { - @extend %acls-row; +table.has-actions tr > *:nth-last-child(2):first-child, +table.has-actions tr > *:nth-last-child(2):first-child ~ * { + width: calc(100% - 60px); } -html.template-policy.template-list main table tr { - @extend %policies-row; +table.has-actions tr > *:nth-last-child(3):first-child, +table.has-actions tr > *:nth-last-child(3):first-child ~ * { + width: calc(50% - 30px); } -html.template-role.template-list main table tr { - @extend %roles-row; +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; +} +html.template-service.template-show #instances table tr { + @extend %instances-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-role.template-edit [role='dialog'] table tr, -html.template-role.template-edit main 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, @@ -70,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) @@ -106,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 */ @@ -153,43 +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; -} -%roles-row > * { - width: calc(33% - 20px); -} -%roles-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); -} diff --git a/ui-v2/app/styles/components/tabular-details/layout.scss b/ui-v2/app/styles/components/tabular-details/layout.scss index 2f0a958fb272..1cf49f1cb3fa 100644 --- a/ui-v2/app/styles/components/tabular-details/layout.scss +++ b/ui-v2/app/styles/components/tabular-details/layout.scss @@ -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; diff --git a/ui-v2/app/styles/components/tabular-details/skin.scss b/ui-v2/app/styles/components/tabular-details/skin.scss index 7a7faae95845..643c26fe6da3 100644 --- a/ui-v2/app/styles/components/tabular-details/skin.scss +++ b/ui-v2/app/styles/components/tabular-details/skin.scss @@ -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); +} diff --git a/ui-v2/app/styles/components/tag-list/layout.scss b/ui-v2/app/styles/components/tag-list/layout.scss index 2590f8b4c144..e1f28e640206 100644 --- a/ui-v2/app/styles/components/tag-list/layout.scss +++ b/ui-v2/app/styles/components/tag-list/layout.scss @@ -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; +} diff --git a/ui-v2/app/styles/components/with-tooltip/layout.scss b/ui-v2/app/styles/components/with-tooltip/layout.scss index 773cbb1e358c..130823941b1d 100644 --- a/ui-v2/app/styles/components/with-tooltip/layout.scss +++ b/ui-v2/app/styles/components/with-tooltip/layout.scss @@ -3,6 +3,7 @@ display: inline-flex; justify-content: center; align-items: center; + vertical-align: text-top; } %tooltip-bubble, %tooltip-tail { diff --git a/ui-v2/app/styles/core/typography.scss b/ui-v2/app/styles/core/typography.scss index 35792cc434c2..134d7090943a 100644 --- a/ui-v2/app/styles/core/typography.scss +++ b/ui-v2/app/styles/core/typography.scss @@ -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, +fieldset > p, +.template-error > div, [role='tabpanel'] > p:only-child, +.with-confirmation p, +%app-content > p:only-child, %app-view > div.disabled > div, -.template-error > div, %button, %form-element > em, %form-element > strong, -.with-confirmation p, %feedback-dialog-inline p { font-size: $typo-size-800; } diff --git a/ui-v2/app/styles/routes/dc/acls/index.scss b/ui-v2/app/styles/routes/dc/acls/index.scss index bf3fb279ba8d..99270f734e8a 100644 --- a/ui-v2/app/styles/routes/dc/acls/index.scss +++ b/ui-v2/app/styles/routes/dc/acls/index.scss @@ -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; +} diff --git a/ui-v2/app/styles/routes/dc/acls/tokens/index.scss b/ui-v2/app/styles/routes/dc/acls/tokens/index.scss index 0eefb379da24..4ad7cd7ba8e6 100644 --- a/ui-v2/app/styles/routes/dc/acls/tokens/index.scss +++ b/ui-v2/app/styles/routes/dc/acls/tokens/index.scss @@ -4,9 +4,6 @@ cursor: pointer; float: right; } -%pill.policy-management { - @extend %with-star; -} %token-yours { text-indent: 20px; color: $blue-500; @@ -29,6 +26,3 @@ .template-token.template-edit dl { @extend %form-row; } -.template-token.template-edit dd .with-feedback { - top: -5px; -} diff --git a/ui-v2/app/styles/routes/dc/intention/index.scss b/ui-v2/app/styles/routes/dc/intention/index.scss index ce502d3dfbd9..d10ca5debbf4 100644 --- a/ui-v2/app/styles/routes/dc/intention/index.scss +++ b/ui-v2/app/styles/routes/dc/intention/index.scss @@ -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; } diff --git a/ui-v2/app/styles/routes/dc/nodes/index.scss b/ui-v2/app/styles/routes/dc/nodes/index.scss index f8400db878e9..de3b376ceb4a 100644 --- a/ui-v2/app/styles/routes/dc/nodes/index.scss +++ b/ui-v2/app/styles/routes/dc/nodes/index.scss @@ -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; } diff --git a/ui-v2/app/styles/variables/index.scss b/ui-v2/app/styles/variables/index.scss index a5f40df0c762..6689805bbc18 100644 --- a/ui-v2/app/styles/variables/index.scss +++ b/ui-v2/app/styles/variables/index.scss @@ -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; diff --git a/ui-v2/app/templates/components/code-editor.hbs b/ui-v2/app/templates/components/code-editor.hbs index 4ebd81644b36..dd6b9ae5e9dd 100644 --- a/ui-v2/app/templates/components/code-editor.hbs +++ b/ui-v2/app/templates/components/code-editor.hbs @@ -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}} +
{{yield}}
+{{#if (and (not readonly) (not syntax))}} + {{#power-select + onchange=(action 'change') + selected=mode + searchEnabled=false + options=modes as |mode| + }} + {{mode.name}} + {{/power-select}} {{/if}} diff --git a/ui-v2/app/templates/components/healthcheck-info.hbs b/ui-v2/app/templates/components/healthcheck-info.hbs index 13b62ac08c33..982d4c2c86f2 100644 --- a/ui-v2/app/templates/components/healthcheck-info.hbs +++ b/ui-v2/app/templates/components/healthcheck-info.hbs @@ -1,7 +1,7 @@ {{#if (and (lt passing 1) (lt warning 1) (lt critical 1) )}} 0 {{else}} -
+
{{healthcheck-status width=passingWidth name='passing' value=passing}} {{healthcheck-status width=warningWidth name='warning' value=warning}} {{healthcheck-status width=criticalWidth name='critical' value=critical}} diff --git a/ui-v2/app/templates/components/policy-form.hbs b/ui-v2/app/templates/components/policy-form.hbs index 9149fc2af0de..4b77af4af7bd 100644 --- a/ui-v2/app/templates/components/policy-form.hbs +++ b/ui-v2/app/templates/components/policy-form.hbs @@ -1,7 +1,26 @@ +{{yield}}
+ {{#yield-slot 'template'}} + {{else}} +
+ Policy or service identity? +
+

+ 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. +

+ {{! this should use radio-group }} +
+ {{#each templates as |template|}} + + {{/each}} +
+ {{/yield-slot}}
Valid datacenters
@@ -27,23 +52,25 @@
{{#each datacenters as |dc| }} {{/each}} {{#each item.Datacenters as |dc| }} {{#if (not (find-by 'Name' dc datacenters))}} {{/if}} {{/each}}
{{/if}} +{{#if (eq item.template '') }} +{{/if}}
diff --git a/ui-v2/app/templates/components/policy-selector.hbs b/ui-v2/app/templates/components/policy-selector.hbs index 736ff55eecb1..6f58b1d0a5a7 100644 --- a/ui-v2/app/templates/components/policy-selector.hbs +++ b/ui-v2/app/templates/components/policy-selector.hbs @@ -1,22 +1,4 @@ -{{#modal-dialog data-test-policy-form name="new-policy-toggle"}} - {{#block-slot 'header'}} -

New Policy

- {{/block-slot}} - {{#block-slot 'body'}} - {{policy-form form=form dc=dc}} - {{/block-slot}} - {{#block-slot 'actions' as |close|}} - - - {{/block-slot}} -{{/modal-dialog}} - -{{#child-selector repo=repo dc=dc name="policy" placeholder="Search for policy" items=items onedit=onedit}} +{{#child-selector repo=repo dc=dc type="policy" placeholder="Search for policy" items=items}} {{yield}} {{#block-slot 'label'}} Apply an existing policy @@ -28,6 +10,25 @@ + {{!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'}} +

New Policy

+ {{/block-slot}} + {{#block-slot 'body'}} + {{policy-form form=form dc=dc}} + {{/block-slot}} + {{#block-slot 'actions' as |close|}} + + + {{/block-slot}} + {{/modal-dialog}} {{/yield-slot}} {{/block-slot}} {{#block-slot 'option' as |option|}} @@ -44,8 +45,12 @@ Datacenters {{/block-slot}} {{#block-slot 'row'}} - + +{{#if item.ID }} {{item.Name}} +{{else}} + {{item.Name}} +{{/if}} {{if (not item.isSaving) (join ', ' (policy/datacenters item)) 'Saving...'}} @@ -54,7 +59,13 @@ {{#block-slot 'details'}}
{{#confirmation-dialog message='Are you sure you want to remove this policy from this token?'}} diff --git a/ui-v2/app/templates/components/role-form.hbs b/ui-v2/app/templates/components/role-form.hbs index 854b64c14567..14c6571ef422 100644 --- a/ui-v2/app/templates/components/role-form.hbs +++ b/ui-v2/app/templates/components/role-form.hbs @@ -15,7 +15,8 @@ -
+{{!TODO: temporary policies id, look at the inception token modals and get rid of id="policies" and use something else}} +

Policies

{{#yield-slot 'policy' (block-params item)}} {{yield}} diff --git a/ui-v2/app/templates/components/role-selector.hbs b/ui-v2/app/templates/components/role-selector.hbs index 03e1ca34257a..0300e23e0ff1 100644 --- a/ui-v2/app/templates/components/role-selector.hbs +++ b/ui-v2/app/templates/components/role-selector.hbs @@ -7,21 +7,25 @@ {{/if}} {{/block-slot}} {{#block-slot 'body'}} -{{#if (eq state 'role')}} + + {{#role-form form=form dc=dc}} {{#block-slot 'policy'}} - {{#policy-selector dc=dc items=item.Policies}} + + {{#policy-selector source=source dc=dc items=item.Policies}} {{#block-slot 'trigger'}} -