diff --git a/.changelog/11666.txt b/.changelog/11666.txt new file mode 100644 index 0000000000..65080a0681 --- /dev/null +++ b/.changelog/11666.txt @@ -0,0 +1,3 @@ +```release-note:feature +ui: Upgrade Lock Sessions to use partitions +``` diff --git a/ui/packages/consul-ui/app/components/composite-row/index.scss b/ui/packages/consul-ui/app/components/composite-row/index.scss index 2f56cbe05c..2612d0ca12 100644 --- a/ui/packages/consul-ui/app/components/composite-row/index.scss +++ b/ui/packages/consul-ui/app/components/composite-row/index.scss @@ -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, diff --git a/ui/packages/consul-ui/app/components/composite-row/layout.scss b/ui/packages/consul-ui/app/components/composite-row/layout.scss index fd3b465a5f..b855ef108e 100644 --- a/ui/packages/consul-ui/app/components/composite-row/layout.scss +++ b/ui/packages/consul-ui/app/components/composite-row/layout.scss @@ -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; diff --git a/ui/packages/consul-ui/app/components/composite-row/skin.scss b/ui/packages/consul-ui/app/components/composite-row/skin.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/form/README.mdx b/ui/packages/consul-ui/app/components/consul/lock-session/form/README.mdx new file mode 100644 index 0000000000..8c6a372e91 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/lock-session/form/README.mdx @@ -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) + +--- diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.hbs b/ui/packages/consul-ui/app/components/consul/lock-session/form/index.hbs index d3102b79e8..ddb5a5b1a4 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/lock-session/form/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> \ No newline at end of file + {{/if}} + </BlockSlot> + </DataWriter> +</div> \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.js b/ui/packages/consul-ui/app/components/consul/lock-session/form/index.js deleted file mode 100644 index 5570647734..0000000000 --- a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Component from '@ember/component'; - -export default Component.extend({}); diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.scss b/ui/packages/consul-ui/app/components/consul/lock-session/form/index.scss index 3dab35b60b..acb23ebdd1 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/form/index.scss +++ b/ui/packages/consul-ui/app/components/consul/lock-session/form/index.scss @@ -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; } diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/list/README.mdx b/ui/packages/consul-ui/app/components/consul/lock-session/list/README.mdx index 5344c81688..bc9321fced 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/list/README.mdx +++ b/ui/packages/consul-ui/app/components/consul/lock-session/list/README.mdx @@ -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) --- diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.hbs b/ui/packages/consul-ui/app/components/consul/lock-session/list/index.hbs index 450474ba48..cf7f826901 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/lock-session/list/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}} \ No newline at end of file +</ListCollection> \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.js b/ui/packages/consul-ui/app/components/consul/lock-session/list/index.js deleted file mode 100644 index 4798652642..0000000000 --- a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import Component from '@ember/component'; - -export default Component.extend({ - tagName: '', -}); diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.scss b/ui/packages/consul-ui/app/components/consul/lock-session/list/index.scss index 7d7c82efe2..79327901d1 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/list/index.scss +++ b/ui/packages/consul-ui/app/components/consul/lock-session/list/index.scss @@ -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; } diff --git a/ui/packages/consul-ui/app/components/consul/lock-session/notifications/index.hbs b/ui/packages/consul-ui/app/components/consul/lock-session/notifications/index.hbs index 52b97172c3..8653429328 100644 --- a/ui/packages/consul-ui/app/components/consul/lock-session/notifications/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/lock-session/notifications/index.hbs @@ -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}} \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/data-loader/chart.xstate.js b/ui/packages/consul-ui/app/components/data-loader/chart.xstate.js index 94cc961316..dde7c5349c 100644 --- a/ui/packages/consul-ui/app/components/data-loader/chart.xstate.js +++ b/ui/packages/consul-ui/app/components/data-loader/chart.xstate.js @@ -17,9 +17,15 @@ export default { target: 'loading', }, ], + INVALIDATE: [ + { + target: 'invalidating', + }, + ], }, states: { load: {}, + invalidating: {}, loading: { on: { SUCCESS: { diff --git a/ui/packages/consul-ui/app/components/data-loader/index.hbs b/ui/packages/consul-ui/app/components/data-loader/index.hbs index b62b7c3c38..b29d59a584 100644 --- a/ui/packages/consul-ui/app/components/data-loader/index.hbs +++ b/ui/packages/consul-ui/app/components/data-loader/index.hbs @@ -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"))}} diff --git a/ui/packages/consul-ui/app/components/data-loader/index.js b/ui/packages/consul-ui/app/components/data-loader/index.js index 19967a12ac..0039ba3d49 100644 --- a/ui/packages/consul-ui/app/components/data-loader/index.js +++ b/ui/packages/consul-ui/app/components/data-loader/index.js @@ -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'; }, diff --git a/ui/packages/consul-ui/app/components/data-writer/index.hbs b/ui/packages/consul-ui/app/components/data-writer/index.hbs index 048b775e08..53af6c8565 100644 --- a/ui/packages/consul-ui/app/components/data-writer/index.hbs +++ b/ui/packages/consul-ui/app/components/data-writer/index.hbs @@ -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 diff --git a/ui/packages/consul-ui/app/components/definition-table/layout.scss b/ui/packages/consul-ui/app/components/definition-table/layout.scss index b4ffaebc51..50762c223c 100644 --- a/ui/packages/consul-ui/app/components/definition-table/layout.scss +++ b/ui/packages/consul-ui/app/components/definition-table/layout.scss @@ -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 > * { diff --git a/ui/packages/consul-ui/app/components/list-collection/index.hbs b/ui/packages/consul-ui/app/components/list-collection/index.hbs index a3eb31252a..178144444e 100644 --- a/ui/packages/consul-ui/app/components/list-collection/index.hbs +++ b/ui/packages/consul-ui/app/components/list-collection/index.hbs @@ -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> diff --git a/ui/packages/consul-ui/app/components/list-row/layout.scss b/ui/packages/consul-ui/app/components/list-row/layout.scss index 8440596fa9..e14d32045b 100644 --- a/ui/packages/consul-ui/app/components/list-row/layout.scss +++ b/ui/packages/consul-ui/app/components/list-row/layout.scss @@ -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 { diff --git a/ui/packages/consul-ui/app/components/list-row/skin.scss b/ui/packages/consul-ui/app/components/list-row/skin.scss index 0aacf59bf1..654e6e8947 100644 --- a/ui/packages/consul-ui/app/components/list-row/skin.scss +++ b/ui/packages/consul-ui/app/components/list-row/skin.scss @@ -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; } diff --git a/ui/packages/consul-ui/app/routes/dc/nodes/show/sessions.js b/ui/packages/consul-ui/app/routes/dc/nodes/show/sessions.js deleted file mode 100644 index ed951db9fd..0000000000 --- a/ui/packages/consul-ui/app/routes/dc/nodes/show/sessions.js +++ /dev/null @@ -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'); - } -} diff --git a/ui/packages/consul-ui/app/serializers/kv.js b/ui/packages/consul-ui/app/serializers/kv.js index 82436601de..b7dd2eaf91 100644 --- a/ui/packages/consul-ui/app/serializers/kv.js +++ b/ui/packages/consul-ui/app/serializers/kv.js @@ -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 ); } diff --git a/ui/packages/consul-ui/app/serializers/session.js b/ui/packages/consul-ui/app/serializers/session.js index 119619cea6..b37fe6ff2b 100644 --- a/ui/packages/consul-ui/app/serializers/session.js +++ b/ui/packages/consul-ui/app/serializers/session.js @@ -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 ); } diff --git a/ui/packages/consul-ui/app/styles/layout.scss b/ui/packages/consul-ui/app/styles/layout.scss index 6c689ec61f..1fe6db81fa 100644 --- a/ui/packages/consul-ui/app/styles/layout.scss +++ b/ui/packages/consul-ui/app/styles/layout.scss @@ -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; diff --git a/ui/packages/consul-ui/app/styles/routes/dc/kv/index.scss b/ui/packages/consul-ui/app/styles/routes/dc/kv/index.scss index e55f58ebdf..47f238569a 100644 --- a/ui/packages/consul-ui/app/styles/routes/dc/kv/index.scss +++ b/ui/packages/consul-ui/app/styles/routes/dc/kv/index.scss @@ -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; +} diff --git a/ui/packages/consul-ui/app/styles/routes/dc/nodes/index.scss b/ui/packages/consul-ui/app/styles/routes/dc/nodes/index.scss index 09c09f9f53..aa433dc904 100644 --- a/ui/packages/consul-ui/app/styles/routes/dc/nodes/index.scss +++ b/ui/packages/consul-ui/app/styles/routes/dc/nodes/index.scss @@ -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; +} diff --git a/ui/packages/consul-ui/app/styles/typography.scss b/ui/packages/consul-ui/app/styles/typography.scss index c3e5a7420c..2e9bd6370f 100644 --- a/ui/packages/consul-ui/app/styles/typography.scss +++ b/ui/packages/consul-ui/app/styles/typography.scss @@ -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 { diff --git a/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs b/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs index a375e575f0..b84750d099 100644 --- a/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs +++ b/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs @@ -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> diff --git a/ui/packages/consul-ui/app/templates/dc/nodes/show/sessions.hbs b/ui/packages/consul-ui/app/templates/dc/nodes/show/sessions.hbs index 72dcb360cb..b868871f78 100644 --- a/ui/packages/consul-ui/app/templates/dc/nodes/show/sessions.hbs +++ b/ui/packages/consul-ui/app/templates/dc/nodes/show/sessions.hbs @@ -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> diff --git a/ui/packages/consul-ui/mock-api/v1/session/info/_ b/ui/packages/consul-ui/mock-api/v1/session/info/_ index 77a1f99e47..99effb6be1 100644 --- a/ui/packages/consul-ui/mock-api/v1/session/info/_ +++ b/ui/packages/consul-ui/mock-api/v1/session/info/_ @@ -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": [ diff --git a/ui/packages/consul-ui/tests/acceptance/dc/kvs/sessions/invalidate.feature b/ui/packages/consul-ui/tests/acceptance/dc/kvs/sessions/invalidate.feature index 1b3672dc00..6959dba8b7 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/kvs/sessions/invalidate.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/kvs/sessions/invalidate.feature @@ -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