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:
John Cowen 2021-12-01 11:33:33 +00:00 committed by GitHub
parent 5ad82693d8
commit f3d9565277
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 436 additions and 204 deletions

3
.changelog/11666.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Upgrade Lock Sessions to use partitions
```

View File

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

View File

@ -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;

View File

@ -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)
---

View File

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

View File

@ -1,3 +0,0 @@
import Component from '@ember/component';
export default Component.extend({});

View File

@ -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;
}

View File

@ -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)
---

View File

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

View File

@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View File

@ -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;
}

View File

@ -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}}

View File

@ -17,9 +17,15 @@ export default {
target: 'loading',
},
],
INVALIDATE: [
{
target: 'invalidating',
},
],
},
states: {
load: {},
invalidating: {},
loading: {
on: {
SUCCESS: {

View File

@ -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"))}}

View File

@ -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';
},

View File

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

View File

@ -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 > * {

View File

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

View File

@ -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 {

View File

@ -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;
}

View File

@ -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');
}
}

View File

@ -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
);
}

View File

@ -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
);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

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

View File

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

View File

@ -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": [

View File

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