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 8092595057
commit c6dd21f4dd
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 './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,

View File

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

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 <div
@dc={{dc}} class="consul-lock-session-form"
@nspace={{nspace}} data-test-session={{@item.ID}}
@partition={{partition}} ...attributes
@item={{item}}
@type="session"
@onsubmit={{action onsubmit}}
as |api|
> >
<BlockSlot @name="form"> <DataWriter
<div @sink={{uri
class="consul-lock-session-form definition-table" '/${partition}/${nspace}/${dc}/session'
data-test-session={{api.data.ID}} (hash
...attributes partition=@item.Partition
> nspace=@item.Namespace
<h2> dc=@item.Datacenter
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html#session-design" rel="help noopener noreferrer" target="_blank">Lock Session</a> )
</h2> }}
<dl> @type={{'session'}}
{{#if api.data.Name}} @label={{'Lock Session'}}
<dt>Name</dt> @ondelete={{fn (if @ondelete @ondelete @onsubmit) @item}}
<dd>{{api.data.Name}}</dd> @onchange={{fn (optional @onsubmit) @item}}
{{/if}} as |writer|>
<dt>ID</dt> <BlockSlot @name="removed" as |after|>
<dd>{{api.data.ID}}</dd> <Consul::LockSession::Notifications
<dt>Node</dt> {{notification
<dd> after=(action after)
<a href={{href-to 'dc.nodes.show' api.data.Node}}>{{api.data.Node}}</a> }}
</dd> @type="remove"
<dt>Delay</dt> />
<dd>{{duration-from api.data.LockDelay}}</dd> </BlockSlot>
<dt>TTL</dt> <BlockSlot @name="error" as |after error|>
<dd>{{or api.data.TTL '-'}}</dd> <Consul::LockSession::Notifications
<dt>Behavior</dt> {{notification
<dd>{{api.data.Behavior}}</dd> after=(action after)
{{#let api.data.checks as |checks|}} }}
<dt>Health Checks</dt> @type="remove"
<dd> @error={{error}}
{{#if (gt checks.length 0)}} />
{{ join ', ' checks}} </BlockSlot>
{{else}} <BlockSlot @name="content">
- <div
class="definition-table"
>
<dl>
{{#if @item.Name}}
<dt>Name</dt>
<dd>{{@item.Name}}</dd>
{{/if}} {{/if}}
</dd> <dt>ID</dt>
{{/let}} <dd>{{@item.ID}}</dd>
</dl> <dt>Node</dt>
{{#if (can 'delete session' item=api.data)}} <dd>
<ConfirmationDialog @message="Are you sure you want to invalidate this session?"> <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|> <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> </DataWriter>
</DataForm> </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 { .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;
}
} }

View File

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

View File

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

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 { .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;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,11 +39,17 @@ 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
</li> data-test-back
@href={{href-to 'dc.kv.index'}}
>
Key / Values
</Action>
</li>
{{#if (not-eq parentKey separator)}} {{#if (not-eq parentKey separator)}}
{{#let {{#let
@ -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,18 +81,21 @@ 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))}}
<route.Title @title="Edit Key / Value" @render={{false}} /> <route.Title @title="Edit Key / Value" @render={{false}} />
{{left-trim item.Key parentKey}} {{left-trim item.Key parentKey}}
{{else}} {{else}}
<route.Title @title="New Key / Value" @render={{true}} /> <route.Title @title="New Key / Value" @render={{true}} />
{{/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
@type="warning" @type="warning"
@ -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>

View File

@ -18,38 +18,97 @@ 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
{{else}} dc=route.params.dc
<EmptyState )
@login={{route.model.app.login.open}} }}
> @type="session"
<BlockSlot @name="header"> @label="Lock Session"
<h2> @ondelete={{refresh-route}}
Welcome to Lock Sessions as |writer|>
</h2>
<BlockSlot @name="removed" as |after|>
<Consul::LockSession::Notifications
{{notification
after=(action after)
}}
@type="remove"
/>
</BlockSlot> </BlockSlot>
<BlockSlot @name="body">
<p> <BlockSlot @name="error" as |after error|>
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::LockSession::Notifications
</p> {{notification
after=(action after)
}}
@type="remove"
@error={{error}}
/>
</BlockSlot> </BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link"> <BlockSlot @name="content">
<a href="{{env 'CONSUL_DOCS_URL'}}/internals/sessions.html" rel="noopener noreferrer" target="_blank">Documentation on sessions</a>
</li> <DataCollection
<li class="learn-link"> @type="session"
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/tutorials/consul/distributed-semaphore" rel="noopener noreferrer" target="_blank">Read the guide</a> @items={{items}}
</li> 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> </BlockSlot>
</EmptyState> </DataWriter>
{{/if}} </div>
</div>
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</DataLoader> </DataLoader>

View File

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

View File

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