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
8092595057
commit
c6dd21f4dd
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: Upgrade Lock Sessions to use partitions
|
||||
```
|
|
@ -1,5 +1,4 @@
|
|||
@import './layout';
|
||||
@import './skin';
|
||||
%composite-row {
|
||||
@extend %list-row;
|
||||
}
|
||||
|
@ -33,12 +32,8 @@
|
|||
.consul-auth-method-list > ul > li:not(:first-child) {
|
||||
@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
|
||||
// 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-token-list > ul > li:not(:first-child) dt,
|
||||
.consul-policy-list > ul li:not(:first-child) dl:not(.datacenter) dt,
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
'header actions'
|
||||
'detail actions';
|
||||
}
|
||||
%with-one-action-row {
|
||||
@extend %composite-row;
|
||||
grid-template-columns: 1fr auto;
|
||||
padding-right: 12px;
|
||||
}
|
||||
%composite-row-header {
|
||||
grid-area: header;
|
||||
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,63 +1,106 @@
|
|||
<DataForm
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@item={{item}}
|
||||
@type="session"
|
||||
@onsubmit={{action onsubmit}}
|
||||
as |api|
|
||||
<div
|
||||
class="consul-lock-session-form"
|
||||
data-test-session={{@item.ID}}
|
||||
...attributes
|
||||
>
|
||||
<BlockSlot @name="form">
|
||||
<div
|
||||
class="consul-lock-session-form definition-table"
|
||||
data-test-session={{api.data.ID}}
|
||||
...attributes
|
||||
>
|
||||
<h2>
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html#session-design" rel="help noopener noreferrer" target="_blank">Lock Session</a>
|
||||
</h2>
|
||||
<dl>
|
||||
{{#if api.data.Name}}
|
||||
<dt>Name</dt>
|
||||
<dd>{{api.data.Name}}</dd>
|
||||
{{/if}}
|
||||
<dt>ID</dt>
|
||||
<dd>{{api.data.ID}}</dd>
|
||||
<dt>Node</dt>
|
||||
<dd>
|
||||
<a href={{href-to 'dc.nodes.show' api.data.Node}}>{{api.data.Node}}</a>
|
||||
</dd>
|
||||
<dt>Delay</dt>
|
||||
<dd>{{duration-from api.data.LockDelay}}</dd>
|
||||
<dt>TTL</dt>
|
||||
<dd>{{or api.data.TTL '-'}}</dd>
|
||||
<dt>Behavior</dt>
|
||||
<dd>{{api.data.Behavior}}</dd>
|
||||
{{#let api.data.checks as |checks|}}
|
||||
<dt>Health Checks</dt>
|
||||
<dd>
|
||||
{{#if (gt checks.length 0)}}
|
||||
{{ join ', ' checks}}
|
||||
{{else}}
|
||||
-
|
||||
<DataWriter
|
||||
@sink={{uri
|
||||
'/${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>
|
||||
{{#if @item.Name}}
|
||||
<dt>Name</dt>
|
||||
<dd>{{@item.Name}}</dd>
|
||||
{{/if}}
|
||||
</dd>
|
||||
{{/let}}
|
||||
</dl>
|
||||
{{#if (can 'delete session' item=api.data)}}
|
||||
<ConfirmationDialog @message="Are you sure you want to invalidate this session?">
|
||||
<dt>ID</dt>
|
||||
<dd>{{@item.ID}}</dd>
|
||||
<dt>Node</dt>
|
||||
<dd>
|
||||
<a
|
||||
href={{href-to 'dc.nodes.show' @item.Node}}
|
||||
>
|
||||
{{@item.Node}}
|
||||
</a>
|
||||
</dd>
|
||||
<dt>Delay</dt>
|
||||
<dd>{{duration-from @item.LockDelay}}</dd>
|
||||
<dt>TTL</dt>
|
||||
<dd>{{or @item.TTL '-'}}</dd>
|
||||
<dt>Behavior</dt>
|
||||
<dd>{{@item.Behavior}}</dd>
|
||||
{{#let @item.checks as |checks|}}
|
||||
<dt>Health Checks</dt>
|
||||
<dd>
|
||||
{{#if (gt checks.length 0)}}
|
||||
{{ join ', ' checks}}
|
||||
{{else}}
|
||||
-
|
||||
{{/if}}
|
||||
</dd>
|
||||
{{/let}}
|
||||
</dl>
|
||||
</div>
|
||||
{{#if (can 'delete session' item=@item)}}
|
||||
<ConfirmationDialog @message="Are you sure you want to invalidate this Lock Session?">
|
||||
<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 @name="dialog" as |execute cancel message|>
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<button type="button" class="type-delete" {{action execute}}>Confirm Invalidation</button>
|
||||
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||
<Action
|
||||
class="type-delete"
|
||||
{{on 'click' (fn execute)}}
|
||||
>
|
||||
Confirm Invalidation
|
||||
</Action>
|
||||
<Action
|
||||
class="type-cancel"
|
||||
{{on 'click' (fn cancel)}}
|
||||
>
|
||||
Cancel
|
||||
</Action>
|
||||
</BlockSlot>
|
||||
</ConfirmationDialog>
|
||||
{{/if}}
|
||||
</div>
|
||||
</BlockSlot>
|
||||
</DataForm>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</DataWriter>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({});
|
|
@ -1,9 +1,3 @@
|
|||
.consul-lock-session-form {
|
||||
h2 {
|
||||
@extend %h200;
|
||||
border-bottom: var(--decor-border-200);
|
||||
border-color: rgb(var(--tone-gray-200));
|
||||
padding-bottom: 0.2em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
---
|
||||
class: ember
|
||||
---
|
||||
# Consul::LockSession::List
|
||||
|
||||
A presentational component for rendering Node Lock Sessions
|
||||
|
||||
```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
|
||||
@items={{source.data}}
|
||||
@onInvalidate={{action (noop)}}
|
||||
@ondelete={{action (noop)}}
|
||||
/>
|
||||
</DataSource>
|
||||
```
|
||||
|
||||
A presentational component for rendering Node Lock Sessions
|
||||
|
||||
### Arguments
|
||||
## Arguments
|
||||
|
||||
| Argument/Attribute | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `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)
|
||||
|
||||
---
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{{#if (gt items.length 0)}}
|
||||
<ListCollection @items={{items}} class="consul-lock-session-list" as |item index|>
|
||||
<ListCollection
|
||||
class="consul-lock-session-list"
|
||||
...attributes
|
||||
@items={{@items}}
|
||||
as |item index|>
|
||||
<BlockSlot @name="header">
|
||||
{{#if item.Name}}
|
||||
<span>{{item.Name}}</span>
|
||||
|
@ -35,7 +38,9 @@
|
|||
<dd data-test-session-delay>{{duration-from item.LockDelay}}</dd>
|
||||
</dl>
|
||||
<dl class="ttl">
|
||||
<dt {{tooltip}}>
|
||||
<dt
|
||||
{{tooltip}}
|
||||
>
|
||||
TTL
|
||||
</dt>
|
||||
{{#if (eq item.TTL "")}}
|
||||
|
@ -45,14 +50,18 @@
|
|||
{{/if}}
|
||||
</dl>
|
||||
<dl class="behavior">
|
||||
<dt {{tooltip}}>
|
||||
<dt
|
||||
{{tooltip}}
|
||||
>
|
||||
Behavior
|
||||
</dt>
|
||||
<dd>{{item.Behavior}}</dd>
|
||||
</dl>
|
||||
{{#let (union item.NodeChecks item.ServiceChecks) as |checks|}}
|
||||
<dl class="checks">
|
||||
<dt {{tooltip}}>
|
||||
<dt
|
||||
{{tooltip}}
|
||||
>
|
||||
Checks
|
||||
</dt>
|
||||
<dd>
|
||||
|
@ -67,27 +76,36 @@
|
|||
</dl>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
{{#if (can "delete sessions")}}
|
||||
<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|>
|
||||
<button data-test-delete
|
||||
type="button"
|
||||
class="type-delete"
|
||||
onclick={{action confirm onInvalidate item}}
|
||||
>
|
||||
Invalidate
|
||||
</button>
|
||||
<Action
|
||||
data-test-delete
|
||||
class="type-delete"
|
||||
{{on 'click' (fn confirm (fn @ondelete item))}}
|
||||
>
|
||||
Invalidate
|
||||
</Action>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<button type="button" class="type-delete" onclick={{action execute}}>Confirm Invalidate</button>
|
||||
<button type="button" class="type-cancel" onclick={{action cancel}}>Cancel</button>
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<Action
|
||||
class="type-delete"
|
||||
{{on 'click' (fn execute)}}
|
||||
>
|
||||
Confirm Invalidate
|
||||
</Action>
|
||||
<Action
|
||||
class="type-cancel"
|
||||
{{on 'click' (fn cancel)}}
|
||||
>
|
||||
Cancel
|
||||
</Action>
|
||||
</BlockSlot>
|
||||
</ConfirmationDialog>
|
||||
</BlockSlot>
|
||||
{{/if}}
|
||||
</ListCollection>
|
||||
{{/if}}
|
||||
</ListCollection>
|
|
@ -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 {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 0px;
|
||||
}
|
||||
.consul-lock-session-list .checks dd > *:not(:last-child)::after {
|
||||
content: ',';
|
||||
margin-right: 0.3em;
|
||||
display: inline;
|
||||
@extend %csv-list;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,36 @@
|
|||
{{#if (eq @type 'delete')}}
|
||||
{{#if (eq @status 'success') }}
|
||||
The session was invalidated.
|
||||
{{#if (eq @type 'remove')}}
|
||||
{{#if @error}}
|
||||
<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}}
|
||||
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}}
|
|
@ -17,9 +17,15 @@ export default {
|
|||
target: 'loading',
|
||||
},
|
||||
],
|
||||
INVALIDATE: [
|
||||
{
|
||||
target: 'invalidating',
|
||||
},
|
||||
],
|
||||
},
|
||||
states: {
|
||||
load: {},
|
||||
invalidating: {},
|
||||
loading: {
|
||||
on: {
|
||||
SUCCESS: {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
{{#let (hash
|
||||
data=data
|
||||
error=error
|
||||
invalidate=(action "invalidate")
|
||||
dispatchError=(queue (action (mut error) value="error.errors.firstObject") (action dispatch "ERROR"))
|
||||
) as |api|}}
|
||||
|
||||
|
@ -16,7 +17,7 @@
|
|||
{{! if we didn't specify any data}}
|
||||
{{#if (not items)}}
|
||||
{{! 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}}
|
||||
{{#if (and src (or (not once) (state-matches state "loading")))}}
|
||||
<DataSource
|
||||
|
@ -46,7 +47,7 @@
|
|||
{{/yield-slot}}
|
||||
</State>
|
||||
|
||||
<State @matches={{array "idle" "disconnected"}}>
|
||||
<State @matches={{array "idle" "disconnected" "invalidating"}}>
|
||||
|
||||
<State @matches="disconnected">
|
||||
{{#yield-slot name="disconnected" params=(block-params (action dispatch "RESET"))}}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Component from '@ember/component';
|
||||
import { set } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import Slotted from 'block-slots';
|
||||
|
||||
import chart from './chart.xstate';
|
||||
|
@ -21,6 +22,12 @@ export default Component.extend(Slotted, {
|
|||
this.dispatch('LOAD');
|
||||
},
|
||||
actions: {
|
||||
invalidate() {
|
||||
this.dispatch('INVALIDATE');
|
||||
schedule('afterRender', () => {
|
||||
this.dispatch('LOAD');
|
||||
});
|
||||
},
|
||||
isLoaded: function() {
|
||||
return typeof this.items !== 'undefined' || typeof this.src === 'undefined';
|
||||
},
|
||||
|
|
|
@ -90,7 +90,7 @@ as |after|}}
|
|||
{{#let
|
||||
(action dispatch "RESET")
|
||||
as |after|}}
|
||||
{{#yield-slot name="error" params=(block-params after)}}
|
||||
{{#yield-slot name="error" params=(block-params after api.error)}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
<Notice
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
display: grid;
|
||||
grid-template-columns: 140px auto;
|
||||
grid-gap: 0.4em 20px;
|
||||
}
|
||||
%definition-table > dl {
|
||||
margin-bottom: 1.4em;
|
||||
}
|
||||
%definition-table dd > * {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<li
|
||||
data-test-list-row
|
||||
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="details"><div class="detail">{{yield cell.item cell.index}}</div></YieldSlot>
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
%list-row {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
/* this can be reused by internal components */
|
||||
/* 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 */
|
||||
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-header-icon {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
}
|
||||
%list-row-intent {
|
||||
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);
|
||||
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) {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,20 @@ export default class SessionSerializer extends Serializer {
|
|||
|
||||
respondForQueryRecord(respond, query) {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -53,8 +53,18 @@ html[data-route$='edit'] .app-view > header + div > *:first-child {
|
|||
%app-view-content .container {
|
||||
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;
|
||||
}
|
||||
/* 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-service-instance-list ul,
|
||||
.consul-node-list ul,
|
||||
.consul-lock-session-list ul,
|
||||
.consul-role-list ul,
|
||||
.consul-policy-list ul,
|
||||
.consul-token-list ul,
|
||||
.consul-auth-method-list ul {
|
||||
border-top-width: 0 !important;
|
||||
@extend %list-after-filter-bar;
|
||||
}
|
||||
.notice + .consul-token-list ul {
|
||||
border-top-width: 1px !important;
|
||||
|
|
|
@ -2,3 +2,10 @@ html[data-route^='dc.kv'] .type-toggle {
|
|||
float: right;
|
||||
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 {
|
||||
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 {
|
||||
@extend %h400;
|
||||
}
|
||||
%definition-table dt {
|
||||
line-height: var(--typo-lead-700);
|
||||
}
|
||||
%internal-button,
|
||||
%breadcrumbs li > *,
|
||||
%tab-nav {
|
||||
|
|
|
@ -39,11 +39,17 @@ as |parentKey|}}
|
|||
as |dc partition nspace item|}}
|
||||
|
||||
<AppView>
|
||||
|
||||
<BlockSlot @name="breadcrumbs">
|
||||
<ol>
|
||||
<li>
|
||||
<a data-test-back href={{href-to 'dc.kv.index'}}>Key / Values</a>
|
||||
</li>
|
||||
<li>
|
||||
<Action
|
||||
data-test-back
|
||||
@href={{href-to 'dc.kv.index'}}
|
||||
>
|
||||
Key / Values
|
||||
</Action>
|
||||
</li>
|
||||
{{#if (not-eq parentKey separator)}}
|
||||
|
||||
{{#let
|
||||
|
@ -56,8 +62,8 @@ as |parts|}}
|
|||
{{! 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 }}
|
||||
<li>
|
||||
<a
|
||||
href={{href-to 'dc.kv.folder'
|
||||
<Action
|
||||
@href={{href-to 'dc.kv.folder'
|
||||
(join '/'
|
||||
(append
|
||||
(slice 0 (add index 1) parts) ''
|
||||
|
@ -66,7 +72,7 @@ as |parts|}}
|
|||
}}
|
||||
>
|
||||
{{breadcrumb}}
|
||||
</a>
|
||||
</Action>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
@ -75,18 +81,21 @@ as |parts|}}
|
|||
{{/if}}
|
||||
</ol>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
{{#if (and item.Key (not-eq item.Key parentKey))}}
|
||||
{{#if (and item.Key (not-eq item.Key parentKey))}}
|
||||
<route.Title @title="Edit Key / Value" @render={{false}} />
|
||||
{{left-trim item.Key parentKey}}
|
||||
{{else}}
|
||||
{{else}}
|
||||
<route.Title @title="New Key / Value" @render={{true}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
|
||||
<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}}
|
||||
<Notice
|
||||
@type="warning"
|
||||
|
@ -99,6 +108,7 @@ as |parts|}}
|
|||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
|
||||
<Consul::Kv::Form
|
||||
@item={{item}}
|
||||
@dc={{route.params.dc}}
|
||||
|
@ -108,6 +118,7 @@ as |parts|}}
|
|||
@parent={{parentKey}}
|
||||
/>
|
||||
|
||||
|
||||
{{! `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 }}
|
||||
{{! `item.Session` without any session perms }}
|
||||
|
@ -123,17 +134,21 @@ as |parts|}}
|
|||
}}
|
||||
@onchange={{action (mut session) value="data"}}
|
||||
/>
|
||||
{{#if session}}
|
||||
{{!FIXME}}
|
||||
<h2>
|
||||
<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
|
||||
@item={{session}}
|
||||
@dc={{route.params.dc}}
|
||||
@nspace={{route.params.nspace}}
|
||||
@partition={{route.params.partition}}
|
||||
@onsubmit={{action (noop) undefined}}
|
||||
@ondelete={{loader.invalidate}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
</BlockSlot>
|
||||
|
|
|
@ -18,38 +18,97 @@ as |route|>
|
|||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="loaded">
|
||||
{{#let api.data as |sessions|}}
|
||||
<div class="tab-section">
|
||||
{{#if (gt sessions.length 0)}}
|
||||
<Consul::LockSession::List
|
||||
@items={{sessions}}
|
||||
@onInvalidate={{action send 'invalidateSession'}}
|
||||
/>
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@login={{route.model.app.login.open}}
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<h2>
|
||||
Welcome to Lock Sessions
|
||||
</h2>
|
||||
{{#let api.data as |items|}}
|
||||
<div class="tab-section">
|
||||
<DataWriter
|
||||
@sink={{uri '/${partition}/${dc}/${nspace}/session/'
|
||||
(hash
|
||||
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"
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<BlockSlot @name="error" as |after error|>
|
||||
<Consul::LockSession::Notifications
|
||||
{{notification
|
||||
after=(action after)
|
||||
}}
|
||||
@type="remove"
|
||||
@error={{error}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" rel="noopener noreferrer" target="_blank">Documentation on sessions</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore" rel="noopener noreferrer" target="_blank">Read the guide</a>
|
||||
</li>
|
||||
|
||||
<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
|
||||
@login={{route.model.app.login.open}}
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<h2>
|
||||
Welcome to Lock Sessions
|
||||
</h2>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="body">
|
||||
<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.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<Action
|
||||
@href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html"
|
||||
@external={{true}}
|
||||
>
|
||||
Documentation on Lock Sessions
|
||||
</Action>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<Action
|
||||
@href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore"
|
||||
@external={{true}}
|
||||
>
|
||||
Read the guide
|
||||
</Action>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
|
||||
</EmptyState>
|
||||
</collection.Empty>
|
||||
|
||||
</DataCollection>
|
||||
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
</div>
|
||||
</DataWriter>
|
||||
</div>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</DataLoader>
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
typeof location.search.ns !== 'undefined' ? location.search.ns :
|
||||
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",
|
||||
"NodeChecks":["serfHealth"],
|
||||
"ServiceChecks": [
|
||||
|
|
|
@ -28,5 +28,5 @@ Feature: dc / kvs / sessions / invalidate: Invalidate Lock Sessions
|
|||
And I click delete on the session
|
||||
And I click confirmDelete on the session
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue