mirror of https://github.com/status-im/consul.git
ui: Refactor KV and Lock Sessions following partitions update (#11666)
This commit uses all our new ways of doing things to Lock Sessions and their interactions with KV and Nodes. This is mostly around are new under-the-hood things, but also I took the opportunity to upgrade some of the CSS to reuse some of our CSS utils that have been made over the past few months (%csv-list and %horizontal-kv-list). Also added (and worked on existing) documentation for Lock Session related components.
This commit is contained in:
parent
5ad82693d8
commit
f3d9565277
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
ui: Upgrade Lock Sessions to use partitions
|
||||||
|
```
|
|
@ -1,5 +1,4 @@
|
||||||
@import './layout';
|
@import './layout';
|
||||||
@import './skin';
|
|
||||||
%composite-row {
|
%composite-row {
|
||||||
@extend %list-row;
|
@extend %list-row;
|
||||||
}
|
}
|
||||||
|
@ -33,12 +32,8 @@
|
||||||
.consul-auth-method-list > ul > li:not(:first-child) {
|
.consul-auth-method-list > ul > li:not(:first-child) {
|
||||||
@extend %with-composite-row-intent;
|
@extend %with-composite-row-intent;
|
||||||
}
|
}
|
||||||
.consul-lock-session-list ul > li:not(:first-child) {
|
|
||||||
@extend %with-one-action-row;
|
|
||||||
}
|
|
||||||
// TODO: This hides the iconless dt's in the below lists as they don't have
|
// TODO: This hides the iconless dt's in the below lists as they don't have
|
||||||
// tooltips the todo would be to wrap these texts in spans
|
// tooltips the todo would be to wrap these texts in spans
|
||||||
.consul-lock-session-list ul > li:not(:first-child) dl:not([class]) dt,
|
|
||||||
.consul-nspace-list > ul > li:not(:first-child) dt,
|
.consul-nspace-list > ul > li:not(:first-child) dt,
|
||||||
.consul-token-list > ul > li:not(:first-child) dt,
|
.consul-token-list > ul > li:not(:first-child) dt,
|
||||||
.consul-policy-list > ul li:not(:first-child) dl:not(.datacenter) dt,
|
.consul-policy-list > ul li:not(:first-child) dl:not(.datacenter) dt,
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
'header actions'
|
'header actions'
|
||||||
'detail actions';
|
'detail actions';
|
||||||
}
|
}
|
||||||
%with-one-action-row {
|
|
||||||
@extend %composite-row;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
padding-right: 12px;
|
|
||||||
}
|
|
||||||
%composite-row-header {
|
%composite-row-header {
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Consul::LockSession::Form
|
||||||
|
|
||||||
|
A component for rendering and deleting/invalidating a Lock Session.
|
||||||
|
|
||||||
|
The form is fully functional and will delete/invalidate and show its
|
||||||
|
notifications when pressing the delete/invalidate button.
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<DataSource
|
||||||
|
@src={{uri "/partition/default/dc-1/sessions/for-key/my-kv"}} as |source|>
|
||||||
|
{{#if source.data.ID}}
|
||||||
|
<Consul::LockSession::Form
|
||||||
|
@item={{source.data}}
|
||||||
|
@ondelete={{noop}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</DataSource>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
| Argument/Attribute | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `item` | `array` | | A Lock Session |
|
||||||
|
| `ondelete` | `function` | | An action to confirm when the `delete` (or Invalidate) action is clicked and confirmed |
|
||||||
|
|
||||||
|
## See
|
||||||
|
|
||||||
|
- [Template Source Code](./index.hbs)
|
||||||
|
|
||||||
|
---
|
|
@ -1,39 +1,65 @@
|
||||||
<DataForm
|
|
||||||
@dc={{dc}}
|
|
||||||
@nspace={{nspace}}
|
|
||||||
@partition={{partition}}
|
|
||||||
@item={{item}}
|
|
||||||
@type="session"
|
|
||||||
@onsubmit={{action onsubmit}}
|
|
||||||
as |api|
|
|
||||||
>
|
|
||||||
<BlockSlot @name="form">
|
|
||||||
<div
|
<div
|
||||||
class="consul-lock-session-form definition-table"
|
class="consul-lock-session-form"
|
||||||
data-test-session={{api.data.ID}}
|
data-test-session={{@item.ID}}
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
<h2>
|
<DataWriter
|
||||||
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html#session-design" rel="help noopener noreferrer" target="_blank">Lock Session</a>
|
@sink={{uri
|
||||||
</h2>
|
'/${partition}/${nspace}/${dc}/session'
|
||||||
|
(hash
|
||||||
|
partition=@item.Partition
|
||||||
|
nspace=@item.Namespace
|
||||||
|
dc=@item.Datacenter
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@type={{'session'}}
|
||||||
|
@label={{'Lock Session'}}
|
||||||
|
@ondelete={{fn (if @ondelete @ondelete @onsubmit) @item}}
|
||||||
|
@onchange={{fn (optional @onsubmit) @item}}
|
||||||
|
as |writer|>
|
||||||
|
<BlockSlot @name="removed" as |after|>
|
||||||
|
<Consul::LockSession::Notifications
|
||||||
|
{{notification
|
||||||
|
after=(action after)
|
||||||
|
}}
|
||||||
|
@type="remove"
|
||||||
|
/>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="error" as |after error|>
|
||||||
|
<Consul::LockSession::Notifications
|
||||||
|
{{notification
|
||||||
|
after=(action after)
|
||||||
|
}}
|
||||||
|
@type="remove"
|
||||||
|
@error={{error}}
|
||||||
|
/>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="content">
|
||||||
|
<div
|
||||||
|
class="definition-table"
|
||||||
|
>
|
||||||
<dl>
|
<dl>
|
||||||
{{#if api.data.Name}}
|
{{#if @item.Name}}
|
||||||
<dt>Name</dt>
|
<dt>Name</dt>
|
||||||
<dd>{{api.data.Name}}</dd>
|
<dd>{{@item.Name}}</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<dt>ID</dt>
|
<dt>ID</dt>
|
||||||
<dd>{{api.data.ID}}</dd>
|
<dd>{{@item.ID}}</dd>
|
||||||
<dt>Node</dt>
|
<dt>Node</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href={{href-to 'dc.nodes.show' api.data.Node}}>{{api.data.Node}}</a>
|
<a
|
||||||
|
href={{href-to 'dc.nodes.show' @item.Node}}
|
||||||
|
>
|
||||||
|
{{@item.Node}}
|
||||||
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
<dt>Delay</dt>
|
<dt>Delay</dt>
|
||||||
<dd>{{duration-from api.data.LockDelay}}</dd>
|
<dd>{{duration-from @item.LockDelay}}</dd>
|
||||||
<dt>TTL</dt>
|
<dt>TTL</dt>
|
||||||
<dd>{{or api.data.TTL '-'}}</dd>
|
<dd>{{or @item.TTL '-'}}</dd>
|
||||||
<dt>Behavior</dt>
|
<dt>Behavior</dt>
|
||||||
<dd>{{api.data.Behavior}}</dd>
|
<dd>{{@item.Behavior}}</dd>
|
||||||
{{#let api.data.checks as |checks|}}
|
{{#let @item.checks as |checks|}}
|
||||||
<dt>Health Checks</dt>
|
<dt>Health Checks</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{#if (gt checks.length 0)}}
|
{{#if (gt checks.length 0)}}
|
||||||
|
@ -44,20 +70,37 @@
|
||||||
</dd>
|
</dd>
|
||||||
{{/let}}
|
{{/let}}
|
||||||
</dl>
|
</dl>
|
||||||
{{#if (can 'delete session' item=api.data)}}
|
</div>
|
||||||
<ConfirmationDialog @message="Are you sure you want to invalidate this session?">
|
{{#if (can 'delete session' item=@item)}}
|
||||||
|
<ConfirmationDialog @message="Are you sure you want to invalidate this Lock Session?">
|
||||||
<BlockSlot @name="action" as |confirm|>
|
<BlockSlot @name="action" as |confirm|>
|
||||||
<button type="button" data-test-delete class="type-delete" {{action confirm api.delete session}} disabled={{api.disabled}}>Invalidate Session</button>
|
<Action
|
||||||
|
data-test-delete
|
||||||
|
class="type-delete"
|
||||||
|
{{on 'click' (fn confirm (fn writer.delete @item))}}
|
||||||
|
>
|
||||||
|
Invalidate Session
|
||||||
|
</Action>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||||
<p>
|
<p>
|
||||||
{{message}}
|
{{message}}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" class="type-delete" {{action execute}}>Confirm Invalidation</button>
|
<Action
|
||||||
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
class="type-delete"
|
||||||
|
{{on 'click' (fn execute)}}
|
||||||
|
>
|
||||||
|
Confirm Invalidation
|
||||||
|
</Action>
|
||||||
|
<Action
|
||||||
|
class="type-cancel"
|
||||||
|
{{on 'click' (fn cancel)}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Action>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</DataForm>
|
</DataWriter>
|
||||||
|
</div>
|
|
@ -1,3 +0,0 @@
|
||||||
import Component from '@ember/component';
|
|
||||||
|
|
||||||
export default Component.extend({});
|
|
|
@ -1,9 +1,3 @@
|
||||||
.consul-lock-session-form {
|
.consul-lock-session-form {
|
||||||
h2 {
|
overflow: hidden;
|
||||||
@extend %h200;
|
|
||||||
border-bottom: var(--decor-border-200);
|
|
||||||
border-color: rgb(var(--tone-gray-200));
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
---
|
|
||||||
class: ember
|
|
||||||
---
|
|
||||||
# Consul::LockSession::List
|
# Consul::LockSession::List
|
||||||
|
|
||||||
|
A presentational component for rendering Node Lock Sessions
|
||||||
|
|
||||||
```hbs preview-template
|
```hbs preview-template
|
||||||
<DataSource @src="/partition/default/dc-1/sessions/for-node/my-node" as |source|>
|
<DataSource
|
||||||
|
@src={{uri "/partition/default/dc-1/sessions/for-node/my-node"}} as |source|>
|
||||||
<Consul::LockSession::List
|
<Consul::LockSession::List
|
||||||
@items={{source.data}}
|
@items={{source.data}}
|
||||||
@onInvalidate={{action (noop)}}
|
@ondelete={{action (noop)}}
|
||||||
/>
|
/>
|
||||||
</DataSource>
|
</DataSource>
|
||||||
```
|
```
|
||||||
|
|
||||||
A presentational component for rendering Node Lock Sessions
|
## Arguments
|
||||||
|
|
||||||
### Arguments
|
|
||||||
|
|
||||||
| Argument/Attribute | Type | Default | Description |
|
| Argument/Attribute | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `items` | `array` | | An array of Node Lock Sessions |
|
| `items` | `array` | | An array of Node Lock Sessions |
|
||||||
| `onInvalidate` | `function` | | An action to confirm when the `Invalidate` action is clicked and confirmed |
|
| `ondelete` | `function` | | An action to confirm when the `delete` (or Invalidate) action is clicked and confirmed |
|
||||||
|
|
||||||
### See
|
## See
|
||||||
|
|
||||||
- [Component Source Code](./index.js)
|
|
||||||
- [Template Source Code](./index.hbs)
|
- [Template Source Code](./index.hbs)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{{#if (gt items.length 0)}}
|
<ListCollection
|
||||||
<ListCollection @items={{items}} class="consul-lock-session-list" as |item index|>
|
class="consul-lock-session-list"
|
||||||
|
...attributes
|
||||||
|
@items={{@items}}
|
||||||
|
as |item index|>
|
||||||
<BlockSlot @name="header">
|
<BlockSlot @name="header">
|
||||||
{{#if item.Name}}
|
{{#if item.Name}}
|
||||||
<span>{{item.Name}}</span>
|
<span>{{item.Name}}</span>
|
||||||
|
@ -35,7 +38,9 @@
|
||||||
<dd data-test-session-delay>{{duration-from item.LockDelay}}</dd>
|
<dd data-test-session-delay>{{duration-from item.LockDelay}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="ttl">
|
<dl class="ttl">
|
||||||
<dt {{tooltip}}>
|
<dt
|
||||||
|
{{tooltip}}
|
||||||
|
>
|
||||||
TTL
|
TTL
|
||||||
</dt>
|
</dt>
|
||||||
{{#if (eq item.TTL "")}}
|
{{#if (eq item.TTL "")}}
|
||||||
|
@ -45,14 +50,18 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="behavior">
|
<dl class="behavior">
|
||||||
<dt {{tooltip}}>
|
<dt
|
||||||
|
{{tooltip}}
|
||||||
|
>
|
||||||
Behavior
|
Behavior
|
||||||
</dt>
|
</dt>
|
||||||
<dd>{{item.Behavior}}</dd>
|
<dd>{{item.Behavior}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{{#let (union item.NodeChecks item.ServiceChecks) as |checks|}}
|
{{#let (union item.NodeChecks item.ServiceChecks) as |checks|}}
|
||||||
<dl class="checks">
|
<dl class="checks">
|
||||||
<dt {{tooltip}}>
|
<dt
|
||||||
|
{{tooltip}}
|
||||||
|
>
|
||||||
Checks
|
Checks
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
@ -67,27 +76,36 @@
|
||||||
</dl>
|
</dl>
|
||||||
{{/let}}
|
{{/let}}
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
{{#if (can "delete sessions")}}
|
|
||||||
<BlockSlot @name="actions">
|
<BlockSlot @name="actions">
|
||||||
<ConfirmationDialog @message="Are you sure you want to invalidate this session?">
|
<ConfirmationDialog
|
||||||
|
@message="Are you sure you want to invalidate this session?"
|
||||||
|
>
|
||||||
<BlockSlot @name="action" as |confirm|>
|
<BlockSlot @name="action" as |confirm|>
|
||||||
<button data-test-delete
|
<Action
|
||||||
type="button"
|
data-test-delete
|
||||||
class="type-delete"
|
class="type-delete"
|
||||||
onclick={{action confirm onInvalidate item}}
|
{{on 'click' (fn confirm (fn @ondelete item))}}
|
||||||
>
|
>
|
||||||
Invalidate
|
Invalidate
|
||||||
</button>
|
</Action>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||||
<p>
|
<p>
|
||||||
{{message}}
|
{{message}}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" class="type-delete" onclick={{action execute}}>Confirm Invalidate</button>
|
<Action
|
||||||
<button type="button" class="type-cancel" onclick={{action cancel}}>Cancel</button>
|
class="type-delete"
|
||||||
|
{{on 'click' (fn execute)}}
|
||||||
|
>
|
||||||
|
Confirm Invalidate
|
||||||
|
</Action>
|
||||||
|
<Action
|
||||||
|
class="type-cancel"
|
||||||
|
{{on 'click' (fn cancel)}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Action>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
{{/if}}
|
|
||||||
</ListCollection>
|
</ListCollection>
|
||||||
{{/if}}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import Component from '@ember/component';
|
|
||||||
|
|
||||||
export default Component.extend({
|
|
||||||
tagName: '',
|
|
||||||
});
|
|
|
@ -1,10 +1,16 @@
|
||||||
|
.consul-lock-session-list ul > li:not(:first-child) {
|
||||||
|
@extend %composite-row;
|
||||||
|
}
|
||||||
|
.consul-lock-session-list button {
|
||||||
|
/* knock the button over a little */
|
||||||
|
/* for meatball menus we use as much clickable space */
|
||||||
|
/* as possible which is invisible hence why we need to */
|
||||||
|
/* do this for a single button */
|
||||||
|
margin-right: var(--horizontal-padding);
|
||||||
|
}
|
||||||
|
.consul-lock-session-list dl {
|
||||||
|
@extend %horizontal-kv-list;
|
||||||
|
}
|
||||||
.consul-lock-session-list .checks dd {
|
.consul-lock-session-list .checks dd {
|
||||||
display: inline-flex;
|
@extend %csv-list;
|
||||||
flex-wrap: wrap;
|
|
||||||
padding-left: 0px;
|
|
||||||
}
|
|
||||||
.consul-lock-session-list .checks dd > *:not(:last-child)::after {
|
|
||||||
content: ',';
|
|
||||||
margin-right: 0.3em;
|
|
||||||
display: inline;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,36 @@
|
||||||
{{#if (eq @type 'delete')}}
|
{{#if (eq @type 'remove')}}
|
||||||
{{#if (eq @status 'success') }}
|
{{#if @error}}
|
||||||
The session was invalidated.
|
<Notice
|
||||||
|
class="notification-delete"
|
||||||
|
@type="error"
|
||||||
|
...attributes
|
||||||
|
as |notice|>
|
||||||
|
<notice.Header>
|
||||||
|
<strong>Error!</strong>
|
||||||
|
</notice.Header>
|
||||||
|
<notice.Body>
|
||||||
|
<p>
|
||||||
|
There was an error invalidating the Lock Session.
|
||||||
|
{{#if (and @error.status @error.detail)}}
|
||||||
|
<br />{{@error.status}}: {{@error.detail}}
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
</notice.Body>
|
||||||
|
</Notice>
|
||||||
{{else}}
|
{{else}}
|
||||||
There was an error invalidating the session.
|
<Notice
|
||||||
|
class="notification-delete"
|
||||||
|
@type="success"
|
||||||
|
...attributes
|
||||||
|
as |notice|>
|
||||||
|
<notice.Header>
|
||||||
|
<strong>Success!</strong>
|
||||||
|
</notice.Header>
|
||||||
|
<notice.Body>
|
||||||
|
<p>
|
||||||
|
Your Lock Session has been invalidated.
|
||||||
|
</p>
|
||||||
|
</notice.Body>
|
||||||
|
</Notice>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
|
@ -17,9 +17,15 @@ export default {
|
||||||
target: 'loading',
|
target: 'loading',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
INVALIDATE: [
|
||||||
|
{
|
||||||
|
target: 'invalidating',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
load: {},
|
load: {},
|
||||||
|
invalidating: {},
|
||||||
loading: {
|
loading: {
|
||||||
on: {
|
on: {
|
||||||
SUCCESS: {
|
SUCCESS: {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
{{#let (hash
|
{{#let (hash
|
||||||
data=data
|
data=data
|
||||||
error=error
|
error=error
|
||||||
|
invalidate=(action "invalidate")
|
||||||
dispatchError=(queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR"))
|
dispatchError=(queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR"))
|
||||||
) as |api|}}
|
) as |api|}}
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
{{! if we didn't specify any data}}
|
{{! if we didn't specify any data}}
|
||||||
{{#if (not items)}}
|
{{#if (not items)}}
|
||||||
{{! try and load the data if we aren't in an error state}}
|
{{! try and load the data if we aren't in an error state}}
|
||||||
<State @notMatches={{array "error" "disconnected"}}>
|
<State @notMatches={{array "error" "disconnected" "invalidating"}}>
|
||||||
{{! but only if we only asked for a single load and we are in loading state}}
|
{{! but only if we only asked for a single load and we are in loading state}}
|
||||||
{{#if (and src (or (not once) (state-matches state "loading")))}}
|
{{#if (and src (or (not once) (state-matches state "loading")))}}
|
||||||
<DataSource
|
<DataSource
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
{{/yield-slot}}
|
{{/yield-slot}}
|
||||||
</State>
|
</State>
|
||||||
|
|
||||||
<State @matches={{array "idle" "disconnected"}}>
|
<State @matches={{array "idle" "disconnected" "invalidating"}}>
|
||||||
|
|
||||||
<State @matches="disconnected">
|
<State @matches="disconnected">
|
||||||
{{#yield-slot name="disconnected" params=(block-params (action dispatch "RESET"))}}
|
{{#yield-slot name="disconnected" params=(block-params (action dispatch "RESET"))}}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { set } from '@ember/object';
|
import { set } from '@ember/object';
|
||||||
|
import { schedule } from '@ember/runloop';
|
||||||
import Slotted from 'block-slots';
|
import Slotted from 'block-slots';
|
||||||
|
|
||||||
import chart from './chart.xstate';
|
import chart from './chart.xstate';
|
||||||
|
@ -21,6 +22,12 @@ export default Component.extend(Slotted, {
|
||||||
this.dispatch('LOAD');
|
this.dispatch('LOAD');
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
invalidate() {
|
||||||
|
this.dispatch('INVALIDATE');
|
||||||
|
schedule('afterRender', () => {
|
||||||
|
this.dispatch('LOAD');
|
||||||
|
});
|
||||||
|
},
|
||||||
isLoaded: function() {
|
isLoaded: function() {
|
||||||
return typeof this.items !== 'undefined' || typeof this.src === 'undefined';
|
return typeof this.items !== 'undefined' || typeof this.src === 'undefined';
|
||||||
},
|
},
|
||||||
|
|
|
@ -90,7 +90,7 @@ as |after|}}
|
||||||
{{#let
|
{{#let
|
||||||
(action dispatch "RESET")
|
(action dispatch "RESET")
|
||||||
as |after|}}
|
as |after|}}
|
||||||
{{#yield-slot name="error" params=(block-params after)}}
|
{{#yield-slot name="error" params=(block-params after api.error)}}
|
||||||
{{yield api}}
|
{{yield api}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<Notice
|
<Notice
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 140px auto;
|
grid-template-columns: 140px auto;
|
||||||
grid-gap: 0.4em 20px;
|
grid-gap: 0.4em 20px;
|
||||||
|
}
|
||||||
|
%definition-table > dl {
|
||||||
margin-bottom: 1.4em;
|
margin-bottom: 1.4em;
|
||||||
}
|
}
|
||||||
%definition-table dd > * {
|
%definition-table dd > * {
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<li
|
<li
|
||||||
data-test-list-row
|
data-test-list-row
|
||||||
onclick={{action 'click'}} style={{{cell.style}}}
|
onclick={{action 'click'}} style={{{cell.style}}}
|
||||||
class={{if (not linkable) 'linkable' (if (is linkable item=cell.item) 'linkable')}}
|
class={{if linkable (if (is linkable item=cell.item) 'linkable')}}
|
||||||
>
|
>
|
||||||
<YieldSlot @name="header"><div class="header">{{yield cell.item cell.index}}</div></YieldSlot>
|
<YieldSlot @name="header"><div class="header">{{yield cell.item cell.index}}</div></YieldSlot>
|
||||||
<YieldSlot @name="details"><div class="detail">{{yield cell.item cell.index}}</div></YieldSlot>
|
<YieldSlot @name="details"><div class="detail">{{yield cell.item cell.index}}</div></YieldSlot>
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
%list-row {
|
%list-row {
|
||||||
padding-top: 10px;
|
/* this can be reused by internal components */
|
||||||
padding-bottom: 10px;
|
/* for positioning if required */
|
||||||
|
--horizontal-padding: 12px;
|
||||||
|
--vertical-padding: 10px;
|
||||||
|
padding: var(--vertical-padding) 0;
|
||||||
/* whilst this isn't in the designs this makes our temporary rollover look better */
|
/* whilst this isn't in the designs this makes our temporary rollover look better */
|
||||||
padding-left: 12px;
|
/* it doesn't happen on the right as we use a larger hit area with our */
|
||||||
|
/* meatball menu which would overlap this and the meatball is the most */
|
||||||
|
/* right hand 'action' button */
|
||||||
|
padding-left: var(--horizontal-padding);
|
||||||
|
/* once we have our scroll pane refresh we no longer need a padding for */
|
||||||
|
/* shadowing/rollover purposes so a lot of that ^ can go */
|
||||||
}
|
}
|
||||||
%list-row-detail,
|
%list-row-detail,
|
||||||
%list-row-header-icon {
|
%list-row-header-icon {
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
}
|
}
|
||||||
%list-row-intent {
|
%list-row-intent {
|
||||||
border-color: rgb(var(--tone-gray-200));
|
border-color: rgb(var(--tone-gray-200));
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
/*TODO: This should use a shared/CSS prop shadow*/
|
||||||
|
box-shadow: 0 2px 4px rgb(var(--black) / 10%);
|
||||||
border-top-color: var(--transparent);
|
border-top-color: var(--transparent);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import Route from 'consul-ui/routing/route';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import { action } from '@ember/object';
|
|
||||||
|
|
||||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
|
||||||
|
|
||||||
export default class SessionsRoute extends Route.extend(WithBlockingActions) {
|
|
||||||
@service('repository/session') sessionRepo;
|
|
||||||
@service('feedback') feedback;
|
|
||||||
|
|
||||||
@action
|
|
||||||
invalidateSession(item) {
|
|
||||||
const route = this;
|
|
||||||
return this.feedback.execute(() => {
|
|
||||||
return this.sessionRepo.remove(item).then(() => {
|
|
||||||
route.refresh();
|
|
||||||
});
|
|
||||||
}, 'delete');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,18 @@ export default class KvSerializer extends Serializer {
|
||||||
|
|
||||||
respondForQueryRecord(respond, query) {
|
respondForQueryRecord(respond, query) {
|
||||||
return super.respondForQueryRecord(
|
return super.respondForQueryRecord(
|
||||||
cb => respond((headers, body) => cb(headers, body[0])),
|
cb =>
|
||||||
|
respond((headers, body) => {
|
||||||
|
// If item.Session is not set make sure we overwrite any existing one.
|
||||||
|
// Using @replace, defaultValue or similar model apporaches does not work
|
||||||
|
// as if a property is undefined ember-data just ignores it instead of
|
||||||
|
// deleting the value of the existing property.
|
||||||
|
if (typeof body[0].Session === 'undefined') {
|
||||||
|
body[0].Session = '';
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return cb(headers, body[0]);
|
||||||
|
}),
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,20 @@ export default class SessionSerializer extends Serializer {
|
||||||
|
|
||||||
respondForQueryRecord(respond, query) {
|
respondForQueryRecord(respond, query) {
|
||||||
return super.respondForQueryRecord(
|
return super.respondForQueryRecord(
|
||||||
cb => respond((headers, body) => cb(headers, body[0])),
|
cb =>
|
||||||
|
respond((headers, body) => {
|
||||||
|
if (body.length === 0) {
|
||||||
|
const e = new Error();
|
||||||
|
e.errors = [
|
||||||
|
{
|
||||||
|
status: '404',
|
||||||
|
title: 'Not found',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return cb(headers, body[0]);
|
||||||
|
}),
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,18 @@ html[data-route$='edit'] .app-view > header + div > *:first-child {
|
||||||
%app-view-content .container {
|
%app-view-content .container {
|
||||||
margin-top: 1.25em;
|
margin-top: 1.25em;
|
||||||
}
|
}
|
||||||
.consul-upstream-instance-list,
|
|
||||||
.consul-lock-session-list {
|
%list-after-secondary-nav {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
%list-after-secondary-nav ul {
|
||||||
|
border-top-width: 0 !important;
|
||||||
|
}
|
||||||
|
%list-after-filter-bar {
|
||||||
|
border-top-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consul-upstream-instance-list {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
/* turn off top borders for things flush up to a filter bar */
|
/* turn off top borders for things flush up to a filter bar */
|
||||||
|
@ -62,12 +72,11 @@ html[data-route='dc.services.index'] .consul-service-list ul,
|
||||||
.consul-nspace-list ul,
|
.consul-nspace-list ul,
|
||||||
.consul-service-instance-list ul,
|
.consul-service-instance-list ul,
|
||||||
.consul-node-list ul,
|
.consul-node-list ul,
|
||||||
.consul-lock-session-list ul,
|
|
||||||
.consul-role-list ul,
|
.consul-role-list ul,
|
||||||
.consul-policy-list ul,
|
.consul-policy-list ul,
|
||||||
.consul-token-list ul,
|
.consul-token-list ul,
|
||||||
.consul-auth-method-list ul {
|
.consul-auth-method-list ul {
|
||||||
border-top-width: 0 !important;
|
@extend %list-after-filter-bar;
|
||||||
}
|
}
|
||||||
.notice + .consul-token-list ul {
|
.notice + .consul-token-list ul {
|
||||||
border-top-width: 1px !important;
|
border-top-width: 1px !important;
|
||||||
|
|
|
@ -2,3 +2,10 @@ html[data-route^='dc.kv'] .type-toggle {
|
||||||
float: right;
|
float: right;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
html[data-route^='dc.kv.edit'] h2 {
|
||||||
|
@extend %h200;
|
||||||
|
border-bottom: var(--decor-border-200);
|
||||||
|
border-color: rgb(var(--tone-gray-200));
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
html[data-route^='dc.nodes.show.metadata'] table tr {
|
html[data-route^='dc.nodes.show.metadata'] table tr {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
html[data-route^='dc.nodes.show.sessions'] .consul-lock-session-list {
|
||||||
|
@extend %list-after-secondary-nav;
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,9 @@ fieldset > header,
|
||||||
%form-element > span {
|
%form-element > span {
|
||||||
@extend %h400;
|
@extend %h400;
|
||||||
}
|
}
|
||||||
|
%definition-table dt {
|
||||||
|
line-height: var(--typo-lead-700);
|
||||||
|
}
|
||||||
%internal-button,
|
%internal-button,
|
||||||
%breadcrumbs li > *,
|
%breadcrumbs li > *,
|
||||||
%tab-nav {
|
%tab-nav {
|
||||||
|
|
|
@ -39,10 +39,16 @@ as |parentKey|}}
|
||||||
as |dc partition nspace item|}}
|
as |dc partition nspace item|}}
|
||||||
|
|
||||||
<AppView>
|
<AppView>
|
||||||
|
|
||||||
<BlockSlot @name="breadcrumbs">
|
<BlockSlot @name="breadcrumbs">
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
<a data-test-back href={{href-to 'dc.kv.index'}}>Key / Values</a>
|
<Action
|
||||||
|
data-test-back
|
||||||
|
@href={{href-to 'dc.kv.index'}}
|
||||||
|
>
|
||||||
|
Key / Values
|
||||||
|
</Action>
|
||||||
</li>
|
</li>
|
||||||
{{#if (not-eq parentKey separator)}}
|
{{#if (not-eq parentKey separator)}}
|
||||||
|
|
||||||
|
@ -56,8 +62,8 @@ as |parts|}}
|
||||||
{{! to make the correct href. 'Enough' is the current index plus 1.}}
|
{{! to make the correct href. 'Enough' is the current index plus 1.}}
|
||||||
{{! We push on a '' here so make sure we get a trailing slash/separator }}
|
{{! We push on a '' here so make sure we get a trailing slash/separator }}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<Action
|
||||||
href={{href-to 'dc.kv.folder'
|
@href={{href-to 'dc.kv.folder'
|
||||||
(join '/'
|
(join '/'
|
||||||
(append
|
(append
|
||||||
(slice 0 (add index 1) parts) ''
|
(slice 0 (add index 1) parts) ''
|
||||||
|
@ -66,7 +72,7 @@ as |parts|}}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{{breadcrumb}}
|
{{breadcrumb}}
|
||||||
</a>
|
</Action>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -75,6 +81,7 @@ as |parts|}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</ol>
|
</ol>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
<BlockSlot @name="header">
|
<BlockSlot @name="header">
|
||||||
<h1>
|
<h1>
|
||||||
{{#if (and item.Key (not-eq item.Key parentKey))}}
|
{{#if (and item.Key (not-eq item.Key parentKey))}}
|
||||||
|
@ -85,7 +92,9 @@ as |parts|}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</h1>
|
</h1>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
<BlockSlot @name="content">
|
<BlockSlot @name="content">
|
||||||
|
|
||||||
{{! if a KV has a session `Session` will always be populated despite any specific session permissions }}
|
{{! if a KV has a session `Session` will always be populated despite any specific session permissions }}
|
||||||
{{#if item.Session}}
|
{{#if item.Session}}
|
||||||
<Notice
|
<Notice
|
||||||
|
@ -99,6 +108,7 @@ as |parts|}}
|
||||||
</notice.Body>
|
</notice.Body>
|
||||||
</Notice>
|
</Notice>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<Consul::Kv::Form
|
<Consul::Kv::Form
|
||||||
@item={{item}}
|
@item={{item}}
|
||||||
@dc={{route.params.dc}}
|
@dc={{route.params.dc}}
|
||||||
|
@ -108,6 +118,7 @@ as |parts|}}
|
||||||
@parent={{parentKey}}
|
@parent={{parentKey}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
{{! `session` is slightly different to `item.Session` as we only have `session` }}
|
{{! `session` is slightly different to `item.Session` as we only have `session` }}
|
||||||
{{! if you have `session:read perms` whereas you can get the sessions ID from }}
|
{{! if you have `session:read perms` whereas you can get the sessions ID from }}
|
||||||
{{! `item.Session` without any session perms }}
|
{{! `item.Session` without any session perms }}
|
||||||
|
@ -123,17 +134,21 @@ as |parts|}}
|
||||||
}}
|
}}
|
||||||
@onchange={{action (mut session) value="data"}}
|
@onchange={{action (mut session) value="data"}}
|
||||||
/>
|
/>
|
||||||
{{#if session}}
|
<h2>
|
||||||
{{!FIXME}}
|
<Action
|
||||||
|
rel="help"
|
||||||
|
@href={{concat (env 'CONSUL_DOCS_URL') '/internals/sessions.html#session-design'}}
|
||||||
|
@external={{true}}
|
||||||
|
>
|
||||||
|
Lock Session
|
||||||
|
</Action>
|
||||||
|
</h2>
|
||||||
|
{{#if session.ID}}
|
||||||
<Consul::LockSession::Form
|
<Consul::LockSession::Form
|
||||||
@item={{session}}
|
@item={{session}}
|
||||||
@dc={{route.params.dc}}
|
@ondelete={{loader.invalidate}}
|
||||||
@nspace={{route.params.nspace}}
|
|
||||||
@partition={{route.params.partition}}
|
|
||||||
@onsubmit={{action (noop) undefined}}
|
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
|
@ -18,14 +18,55 @@ as |route|>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
<BlockSlot @name="loaded">
|
<BlockSlot @name="loaded">
|
||||||
{{#let api.data as |sessions|}}
|
{{#let api.data as |items|}}
|
||||||
<div class="tab-section">
|
<div class="tab-section">
|
||||||
{{#if (gt sessions.length 0)}}
|
<DataWriter
|
||||||
<Consul::LockSession::List
|
@sink={{uri '/${partition}/${dc}/${nspace}/session/'
|
||||||
@items={{sessions}}
|
(hash
|
||||||
@onInvalidate={{action send 'invalidateSession'}}
|
partition=route.params.partition
|
||||||
|
nspace=route.params.nspace
|
||||||
|
dc=route.params.dc
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@type="session"
|
||||||
|
@label="Lock Session"
|
||||||
|
@ondelete={{refresh-route}}
|
||||||
|
as |writer|>
|
||||||
|
|
||||||
|
<BlockSlot @name="removed" as |after|>
|
||||||
|
<Consul::LockSession::Notifications
|
||||||
|
{{notification
|
||||||
|
after=(action after)
|
||||||
|
}}
|
||||||
|
@type="remove"
|
||||||
/>
|
/>
|
||||||
{{else}}
|
</BlockSlot>
|
||||||
|
|
||||||
|
<BlockSlot @name="error" as |after error|>
|
||||||
|
<Consul::LockSession::Notifications
|
||||||
|
{{notification
|
||||||
|
after=(action after)
|
||||||
|
}}
|
||||||
|
@type="remove"
|
||||||
|
@error={{error}}
|
||||||
|
/>
|
||||||
|
</BlockSlot>
|
||||||
|
|
||||||
|
<BlockSlot @name="content">
|
||||||
|
|
||||||
|
<DataCollection
|
||||||
|
@type="session"
|
||||||
|
@items={{items}}
|
||||||
|
as |collection|>
|
||||||
|
|
||||||
|
<collection.Collection>
|
||||||
|
<Consul::LockSession::List
|
||||||
|
@items={{collection.items}}
|
||||||
|
@ondelete={{writer.delete}}
|
||||||
|
/>
|
||||||
|
</collection.Collection>
|
||||||
|
|
||||||
|
<collection.Empty>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@login={{route.model.app.login.open}}
|
@login={{route.model.app.login.open}}
|
||||||
>
|
>
|
||||||
|
@ -34,21 +75,39 @@ as |route|>
|
||||||
Welcome to Lock Sessions
|
Welcome to Lock Sessions
|
||||||
</h2>
|
</h2>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
<BlockSlot @name="body">
|
<BlockSlot @name="body">
|
||||||
<p>
|
<p>
|
||||||
Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between nodes, health checks, and key/value data. There are currently no lock sessions present, or you may not have permission to view lock sessions.
|
Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between nodes, health checks, and key/value data. There are currently no lock sessions present, or you may not have permission to view lock sessions.
|
||||||
</p>
|
</p>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
<BlockSlot @name="actions">
|
<BlockSlot @name="actions">
|
||||||
<li class="docs-link">
|
<li class="docs-link">
|
||||||
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" rel="noopener noreferrer" target="_blank">Documentation on sessions</a>
|
<Action
|
||||||
|
@href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html"
|
||||||
|
@external={{true}}
|
||||||
|
>
|
||||||
|
Documentation on Lock Sessions
|
||||||
|
</Action>
|
||||||
</li>
|
</li>
|
||||||
<li class="learn-link">
|
<li class="learn-link">
|
||||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore" rel="noopener noreferrer" target="_blank">Read the guide</a>
|
<Action
|
||||||
|
@href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore"
|
||||||
|
@external={{true}}
|
||||||
|
>
|
||||||
|
Read the guide
|
||||||
|
</Action>
|
||||||
</li>
|
</li>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
{{/if}}
|
</collection.Empty>
|
||||||
|
|
||||||
|
</DataCollection>
|
||||||
|
|
||||||
|
</BlockSlot>
|
||||||
|
</DataWriter>
|
||||||
</div>
|
</div>
|
||||||
{{/let}}
|
{{/let}}
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
typeof location.search.ns !== 'undefined' ? location.search.ns :
|
typeof location.search.ns !== 'undefined' ? location.search.ns :
|
||||||
typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default'
|
typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default'
|
||||||
}",
|
}",
|
||||||
|
"Partition": "${
|
||||||
|
typeof location.search.partition !== 'undefined' ? location.search.partition :
|
||||||
|
typeof http.body.Partition !== 'undefined' ? http.body.Partition : 'default'
|
||||||
|
}",
|
||||||
"Node":"node-1",
|
"Node":"node-1",
|
||||||
"NodeChecks":["serfHealth"],
|
"NodeChecks":["serfHealth"],
|
||||||
"ServiceChecks": [
|
"ServiceChecks": [
|
||||||
|
|
|
@ -28,5 +28,5 @@ Feature: dc / kvs / sessions / invalidate: Invalidate Lock Sessions
|
||||||
And I click delete on the session
|
And I click delete on the session
|
||||||
And I click confirmDelete on the session
|
And I click confirmDelete on the session
|
||||||
Then the url should be /datacenter/kv/key/edit
|
Then the url should be /datacenter/kv/key/edit
|
||||||
And "[data-notification]" has the "notification-update" class
|
And "[data-notification]" has the "notification-delete" class
|
||||||
And "[data-notification]" has the "error" class
|
And "[data-notification]" has the "error" class
|
||||||
|
|
Loading…
Reference in New Issue