ui: New Confirmation Dialogs (#7007)

* ui: Change action-group to use new popup-menu component in intentions

* ui: Slight amends to aria-menu to prevent scrolling

* ui: Begin to use aria-menu/popover-menu for other elements

* Use a simpler, hackier method to fix up zIndexing

* ui: Implement new confirmation dialogs in other list views (#7080)

This includes another amend to the popover-menu in order to allow
mutiple confirmations/subpanels in the same popover menu.

The functionality added here to allow this is likely to change in the
future.
This commit is contained in:
John Cowen 2020-01-22 12:08:29 +00:00 committed by GitHub
parent 1b512badd7
commit 72ff1f0607
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 949 additions and 761 deletions

View File

@ -42,6 +42,7 @@ export default Component.extend({
guid: '',
expanded: false,
orientation: 'vertical',
keyboardAccess: true,
init: function() {
this._super(...arguments);
set(this, 'guid', this.dom.guid(this));
@ -59,6 +60,9 @@ export default Component.extend({
this._listeners.remove();
},
actions: {
keypressClick: function(e) {
e.target.dispatchEvent(new MouseEvent('click'));
},
keypress: function(e) {
// If the event is from the trigger and its not an opening/closing
// key then don't do anything
@ -83,6 +87,8 @@ export default Component.extend({
if (typeof keys[this.orientation][e.keyCode] === 'undefined') {
return;
}
// prevent any scroll, or default actions
e.preventDefault();
const $focused = this.dom.element(`${MENU_ITEMS}:focus`, this.$menu);
let i;
if ($focused) {
@ -95,11 +101,12 @@ export default Component.extend({
},
// TODO: The argument here needs to change to an event
// see toggle-button.change
change: function(open) {
change: function(e) {
const open = e.target.checked;
if (open) {
this.actions.open.apply(this, []);
this.actions.open.apply(this, [e]);
} else {
this.actions.close.apply(this, []);
this.actions.close.apply(this, [e]);
}
},
close: function(e) {
@ -127,6 +134,9 @@ export default Component.extend({
this.$trigger.dispatchEvent(new MouseEvent('click'));
return;
}
if (!this.keyboardAccess) {
return;
}
this.actions.keypress.apply(this, [e]);
},
});

View File

@ -1,6 +1,32 @@
/*eslint ember/closure-actions: "warn"*/
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import Slotted from 'block-slots';
export default Component.extend(Slotted, {
tagName: '',
dom: service('dom'),
expanded: false,
keyboardAccess: true,
onchange: function() {},
init: function() {
this._super(...arguments);
this.guid = this.dom.guid(this);
},
actions: {
change: function(e) {
if (!e.target.checked) {
[...this.dom.elements(`[id^=popover-menu-${this.guid}]`)].forEach(function($item) {
$item.checked = false;
});
}
this.onchange(e);
},
// Temporary send here so we can send route actions
// easily. It kind of makes sense that you'll want to perform
// route actions from a popup menu for the moment
send: function() {
this.sendAction(...arguments);
},
},
});

View File

@ -1,134 +1,38 @@
import CollectionComponent from 'ember-collection/components/ember-collection';
import needsRevalidate from 'ember-collection/utils/needs-revalidate';
import identity from 'ember-collection/utils/identity';
import Grid from 'ember-collection/layouts/grid';
import SlotsMixin from 'block-slots';
import WithResizing from 'consul-ui/mixins/with-resizing';
import style from 'ember-computed-style';
import { inject as service } from '@ember/service';
import { computed, get, set } from '@ember/object';
/**
* Heavily extended `ember-collection` component
* This adds support for z-index calculations to enable
* Popup menus to pop over either rows above or below
* the popup.
* Additionally adds calculations for figuring out what the height
* of the tabular component should be depending on the other elements
* in the page.
* Currently everything is here together for clarity, but to be split up
* in the future
*/
import CollectionComponent from 'ember-collection/components/ember-collection';
import needsRevalidate from 'ember-collection/utils/needs-revalidate';
import Grid from 'ember-collection/layouts/grid';
import style from 'ember-computed-style';
import SlotsMixin from 'block-slots';
import WithResizing from 'consul-ui/mixins/with-resizing';
// need to copy Cell in wholesale as there is no way to import it
// there is no change made to `Cell` here, its only here as its
// private in `ember-collection`
// TODO: separate both Cell and ZIndexedGrid out
class Cell {
constructor(key, item, index, style) {
this.key = key;
this.hidden = false;
this.item = item;
this.index = index;
this.style = style;
}
}
// this is an amount of rows in the table NOT items
// unlikely to have 10000 DOM rows ever :)
const maxZIndex = 10000;
// Adds z-index styling to the default Grid
class ZIndexedGrid extends Grid {
formatItemStyle(index, w, h, checked) {
let style = super.formatItemStyle(index, w, h);
// count backwards from maxZIndex
let zIndex = maxZIndex - index;
// apart from the row that contains an opened dropdown menu
// this one should be highest z-index, so use max plus 1
if (checked == index) {
zIndex = maxZIndex + 1;
}
style += 'z-index: ' + zIndex;
return style;
}
}
/**
* The tabular-collection can contain 'actions' the UI for which
* uses dropdown 'action groups', so a group of different actions.
* State makes use of native HTML state using radiogroups
* to ensure that only a single dropdown can be open at one time.
* Therefore we listen to change events to do anything extra when
* a dropdown is opened (the change function is bound to the instance of
* the `tabular-component` on init, hoisted here for visibility)
*
* The extra functionality we have here is to detect whether the opened
* dropdown menu would be cut off or not if it 'dropped down'.
* If it would be cut off we use CSS to 'drop it up' instead.
* We also set this row to have the max z-index here, and mark this
* row as the 'checked row' for when a scroll/grid re-calculation is
* performed
*/
const change = function(e) {
if (e instanceof MouseEvent) {
return;
}
// TODO: Why am I getting a jQuery event here?!
if (e instanceof Event) {
const value = e.currentTarget.value;
if (value != get(this, 'checked')) {
set(this, 'checked', value);
// 'actions_close' would mean that all menus have been closed
// therefore we don't need to calculate
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
const dom = get(this, 'dom');
const formatItemStyle = Grid.prototype.formatItemStyle;
const $tr = dom.closest('tr', e.currentTarget);
const $group = dom.sibling(e.currentTarget, 'div');
const groupRect = $group.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 {
$group.classList.remove('above');
}
$tr.style.zIndex = maxZIndex + 1;
}
} else {
set(this, 'checked', null);
}
} else if (e.detail && e.detail.index) {
if (e.detail.confirming) {
this.confirming.push(e.detail.index);
} else {
const pos = this.confirming.indexOf(e.detail.index);
if (pos !== -1) {
this.confirming.splice(pos, 1);
}
}
}
};
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
tagName: 'table',
classNames: ['dom-recycling'],
classNameBindings: ['hasActions'],
attributeBindings: ['style'],
dom: service('dom'),
style: style('getStyle'),
width: 1150,
rowHeight: 50,
maxHeight: 500,
style: style('getStyle'),
checked: null,
hasCaption: false,
dom: service('dom'),
init: function() {
this._super(...arguments);
this.change = change.bind(this);
this.confirming = [];
// TODO: The row height should auto calculate properly from the CSS
this['cell-layout'] = new ZIndexedGrid(get(this, 'width'), get(this, 'rowHeight'));
const o = this;
this['cell-layout'] = new Grid(get(this, 'width'), get(this, 'rowHeight'));
this['cell-layout'].formatItemStyle = function(itemIndex) {
let style = formatItemStyle.apply(this, arguments);
if (o.checked === itemIndex) {
style = `${style};z-index: 1`;
}
return style;
};
},
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
const maxRows = get(this, 'rows');
@ -144,17 +48,24 @@ export default CollectionComponent.extend(SlotsMixin, WithResizing, {
}),
resize: function(e) {
const $tbody = this.element;
const dom = get(this, 'dom');
const $appContent = dom.element('main > div');
const $appContent = this.dom.element('main > div');
if ($appContent) {
const border = 1;
const rect = $tbody.getBoundingClientRect();
const $footer = dom.element('footer[role="contentinfo"]');
const $footer = this.dom.element('footer[role="contentinfo"]');
const space = rect.top + $footer.clientHeight + border;
const height = e.detail.height - space;
this.set('maxHeight', Math.max(0, height));
// TODO: The row height should auto calculate properly from the CSS
this['cell-layout'] = new ZIndexedGrid($appContent.clientWidth, get(this, 'rowHeight'));
this['cell-layout'] = new Grid($appContent.clientWidth, get(this, 'rowHeight'));
const o = this;
this['cell-layout'].formatItemStyle = function(itemIndex) {
let style = formatItemStyle.apply(this, arguments);
if (o.checked === itemIndex) {
style = `${style};z-index: 1`;
}
return style;
};
this.updateItems();
this.updateScrollPosition();
}
@ -176,113 +87,39 @@ export default CollectionComponent.extend(SlotsMixin, WithResizing, {
needsRevalidate(this);
}
},
// need to overwrite this completely so I can pass through the checked index
// unfortunately the nicest way I could think to do this is to copy this in wholesale
// to add an extra argument for `formatItemStyle` in 3 places
// tradeoff between changing as little code as possible in the original code
updateCells: function() {
if (!this._items) {
return;
}
const numItems = get(this._items, 'length');
if (this._cellLayout.length !== numItems) {
this._cellLayout.length = numItems;
}
var priorMap = this._cellMap;
var cellMap = Object.create(null);
var index = this._cellLayout.indexAt(
this._scrollLeft,
this._scrollTop,
this._clientWidth,
this._clientHeight
);
var count = this._cellLayout.count(
this._scrollLeft,
this._scrollTop,
this._clientWidth,
this._clientHeight
);
var items = this._items;
var bufferBefore = Math.min(index, this._buffer);
index -= bufferBefore;
count += bufferBefore;
count = Math.min(count + this._buffer, get(items, 'length') - index);
var i, style, itemIndex, itemKey, cell;
var newItems = [];
for (i = 0; i < count; i++) {
itemIndex = index + i;
itemKey = identity(items.objectAt(itemIndex));
if (priorMap) {
cell = priorMap[itemKey];
}
if (cell) {
// additional `checked` argument
style = this._cellLayout.formatItemStyle(
itemIndex,
this._clientWidth,
this._clientHeight,
this.checked
);
set(cell, 'style', style);
set(cell, 'hidden', false);
set(cell, 'key', itemKey);
cellMap[itemKey] = cell;
} else {
newItems.push(itemIndex);
}
}
for (i = 0; i < this._cells.length; i++) {
cell = this._cells[i];
if (!cellMap[cell.key]) {
if (newItems.length) {
itemIndex = newItems.pop();
let item = items.objectAt(itemIndex);
itemKey = identity(item);
// additional `checked` argument
style = this._cellLayout.formatItemStyle(
itemIndex,
this._clientWidth,
this._clientHeight,
this.checked
);
set(cell, 'style', style);
set(cell, 'key', itemKey);
set(cell, 'index', itemIndex);
set(cell, 'item', item);
set(cell, 'hidden', false);
cellMap[itemKey] = cell;
} else {
set(cell, 'hidden', true);
set(cell, 'style', 'height: 0; display: none;');
}
}
}
for (i = 0; i < newItems.length; i++) {
itemIndex = newItems[i];
let item = items.objectAt(itemIndex);
itemKey = identity(item);
// additional `checked` argument
style = this._cellLayout.formatItemStyle(
itemIndex,
this._clientWidth,
this._clientHeight,
this.checked
);
cell = new Cell(itemKey, item, itemIndex, style);
cellMap[itemKey] = cell;
this._cells.pushObject(cell);
}
this._cellMap = cellMap;
},
actions: {
click: function(e) {
return get(this, 'dom').clickFirstAnchor(e);
return this.dom.clickFirstAnchor(e);
},
change: function(index, e = {}) {
if (typeof index !== 'string') {
return;
}
if (this.$tr) {
this.$tr.style.zIndex = null;
}
if (e.target.checked && index != get(this, 'checked')) {
set(this, 'checked', parseInt(index));
const target = e.target;
const $tr = this.dom.closest('tr', target);
const $group = this.dom.sibling(target, 'div');
const groupRect = $group.getBoundingClientRect();
const groupBottom = groupRect.top + $group.clientHeight;
const $footer = this.dom.element('footer[role="contentinfo"]');
const footerRect = $footer.getBoundingClientRect();
const footerTop = footerRect.top;
if (groupBottom > footerTop) {
$group.classList.add('above');
} else {
$group.classList.remove('above');
}
$tr.style.zIndex = 1;
this.$tr = $tr;
} else {
set(this, 'checked', null);
}
},
},
});

View File

@ -12,6 +12,7 @@ export default Component.extend({
tagName: '',
// TODO: reserved for the moment but we don't need it yet
onblur: null,
checked: false,
onchange: function() {},
init: function() {
this._super(...arguments);
@ -32,6 +33,11 @@ export default Component.extend({
click: function(e) {
e.preventDefault();
this.input.checked = !this.input.checked;
// manually dispatched mouse events have a detail = 0
// real mouse events have the number of click counts
if (e.detail !== 0) {
e.target.blur();
}
this.actions.change.apply(this, [e]);
},
change: function(e) {
@ -40,15 +46,19 @@ export default Component.extend({
this._listeners.remove();
this._listeners.add(this.dom.document(), 'click', e => {
if (this.dom.isOutside(this.label, e.target)) {
this.input.checked = false;
// TODO: This should be an event
this.onchange(this.input.checked);
this._listeners.remove();
if (this.dom.isOutside(this.label.nextElementSibling, e.target)) {
if (this.input.checked) {
this.input.checked = false;
// TODO: This should be an event
this.onchange({ target: this.input });
}
this._listeners.remove();
}
}
});
}
// TODO: This should be an event
this.onchange(this.input.checked);
this.onchange({ target: this.input });
},
},
});

View File

@ -12,3 +12,9 @@
%confirmation-alert > ul > li > * {
width: 100%;
}
%confirmation-alert.warning header::before {
margin-right: 5px;
}
%confirmation-alert.warning header {
margin-bottom: 0.5em;
}

View File

@ -24,3 +24,14 @@
%confirmation-alert > ul > .dangerous > *:focus {
@extend %frame-red-700;
}
%confirmation-alert.warning header {
color: $orange-700;
}
%confirmation-alert.warning header::before {
@extend %with-alert-triangle-mask, %as-pseudo;
background-color: $yellow-500;
margin-right: 5px;
}
%confirmation-alert p {
color: $black;
}

View File

@ -0,0 +1,2 @@
@import './skin';
@import './layout';

View File

@ -0,0 +1,7 @@
%display-toggle-siblings,
%display-toggle-siblings ~ *:not(.animating):not(label) {
display: none;
}
%display-toggle-siblings:checked ~ *:not(label) {
display: block;
}

View File

@ -0,0 +1,3 @@
%display-toggle-siblings ~ label {
cursor: pointer;
}

View File

@ -1,9 +1,16 @@
@import './skin';
@import './layout';
%menu-panel > ul > li > * {
%menu-panel [role='separator'] {
@extend %menu-panel-separator;
}
%menu-panel > div {
@extend %menu-panel-header;
}
// %menu-panel > ul > li > *:not(div),
%menu-panel [role='menuitem'] {
@extend %internal-button;
}
%menu-panel > ul > li.dangerous > * {
%menu-panel > ul > li.dangerous > *:not(div) {
@extend %internal-button-dangerous;
}

View File

@ -2,6 +2,30 @@
position: absolute;
overflow: hidden;
}
%menu-panel [type='checkbox'] {
display: none;
}
%menu-panel [type='checkbox'] ~ * {
transition: transform 150ms, min-height 150ms, max-height 150ms;
min-height: 0;
}
%menu-panel [type='checkbox']:checked ~ * {
transform: translateX(calc(-100% - 10px));
/* this needs to autocalculate */
/* or be hardcoded */
/* min-height: 143px; */
}
%menu-panel-sub-panel {
position: absolute;
top: 0;
left: calc(100% + 10px);
}
%menu-panel > ul > li > div[role='menu'] {
@extend %menu-panel-sub-panel;
}
%menu-panel > ul > li > *:not(div[role='menu']) {
position: relative;
}
%menu-panel:not(.left) {
right: 0px;
}
@ -12,7 +36,7 @@
top: 28px;
}
%menu-panel.above {
bottom: 28px;
bottom: 42px;
}
%menu-panel > ul {
margin: 0;
@ -26,3 +50,37 @@
%menu-panel > ul > li > * {
text-align: left !important;
}
%menu-panel-separator {
padding-top: 0.35em;
}
%menu-panel-separator:not(:first-child) {
margin-top: 0.35em;
}
%menu-panel-separator:not(:empty) {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 0.1em;
}
%menu-panel-header {
padding: 10px;
padding-left: 36px;
}
%menu-panel .is-active > *::after {
position: absolute;
top: 50%;
margin-top: -8px;
right: 10px;
}
%menu-panel-header::before {
position: absolute;
left: 15px;
top: calc(10px + 0.1em);
}
%menu-panel-header {
max-width: fit-content;
}
@supports not (max-width: fit-content) {
%menu-panel-header {
max-width: 200px;
}
}

View File

@ -10,3 +10,23 @@
%menu-panel > ul > li {
list-style-type: none;
}
%menu-panel-separator {
text-transform: uppercase;
color: $gray-400;
}
%menu-panel-header + ul,
%menu-panel-separator:not(:first-child) {
border-top: $decor-border-100;
border-color: $gray-300;
}
%menu-panel-header {
background-color: $gray-050;
}
%menu-panel-header::before {
@extend %with-info-circle-fill-color-icon, %as-pseudo;
font-size: 1.1em;
}
%menu-panel .is-active > *::after {
@extend %with-check-plain-mask, %as-pseudo;
background-color: $magenta-600;
}

View File

@ -0,0 +1,6 @@
@import '../display-toggle/index';
@import '../toggle-button/index';
@import '../menu-panel/index';
@import '../confirmation-alert/index';
@import './skin';
@import './layout';

View File

@ -0,0 +1,25 @@
%more-popover-menu {
@extend %display-toggle-siblings;
}
%more-popover-menu + label > * {
@extend %toggle-button;
}
%more-popover-menu-panel {
@extend %menu-panel;
width: 192px;
}
%more-popover-menu + label + div {
@extend %more-popover-menu-panel;
}
%more-popover-menu-panel:not(.above) {
top: 38px;
}
%more-popover-menu-panel:not(.left) {
right: 10px;
}
%more-popover-menu-panel li [role='menu'] {
display: none;
}
%more-popover-menu-panel [id$='-']:first-child:checked ~ ul label[for$='-'] + [role='menu'] {
display: block;
}

View File

@ -0,0 +1,9 @@
%more-popover-menu + label > *::after {
@extend %with-more-horizontal-icon, %as-pseudo;
opacity: 0.7;
width: 16px;
height: 16px;
}
%more-popover-menu + label > * {
font-size: 0;
}

View File

@ -17,6 +17,10 @@
@extend %with-icon;
background-image: $alert-triangle-svg;
}
%with-alert-triangle-mask {
@extend %with-mask;
mask-image: $alert-triangle-svg;
}
%with-arrow-down-icon {
@extend %with-icon;

View File

@ -1,6 +1,32 @@
@import './app-view/index';
@import './filter-bar/index';
@import '../base/components/buttons/index';
@import '../base/components/popover-menu/index';
%app-view-header .actions > [type='checkbox'] {
@extend %more-popover-menu;
}
%more-popover-menu-panel [type='checkbox']:checked ~ * {
/* this needs to autocalculate */
min-height: 143px;
max-height: 143px;
}
%more-popover-menu-panel [id$='logout']:checked ~ * {
/* this needs to autocalculate */
min-height: 163px;
max-height: 163px;
}
%more-popover-menu-panel [id$='delete']:checked ~ ul label[for$='delete'] + [role='menu'],
%more-popover-menu-panel [id$='logout']:checked ~ ul label[for$='logout'] + [role='menu'],
%more-popover-menu-panel [id$='use']:checked ~ ul label[for$='use'] + [role='menu'] {
display: block;
}
%app-view-header .actions label + div {
// We need this extra to allow tooltips to show
overflow: visible !important;
}
main {
@extend %app-view;
}

View File

@ -1,3 +1,4 @@
@import './form-elements';
@import './breadcrumbs';
@import './anchors';
@import './progress';
@ -24,7 +25,6 @@
@import './phrase-editor';
@import './filter-bar';
@import './tomography-graph';
@import './action-group';
@import './flash-message';
@import './code-editor';
@import './confirmation-dialog';

View File

@ -21,10 +21,3 @@
%main-nav-horizontal-drop-nav {
@extend %menu-panel;
}
/*TODO: %menu-panel? */
%main-nav-horizontal-drop-nav [role='separator'] {
@extend %main-nav-horizontal-drop-nav-separator;
}
%main-nav-horizontal-drop-nav > div {
@extend %main-nav-horizontal-drop-nav-header;
}

View File

@ -1,64 +1,27 @@
%main-nav-horizontal > ul > li,
%main-nav-horizontal-drop-nav a {
position: relative;
}
%main-nav-horizontal [type='checkbox'] ~ div {
display: none;
}
%main-nav-horizontal [type='checkbox']:checked ~ div {
display: block;
}
%main-nav-horizontal [type='checkbox'] + label > * {
/* less space as the chevron adds space */
padding-right: 4px !important;
}
%main-nav-horizontal > ul > li {
position: relative;
}
%main-nav-horizontal-action {
display: block;
padding: 5px 12px;
white-space: nowrap;
}
%main-nav-horizontal input + label > * {
/* less space as the chevron adds space */
padding-right: 4px !important;
}
%main-nav-horizontal-drop-nav {
z-index: 400;
/* TODO: We should probably make menu-panel default to left hand side*/
left: 0;
right: auto;
}
/* TODO: Revisit this once its part of menu-panel as its probably */
/* a property of that */
%main-nav-horizontal-drop-nav-separator:not(:empty) {
padding-left: 1em;
padding-right: 1em;
padding-bottom: 0.1em;
}
%main-nav-horizontal-drop-nav-separator {
padding-top: 0.35em;
}
%main-nav-horizontal-drop-nav-separator:not(:first-child) {
margin-top: 0.35em;
}
%main-nav-horizontal-drop-nav-header {
padding: 10px;
padding-left: 36px;
}
%main-nav-horizontal-drop-nav-header::before {
position: absolute;
left: 15px;
top: calc(10px + 0.1em);
}
%main-nav-horizontal-drop-nav-header {
max-width: fit-content;
}
@supports not (max-width: fit-content) {
%main-nav-horizontal-drop-nav-header {
max-width: 200px;
}
}
%main-nav-horizontal-drop-nav .is-active > *::after {
position: absolute;
top: 50%;
margin-top: -8px;
right: 10px;
}
@media #{$--lt-horizontal-nav} {
%main-nav-horizontal-panel label span {
visibility: visible !important;
@ -120,9 +83,7 @@
}
%main-nav-horizontal-drop-nav {
width: 180px;
}
%main-nav-horizontal-drop-nav-header {
text-align: left;
top: 38px !important;
}
%main-nav-horizontal input + label > * {
right: -15px;
@ -132,9 +93,6 @@
padding-top: 15px;
padding-bottom: 15px;
}
%main-nav-horizontal-drop-nav {
top: 38px !important;
}
}
@media #{$--horizontal-nav} {
%main-nav-horizontal-panel {

View File

@ -35,14 +35,14 @@
%main-nav-horizontal input {
display: none;
}
%main-nav-horizontal input + label > *::after {
%main-nav-horizontal [type='checkbox'] + label > *::after {
@extend %as-pseudo;
@extend %with-chevron-down-mask;
position: relative;
top: 2px;
background-color: $white;
}
%main-nav-horizontal input:checked + label > *::after {
%main-nav-horizontal [type='checkbox']:checked + label > *::after {
@extend %with-chevron-up-mask;
}
%main-nav-horizontal-toggle-button span {
@ -57,23 +57,3 @@
}
}
/**/
%main-nav-horizontal-drop-nav-header {
background-color: $gray-050;
}
%main-nav-horizontal-drop-nav-separator {
text-transform: uppercase;
color: $gray-400;
}
%main-nav-horizontal-drop-nav .is-active > *::after {
@extend %with-check-plain-mask, %as-pseudo;
background-color: $magenta-600;
}
%main-nav-horizontal-drop-nav-header + ul,
%main-nav-horizontal-drop-nav-separator:not(:first-child) {
border-top: $decor-border-100;
border-color: $gray-300;
}
%main-nav-horizontal-drop-nav-header::before {
@extend %with-info-circle-fill-color-icon, %as-pseudo;
font-size: 1.1em;
}

View File

@ -1,8 +1,21 @@
@import '../base/components/table/index';
@import '../base/components/popover-menu/index';
table {
@extend %table, %table-flex;
}
%table-actions > [type='checkbox'] {
@extend %more-popover-menu;
}
%table-actions .confirmation-alert {
@extend %confirmation-alert;
}
%table-actions > [type='checkbox'] + label {
position: absolute;
top: 8px;
right: 15px;
}
html.template-service.template-list td:first-child a span,
html.template-node.template-show #services td:first-child a span,
html.template-service.template-show #instances td:first-child a span {

View File

@ -19,6 +19,7 @@ fieldset > header,
%app-view-content div > dl > dt,
%table caption,
%tbody-th,
%confirmation-alert header,
%form-element > span {
@extend %h4;
}
@ -50,7 +51,7 @@ pre code,
%form-element-label,
%stats-card header a span,
%footer,
%main-nav-horizontal-drop-nav > div,
%menu-panel-header,
%app-view h1 span.kind-proxy {
@extend %p2;
}
@ -59,7 +60,7 @@ pre code,
%main-content p,
%app-view > div.disabled > div,
%form-element-note,
%main-nav-horizontal-drop-nav-separator,
%menu-panel-separator,
%form-element-error > strong {
@extend %p3;
}
@ -81,7 +82,7 @@ pre code,
%splitter-card > header {
font-weight: $typo-weight-bold;
}
%main-nav-horizontal-drop-nav-separator {
%menu-panel-separator {
font-weight: $typo-weight-medium;
}
/**/

View File

@ -1 +1,8 @@
{{yield (action 'change') (action 'keypress') (concat 'component-aria-menu-trigger-' guid) (concat 'component-aria-menu-menu-' guid) (if expanded 'true' undefined)}}
{{yield
(action 'change')
(action 'keypress')
(concat 'component-aria-menu-trigger-' guid)
(concat 'component-aria-menu-menu-' guid)
(if expanded 'true' undefined)
(action 'keypressClick')
}}

View File

@ -1,21 +1,25 @@
{{yield}}
{{#aria-menu as |change keydown ariaLabelledBy ariaControls expanded|}}
{{#toggle-button onchange=change as |click|}}
<button type="button" aria-haspopup="menu" onkeydown={{keydown}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
{{yield (concat 'popover-menu-' guid)}}
{{#aria-menu keyboardAccess=keyboardAccess as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|}}
{{#toggle-button checked=expanded onchange=(queue change (action 'change')) as |click|}}
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
{{#yield-slot name='trigger'}}
{{yield}}
{{/yield-slot}}
</button>
{{/toggle-button}}
<div>
<input type="checkbox" id={{concat 'popover-menu-' guid '-'}} />
{{#each submenus as |sub|}}
<input type="checkbox" id={{concat 'popover-menu-' guid '-' sub}} />
{{/each}}
{{#yield-slot name='header'}}
<div>
{{yield}}
</div>
{{else}}
{{/yield-slot}}
<ul role="menu" id={{ariaControls}} aria-labelledby={{ariaLabelledBy}} aria-expanded={{expanded}}>
{{#yield-slot name='menu'}}
<ul role="menu" id={{ariaControls}} aria-labelledby={{ariaLabelledBy}} aria-expanded={{ariaExpanded}}>
{{#yield-slot name='menu' params=(block-params (concat 'popover-menu-' guid '-') send keypressClick) }}
{{yield}}
{{/yield-slot}}
</ul>

View File

@ -82,25 +82,40 @@
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to remove this Role?"}}
{{#block-slot name='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>
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a role="menuitem" tabindex="-1" href={{href-to 'dc.acls.roles.edit' item.ID}}>Edit</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Remove</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Remove
</header>
<p>
Are you sure you want to remove this role?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'remove' item items}}>Remove</button>
</li>
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'remove' item items}}>Remove</button>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
{{/action-group}}
</ul>
</div>
</div>
</li>
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
{{delete-confirmation message=message execute=execute cancel=cancel}}
{{/block-slot}}
{{/confirmation-dialog}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{/child-selector}}

View File

@ -3,23 +3,25 @@
<caption>{{#yield-slot name='caption'}}{{yield}}{{/yield-slot}}</caption>
{{/if}}
<thead>
<tr>
{{#yield-slot name='header'}}{{yield}}{{/yield-slot}}
<tr>
{{#yield-slot name='header'}}{{yield}}{{/yield-slot}}
{{#if hasActions }}
<th class="actions">Actions<input type="radio" name="actions" id="actions_close" onchange={{action change}} value="" /></th>
<th class="actions">Actions</th>
{{/if}}
</tr>
</tr>
</thead>
{{#ember-native-scrollable tagName='tbody' content-size=_contentSize scroll-left=_scrollLeft scroll-top=_scrollTop scrollChange=(action "scrollChange") clientSizeChange=(action "clientSizeChange")}}
<tr></tr>
{{#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 index|~}}
<tr data-test-tabular-row style={{{cell.style}}} onclick={{action 'click'}}>
{{#yield-slot name='row'}}{{yield cell.item index}}{{/yield-slot}}
<tr data-test-tabular-row style={{{cell.style}}} onclick={{action 'click'}}>
{{#yield-slot name='row'}}{{yield cell.item index}}{{/yield-slot}}
{{#if hasActions }}
<td class="actions">
{{#yield-slot name='actions' params=(block-params (concat index) change checked)}}{{yield cell.item index}}{{/yield-slot}}
</td>
<td class="actions">
{{#yield-slot name='actions' params=(block-params (concat cell.index) (action 'change') checked)}}
{{yield cell.item index}}
{{/yield-slot}}
</td>
{{/if}}
</tr>
</tr>
{{~/each~}}
{{/ember-native-scrollable}}

View File

@ -1,4 +1,4 @@
<input type="checkbox" id={{concat 'toggle-button-' guid}} onchange={{action 'change'}} />
<input type="checkbox" checked={{if checked 'checked' undefined}} id={{concat 'toggle-button-' guid}} onchange={{action 'change'}} />
<label for={{concat 'toggle-button-' guid}}>
{{yield (action 'click')}}
</label>

View File

@ -1,102 +1,144 @@
{{#app-view class="acl list" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='toolbar'}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
ACL Tokens <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='toolbar'}}
{{#if (gt items.length 0) }}
{{acl-filter searchable=searchable filters=typeFilters search=filters.s type=filters.type onchange=(action 'filter')}}
{{acl-filter searchable=searchable filters=typeFilters search=filters.s type=filters.type onchange=(action 'filter')}}
{{/if}}
{{/block-slot}}
{{#block-slot name='content'}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'Name:asc' filtered) as |item index|
{{/block-slot}}
{{#block-slot name='content'}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'Name:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Type</th>
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-acl="{{item.Name}}">
<a href={{href-to 'dc.acls.edit' item.ID}}>{{item.Name}}</a>
</td>
<td>
{{#if (eq item.Type 'management')}}
<strong>{{item.Type}}</strong>
{{else}}
<span>{{item.Type}}</span>
{{/if}}
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#popover-menu
expanded=(if (eq checked index) true false)
onchange=(action change index)
keyboardAccess=false
submenus=(array 'logout' 'use' 'delete')
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Type</th>
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-acl="{{item.Name}}">
<a href={{href-to 'dc.acls.edit' item.ID}}>{{item.Name}}</a>
</td>
<td>
{{#if (eq item.Type 'management')}}
<strong>{{item.Type}}</strong>
{{else}}
<span>{{item.Type}}</span>
{{/if}}
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index}}
{{#block-slot name='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.edit' item.ID}}>Edit</a>
</li>
{{#if (eq item.ID token.SecretID) }}
<li>
<button type="button" data-test-logout {{action confirm 'logout' item}}>Stop using</button>
</li>
{{else}}
<li>
<button type="button" data-test-use {{action confirm 'use' item}}>Use</button>
</li>
{{/if}}
<li>
<button type="button" data-test-clone {{action 'sendClone' item}}>Clone</button>
</li>
{{# if (not-eq item.ID 'anonymous') }}
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
{{/if}}
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
<p>
{{#if (eq name 'delete')}}
Are you sure you want to delete this ACL token?
{{else if (eq name 'logout')}}
Are you sure you want to stop using this ACL token? This will log you out.
{{ else if (eq name 'use')}}
Are you sure you want to use this ACL token?
{{/if}}
</p>
<button type="button" class="type-delete" {{action execute}}>
{{#if (eq name 'delete')}}
Confirm Delete
{{else if (eq name 'logout')}}
Confirm Logout
{{ else if (eq name 'use')}}
Confirm Use
{{/if}}
</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
{{/block-slot}}
{{/confirmation-dialog}}
{{/block-slot}}
{{/tabular-collection}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.edit' item.ID}}>Edit</a>
</li>
{{#if (eq item.ID token.SecretID) }}
<li role="none">
<label for={{concat confirm 'logout'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-logout>Stop using</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm logout
</header>
<p>
Are you sure you want to stop using this ACL token? This will log you out.
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" onclick={{action send 'logout' item}}>Logout</button>
</li>
<li>
<label for={{concat confirm 'logout'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{else}}
<li role="none">
<label for={{concat confirm 'use'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-use>Use</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm use
</header>
<p>
Are you sure you want to use this ACL token?
</p>
</div>
<ul>
<li class="dangerous">
<button data-test-confirm-use tabindex="-1" type="button" onclick={{action send 'use' item}}>Use</button>
</li>
<li>
<label for={{concat confirm 'use'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
<li role="none">
<button role="menuitem" tabindex="-1" type="button" data-test-clone {{action 'sendClone' item}}>Duplicate</button>
</li>
{{# if (not-eq item.ID 'anonymous') }}
<li role="none" class="dangerous">
<label for={{concat confirm 'delete'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this token?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{concat confirm 'delete'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no ACLs.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no ACLs.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/app-view}}

View File

@ -1,84 +1,100 @@
{{#app-view class=(concat 'policy ' (if (not isAuthorized) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/policies/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
Access Controls
</h1>
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
{{/block-slot}}
{{#block-slot name='disabled'}}
{{partial 'dc/acls/disabled'}}
{{/block-slot}}
{{#block-slot name='authorization'}}
{{partial 'dc/acls/authorization'}}
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='content'}}
{{#if (gt items.length 0) }}
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search"}}
</form>
{{/if}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Datacenters</th>
<th>Description</th>
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-policy="{{item.Name}}">
<a href={{href-to 'dc.acls.policies.edit' item.ID}} class={{if (eq (policy/typeof item) 'policy-management') 'is-management'}}>{{item.Name}}</a>
</td>
<td>
{{join ', ' (policy/datacenters item)}}
</td>
<td data-test-description>
<p>{{item.Description}}</p>
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Policy?"}}
{{#block-slot name='action' as |confirm|}}
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
<ul>
{{#if (eq (policy/typeof item) 'policy-management')}}
<li>
<a data-test-edit href={{href-to 'dc.acls.policies.edit' item.ID}}>View</a>
</li>
{{else}}
<li>
<a data-test-edit href={{href-to 'dc.acls.policies.edit' item.ID}}>Edit</a>
</li>
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/policies/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
Access Controls
</h1>
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
{{delete-confirmation message=message execute=execute cancel=cancel}}
{{/block-slot}}
{{/confirmation-dialog}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='disabled'}}
{{partial 'dc/acls/disabled'}}
{{/block-slot}}
{{#block-slot name='authorization'}}
{{partial 'dc/acls/authorization'}}
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.policies.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='content'}}
{{#if (gt items.length 0) }}
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search"}}
</form>
{{/if}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Datacenters</th>
<th>Description</th>
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Policies.
</p>
{{#block-slot name='row'}}
<td data-test-policy="{{item.Name}}">
<a href={{href-to 'dc.acls.policies.edit' item.ID}} class={{if (eq (policy/typeof item) 'policy-management') 'is-management'}}>{{item.Name}}</a>
</td>
<td>
{{join ', ' (policy/datacenters item)}}
</td>
<td data-test-description>
<p>{{item.Description}}</p>
</td>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
{{#if (eq (policy/typeof item) 'policy-management')}}
<li role="none">
<a role="menuitem" tabindex="-1" data-test-edit href={{href-to 'dc.acls.policies.edit' item.ID}}>View</a>
</li>
{{else}}
<li role="none">
<a role="menuitem" tabindex="-1" data-test-edit href={{href-to 'dc.acls.policies.edit' item.ID}}>Edit</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this policy?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Policies.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/app-view}}

View File

@ -1,79 +1,95 @@
{{#app-view class=(concat 'role ' (if (not isAuthorized) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/roles/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
Access Controls
</h1>
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
{{/block-slot}}
{{#block-slot name='disabled'}}
{{partial 'dc/acls/disabled'}}
{{/block-slot}}
{{#block-slot name='authorization'}}
{{partial 'dc/acls/authorization'}}
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.roles.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='content'}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/roles/notifications'}}
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
Access Controls
</h1>
{{#if isAuthorized }}
{{partial 'dc/acls/nav'}}
{{/if}}
{{/block-slot}}
{{#block-slot name='disabled'}}
{{partial 'dc/acls/disabled'}}
{{/block-slot}}
{{#block-slot name='authorization'}}
{{partial 'dc/acls/authorization'}}
{{/block-slot}}
{{#block-slot name='actions'}}
<a data-test-create href="{{href-to 'dc.acls.roles.create'}}" class="type-create">Create</a>
{{/block-slot}}
{{#block-slot name='content'}}
{{#if (gt items.length 0) }}
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search"}}
</form>
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search"}}
</form>
{{/if}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Description</th>
<th>Policies</th>
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Description</th>
<th>Policies</th>
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-role="{{item.Name}}">
<a href={{href-to 'dc.acls.roles.edit' item.ID}}>{{item.Name}}</a>
</td>
<td data-test-description>
<p>{{item.Description}}</p>
</td>
<td>
{{#each item.Policies as |item|}}
<strong data-test-policy class={{policy/typeof item}}>{{item.Name}}</strong>
{{/each}}
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-role="{{item.Name}}">
<a href={{href-to 'dc.acls.roles.edit' item.ID}}>{{item.Name}}</a>
</td>
<td data-test-description>
<p>{{item.Description}}</p>
</td>
<td>
{{#each item.Policies as |item|}}
<strong data-test-policy class={{policy/typeof item}}>{{item.Name}}</strong>
{{/each}}
</td>
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a role="menuitem" tabindex="-1" href={{href-to 'dc.acls.roles.edit' item.ID}}>Edit</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this role?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Role?"}}
{{#block-slot name='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 'delete' item}}>Delete</button>
</li>
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
{{delete-confirmation message=message execute=execute cancel=cancel}}
{{/block-slot}}
{{/confirmation-dialog}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Roles.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Roles.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/app-view}}

View File

@ -64,66 +64,100 @@
{{/if}}
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Token?"}}
{{#block-slot name='action' as |confirm|}}
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
<ul>
{{#if false}}
<li>
{{#copy-button-feedback title="Copy AccessorID to the clipboard" copy=item.AccessorID name="AccessorID"}}Copy AccessorID{{/copy-button-feedback}}
</li>
{{/if}}
<li>
<a data-test-edit href={{href-to 'dc.acls.tokens.edit' item.AccessorID}}>Edit</a>
</li>
{{#popover-menu
expanded=(if (eq checked index) true false)
onchange=(action change index)
keyboardAccess=false
submenus=(array 'logout' 'use' 'delete')
}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.tokens.edit' item.AccessorID}}>Edit</a>
</li>
{{#if (not (token/is-legacy item))}}
<li>
<button type="button" data-test-clone {{action 'sendClone' item}}>Duplicate</button>
</li>
<li role="none">
<button role="menuitem" tabindex="-1" type="button" data-test-clone {{action 'sendClone' item}}>Duplicate</button>
</li>
{{/if}}
{{#if (eq item.AccessorID token.AccessorID) }}
<li>
<button type="button" data-test-logout {{action confirm 'logout' item}}>Stop using</button>
</li>
<li role="none">
<label for={{concat confirm 'logout'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-logout>Stop using</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm logout
</header>
<p>
Are you sure you want to stop using this ACL token? This will log you out.
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" onclick={{action send 'logout' item}}>Logout</button>
</li>
<li>
<label for={{concat confirm 'logout'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{else}}
<li>
<button type="button" data-test-use {{action confirm 'use' item}}>Use</button>
</li>
<li role="none">
<label for={{concat confirm 'use'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-use>Use</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm use
</header>
<p>
Are you sure you want to use this ACL token?
</p>
</div>
<ul>
<li class="dangerous">
<button data-test-confirm-use tabindex="-1" type="button" onclick={{action send 'use' item}}>Use</button>
</li>
<li>
<label for={{concat confirm 'use'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/if}}
{{#unless (or (token/is-anonymous item) (eq item.AccessorID token.AccessorID)) }}
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
<li role="none" class="dangerous">
<label for={{concat confirm 'delete'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this token?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{concat confirm 'delete'}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/unless}}
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
<p>
{{#if (eq name 'delete')}}
{{message}}
{{#if (eq item.AccessorID token.AccessorID)}}
Warning: This is the token you are currently using!
{{/if}}
{{else if (eq name 'logout')}}
Are you sure you want to stop using this ACL token? This will log you out.
{{else if (eq name 'use')}}
Are you sure you want to use this ACL token?
{{/if}}
</p>
<button type="button" class="type-delete" {{action execute}}>
{{#if (eq name 'delete')}}
Confirm Delete
{{else if (eq name 'logout')}}
Confirm Logout
{{ else if (eq name 'use')}}
Confirm Use
{{/if}}
</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
{{/block-slot}}
{{/confirmation-dialog}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}

View File

@ -61,23 +61,39 @@
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this intention?'}}
{{#block-slot name='action' as |confirm|}}
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
<ul>
<li>
<a href={{href-to 'dc.intentions.edit' item.ID}}>Edit</a>
</li>
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message|}}
{{delete-confirmation message=message execute=execute cancel=cancel}}
{{/block-slot}}
{{/confirmation-dialog}}
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a role="menuitem" tabindex="-1" href={{href-to 'dc.intentions.edit' item.ID}}>Edit</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this intention?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}

View File

@ -1,81 +1,97 @@
{{#app-view class="kv list" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/kv/notifications'}}
{{/block-slot}}
{{#block-slot name='breadcrumbs'}}
<ol>
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/kv/notifications'}}
{{/block-slot}}
{{#block-slot name='breadcrumbs'}}
<ol>
{{#if (not-eq parent.Key '/') }}
<li><a href={{href-to 'dc.kv'}}>Key / Values</a></li>
<li><a href={{href-to 'dc.kv'}}>Key / Values</a></li>
{{/if}}
{{#each (slice 0 -2 (split parent.Key '/')) as |breadcrumb index|}}
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
{{/each}}
</ol>
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
{{#if (eq parent.Key '/') }}
Key / Value
{{else}}
{{ take 1 (drop 1 (reverse (split parent.Key '/'))) }}
{{/if}}
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot name='toolbar'}}
</ol>
{{/block-slot}}
{{#block-slot name='header'}}
<h1>
{{#if (eq parent.Key '/')}}
Key / Value
{{else}}
{{take 1 (drop 1 (reverse (split parent.Key '/')))}}
{{/if}}
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot name='toolbar'}}
{{#if (gt items.length 0) }}
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search by name"}}
</form>
<form class="filter-bar">
{{freetext-filter searchable=searchable value=s placeholder="Search by name"}}
</form>
{{/if}}
{{/block-slot}}
{{#block-slot name='actions'}}
{{/block-slot}}
{{#block-slot name='actions'}}
{{#if (not-eq parent.Key '/') }}
<a data-test-create href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
<a data-test-create href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
{{else}}
<a data-test-create href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
<a data-test-create href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
{{/if}}
{{/block-slot}}
{{#block-slot name='content'}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'isFolder:desc' 'Key:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
{{/block-slot}}
{{#block-slot name='row'}}
<td data-test-kv="{{item.Key}}" class={{if item.isFolder 'folder' 'file' }}>
<a href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{right-trim (left-trim item.Key parent.Key) '/'}}</a>
</td>
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this key?'}}
{{#block-slot name='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 (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{if item.isFolder 'View' 'Edit'}}</a>
</li>
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
</ul>
{{/action-group}}
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message|}}
{{delete-confirmation message=message execute=execute cancel=cancel}}
{{/block-slot}}
{{/confirmation-dialog}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='content'}}
{{#changeable-set dispatcher=searchable}}
{{#block-slot name='set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'isFolder:desc' 'Key:asc' filtered) as |item index|
}}
{{#block-slot name='header'}}
<th>Name</th>
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Key / Value pairs.
</p>
{{#block-slot name='row'}}
<td data-test-kv={{item.Key}} class={{if item.isFolder 'folder' 'file' }}>
<a href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{right-trim (left-trim item.Key parent.Key) '/'}}</a>
</td>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to (if item.isFolder 'dc.kv.folder' 'dc.kv.edit') item.Key}}>{{if item.isFolder 'View' 'Edit'}}</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this key?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}
{{#block-slot name='empty'}}
<p>
There are no Key / Value pairs.
</p>
{{/block-slot}}
{{/changeable-set}}
{{/block-slot}}
{{/app-view}}

View File

@ -52,31 +52,39 @@
{{/if}}
{{/block-slot}}
{{#block-slot name='actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Namespace?"}}
{{#block-slot name='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.nspaces.edit' item.Name}}>Edit</a>
</li>
{{#if (not-eq item.Name 'default')}}
<li>
<button type="button" class="type-delete" data-test-delete {{action confirm 'delete' item}}>Delete</button>
</li>
{{/if}}
</ul>
{{/action-group}}
{{#popover-menu expanded=(if (eq checked index) true false) onchange=(action change index) keyboardAccess=false}}
{{#block-slot name='trigger'}}
More
{{/block-slot}}
{{#block-slot name='dialog' as |execute cancel message name|}}
<p>
{{message}}
</p>
<button type="button" class="type-delete" {{action execute}}>
Confirm Delete
</button>
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
{{#block-slot name='menu' as |confirm send keypressClick|}}
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.nspaces.edit' item.Name}}>Edit</a>
</li>
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
<div class="confirmation-alert warning">
<div>
<header>
Confirm Delete
</header>
<p>
Are you sure you want to delete this key?
</p>
</div>
<ul>
<li class="dangerous">
<button tabindex="-1" type="button" class="type-delete" onclick={{action send 'delete' item}}>Delete</button>
</li>
<li>
<label for={{confirm}}>Cancel</label>
</li>
</ul>
</div>
</div>
</li>
{{/block-slot}}
{{/confirmation-dialog}}
{{/popover-menu}}
{{/block-slot}}
{{/tabular-collection}}
{{/block-slot}}

View File

@ -8,7 +8,7 @@ export default function(visitable, deletable, creatable, clickable, attribute, c
acl: clickable('a'),
actions: clickable('label'),
use: clickable('[data-test-use]'),
confirmUse: clickable('button.type-delete'),
confirmUse: clickable('[data-test-confirm-use]'),
})
),
filter: filter,

View File

@ -24,7 +24,7 @@ export default function(
token: clickable('a'),
actions: clickable('label'),
use: clickable('[data-test-use]'),
confirmUse: clickable('button.type-delete'),
confirmUse: clickable('[data-test-confirm-use]'),
clone: clickable('[data-test-clone]'),
})
),