Merge pull request #7125 from hashicorp/ui-staging

ui: UI Release Merge (ui-staging merge)
pull/7127/head
John Cowen 2020-01-24 14:55:19 +00:00 committed by GitHub
commit 47ac0bcd01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
225 changed files with 2792 additions and 2485 deletions

View File

@ -4,6 +4,9 @@ import { SLUG_KEY } from 'consul-ui/models/policy';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id }) {
@ -32,7 +35,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/policy?${params}
@ -42,13 +44,13 @@ export default Adapter.extend({
Description: serialized.Description,
Rules: serialized.Rules,
Datacenters: serialized.Datacenters,
...Namespace(serialized.Namespace),
}}
`;
},
requestForUpdateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/policy/${data[SLUG_KEY]}?${params}
@ -58,6 +60,7 @@ export default Adapter.extend({
Description: serialized.Description,
Rules: serialized.Rules,
Datacenters: serialized.Datacenters,
Namespace: serialized.Namespace,
}}
`;
},

View File

@ -3,7 +3,9 @@ import Adapter from './application';
import { SLUG_KEY } from 'consul-ui/models/role';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id }) {
@ -32,7 +34,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/role?${params}
@ -40,16 +41,15 @@ export default Adapter.extend({
${{
Name: serialized.Name,
Description: serialized.Description,
Namespace: serialized.Namespace,
Policies: serialized.Policies,
ServiceIdentities: serialized.ServiceIdentities,
...Namespace(serialized.Namespace),
}}
`;
},
requestForUpdateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/role/${data[SLUG_KEY]}?${params}

View File

@ -4,7 +4,9 @@ import { inject as service } from '@ember/service';
import { SLUG_KEY } from 'consul-ui/models/token';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
store: service('store'),
@ -35,7 +37,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/token?${params}
@ -46,6 +47,7 @@ export default Adapter.extend({
Roles: serialized.Roles,
ServiceIdentities: serialized.ServiceIdentities,
Local: serialized.Local,
...Namespace(serialized.Namespace),
}}
`;
},
@ -66,13 +68,13 @@ export default Adapter.extend({
}
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/token/${data[SLUG_KEY]}?${params}
${{
Description: serialized.Description,
Namespace: serialized.Namespace,
Policies: serialized.Policies,
Roles: serialized.Roles,
ServiceIdentities: serialized.ServiceIdentities,

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

@ -25,7 +25,7 @@ export default Component.extend(SlotsMixin, WithListeners, {
this._super(...arguments);
this.searchable = this.container.searchable(this.type);
this.form = this.formContainer.form(this.type);
this.form.clear({ Datacenter: this.dc });
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
},
options: computed('selectedOptions.[]', 'allOptions.[]', function() {
// It's not massively important here that we are defaulting `items` and
@ -52,7 +52,7 @@ export default Component.extend(SlotsMixin, WithListeners, {
});
},
reset: function() {
this.form.clear({ Datacenter: this.dc });
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
},
open: function() {
if (!get(this, 'allOptions.closed')) {

View File

@ -3,93 +3,12 @@ import { inject as service } from '@ember/service';
import { set, get, computed } from '@ember/object';
import { next } from '@ember/runloop';
const getNodesByType = function(nodes = {}, type) {
return Object.values(nodes).filter(item => item.Type === type);
};
const targetsToFailover = function(targets, a) {
let type;
const Targets = targets.map(function(b) {
// TODO: this isn't going to work past namespace for services
// with dots in the name
const [aRev, bRev] = [a, b].map(item => item.split('.').reverse());
const types = ['Datacenter', 'Namespace', 'Service', 'Subset'];
return bRev.find(function(item, i) {
const res = item !== aRev[i];
if (res) {
type = types[i];
}
return res;
});
});
return {
Type: type,
Targets: Targets,
};
};
const getNodeResolvers = function(nodes = {}) {
const failovers = getFailovers(nodes);
const resolvers = {};
Object.keys(nodes).forEach(function(key) {
const node = nodes[key];
if (node.Type === 'resolver' && !failovers.includes(key.split(':').pop())) {
resolvers[node.Name] = node;
}
});
return resolvers;
};
const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes = {}) {
const resolvers = {};
Object.values(targets).forEach(item => {
let node = nodes[item.ID];
if (node) {
if (typeof resolvers[item.Service] === 'undefined') {
resolvers[item.Service] = {
ID: item.ID,
Name: item.Service,
Children: [],
Failover: null,
Redirect: null,
};
}
const resolver = resolvers[item.Service];
let failoverable = resolver;
if (item.ServiceSubset) {
failoverable = item;
// TODO: Sometimes we have set the resolvers ID to the ID of the
// subset this just shifts the subset of the front of the URL for the moment
const temp = item.ID.split('.');
temp.shift();
resolver.ID = temp.join('.');
resolver.Children.push(item);
}
if (typeof node.Resolver.Failover !== 'undefined') {
// TODO: Figure out if we can get rid of this
/* eslint ember/no-side-effects: "warn" */
set(failoverable, 'Failover', targetsToFailover(node.Resolver.Failover.Targets, item.ID));
} else {
const res = targetsToFailover([node.Resolver.Target], `service.${nspace}.${dc}`);
if (res.Type === 'Datacenter' || res.Type === 'Namespace') {
resolver.Children.push(item);
set(failoverable, 'Redirect', true);
}
}
}
});
return Object.values(resolvers);
};
const getFailovers = function(nodes = {}) {
const failovers = [];
Object.values(nodes)
.filter(item => item.Type === 'resolver')
.forEach(function(item) {
(get(item, 'Resolver.Failover.Targets') || []).forEach(failover => {
failovers.push(failover);
});
});
return failovers;
};
import {
createRoute,
getSplitters,
getRoutes,
getResolvers,
} from 'consul-ui/utils/components/discovery-chain/index';
export default Component.extend({
dom: service('dom'),
@ -134,74 +53,33 @@ export default Component.extend({
this.ticker.destroy(this);
},
splitters: computed('chain.Nodes', function() {
return getNodesByType(get(this, 'chain.Nodes'), 'splitter').map(function(item) {
set(item, 'ID', `splitter:${item.Name}`);
return item;
});
return getSplitters(get(this, 'chain.Nodes'));
}),
routers: computed('chain.Nodes', function() {
// Right now there should only ever be one 'Router'.
return getNodesByType(get(this, 'chain.Nodes'), 'router');
routes: computed('chain.Nodes', function() {
return getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
}),
routes: computed('chain', 'routers', function() {
const routes = get(this, 'routers').reduce(function(prev, item) {
return prev.concat(
item.Routes.map(function(route, i) {
return {
...route,
ID: `route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`,
};
})
);
}, []);
if (routes.length === 0) {
let nextNode = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
const splitterID = `splitter:${this.chain.ServiceName}`;
if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
nextNode = splitterID;
}
routes.push({
Default: true,
ID: `route:${this.chain.ServiceName}`,
Name: this.chain.ServiceName,
Definition: {
Match: {
HTTP: {
PathPrefix: '/',
},
},
},
NextNode: nextNode,
});
}
return routes;
}),
nodeResolvers: computed('chain.Nodes', function() {
return getNodeResolvers(get(this, 'chain.Nodes'));
}),
resolvers: computed('nodeResolvers.[]', 'chain.Targets', function() {
return getTargetResolvers(
resolvers: computed('chain.{Nodes,Targets}', function() {
return getResolvers(
this.chain.Datacenter,
this.chain.Namespace,
get(this, 'chain.Targets'),
this.nodeResolvers
get(this, 'chain.Nodes')
);
}),
graph: computed('chain.Nodes', function() {
const graph = this.dataStructs.graph();
Object.entries(get(this, 'chain.Nodes')).forEach(function([key, item]) {
const router = this.chain.ServiceName;
Object.entries(get(this, 'chain.Nodes')).forEach(([key, item]) => {
switch (item.Type) {
case 'splitter':
item.Splits.forEach(function(splitter) {
item.Splits.forEach(splitter => {
graph.addLink(`splitter:${item.Name}`, splitter.NextNode);
});
break;
case 'router':
item.Routes.forEach(function(route, i) {
graph.addLink(
`route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`,
route.NextNode
);
item.Routes.forEach((route, i) => {
route = createRoute(route, router, this.dom.guid);
graph.addLink(route.ID, route.NextNode);
});
break;
}
@ -212,18 +90,15 @@ export default Component.extend({
if (this.selectedId === '' || !this.dom.element(`#${this.selectedId}`)) {
return {};
}
const getTypeFromId = function(id) {
return id.split(':').shift();
};
const id = this.selectedId;
const type = getTypeFromId(id);
const type = id.split(':').shift();
const nodes = [id];
const edges = [];
this.graph.forEachLinkedNode(id, (linkedNode, link) => {
nodes.push(linkedNode.id);
edges.push(`${link.fromId}>${link.toId}`);
this.graph.forEachLinkedNode(linkedNode.id, (linkedNode, link) => {
const nodeType = getTypeFromId(linkedNode.id);
const nodeType = linkedNode.id.split(':').shift();
if (type !== nodeType && type !== 'splitter' && nodeType !== 'splitter') {
nodes.push(linkedNode.id);
edges.push(`${link.fromId}>${link.toId}`);
@ -248,6 +123,16 @@ export default Component.extend({
// TODO: Figure out if we can remove this next
next(() => {
this._listeners.remove();
this._listeners.add(this.dom.document(), {
click: e => {
// all route/splitter/resolver components currently
// have classes that end in '-card'
if (!this.dom.closest('[class$="-card"]', e.target)) {
set(this, 'active', false);
set(this, 'selectedId', '');
}
},
});
[...this.dom.elements('path.split', this.element)].forEach(item => {
this._listeners.add(item, {
mouseover: e => this.actions.showSplit.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,5 +1,14 @@
import Component from '@ember/component';
const ENTER = 13;
export default Component.extend({
tagName: 'fieldset',
tagName: '',
keyboardAccess: false,
actions: {
keydown: function(e) {
if (e.keyCode === ENTER) {
e.target.dispatchEvent(new MouseEvent('click'));
}
},
},
});

View File

@ -1,7 +1,14 @@
import Component from '@ember/component';
const ENTER = 13;
export default Component.extend({
name: 'tab',
tagName: 'nav',
classNames: ['tab-nav'],
tagName: '',
actions: {
keydown: function(e) {
if (e.keyCode === ENTER) {
e.target.dispatchEvent(new MouseEvent('click'));
}
},
},
});

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

@ -1,61 +1,3 @@
import _config from './config/environment';
const doc = document;
const getDevEnvVars = function() {
return doc.cookie
.split(';')
.filter(item => item !== '')
.map(item => item.trim().split('='));
};
const getUserEnvVar = function(str) {
return window.localStorage.getItem(str);
};
// TODO: Look at `services/client` for pulling
// HTTP headers in here so we can let things be controlled
// via HTTP proxies, for example turning off blocking
// queries if its a busy cluster
// const getOperatorEnvVars = function() {}
// TODO: Not necessarily here but the entire app should
// use the `env` export not the `default` one
// but we might also change the name of this file, so wait for that first
export const env = function(str) {
let user = null;
switch (str) {
case 'CONSUL_UI_DISABLE_REALTIME':
case 'CONSUL_UI_DISABLE_ANCHOR_SELECTION':
case 'CONSUL_UI_REALTIME_RUNNER':
user = getUserEnvVar(str);
break;
}
// We use null here instead of an undefined check
// as localStorage will return null if not set
return user !== null ? user : _config[str];
};
export const config = function(key) {
let $;
switch (_config.environment) {
case 'development':
case 'staging':
case 'test':
$ = getDevEnvVars().reduce(function(prev, [key, value]) {
const val = !!JSON.parse(String(value).toLowerCase());
switch (key) {
case 'CONSUL_ACLS_ENABLE':
prev['CONSUL_ACLS_ENABLED'] = val;
break;
case 'CONSUL_NSPACES_ENABLE':
prev['CONSUL_NSPACES_ENABLED'] = val;
break;
default:
prev[key] = value;
}
return prev;
}, {});
if (typeof $[key] !== 'undefined') {
return $[key];
}
break;
}
return _config[key];
};
export default env;
import config from './config/environment';
import getEnvironment from './utils/get-environment';
export const env = getEnvironment(config, window, document);

View File

@ -1,9 +1,6 @@
import { helper } from '@ember/component/helper';
import { config } from 'consul-ui/env';
// TODO: env actually uses config values not env values
// see `app/env` for the renaming TODO's also
export function env([name, def = ''], hash) {
return config(name) != null ? config(name) : def;
}
export default helper(env);
import { env } from 'consul-ui/env';
export default helper(function([name, def = ''], hash) {
const val = env(name);
return val != null ? val : def;
});

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html class="{{content-for "root-class"}}">
<html lang="en" class="{{content-for "root-class"}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">

View File

@ -1,15 +0,0 @@
const scripts = document.getElementsByTagName('script');
const current = scripts[scripts.length - 1];
export function initialize(application) {
const Client = application.resolveRegistration('service:client/http');
Client.reopen({
isCurrent: function(src) {
return current.src === src;
},
});
}
export default {
initialize,
};

View File

@ -1,5 +1,5 @@
import Route from '@ember/routing/route';
import { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
import { routes } from 'consul-ui/router';
import flat from 'flat';
@ -26,7 +26,7 @@ Route.reopen(
return prev;
}, {})
);
if (config('CONSUL_NSPACES_ENABLED')) {
if (env('CONSUL_NSPACES_ENABLED')) {
const dotRe = /\./g;
initialize = function(container) {
const all = Object.keys(flat(routes))

View File

@ -1,11 +1,11 @@
import env, { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
export function initialize(container) {
if (env('CONSUL_UI_DISABLE_REALTIME')) {
return;
}
['node', 'coordinate', 'session', 'service', 'proxy', 'discovery-chain']
.concat(config('CONSUL_NSPACES_ENABLED') ? ['nspace/enabled'] : [])
.concat(env('CONSUL_NSPACES_ENABLED') ? ['nspace/enabled'] : [])
.map(function(item) {
// create repositories that return a promise resolving to an EventSource
return {
@ -79,7 +79,7 @@ export function initialize(container) {
},
])
.concat(
config('CONSUL_NSPACES_ENABLED')
env('CONSUL_NSPACES_ENABLED')
? [
{
route: 'dc/nspaces/index',
@ -104,7 +104,7 @@ export function initialize(container) {
// but hardcode this for the moment
if (typeof definition.route !== 'undefined') {
container.inject(`route:${definition.route}`, name, `service:${servicePath}`);
if (config('CONSUL_NSPACES_ENABLED') && definition.route.startsWith('dc/')) {
if (env('CONSUL_NSPACES_ENABLED') && definition.route.startsWith('dc/')) {
container.inject(`route:nspace/${definition.route}`, name, `service:${servicePath}`);
}
} else {

View File

@ -1,6 +1,6 @@
import { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
export function initialize(container) {
if (config('CONSUL_NSPACES_ENABLED')) {
if (env('CONSUL_NSPACES_ENABLED')) {
['dc', 'settings', 'dc.intentions.edit', 'dc.intentions.create'].forEach(function(item) {
container.inject(`route:${item}`, 'nspacesRepo', 'service:repository/nspace/enabled');
container.inject(`route:nspace.${item}`, 'nspacesRepo', 'service:repository/nspace/enabled');
@ -15,7 +15,7 @@ export function initialize(container) {
// 1. Make it be about adding classes to the root dom node
// 2. Make it be about config and things to do on initialization re: config
// If we go with 1 then we need to move both this and the above nspaces class
if (config('CONSUL_ACLS_ENABLED')) {
if (env('CONSUL_ACLS_ENABLED')) {
container
.lookup('service:dom')
.root()

View File

@ -1,4 +1,4 @@
import env from 'consul-ui/env';
import { env } from 'consul-ui/env';
const SECONDARY_BUTTON = 2;
const isSelecting = function(win = window) {

View File

@ -8,12 +8,14 @@ export default Mixin.create(WithBlockingActions, {
actions: {
use: function(item) {
return this.feedback.execute(() => {
// old style legacy ACLs don't have AccessorIDs
// therefore set it to null, this way the frontend knows
// old style legacy ACLs don't have AccessorIDs or Namespaces
// therefore set AccessorID to null, this way the frontend knows
// to use legacy ACLs
// set the Namespace to just use default
return this.settings
.persist({
token: {
Namespace: 'default',
AccessorID: null,
SecretID: get(item, 'ID'),
},

View File

@ -1,10 +1,10 @@
import EmberRouter from '@ember/routing/router';
import { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
import walk from 'consul-ui/utils/routing/walk';
const Router = EmberRouter.extend({
location: config('locationType'),
rootURL: config('rootURL'),
location: env('locationType'),
rootURL: env('rootURL'),
});
export const routes = {
// Our parent datacenter resource sets the namespace
@ -107,7 +107,7 @@ export const routes = {
_options: { path: '/*path' },
},
};
if (config('CONSUL_NSPACES_ENABLED')) {
if (env('CONSUL_NSPACES_ENABLED')) {
routes.dc.nspaces = {
_options: { path: '/namespaces' },
edit: {

View File

@ -1,6 +1,6 @@
import Route from '@ember/routing/route';
import { get } from '@ember/object';
import { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
import { inject as service } from '@ember/service';
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
export default Route.extend(WithBlockingActions, {
@ -16,9 +16,9 @@ export default Route.extend(WithBlockingActions, {
return this.settings
.persist({
token: {
Namespace: get(item, 'Namespace'),
AccessorID: get(item, 'AccessorID'),
SecretID: secret,
Namespace: get(item, 'Namespace'),
},
})
.then(item => {
@ -32,9 +32,9 @@ export default Route.extend(WithBlockingActions, {
return false;
});
} else {
// TODO: Ideally we wouldn't need to use config() at a route level
// TODO: Ideally we wouldn't need to use env() at a route level
// transitionTo should probably remove it instead if NSPACES aren't enabled
if (config('CONSUL_NSPACES_ENABLED') && get(item, 'token.Namespace') !== nspace) {
if (env('CONSUL_NSPACES_ENABLED') && get(item, 'token.Namespace') !== nspace) {
let routeName = this.router.currentRouteName;
if (!routeName.startsWith('nspace')) {
routeName = `nspace.${routeName}`;

View File

@ -15,7 +15,7 @@ export default Route.extend(WithIntentionActions, {
},
model: function(params) {
const dc = this.modelFor('dc').dc.Name;
const nspace = this.modelFor('nspace').nspace.substr(1);
const nspace = '*';
this.item = this.repo.create({
Datacenter: dc,
});

View File

@ -3,6 +3,7 @@ import Service, { inject as service } from '@ember/service';
import { get, set } from '@ember/object';
import { Promise } from 'rsvp';
import { env } from 'consul-ui/env';
import getObjectPool from 'consul-ui/utils/get-object-pool';
import Request from 'consul-ui/utils/http/request';
import createURL from 'consul-ui/utils/createURL';
@ -41,31 +42,7 @@ export default Service.extend({
settings: service('settings'),
init: function() {
this._super(...arguments);
let protocol = 'http/1.1';
try {
protocol = performance.getEntriesByType('resource').find(item => {
// isCurrent is added in initializers/client and is used
// to ensure we use the consul-ui.js src to sniff what the protocol
// is. Based on the assumption that whereever this script is it's
// likely to be the same as the xmlhttprequests
return item.initiatorType === 'script' && this.isCurrent(item.name);
}).nextHopProtocol;
} catch (e) {
// pass through
}
let maxConnections;
// http/2, http2+QUIC/39 and SPDY don't have connection limits
switch (true) {
case protocol.indexOf('h2') === 0:
case protocol.indexOf('hq') === 0:
case protocol.indexOf('spdy') === 0:
break;
default:
// generally 6 are available
// reserve 1 for traffic that we can't manage
maxConnections = 5;
break;
}
const maxConnections = env('CONSUL_HTTP_MAX_CONNECTIONS');
set(this, 'connections', getObjectPool(dispose, maxConnections));
if (typeof maxConnections !== 'undefined') {
set(this, 'maxConnections', maxConnections);

View File

@ -1,6 +1,6 @@
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import { config } from 'consul-ui/env';
import { env } from 'consul-ui/env';
import RepositoryService from 'consul-ui/services/repository';
const modelName = 'nspace';
@ -18,7 +18,7 @@ export default RepositoryService.extend({
return this.store.query(this.getModelName(), query);
},
authorize: function(dc, nspace) {
if (!config('CONSUL_ACLS_ENABLED')) {
if (!env('CONSUL_ACLS_ENABLED')) {
return Promise.resolve([
{
Resource: 'operator',

View File

@ -34,6 +34,14 @@
padding-top: calc(0.4em - 1px) !important;
padding-bottom: calc(0.4em - 1px) !important;
}
%copy-button:empty {
padding: 5px !important;
margin-right: 0;
margin-top: -5px;
}
%copy-button:not(:empty)::before {
margin-right: 10px;
}
%internal-button {
padding: 0.9em 1em;
text-align: center;

View File

@ -15,15 +15,11 @@
min-height: 17px;
}
%copy-button::before {
@extend %with-copy-action-icon;
@extend %as-pseudo;
width: 16px;
height: 16px;
position: absolute;
left: 12px;
@extend %with-copy-action-mask, %as-pseudo;
background-color: $gray-500;
}
%copy-button:not(:empty) {
padding-left: 38px !important;
%copy-button:not(:empty)::before {
margin-right: 10px;
}
%primary-button,
%secondary-button,
@ -44,7 +40,8 @@
color: $color-action;
background-color: $gray-050;
}
%copy-button:disabled {
%copy-button:hover::before {
background-color: $blue-500;
}
%copy-button:active {
background-color: $gray-200;

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,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

@ -42,7 +42,6 @@ $edit-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns=
$exit-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 19V5h5.944V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2v-5.98h-2V19H5zm9-16v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" fill="%23000"/></svg>');
$expand-less-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 13l2.3 2.3-2.89 2.87 1.42 1.42L8.7 16.7 11 19v-6H5zm14-2l-2.3-2.3 2.89-2.87-1.42-1.42L15.3 7.3 13 5v6h6z" fill="%23000"/></svg>');
$expand-more-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14 4l2.3 2.3-2.89 2.87 1.42 1.42L17.7 7.7 20 10V4h-6zm-4 16l-2.3-2.3 2.89-2.87-1.42-1.42L6.3 16.3 4 14v6h6z" fill="%23000"/></svg>');
$eye-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="16" height="8" viewBox="0 0 16 8" xmlns="http://www.w3.org/2000/svg"><path d="M10.229 1.301A3.493 3.493 0 0 1 11.5 4a3.493 3.493 0 0 1-1.271 2.699c1.547-.431 3.008-1.326 4.393-2.699-1.385-1.373-2.846-2.268-4.393-2.699zM5.771 6.7A3.493 3.493 0 0 1 4.5 4c0-1.086.495-2.057 1.271-2.699C4.224 1.732 2.763 2.627 1.378 4c1.385 1.373 2.846 2.268 4.393 2.699zM8 8C5.054 8 2.388 6.667 0 4c2.388-2.667 5.054-4 8-4 2.946 0 5.612 1.333 8 4-2.388 2.667-5.054 4-8 4zm.965-4.25a1 1 0 1 0 .07-2 1 1 0 0 0-.07 2z" fill="%237C8896"/></svg>');
$file-fill-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.714 7.714L14.43 3.43h-10v17.14h14.285V7.714zM20.143 7v13.571c0 .786-.643 1.429-1.429 1.429H4.43A1.433 1.433 0 0 1 3 20.571V3.43C3 2.643 3.643 2 4.429 2h10.714l5 5zM5.857 17.714h10v-1.428h-10v1.428zm0-2.857h10V13.43h-10v1.428zm0-2.857h10v-1.429h-10V12zm0-4.286h5.714V6.286H5.857v1.428z" fill="%23000"/></svg>');
$file-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20.143 7v13.571c0 .786-.643 1.429-1.429 1.429H4.43A1.433 1.433 0 0 1 3 20.571V3.43C3 2.643 3.643 2 4.429 2h10.714l5 5zm-1.429.714H14.43V3.43h-10v17.14h14.285V7.714z" fill="%23000"/></svg>');
$filter-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" fill="%23000"/></svg>');

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;
@ -98,6 +102,11 @@
background-image: $check-circle-fill-svg;
}
%with-check-circle-fill-mask {
@extend %with-mask;
mask-image: $check-circle-fill-svg;
}
%with-check-circle-outline-icon {
@extend %with-icon;
background-image: $check-circle-outline-svg;
@ -178,6 +187,10 @@
@extend %with-icon;
background-image: $copy-action-svg;
}
%with-copy-action-mask {
@extend %with-mask;
mask-image: $copy-action-svg;
}
%with-copy-success-icon {
@extend %with-icon;
@ -229,11 +242,6 @@
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;
@ -269,6 +277,11 @@
background-image: $folder-outline-svg;
}
%with-folder-outline-mask {
@extend %with-mask;
mask-image: $folder-outline-svg;
}
%with-git-branch-icon {
@extend %with-icon;
background-image: $git-branch-svg;
@ -289,6 +302,10 @@
background-image: $hashicorp-logo-svg;
}
%with-hashicorp-logo-mask {
@extend %with-mask;
mask-image: $hashicorp-logo-svg;
}
%with-help-circle-fill-icon {
@extend %with-icon;
background-image: $help-circle-fill-svg;
@ -473,6 +490,11 @@
background-image: $star-fill-svg;
}
%with-star-fill-mask {
@extend %with-mask;
mask-image: $star-fill-svg;
}
%with-star-outline-icon {
@extend %with-icon;
background-image: $star-outline-svg;
@ -572,8 +594,16 @@
@extend %with-icon;
background-image: $visibility-hide-svg;
}
%with-visibility-hide-mask {
@extend %with-mask;
mask-image: $visibility-hide-svg;
}
%with-visibility-show-icon {
@extend %with-icon;
background-image: $visibility-show-svg;
}
%with-visibility-show-mask {
@extend %with-mask;
mask-image: $visibility-show-svg;
}

View File

@ -1,6 +1,7 @@
@import '../base/components/anchors/index';
a[rel*='external'] {
@extend %with-exit;
a[rel*='external']::after {
@extend %with-exit-icon, %as-pseudo;
margin-left: 8px;
}
%main-content label a[rel*='help'] {
color: $gray-400;

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,10 @@
/*TODO: Rename this to %app-view-brand-icon or similar */
%with-external-source-icon {
background-repeat: no-repeat;
background-size: contain;
width: 18px;
height: 18px;
}
%app-view h2,
%app-view fieldset {
border-bottom: $decor-border-200;

View File

@ -1,7 +1,4 @@
@import '../base/components/buttons/index';
%copy-button {
@extend %with-clipboard;
}
button[type='submit'],
a.type-create {
@extend %primary-button;

View File

@ -1,9 +0,0 @@
%checkbox-group span {
display: inline-block;
margin-left: 10px;
min-width: 50px;
}
%checkbox-group label {
margin-right: 10px;
white-space: nowrap;
}

View File

@ -1,3 +0,0 @@
%checkbox-group label {
cursor: pointer;
}

View File

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

View File

@ -1,62 +0,0 @@
%form-row {
margin-bottom: 1.4em;
}
%form-element {
@extend %form-row;
}
%form-element,
%form-element > em,
%form-element > span,
%form-element textarea {
display: block;
}
%form-element a {
display: inline;
}
%form-element > em > code {
display: inline-block;
}
%form-element [type='text'],
%form-element [type='password'] {
display: inline-flex;
justify-content: flex-start;
}
%form-element textarea {
resize: vertical;
}
%form-element > span {
margin-bottom: 0.5em;
}
%form-element > span + em {
margin-top: -0.5em;
margin-bottom: 0.5em;
}
%form-element textarea {
max-width: 100%;
min-width: 100%;
min-height: 70px;
padding: 0.625em 15px;
}
%form-element [type='text'],
%form-element [type='password'] {
max-width: 100%;
width: 100%;
height: 2.25em;
}
%form-element > em {
margin-top: 2px;
}
%form-element > em > code {
padding: 0 4px;
}
%form-element [type='text'],
%form-element [type='password'],
%form-element textarea {
padding: 17px 13px;
}
%form-element textarea {
padding: 6px 13px;
}
%form-element > span {
margin-bottom: 0.4em !important;
}

View File

@ -1,61 +0,0 @@
%form-element > strong {
@extend %with-error;
}
%form-element-error > input,
%form-element-error > textarea {
border: $decor-border-100;
}
%form-element [type='text'],
%form-element [type='password'],
%form-element textarea {
-moz-appearance: none;
-webkit-appearance: none;
box-shadow: inset 0 4px 1px rgba(0, 0, 0, 0.06);
border-radius: $decor-radius-100;
border: $decor-border-100;
}
%form-element [type='text']:focus,
%form-element [type='password']:focus,
%form-element textarea:focus {
outline: none;
}
%form-element > em > code {
border-radius: $decor-radius-100;
}
%form-element-error > input {
border-color: $color-failure !important;
}
%form-element > strong {
color: $color-failure;
}
%form-element > em {
color: $gray-400;
}
%form-element > em > code {
background-color: $gray-200;
color: $magenta-600;
border-radius: $decor-radius-100;
}
%form-element > span {
color: $black;
}
%form-element [type='text'],
%form-element [type='password'],
%form-element textarea {
color: $gray-500;
}
%form-element [type='text'],
%form-element [type='password'],
%form-element textarea {
border-color: $gray-300;
}
%form-element [type='text']:hover,
%form-element [type='password']:hover,
%form-element textarea:hover {
border-color: $gray-500;
}
%form-element [type='text']:focus,
%form-element [type='password']:focus,
%form-element textarea:focus {
border-color: $blue-500;
}

View File

@ -1,5 +1,4 @@
@import './freetext-filter/index';
@import './icons/index';
.freetext-filter {
@extend %freetext-filter;
}

View File

@ -1,10 +1,9 @@
@import './healthcheck-info/index';
@import './icons/index';
tr .healthcheck-info {
%table tr .healthcheck-info {
@extend %healthcheck-info;
}
// TODO: Look at why we can't have the zeros in the healthcheck-info
td span.zero {
%table td span.zero {
@extend %with-minus-square-fill-color-icon;
background-position: left center;
display: block;

View File

@ -13,6 +13,11 @@
}
%stats-card-icon:last-child {
/* TODO: In order to get rid of our colored star */
/* this needs to use a %mask, and we are already using */
/* our before/after psuedo elements for the tooltip */
/* so this will need reworking slighly before we can */
/* get rid of our hardcoded magenta star icon */
@extend %with-star-icon;
}
%stats-card header > .zero {

View File

@ -1,140 +0,0 @@
/*TODO: The old pseudo-icon was to specific */
/* make a temporary one with the -- prefix */
/* to make it more reusable temporarily */
%bg-icon {
background-repeat: no-repeat;
background-position: center;
}
%--pseudo-icon {
display: inline-block;
content: '';
visibility: visible;
background-repeat: no-repeat;
background-position: center;
}
%pseudo-icon-bg-img {
@extend %--pseudo-icon;
position: relative;
background-size: contain;
background-color: transparent;
}
%pseudo-icon-css {
@extend %--pseudo-icon;
display: block;
position: absolute;
top: 50%;
width: 1em;
height: 1em;
margin-top: -0.6em;
background-color: currentColor;
}
/* %pseudo-icon-mask, %pseudo-icon-overlay ?*/
%pseudo-icon {
@extend %pseudo-icon-css;
}
%with-external-source-icon {
background-repeat: no-repeat;
background-size: contain;
width: 18px;
height: 18px;
}
%with-folder {
text-indent: 30px;
}
%with-hashicorp,
%with-folder,
%with-chevron,
%with-clipboard {
position: relative;
}
%with-chevron {
padding-left: 10px;
display: inline-block;
}
%with-hashicorp {
background-color: $white;
}
%with-hashicorp::before {
@extend %pseudo-icon;
opacity: 0.45;
background-image: $hashicorp-logo-svg;
background-size: cover;
width: 20px;
height: 20px;
left: -25px;
margin-top: -10px;
background-color: $color-transparent;
}
%with-clipboard {
padding-left: 38px !important;
}
%with-clipboard::before {
@extend %pseudo-icon;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="16" height="17" xmlns="http://www.w3.org/2000/svg"><g fill="%231563FF"><path d="M1.5 10.6v.156c0-.117-.043-.156-.033-.156H1.5zm0 0V1.5h8v2.97H11V1.344C11 .602 10.343 0 9.533 0H1.467C.657 0 0 .602 0 1.344v9.412c0 .742.657 1.344 1.467 1.344h2.995v-1.5H1.5zm-.033-9.1c-.01 0 .033-.04.033-.156V1.5h-.033zm8.033 0v-.156c0 .117.043.156.033.156H9.5zm0 0v-.156c0 .117.043.156.033.156H9.5zm0 0v2.97H11V1.344C11 .602 10.343 0 9.533 0H1.467C.657 0 0 .602 0 1.344v9.412c0 .742.657 1.344 1.467 1.344h2.995v-1.5H1.5V1.5h8zm-8 0h-.033c-.01 0 .033-.04.033-.156V1.5zm0 9.256c0-.117-.043-.156-.033-.156H1.5v.156z" fill-rule="nonzero"/><path d="M14.4 4.5H5.6c-.083 0-.1.016-.1-.033v10.266c0-.049.017-.033.1-.033h8.8c.083 0 .1-.016.1.033V4.467c0 .049-.017.033-.1.033zm0-1.5c.884 0 1.6.657 1.6 1.467v10.266c0 .81-.716 1.467-1.6 1.467H5.6c-.884 0-1.6-.657-1.6-1.467V4.467C4 3.657 4.716 3 5.6 3h8.8z" fill-rule="nonzero"/><path d="M2.5 8.25H10v1.154H2.5z"/><path d="M10.942 8.705l.001.001-2.827 2.828-.807-.808 2.02-2.02-2.02-2.021.807-.808 2.827 2.827v.001z"/></g></svg>');
width: 16px;
height: 17px;
left: 12px;
margin-top: -8px;
background-color: $color-transparent;
}
%with-chevron::before {
@extend %pseudo-icon;
content: '';
width: 6px;
background-color: transparent;
left: 0;
font-size: 0.7rem;
}
%with-folder::before {
@extend %pseudo-icon;
width: 12px;
height: 12px;
top: 50%;
margin-top: -6px;
left: 2px;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="14" height="13" xmlns="http://www.w3.org/2000/svg"><path d="M4.779 1H1.8c-.439 0-.8.37-.8.833v9.334c0 .463.361.833.8.833h10.4c.439 0 .8-.37.8-.833V3.833C13 3.37 12.639 3 12.2 3H6.35a.5.5 0 0 1-.42-.228L4.78 1z" stroke="%23BBC4D2" fill="none"/></svg>');
background-color: $color-transparent;
}
%with-exit::after {
@extend %pseudo-icon-bg-img;
top: 3px;
right: -8px;
background-image: $exit-svg;
background-color: $color-transparent;
width: 16px;
height: 16px;
}
/*TODO: All chevrons need merging */
%with-chevron-down::before {
@extend %pseudo-icon-bg-img;
background-image: $chevron-svg;
width: 10px;
height: 6px;
}
%with-star-before::before,
%with-star-after::after {
@extend %pseudo-icon-bg-img;
background-image: $star-svg;
width: 10px;
height: 9px;
}
%with-star-before::before {
padding-right: 12px;
}
%with-star-after::after {
padding-left: 22px;
}
%with-star {
@extend %with-star-before;
}
%with-eye::before {
@extend %pseudo-icon-bg-img;
background-image: $eye-svg;
width: 16px;
height: 8px;
padding-right: 12px;
}
%with-tick {
@extend %pseudo-icon;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="10" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M8.95 0L10 .985 3.734 8 0 4.737l.924-1.11 2.688 2.349z" fill="%23FFF"/></svg>');
}

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,2 +0,0 @@
@import './skin';
@import './layout';

View File

@ -1,72 +0,0 @@
%modal-dialog > div > div {
@extend %modal-window;
}
%with-modal {
overflow: hidden;
}
%modal-dialog {
z-index: 500;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
%modal-control,
%modal-control + * {
display: none;
}
%modal-control:checked + * {
display: block;
}
%modal-dialog > label {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
%modal-dialog > div {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
%modal-window.overflowing {
overflow: auto;
height: 100%;
}
%modal-window {
max-width: 855px;
position: relative;
z-index: 1;
}
%modal-window > * {
padding-left: 15px;
padding-right: 15px;
}
%modal-window > div {
overflow-y: auto;
max-height: 80vh;
padding: 20px 23px;
}
%modal-window > footer,
%modal-window > header {
padding-top: 12px;
padding-bottom: 10px;
}
%modal-window table {
height: 150px !important;
}
%modal-window tbody {
max-height: 100px;
}
%modal-window > header {
position: relative;
}
%modal-window > header [for='modal_close'] {
float: right;
text-indent: -9000px;
width: 23px;
height: 23px;
}

View File

@ -1,35 +0,0 @@
%modal-dialog > label {
background-color: rgba($white, 0.9);
}
%modal-window {
box-shadow: 2px 8px 8px 0 rgba($black, 0.1);
}
%modal-window {
/*%frame-gray-000*/
background-color: $white;
border: $decor-border-100;
border-color: $gray-300;
}
%modal-window > footer,
%modal-window > header {
@extend %frame-gray-800;
}
%modal-window > footer {
border-top-width: 1px;
}
%modal-window > header {
border-bottom-width: 1px;
}
%modal-window > header [for='modal_close'] {
@extend %bg-icon;
background-image: $cancel-plain-svg;
background-size: 80%;
cursor: pointer;
/*%frame-gray-050??*/
background-color: $gray-050;
border: $decor-border-100;
border-color: $gray-300;
border-radius: $decor-radius-100;
}

View File

@ -1,5 +1,14 @@
%footer > a:first-child {
@extend %with-hashicorp;
position: relative;
}
%footer > a:first-child::before {
@extend %with-hashicorp-logo-mask, %as-pseudo;
background-color: $gray-400;
font-size: 1.4em;
position: absolute;
top: 50%;
margin-top: -0.7em;
left: -25px;
}
%footer {
border-top: $decor-border-100;

View File

@ -1,7 +1,3 @@
/* TODO: All of these folders should be self contained */
/* figure out what the best way to deal with 'icons' is */
/* most probably a centralized module */
@import '../icons/index';
@import './loader';
@import './main-header-horizontal';
@import '../main-nav-horizontal/index';

View File

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

View File

@ -1,23 +0,0 @@
%radio-group {
overflow: hidden;
}
%radio-group label {
float: left;
}
%radio-group label > span {
float: right;
}
%radio-group {
padding-left: 1px;
}
%radio-group label:not(:last-child) {
margin-right: 25px;
}
%radio-group label > span {
margin-left: 1em;
margin-top: 0.2em;
}
%radio-group label,
%radio-group label > span {
margin-bottom: 0 !important;
}

View File

@ -3,9 +3,19 @@
@extend %secret-button;
}
%secret-button {
visibility: hidden;
@extend %with-eye;
position: relative;
}
%secret-button span {
visibility: hidden;
position: absolute;
}
%secret-button em {
margin-left: 22px;
}
%secret-button span::before {
@extend %with-visibility-show-mask, %as-pseudo;
background-color: $gray-500;
}
%secret-button input:checked + span::before {
@extend %with-visibility-hide-mask;
}

View File

@ -4,20 +4,20 @@
%secret-button input {
display: none;
}
%secret-button input + em {
%secret-button input ~ em {
visibility: hidden;
font-style: normal;
}
%secret-button input:checked + em {
%secret-button input:checked ~ em {
@extend %user-select-text;
visibility: visible;
cursor: auto;
}
%secret-button input + em::before {
%secret-button input ~ em::before {
display: inline;
visibility: visible;
content: '■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■';
}
%secret-button input:checked + em::before {
%secret-button input:checked ~ em::before {
display: none;
}

View File

@ -1,9 +1,21 @@
@import './icons/index';
@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 {
@ -19,8 +31,11 @@ html.template-service.template-list main th:first-child {
text-indent: 28px;
}
td.folder {
@extend %with-folder;
td.folder::before {
@extend %with-folder-outline-mask, %as-pseudo;
background-color: $gray-300;
margin-top: 1px;
margin-right: 5px;
}
table:not(.sessions) tbody tr {
cursor: pointer;

View File

@ -1,5 +1,5 @@
%tabular-details-toggle-button {
@extend %with-chevron-down;
%tabular-details-toggle-button::before {
@extend %with-chevron-down-icon, %as-pseudo;
}
%tabular-details td:only-child {
cursor: default;

View File

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

View File

@ -1,36 +0,0 @@
%toggle label {
position: relative;
}
%toggle input {
display: none;
}
%toggle label span {
display: inline-block;
padding-left: 34px;
}
%toggle label span::before,
%toggle label span::after {
position: absolute;
display: block;
content: '';
top: 50%;
}
%toggle label span::before {
left: 0px;
width: 24px;
height: 12px;
margin-top: -5px;
}
%toggle label span::after {
margin-top: -3px;
width: 8px;
height: 8px;
}
%toggle label input:checked + span::after,
%toggle-negative label input + span::after {
left: 14px;
}
%toggle label span::after,
%toggle-negative label input:checked + span::after {
left: 2px;
}

View File

@ -1,30 +0,0 @@
/* TODO: Maybe move this to reset? */
%toggle label span {
cursor: pointer;
}
%toggle label span::after {
border-radius: $decor-radius-full;
}
%toggle label span::before {
border-radius: 7px;
}
%toggle-negative {
border: 0;
}
%toggle.type-negative {
@extend %toggle-negative;
}
%toggle label span {
color: $gray-900;
}
%toggle label span::after {
background-color: $white;
}
%toggle label input:checked + span::before,
%toggle-negative label input + span::before {
background-color: $blue-500;
}
%toggle label span::before,
%toggle-negative label input:checked + span::before {
background-color: $gray-300;
}

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,7 +1,6 @@
td a.is-management {
@extend %with-star-after;
}
td a.is-management::after {
@extend %with-star-fill-mask, %as-pseudo;
background-color: $magenta-600;
height: 16px;
top: 2px;
padding-left: 32px;

View File

@ -5,14 +5,12 @@
float: right;
}
%token-yours {
text-indent: 20px;
color: $blue-500;
padding-left: 15px;
}
%token-yours::after {
@extend %with-tick;
border-radius: 100%;
%token-yours::before {
@extend %with-check-circle-fill-mask, %as-pseudo;
background-color: $blue-500;
margin-right: 5px;
}
.me ~ :nth-last-child(2) {
@extend %token-yours;

View File

@ -2,10 +2,9 @@
position: relative;
}
#urls_service span::after {
@extend %with-tick;
background-color: $green-500;
border-radius: 100%;
top: 13px;
@extend %with-check-circle-fill-color-icon, %as-pseudo;
position: absolute;
top: 3px;
right: 0;
}
#urls_service span::after {

View File

@ -3,7 +3,7 @@
{{else}}
{{#hashicorp-consul id="wrapper" permissions=permissions dcs=dcs dc=dc nspaces=nspaces nspace=nspace}}
{{#app-view class="loading show"}}
{{#block-slot 'content'}}
{{#block-slot name='content'}}
{{partial 'consul-loading'}}
{{/block-slot}}
{{/app-view}}

View File

@ -1,4 +1,4 @@
{{!<form>}}
{{freetext-filter searchable=searchable value=search placeholder="Search by name/token"}}
{{radio-group name="type" value=type items=filters onchange=(action onchange)}}
{{radio-group keyboardAccess=true name="type" value=type items=filters onchange=(action onchange)}}
{{!</form>}}

View File

@ -9,28 +9,28 @@
<strong>
{{component.flashType}}!
</strong>
{{#yield-slot 'notification' (block-params (lowercase component.flashType) (lowercase flash.action) flash.item )}}{{yield}}{{/yield-slot}}
{{#yield-slot name='notification' params=(block-params (lowercase component.flashType) (lowercase flash.action) flash.item )}}{{yield}}{{/yield-slot}}
</p>
{{/flash-message}}
{{/each}}
<div>
<div class="actions">
{{#if authorized}}
{{#yield-slot 'actions'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='actions'}}{{yield}}{{/yield-slot}}
{{/if}}
</div>
<div>
{{#if authorized}}
<nav aria-label="Breadcrumb">
{{#yield-slot 'breadcrumbs'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='breadcrumbs'}}{{yield}}{{/yield-slot}}
</nav>
{{/if}}
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='header'}}{{yield}}{{/yield-slot}}
</div>
</div>
{{#if authorized}}
{{#yield-slot 'toolbar'}}
{{#yield-slot name='toolbar'}}
<input type="checkbox" id="toolbar-toggle" />
{{yield}}
{{/yield-slot}}
@ -42,11 +42,11 @@
{{partial 'consul-loading'}}
{{else}}
{{#if (not enabled) }}
{{#yield-slot 'disabled'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='disabled'}}{{yield}}{{/yield-slot}}
{{else if (not authorized)}}
{{#yield-slot 'authorization'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='authorization'}}{{yield}}{{/yield-slot}}
{{else}}
{{#yield-slot 'content'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='content'}}{{yield}}{{/yield-slot}}
{{/if}}
{{/if}}
</div>

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,6 +1,6 @@
{{!<form>}}
{{freetext-filter searchable=searchable value=search placeholder="Search by name"}}
{{radio-group name="status" value=status items=(array
{{radio-group keyboardAccess=true name="status" value=status items=(array
(hash label='All (Any Status)' value='' )
(hash label='Critical Checks' value='critical')
(hash label='Warning Checks' value='warning')

View File

@ -1,6 +1,6 @@
{{yield}}
{{#if (gt items.length 0)}}
{{#yield-slot 'set' (block-params items)}}{{yield}}{{/yield-slot}}
{{#yield-slot name='set' params=(block-params items)}}{{yield}}{{/yield-slot}}
{{else}}
{{#yield-slot 'empty'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='empty'}}{{yield}}{{/yield-slot}}
{{/if}}

View File

@ -1,7 +1,7 @@
{{yield}}
{{#yield-slot 'create'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='create'}}{{yield}}{{/yield-slot}}
<label class="type-text">
<span>{{#yield-slot 'label'}}{{yield}}{{/yield-slot}}</span>
<span>{{#yield-slot name='label'}}{{yield}}{{/yield-slot}}</span>
{{#power-select
onopen=(action 'open')
search=(action 'search')
@ -11,11 +11,11 @@
searchPlaceholder=placeholder
onchange=(action 'change' 'items[]' items) as |item|
}}
{{#yield-slot 'option' (block-params item)}}{{yield}}{{/yield-slot}}
{{#yield-slot name='option' params=(block-params item)}}{{yield}}{{/yield-slot}}
{{/power-select}}
</label>
{{#if (gt items.length 0)}}
{{#yield-slot 'set'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='set'}}{{yield}}{{/yield-slot}}
{{else}}
{{/if}}

View File

@ -1,10 +1,10 @@
{{yield}}
{{#yield-slot 'action' (block-params confirm cancel)}}
{{#yield-slot name='action' params=(block-params confirm cancel)}}
{{#if (or permanent (not confirming))}}
{{yield}}
{{/if}}
{{/yield-slot}}
{{#yield-slot 'dialog' (block-params execute cancel message actionName)}}
{{#yield-slot name='dialog' params=(block-params execute cancel message actionName)}}
{{#if confirming }}
{{yield}}
{{/if}}

View File

@ -1,13 +1,13 @@
{{#feedback-dialog type='inline'}}
{{#block-slot 'action' as |success error|}}
{{#block-slot name='action' as |success error|}}
{{#copy-button success=(action success) error=(action error) clipboardText=copy title=(concat 'Copy ' name ' to the clipboard')}}{{#if hasBlock }}{{yield}}{{else}}{{value}}{{/if}}{{/copy-button}}
{{/block-slot}}
{{#block-slot 'success' as |transition|}}
{{#block-slot name='success' as |transition|}}
<p class={{transition}}>
Copied {{name}}!
</p>
{{/block-slot}}
{{#block-slot 'error' as |transition|}}
{{#block-slot name='error' as |transition|}}
<p class={{transition}}>
Sorry, something went wrong!
</p>

View File

@ -1,9 +1,9 @@
{{yield}}
{{#if (eq state 'success') }}
{{#yield-slot 'success' (block-params transition)}}{{yield}}{{/yield-slot}}
{{#yield-slot name='success' params=(block-params transition)}}{{yield}}{{/yield-slot}}
{{else if (eq state 'error') }}
{{#yield-slot 'error' (block-params transition)}}{{yield}}{{/yield-slot}}
{{#yield-slot name='error' params=(block-params transition)}}{{yield}}{{/yield-slot}}
{{/if}}
{{#if (or permanent (eq state 'ready')) }}
{{#yield-slot 'action' (block-params success error)}}{{yield}}{{message}}{{/yield-slot}}
{{#yield-slot name='action' params=(block-params success error)}}{{yield}}{{message}}{{/yield-slot}}
{{/if}}

View File

@ -1,6 +1,6 @@
<header role="banner" data-test-navigation>
<a data-test-main-nav-logo href={{href-to 'index'}}><svg width="28" height="27" xmlns="http://www.w3.org/2000/svg"><path d="M13.284 16.178a2.876 2.876 0 1 1-.008-5.751 2.876 2.876 0 0 1 .008 5.75zm5.596-1.547a1.333 1.333 0 1 1 0-2.667 1.333 1.333 0 0 1 0 2.667zm4.853 1.249a1.271 1.271 0 1 1 .027-.107c0 .031 0 .067-.027.107zm-.937-3.436a1.333 1.333 0 1 1 .986-1.595c.033.172.033.348 0 .52-.07.53-.465.96-.986 1.075zm4.72 3.29a1.333 1.333 0 1 1-1.076-1.538 1.333 1.333 0 0 1 1.116 1.417.342.342 0 0 0-.027.12h-.013zm-1.08-3.33a1.333 1.333 0 1 1 1.088-1.524c.014.114.014.229 0 .342a1.333 1.333 0 0 1-1.102 1.182h.014zm-.925 7.925a1.333 1.333 0 1 1 .165-.547c-.01.193-.067.38-.165.547zm-.48-12.191a1.333 1.333 0 1 1 .507-1.814c.14.237.198.514.164.787-.038.438-.289.828-.67 1.045v-.018zM13.333 26.667C5.97 26.667 0 20.697 0 13.333 0 5.97 5.97 0 13.333 0c2.929-.01 5.778.955 8.098 2.742L19.8 4.89a10.667 10.667 0 0 0-17.133 8.444 10.667 10.667 0 0 0 17.137 8.471l1.627 2.13a13.218 13.218 0 0 1-8.098 2.733z" fill="#FFF"/></svg></a>
<input type="checkbox" name="menu" id="main-nav-toggle" onchange={{ action 'change'}} />
<a data-test-main-nav-logo href={{href-to 'index'}}><svg width="28" height="27" xmlns="http://www.w3.org/2000/svg"><title>Consul</title><path d="M13.284 16.178a2.876 2.876 0 1 1-.008-5.751 2.876 2.876 0 0 1 .008 5.75zm5.596-1.547a1.333 1.333 0 1 1 0-2.667 1.333 1.333 0 0 1 0 2.667zm4.853 1.249a1.271 1.271 0 1 1 .027-.107c0 .031 0 .067-.027.107zm-.937-3.436a1.333 1.333 0 1 1 .986-1.595c.033.172.033.348 0 .52-.07.53-.465.96-.986 1.075zm4.72 3.29a1.333 1.333 0 1 1-1.076-1.538 1.333 1.333 0 0 1 1.116 1.417.342.342 0 0 0-.027.12h-.013zm-1.08-3.33a1.333 1.333 0 1 1 1.088-1.524c.014.114.014.229 0 .342a1.333 1.333 0 0 1-1.102 1.182h.014zm-.925 7.925a1.333 1.333 0 1 1 .165-.547c-.01.193-.067.38-.165.547zm-.48-12.191a1.333 1.333 0 1 1 .507-1.814c.14.237.198.514.164.787-.038.438-.289.828-.67 1.045v-.018zM13.333 26.667C5.97 26.667 0 20.697 0 13.333 0 5.97 5.97 0 13.333 0c2.929-.01 5.778.955 8.098 2.742L19.8 4.89a10.667 10.667 0 0 0-17.133 8.444 10.667 10.667 0 0 0 17.137 8.471l1.627 2.13a13.218 13.218 0 0 1-8.098 2.733z" fill="#FFF"/></svg></a>
<input type="checkbox" name="menu" id="main-nav-toggle" onchange={{action 'change'}} />
<label for="main-nav-toggle">
Toggle Menu
</label>
@ -8,7 +8,7 @@
<label for="main-nav-toggle">
<span>Close</span>
</label>
<nav>
<nav aria-label="Main">
{{#if dc}}
<ul>
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (gt nspaces.length 0))}}

View File

@ -6,10 +6,10 @@
class=item.Status
tagName='li'
}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
<h3>{{item.Name}}</h3>
{{/block-slot}}
{{#block-slot 'content'}}
{{#block-slot name='content'}}
<dl>
<dt>ServiceName</dt>
<dd>{{or item.ServiceName '-'}}</dd>
@ -37,15 +37,15 @@
<dd>
<pre><code>{{item.Output}}</code></pre>
{{#feedback-dialog type='inline'}}
{{#block-slot 'action' as |success error|}}
{{#block-slot name='action' as |success error|}}
{{copy-button success=(action success) error=(action error) clipboardText=item.Output title='copy output to clipboard'}}
{{/block-slot}}
{{#block-slot 'success' as |transition|}}
{{#block-slot name='success' as |transition|}}
<p class={{transition}}>
Copied output!
</p>
{{/block-slot}}
{{#block-slot 'error' as |transition|}}
{{#block-slot name='error' as |transition|}}
<p class={{transition}}>
Sorry, something went wrong!
</p>

View File

@ -2,7 +2,7 @@
{{yield}}
<div>
<header>
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='header'}}{{yield}}{{/yield-slot}}
</header>
{{#yield-slot 'content'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='content'}}{{yield}}{{/yield-slot}}
</div>

View File

@ -1,19 +1,19 @@
{{#stats-card}}
{{#block-slot 'icon'}}{{yield}}{{/block-slot}}
{{#block-slot 'mini-stat'}}
{{#block-slot name='icon'}}{{yield}}{{/block-slot}}
{{#block-slot name='mini-stat'}}
{{#if (eq checks.length 0)}}
<span class="zero" data-tooltip="This node has no registered healthchecks">{{checks.length}}</span>
{{else if (eq checks.length healthy.length)}}
<span class="non-zero" data-tooltip={{concat 'All ' healthy.length ' ' (pluralize healthy.length 'check' without-count=true) ' passing'}}>{{healthy.length}}</span>
{{/if}}
{{/block-slot}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
<a href={{href}}>
<strong>{{name}}</strong>
<span>{{address}}</span>
</a>
{{/block-slot}}
{{#block-slot 'body'}}
{{#block-slot name='body'}}
{{#if (not-eq checks.length healthy.length)}}
<ul>
{{#each unhealthy as |item|}}

View File

@ -1,4 +1,4 @@
{{!<form>}}
{{freetext-filter searchable=searchable value=search placeholder="Search by Source or Destination"}}
{{radio-group name="action" value=action items=filters onchange=(action onchange)}}
{{radio-group keyboardAccess=true name="action" value=action items=filters onchange=(action onchange)}}
{{!</form>}}

View File

@ -6,13 +6,13 @@
<div>
<header>
<label for="modal_close">Close</label>
{{#yield-slot 'header'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='header'}}{{yield}}{{/yield-slot}}
</header>
<div>
{{#yield-slot 'body'}}{{yield}}{{/yield-slot}}
{{#yield-slot name='body'}}{{yield}}{{/yield-slot}}
</div>
<footer>
{{#yield-slot 'actions' (block-params (action 'close'))}}{{yield}}{{/yield-slot}}
{{#yield-slot name='actions' params=(block-params (action 'close'))}}{{yield}}{{/yield-slot}}
</footer>
</div>
</div>

View File

@ -1,6 +1,6 @@
{{yield}}
<fieldset>
{{#yield-slot 'template'}}
{{#yield-slot name='template'}}
{{else}}
<header>
Policy or service identity?

View File

@ -1,10 +1,10 @@
{{#child-selector repo=repo dc=dc nspace=nspace type="policy" placeholder="Search for policy" items=items}}
{{yield}}
{{#block-slot 'label'}}
{{#block-slot name='label'}}
Apply an existing policy
{{/block-slot}}
{{#block-slot 'create'}}
{{#yield-slot 'trigger'}}
{{#block-slot name='create'}}
{{#yield-slot name='trigger'}}
{{yield}}
{{else}}
<label class="type-dialog" for="new-policy-toggle">
@ -13,13 +13,13 @@
{{!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 onopen=(action 'open') name="new-policy-toggle"}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
<h2>New Policy</h2>
{{/block-slot}}
{{#block-slot 'body'}}
{{#block-slot name='body'}}
{{policy-form form=form dc=dc}}
{{/block-slot}}
{{#block-slot 'actions' as |close|}}
{{#block-slot name='actions' as |close|}}
<button type="submit" {{action 'save' item items (queue (action close) (action 'reset'))}} disabled={{if (or item.isSaving item.isPristine item.isInvalid) 'disabled'}}>
{{#if item.isSaving }}
<div class="progress indeterminate"></div>
@ -31,20 +31,20 @@
{{/modal-dialog}}
{{/yield-slot}}
{{/block-slot}}
{{#block-slot 'option' as |option|}}
{{#block-slot name='option' as |option|}}
{{option.Name}}
{{/block-slot}}
{{#block-slot 'set'}}
{{#block-slot name='set'}}
{{#tabular-details
data-test-policies
onchange=(action 'loadItem')
items=(sort-by 'CreateTime:desc' 'Name:asc' items) as |item index|
}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Datacenters</th>
{{/block-slot}}
{{#block-slot 'row'}}
{{#block-slot name='row'}}
<td class={{policy/typeof item}}>
{{#if item.ID }}
<a href={{href-to 'dc.acls.policies.edit' item.ID}}>{{item.Name}}</a>
@ -56,7 +56,7 @@
{{if (not item.isSaving) (join ', ' (policy/datacenters item)) 'Saving...'}}
</td>
{{/block-slot}}
{{#block-slot 'details'}}
{{#block-slot name='details'}}
<label class="type-text">
<span>Rules <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
{{#if (eq item.template '')}}
@ -69,10 +69,10 @@
</label>
<div>
{{#confirmation-dialog message='Are you sure you want to remove this policy from this token?'}}
{{#block-slot 'action' as |confirm|}}
{{#block-slot name='action' as |confirm|}}
<button data-test-delete type="button" class="type-delete" {{action confirm 'remove' item items}}>Remove</button>
{{/block-slot}}
{{#block-slot 'dialog' as |execute cancel message|}}
{{#block-slot name='dialog' as |execute cancel message|}}
<p>
{{message}}
</p>

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

@ -1,10 +1,10 @@
{{!<fieldset>}}
<div role="radiogroup" id="radiogroup_{{name}}" data-test-radiogroup="{{name}}">{{! menu?}}
<fieldset>
<div role="radiogroup" id="radiogroup_{{name}}" data-test-radiogroup={{name}}>{{! menu?}}
{{#each items as |item|}}
<label class="type-radio value-{{item.value}}" data-test-radiobutton="{{name}}_{{item.value}}"> {{! slugify }}
<input type="radio" name={{name}} value={{item.value}} checked={{if (eq (concat value) item.value) 'checked'}} onchange={{action onchange}} />
<span>{{item.label}}</span>
</label>
<label tabindex={{if keyboardAccess '0'}} onkeydown={{if keyboardAccess (action 'keydown')}} class="type-radio value-{{item.value}}" data-test-radiobutton="{{name}}_{{item.value}}"> {{! slugify }}
<input type="radio" name={{name}} value={{item.value}} checked={{if (eq (concat value) item.value) 'checked'}} onchange={{action onchange}} />
<span>{{item.label}}</span>
</label>
{{/each}}
</div>
{{!</fieldset>}}
</div>
</fieldset>

View File

@ -15,17 +15,6 @@
</ol>
</dd>
</dl>
{{else if item.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
<ol>
<li>
<span>{{item.ID}}</span>
</li>
</ol>
</dd>
</dl>
{{/if}}
</a>
</header>
@ -47,15 +36,15 @@
</ol>
</dd>
</dl>
{{else if item.Redirect}}
{{else if child.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
{{child.ID}}
{{child.Name}}
</dd>
</dl>
{{else}}
{{child.ServiceSubset}}
{{child.Name}}
{{/if}}
</a>
</li>

View File

@ -18,9 +18,9 @@
{{!TODO: temporary policies id, look at the inception token modals and get rid of id="policies" and use something else}}
<fieldset id="policies" class="policies">
<h2>Policies</h2>
{{#yield-slot 'policy' (block-params item)}}
{{#yield-slot name='policy' params=(block-params item)}}
{{yield}}
{{else}}
{{policy-selector dc=dc items=item.Policies}}
{{policy-selector dc=dc nspace=nspace items=item.Policies}}
{{/yield-slot}}
</fieldset>

View File

@ -1,19 +1,19 @@
{{#modal-dialog data-test-role-form onclose=(action (mut state) 'role') name="new-role-toggle"}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
{{#if (eq state 'role')}}
<h2>New Role</h2>
{{else}}
<h2>New Policy</h2>
{{/if}}
{{/block-slot}}
{{#block-slot 'body'}}
{{#block-slot name='body'}}
<input id="{{name}}_state_role" type="radio" name="{{name}}[state]" value="role" checked={{if (eq state 'role') 'checked'}} onchange={{action 'change'}} />
{{#role-form form=form dc=dc}}
{{#block-slot 'policy'}}
{{#role-form form=form dc=dc nspace=nspace}}
{{#block-slot name='policy'}}
{{#policy-selector source=source dc=dc items=item.Policies}}
{{#block-slot 'trigger'}}
{{#policy-selector source=source dc=dc nspace=nspace items=item.Policies}}
{{#block-slot name='trigger'}}
<label for="{{name}}_state_policy" data-test-create-policy class="type-dialog">
<span>Create new policy</span>
</label>
@ -27,7 +27,7 @@
{{policy-form data-test-policy-form name="role[policy]" form=policyForm dc=dc}}
{{/block-slot}}
{{#block-slot 'actions' as |close|}}
{{#block-slot name='actions' as |close|}}
{{#if (eq state 'role')}}
<button type="submit" {{action 'save' item items (queue (action close) (action 'reset'))}} disabled={{if (or item.isSaving item.isPristine item.isInvalid) 'disabled'}}>
@ -51,29 +51,29 @@
{{/modal-dialog}}
{{#child-selector repo=repo dc=dc nspace=nspace type="role" placeholder="Search for role" items=items}}
{{#block-slot 'label'}}
{{#block-slot name='label'}}
Apply an existing role
{{/block-slot}}
{{#block-slot 'create'}}
{{#block-slot name='create'}}
<label class="type-dialog" for="new-role-toggle">
<span>Create new role</span>
</label>
{{/block-slot}}
{{#block-slot 'option' as |option|}}
{{#block-slot name='option' as |option|}}
{{option.Name}}
{{/block-slot}}
{{#block-slot 'set'}}
{{#block-slot name='set'}}
{{#tabular-collection
data-test-roles
rows=5
items=(sort-by 'CreateTime:desc' 'Name:asc' items) as |item index|
}}
{{#block-slot 'header'}}
{{#block-slot name='header'}}
<th>Name</th>
<th>Description</th>
{{/block-slot}}
{{#block-slot 'row'}}
{{#block-slot name='row'}}
<td>
<a href={{href-to 'dc.acls.roles.edit' item.ID}}>{{item.Name}}</a>
</td>
@ -81,26 +81,41 @@
{{item.Description}}
</td>
{{/block-slot}}
{{#block-slot 'actions' as |index change checked|}}
{{#confirmation-dialog confirming=false index=index message="Are you sure you want to remove this Role?"}}
{{#block-slot 'action' as |confirm|}}
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
<ul>
<li>
<a data-test-edit href={{href-to 'dc.acls.roles.edit' item.ID}}>Edit</a>
{{#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 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 '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

@ -9,7 +9,7 @@
{{/if}}
<dl>
<dt>
{{path.type}}
{{path.type}}
</dt>
<dd>
{{#if (not-eq path.type 'Default')}}

View File

@ -1,5 +1,5 @@
<label class="type-reveal">
<span>Reveal</span>
<input type="checkbox" />
<span>Reveal</span>
<em>{{yield}}</em>
</label>

Some files were not shown because too many files have changed in this diff Show More