diff --git a/ui/packages/consul-ui/app/components/consul/peer/address/list/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.hbs index aabada41ad1..a9a90b2e09a 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/address/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.hbs @@ -3,28 +3,29 @@ SPDX-License-Identifier: BUSL-1.1 }} - - {{#if p.data.height}} -
- -
  • -
    {{address}}
    - + + <:toolbar> + {{yield to="toolbar"}} + + <:row as |item B|> + + + {{item}} + -
  • -
    -
    - {{/if}} -
    \ No newline at end of file + + + + + diff --git a/ui/packages/consul-ui/app/components/consul/peer/address/list/index.js b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.js new file mode 100644 index 00000000000..9f51aa8d1f8 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.js @@ -0,0 +1,21 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; + +// Single column, not sortable (per Figma — no sort icon on "Address"; server +// addresses have no inherent order worth exposing). +const COLUMNS = [{ label: 'Address', width: '100%' }]; + +/** + * Consul::Peer::Address::List + * + * Server-addresses-tab specific configuration for the generic + * Consul::DataTable. Each row is the plain address string plus a copy-to- + * clipboard action; there is no per-row navigation. + */ +export default class ConsulPeerAddressList extends Component { + columns = COLUMNS; +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/address/list/index.scss b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.scss index 18935e6b7c2..3dae812910b 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/address/list/index.scss +++ b/ui/packages/consul-ui/app/components/consul/peer/address/list/index.scss @@ -3,6 +3,14 @@ * SPDX-License-Identifier: BUSL-1.1 */ -.border-bottom-primary { - border-bottom: 1px solid var(--token-color-border-primary); +/* Gap between the tab nav and the table card — matches Figma (24px) and + Consul::Peer::Exported::List's equivalent rule. */ +.consul-peer-address-list { + margin-top: 24px; +} + +.consul-peer-address-list__address { + display: inline-flex; + align-items: center; + gap: 4px; } diff --git a/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.hbs new file mode 100644 index 00000000000..f621390a884 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.hbs @@ -0,0 +1,15 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +{{! Server-addresses-tab toolbar: search only, per Figma — no filter groups + and no sort control (the "Address" column isn't sortable, see + Consul::Peer::Address::List). }} + diff --git a/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.scss b/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.scss new file mode 100644 index 00000000000..ffc173ee036 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/address/toolbar/index.scss @@ -0,0 +1,23 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* The toolbar renders inside .consul-data-table__card, above the table (see + Consul::Peer::Address::List). Give it the same faint-surface background, + border and squared bottom corners as Consul::Peer::Toolbar, so the whole + toolbar+table reads as one card. */ +.consul-peer-address-toolbar { + padding: 8px; + background-color: var(--token-color-surface-faint); + border: 1px solid var(--token-color-border-faint); + border-bottom: none; + border-radius: 6px 6px 0 0; +} + +/* Separator between the controls row (Search) and the applied-filters area + ("No filters applied"). */ +.consul-peer-address-toolbar .hds-filter-bar__actions { + border-bottom: 1px solid var(--token-color-border-faint); + padding-bottom: 8px; +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.hbs new file mode 100644 index 00000000000..652204b83d3 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.hbs @@ -0,0 +1,35 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +
    + + {{! Passed straight through from the caller so the toolbar renders inside + .consul-data-table__card, matching Consul::Peer::List's pattern. }} + <:toolbar> + {{yield to="toolbar"}} + + <:row as |item B|> + + {{item.Name}} + + + +
    diff --git a/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.js b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.js new file mode 100644 index 00000000000..fbee840fb58 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.js @@ -0,0 +1,29 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; + +// Single sortable column, matching Figma (search-only toolbar plus +// click-to-sort on the "Service name" header; no separate quick-sort dropdown +// or per-row actions). +const COLUMNS = [ + { + label: 'Service name', + sortKey: 'Name', + sortValue: (item) => (item.Name || '').toLowerCase(), + width: '100%', + }, +]; + +/** + * Consul::Peer::Exported::List + * + * Peer-exported-services-tab specific configuration for the generic + * Consul::DataTable. Rows link through to the service's show page (in the + * peer's own partition/namespace when set); there are no per-row actions. + */ +export default class ConsulPeerExportedList extends Component { + columns = COLUMNS; +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.scss b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.scss new file mode 100644 index 00000000000..8e991ad4c90 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/exported/list/index.scss @@ -0,0 +1,9 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* Gap between the tab nav and the table card — matches Figma (24px). */ +.consul-peer-exported-list { + margin-top: 24px; +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.hbs new file mode 100644 index 00000000000..9b0004562d9 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.hbs @@ -0,0 +1,15 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +{{! Exported-services-tab toolbar: search only, per Figma — no filter groups + and no quick-sort dropdown (sorting is the "Service name" column's own + click-to-sort, see Consul::Peer::Exported::List). }} + diff --git a/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.scss b/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.scss new file mode 100644 index 00000000000..0db3c6c44c9 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/exported/toolbar/index.scss @@ -0,0 +1,23 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* The toolbar renders inside .consul-data-table__card, above the table (see + Consul::Peer::Exported::List). Give it the same faint-surface background, + border and squared bottom corners as Consul::Peer::Toolbar, so the whole + toolbar+table reads as one card. */ +.consul-peer-exported-toolbar { + padding: 8px; + background-color: var(--token-color-surface-faint); + border: 1px solid var(--token-color-border-faint); + border-bottom: none; + border-radius: 6px 6px 0 0; +} + +/* Separator between the controls row (Search) and the applied-filters area + ("No filters applied"). */ +.consul-peer-exported-toolbar .hds-filter-bar__actions { + border-bottom: 1px solid var(--token-color-border-faint); + padding-bottom: 8px; +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.hbs new file mode 100644 index 00000000000..41888ae1e03 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.hbs @@ -0,0 +1,43 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + +{{#if this.confirming}} + + Discard unsaved changes? + +

    You'll lose the information you've entered on this page.

    +
    + + + + + + +
    +{{/if}} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.js b/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.js new file mode 100644 index 00000000000..0c57f2e7575 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/form/cancel/index.js @@ -0,0 +1,40 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +// Cancel button shared by the peer create form's tabs. When the form has +// unsaved input, clicking it asks for confirmation before discarding it and +// invoking `@onconfirm` (navigating back to the peers list); an empty form +// can be left without asking. +export default class ConsulPeerFormCancelComponent extends Component { + @tracked confirming = false; + + get dirty() { + return Boolean(this.args.dirty); + } + + @action + click() { + if (this.dirty) { + this.confirming = true; + } else { + this.args.onconfirm(); + } + } + + @action + confirm() { + this.confirming = false; + this.args.onconfirm(); + } + + @action + keepEditing() { + this.confirming = false; + } +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/generate/actions/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/generate/actions/index.hbs index 1c16d1d533d..16a3f6c47eb 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/generate/actions/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/generate/actions/index.hbs @@ -3,10 +3,15 @@ SPDX-License-Identifier: BUSL-1.1 }} - \ No newline at end of file + + + + \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/generate/chart.xstate.js b/ui/packages/consul-ui/app/components/consul/peer/form/generate/chart.xstate.js index 646a3421d59..e8fe134bf8b 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/generate/chart.xstate.js +++ b/ui/packages/consul-ui/app/components/consul/peer/form/generate/chart.xstate.js @@ -31,6 +31,12 @@ export default { }, }, }, - error: {}, + error: { + on: { + LOAD: { + target: 'loading', + }, + }, + }, }, }; diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/generate/fieldsets/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/generate/fieldsets/index.hbs index d17bc34a727..a780b4b14dc 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/generate/fieldsets/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/generate/fieldsets/index.hbs @@ -27,14 +27,22 @@ as |Name| }}
    - + + {{t "components.consul.peer.generate.name"}} + {{Name.help}} + {{#if (get fsm.state.context.errors "Name")}} + {{get (get fsm.state.context.errors "Name") "message"}} + {{/if}} + {{yield (hash valid=(not (state-matches fsm.state "error")))}}
    @@ -45,14 +53,18 @@ as |ServerExternalAddresses| }}
    - + + {{t "components.consul.peer.generate.addresses"}} + {{ServerExternalAddresses.help}} + {{yield (hash valid=(not (state-matches fsm.state "error")))}}
    diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/generate/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/generate/index.hbs index cc5888372d5..584612337b3 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/generate/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/generate/index.hbs @@ -16,24 +16,47 @@ {{#let (dom-guid) as |id reset|}}
    - - - - Error - {{fsm.state.context.error.message}} - - - {{yield - (hash - Fieldsets=(component - "consul/peer/form/generate/fieldsets" item=@item + + + Error + {{fsm.state.context.error.message}} + + + + {{! Regenerating an existing peer's token has no name/address fields + to fill in — it jumps straight to loading, so there's nothing to + keep showing. Creating a new peer does, and per Figma those + fields stay visible (filled in, behind the "Copy token" modal's + backdrop) rather than being swapped out once the token's ready — + so, unlike before, these aren't gated to only the idle/error + states. }} + {{yield + (hash + success=(state-matches fsm.state "success") + item=@item + Fieldsets=(if + (not @regenerate) + (component "consul/peer/form/generate/fieldsets" item=@item) + ) + Actions=(if + (not (or @regenerate (state-matches fsm.state "success"))) + (component + "consul/peer/form/generate/actions" + item=@item + id=id + onCancel=@onCancel ) - Actions=(component - "consul/peer/form/generate/actions" item=@item id=id + ) + TokenFieldsets=(if + (state-matches fsm.state "success") + (component + "consul/peer/form/token/fieldsets" + item=@item + token=fsm.state.context.PeeringToken ) ) - }} - + ) + }} - - {{yield - (hash - Fieldsets=(component - "consul/peer/form/token/fieldsets" - item=@item - token=fsm.state.context.PeeringToken - regenerate=@regenerate - onclick=(queue (set @item "Name" "") (fn fsm.dispatch "RESET")) - ) - Actions=(component - "consul/peer/form/token/actions" - token=fsm.state.context.PeeringToken - item=@item - id=id - ) - ) - }} - -
    {{/let}} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/index.hbs index 031ffead3cc..6cea8ac0638 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/index.hbs @@ -9,21 +9,29 @@ as |fsm| > - + {{! Tab order (Generate token, Establish peering) must match + consul/peer/form/index.js's TAB_EVENTS. Hds::Tabs requires one Panel + per Tab, but its panels stay mounted (just `hidden`) rather than + being torn down on switch, which would leave both tabs' same-named + form fields in the DOM at once. So Hds::Tabs only drives the tab + buttons here (its Panels are left empty) — the actual tab content + below is still gated by fsm.State, exactly as before, so only the + active tab is ever mounted. }} + + + {{t "components.consul.peer.form.generate-label"}} + + + {{t "components.consul.peer.form.establish-label"}} + + + + + @@ -32,7 +40,11 @@ as |source| > {{yield - (hash Form=(component "consul/peer/form/generate" item=source.data)) + (hash + Form=(component + "consul/peer/form/generate" item=source.data onCancel=@onCancel + ) + ) }} @@ -45,7 +57,14 @@ as |source| > {{yield - (hash Form=(component "consul/peer/form/initiate" item=source.data)) + (hash + Form=(component + "consul/peer/form/initiate" + item=source.data + onCancel=@onCancel + onsubmit=@onSubmit + ) + ) }} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/index.js b/ui/packages/consul-ui/app/components/consul/peer/form/index.js new file mode 100644 index 00000000000..8a16068c227 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/peer/form/index.js @@ -0,0 +1,17 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { action } from '@ember/object'; + +// Order matches the Hds::Tabs::Tab order rendered in the template below. +const TAB_EVENTS = ['GENERATE', 'INITIATE']; + +export default class ConsulPeerFormComponent extends Component { + @action + onClickTab(dispatch, event, tabIndex) { + dispatch(TAB_EVENTS[tabIndex]); + } +} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/index.scss b/ui/packages/consul-ui/app/components/consul/peer/form/index.scss index df095b98bb5..ef918d7017d 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/index.scss +++ b/ui/packages/consul-ui/app/components/consul/peer/form/index.scss @@ -5,16 +5,69 @@ .consul-peer-form { & { - width: 416px; + /* Fills the page's full content column, instead of the old 416px modal + width this form used before it moved out of a dialog. */ + width: 100%; + /* Tabs -> form content gap, per Figma. */ + display: flex; + flex-direction: column; + gap: 24px; } nav { margin-bottom: 20px; } } +.consul-peer-form-generate, +.consul-peer-form-initiate { + /* Fields -> footer (Cancel/submit) gap, per Figma's Stepper::List -> + Footer spacing. The error alert (when present) shares this gap too. */ + form { + display: flex; + flex-direction: column; + gap: 32px; + } +} + +.consul-peer-form-generate-fieldsets { + /* Name / Server address(es) are two separate
    s (each field has + its own validation state machine); this is the gap between them. */ + display: flex; + flex-direction: column; + gap: 24px; +} + +.consul-peer-form-initiate-fieldsets fieldset { + /* Name / Token share one
    , so the gap goes on its contents. */ + display: flex; + flex-direction: column; + gap: 24px; +} + +.consul-peer-form-generate-fieldsets, +.consul-peer-form-initiate-fieldsets { + /* Figma's field labels carry no "Required"/"(Optional)" badge (the + legend that would normally explain them is hidden in the design too), + so hide the ones Hds::Form::TextInput::Field renders by default. The + @isRequired/@isOptional args stay put so the underlying + and aria semantics are unaffected. */ + .hds-form-indicator { + display: none; + } +} + +.consul-peer-form-generate, +.consul-peer-form-initiate { + /* Cancel / Generate token / Add peer sit right-aligned, per Figma's + Footer (Hds::ButtonSet defaults to flex-start). */ + .hds-button-set { + justify-content: flex-end; + } +} + .consul-peer-form-generate { & { - width: 416px; + width: 100%; min-height: 200px; } ol { diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/actions/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/actions/index.hbs index f5f4c4864f4..9b0f9224cf2 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/actions/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/actions/index.hbs @@ -3,9 +3,15 @@ SPDX-License-Identifier: BUSL-1.1 }} - \ No newline at end of file + + + + \ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/fieldsets/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/fieldsets/index.hbs index 28228a8c8d0..431c41dfdec 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/fieldsets/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/fieldsets/index.hbs @@ -26,28 +26,41 @@ ) as |Name PeeringToken|}} -

    - Enter a token generated in the desired peer. -

    - - + + Name of peer + {{Name.help}} + {{#if (get fsm.state.context.errors "Name")}} + {{get (get fsm.state.context.errors "Name") "message"}} + {{/if}} + + + + Token + {{PeeringToken.help}} + + {{yield (hash valid=(not (state-matches fsm.state 'error')) )}} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/index.hbs index 2dcd08e8f8e..bb3200a418d 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/initiate/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/initiate/index.hbs @@ -28,11 +28,15 @@
    {{yield (hash + item=@item Fieldsets=(component "consul/peer/form/initiate/fieldsets" item=@item ) Actions=(component - "consul/peer/form/initiate/actions" item=@item id=id + "consul/peer/form/initiate/actions" + item=@item + id=id + onCancel=@onCancel ) ) }} diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/token/actions/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/token/actions/index.hbs deleted file mode 100644 index 684fdc4562f..00000000000 --- a/ui/packages/consul-ui/app/components/consul/peer/form/token/actions/index.hbs +++ /dev/null @@ -1,20 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -
    - - - - -
    diff --git a/ui/packages/consul-ui/app/components/consul/peer/form/token/fieldsets/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/form/token/fieldsets/index.hbs index ca4b98d1687..8e0be3baf91 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/form/token/fieldsets/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/form/token/fieldsets/index.hbs @@ -3,40 +3,30 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{#if @regenerate}} -

    - Token regenerated! Here’s what’s next: -

    -{{else}} -

    - Token generated! Here’s what’s next: -

    -{{/if}} -
      -
    1. - Copy the token
      - This token cannot be viewed again after creation. -
      - -
    2. -
    3. - Switch to the peer
      - Someone on your team should log into the Datacenter (Community) or Admin Partition (Enterprise) that you want this one to connect with. -
    4. -
    5. - Initiate the peering
      - From there, initiate a new peering, name it, and paste this token in. -
    6. -
    -{{#if (not @regenerate)}} - -{{/if}} +{{! Copy token step: presented as an Hds::Modal by the caller (the peer + create page for a fresh token, the Regenerate token modal for an + existing one) so the still-filled-in form stays visible behind it, per + Figma's "Copy token" overlay. }} +
      +
    1. + Copy the token
      + This token cannot be viewed again after creation. +
      + +
    2. +
    3. + Switch to the peer
      + Someone on your team should log into the Datacenter (Community) or Admin Partition (Enterprise) that you want this one to connect with. +
    4. +
    5. + Initiate the peering
      + From there, initiate a new peering, name it, and paste this token in. +
    6. +
    diff --git a/ui/packages/consul-ui/app/components/consul/peer/index.scss b/ui/packages/consul-ui/app/components/consul/peer/index.scss index 76be0a8b673..03641aca999 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/index.scss +++ b/ui/packages/consul-ui/app/components/consul/peer/index.scss @@ -6,7 +6,6 @@ @import './components'; @import './list'; -@import './search-bar'; @import './form'; @import './address/list'; diff --git a/ui/packages/consul-ui/app/components/consul/peer/list/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/list/index.hbs index 3be9902c1e4..5ae485fabaf 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/list/index.hbs @@ -4,13 +4,23 @@ }}
    + {{! Default/initial ordering comes from the toolbar's Sort dropdown (data-layer + sort via DataCollection, see templates/dc/peers/index.hbs); no + @initialSortBy here so that upstream order isn't immediately overridden. + Column headers remain independently clickable for in-table sorting. }} + {{! Passed straight through from the caller so the toolbar renders inside + .consul-data-table__card, forming one card with the table below it + (matching Consul::Partition::List::Table's pattern) instead of + floating above it with no background of its own. }} + <:toolbar> + {{yield to="toolbar"}} + <:row as |item B|> {{! ---- Peer name ---- }} @@ -56,8 +66,10 @@ {{! ---- Actions ---- }} + {{! A peer already being deleted has nothing left to act on (per Figma, + the Actions cell is blank for a Deleting row). }} - {{#if (can "delete peer" item=item)}} + {{#if (and (can "delete peer" item=item) (not-eq item.State "DELETING"))}} -``` - -## See - -- [Template Source Code](./index.hbs) - ---- diff --git a/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.hbs deleted file mode 100644 index c6c93789d5b..00000000000 --- a/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.hbs +++ /dev/null @@ -1,142 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - <:status as |search|> - -{{#let - - (t (concat "components.consul.peer.search-bar." search.status.key) - default=(array - (concat "common.search." search.status.key) - (concat "common.consul." search.status.key) - ) - ) - - (t (concat "components.consul.peer.search-bar." search.status.value) - default=(array - (concat "common.search." search.status.value) - (concat "common.consul." search.status.value) - (concat "common.brand." search.status.value) - ) - ) - -as |key value|}} - -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -{{/let}} - - - <:search as |search|> - - - <:selected> - - {{t "common.search.searchproperty"}} - - - <:options as |components|> - {{#let components.Optgroup components.Option as |Optgroup Option|}} - {{#each @filter.searchproperty.default as |prop|}} - - {{/each}} - {{/let}} - - - - - <:filter as |search|> - - <:selected> - - {{t "components.consul.peer.search-bar.state.name"}} - - - <:options as |components|> - {{#let components.Optgroup components.Option as |Optgroup Option|}} - {{#each - (get - (require '/models/peer' - export='schema' - from='/components/consul/peer/search-bar' - ) - 'State.allowedValues' - ) as |upperState|}} - {{#let - (string-to-lower-case upperState) - as |state|}} - - {{/let}} - {{/each}} - {{/let}} - - - - <:sort as |search|> - - <:selected> - - {{#let (from-entries (array - (array "Name:asc" (t "common.sort.alpha.asc")) - (array "Name:desc" (t "common.sort.alpha.desc")) - (array "State:asc" (t "components.consul.peer.search-bar.sort.state.asc")) - (array "State:desc" (t "components.consul.peer.search-bar.sort.state.desc")) - )) - as |selectable| - }} - {{get selectable @sort.value}} - {{/let}} - - - <:options as |components|> - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - - - - - {{/let}} - - - -
    diff --git a/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.scss b/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.scss deleted file mode 100644 index c68b9cdba10..00000000000 --- a/ui/packages/consul-ui/app/components/consul/peer/search-bar/index.scss +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright IBM Corp. 2024, 2026 - * SPDX-License-Identifier: BUSL-1.1 - */ - -.consul-peer-search-bar { - li button span { - @extend %pill-500; - } - .value-pending span { - @extend %pill-pending; - } - .value-establishing span { - @extend %pill-establishing; - } - .value-active span { - @extend %pill-active; - } - .value-failing span { - @extend %pill-failing; - } - .value-terminated span { - @extend %pill-terminated; - } - .value-deleting span { - @extend %pill-deleting; - } -} - diff --git a/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.hbs b/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.hbs index 316d1b6d187..c4ec3416917 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.hbs @@ -3,8 +3,11 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{! Search/filter wiring lives in Consul::ListToolbar; sorting lives on the }} -{{! table's column headers, so this only supplies the Status filter group. }} +{{! Search/filter wiring lives in Consul::ListToolbar, which supplies the }} +{{! Status filter group. Sort is also independently available per-column }} +{{! via the table's clickable headers; the dropdown below just sets the }} +{{! initial/default order (matching the Figma "A to Z" toolbar control) }} +{{! via the data-layer sort passed down from templates/dc/peers/index.hbs. }} +> + <:quickFilters> + {{#if @sort}} + + + {{t "common.sort.alpha.asc"}} + {{t "common.sort.alpha.desc"}} + Status + + {{/if}} + + diff --git a/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.scss b/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.scss index 23bcb3905c8..fabbdbc6a5a 100644 --- a/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.scss +++ b/ui/packages/consul-ui/app/components/consul/peer/toolbar/index.scss @@ -3,6 +3,35 @@ * SPDX-License-Identifier: BUSL-1.1 */ +/* The toolbar renders inside .consul-data-table__card, above the table (see + Consul::Peer::List). Give it the same faint-surface background, border and + squared bottom corners that the partitions / intentions / nodes / services + toolbars use, so the whole toolbar+table reads as one card. */ +.consul-peer-toolbar { + padding: 8px; + background-color: var(--token-color-surface-faint); + border: 1px solid var(--token-color-border-faint); + border-bottom: none; + border-radius: 6px 6px 0 0; + + /* Sort dropdown sits at the right end of the quick-filters row. */ + .consul-peer-toolbar__sort { + flex: 0 0 auto; + margin-left: auto; + + .hds-dropdown-toggle-button { + min-height: 2.25rem; + } + } +} + +/* Separator between the controls row (Filters / Search / Search across / + Sort) and the applied-filters area ("No filters applied" / active tags). */ +.consul-peer-toolbar .hds-filter-bar__actions { + border-bottom: 1px solid var(--token-color-border-faint); + padding-bottom: 8px; +} + /* The peers index uses the HDS Filter Bar for its toolbar, so drop the default header underline above it to avoid a doubled-up separator. */ html[data-route='dc.peers.index'] .app-view > header .title { diff --git a/ui/packages/consul-ui/app/components/peerings/badge/icon/index.hbs b/ui/packages/consul-ui/app/components/peerings/badge/icon/index.hbs deleted file mode 100644 index 8c7b43a4e5a..00000000000 --- a/ui/packages/consul-ui/app/components/peerings/badge/icon/index.hbs +++ /dev/null @@ -1,95 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -{{#if (or (eq @state "PENDING") (eq @state "ESTABLISHING"))}} - -{{/if}} -{{#if (eq @state "ACTIVE")}} - -{{/if}} -{{#if (eq @state "FAILING")}} - -{{/if}} - -{{#if (eq @state "TERMINATED")}} - -{{/if}} -{{#if (eq @state "UNDEFINED")}} - -{{/if}} - -{{#if (eq @state "DELETING")}} - -{{/if}} diff --git a/ui/packages/consul-ui/app/components/peerings/badge/index.hbs b/ui/packages/consul-ui/app/components/peerings/badge/index.hbs index 2bbf9c06ba2..ed74dcd78c5 100644 --- a/ui/packages/consul-ui/app/components/peerings/badge/index.hbs +++ b/ui/packages/consul-ui/app/components/peerings/badge/index.hbs @@ -4,8 +4,14 @@ }} {{#if @peering.State}} -
    - - {{capitalize (lowercase @peering.State)}} -
    + + + {{/if}} diff --git a/ui/packages/consul-ui/app/components/peerings/badge/index.js b/ui/packages/consul-ui/app/components/peerings/badge/index.js index dd28dbf4762..51b6fa83de9 100644 --- a/ui/packages/consul-ui/app/components/peerings/badge/index.js +++ b/ui/packages/consul-ui/app/components/peerings/badge/index.js @@ -5,27 +5,47 @@ import Component from '@glimmer/component'; +// Icon/color per the Peers list Figma design (node 3570:74800). PENDING and +// ESTABLISHING share the same treatment as the legacy icon set did. TERMINATED +// calls for a dark/high-contrast badge that isn't one of Hds::Badge's stock +// colors, so it's approximated with a `dark` flag the template turns into a +// scoped CSS override on top of `color=neutral` (see index.scss). const BADGE_LOOKUP = { ACTIVE: { + color: 'success', + icon: 'check-circle-fill', tooltip: 'This peer connection is currently active.', }, PENDING: { + color: 'warning', + icon: 'loading-static', tooltip: 'This peering connection has not been established yet.', }, ESTABLISHING: { + color: 'warning', + icon: 'loading-static', tooltip: 'This peering connection is in the process of being established.', }, FAILING: { + color: 'critical', + icon: 'x-circle-fill', tooltip: 'This peering connection has some intermittent errors (usually network related). It will continue to retry. ', }, DELETING: { + color: 'neutral', + icon: 'loading-static', tooltip: 'This peer is in the process of being deleted.', }, TERMINATED: { + color: 'neutral', + icon: 'x-square-fill', + dark: true, tooltip: 'Someone in the other peer may have deleted this peering connection.', }, UNDEFINED: { + color: 'neutral', + icon: 'help', tooltip: 'The state of this peering connection is undefined.', }, }; @@ -35,10 +55,16 @@ export default class PeeringsBadge extends Component { peering: { State }, } = this.args; - return BADGE_LOOKUP[State]; + // Fall back to the UNDEFINED treatment for any state we don't recognize, + // rather than throwing — the visible label still reflects the real value. + return BADGE_LOOKUP[State] || BADGE_LOOKUP.UNDEFINED; } get tooltip() { return this.styles.tooltip; } + + get isDark() { + return Boolean(this.styles.dark); + } } diff --git a/ui/packages/consul-ui/app/components/peerings/badge/index.scss b/ui/packages/consul-ui/app/components/peerings/badge/index.scss index 401cdc8bf7b..c2c49a910a5 100644 --- a/ui/packages/consul-ui/app/components/peerings/badge/index.scss +++ b/ui/packages/consul-ui/app/components/peerings/badge/index.scss @@ -3,44 +3,13 @@ * SPDX-License-Identifier: BUSL-1.1 */ -.peerings-badge { - display: flex; - align-items: center; - justify-content: center; - padding: 2px 8px; - border-radius: 5px; - gap: 4px; - - &.active { - background: var(--token-color-surface-success); - color: var(--token-color-foreground-success-on-surface); - } - &.pending { - background: var(--token-color-consul-surface); - color: var(--token-color-consul-foreground); - } - &.establishing { - background: var(--token-color-surface-action); - color: var(--token-color-foreground-action); - } - &.failing { - background: var(--token-color-surface-critical); - color: var(--token-color-foreground-critical-on-surface); - } - &.deleting { - background: var(--token-color-surface-warning); - color: var(--token-color-foreground-warning-on-surface); - } - &.terminated { - background: var(--token-color-surface-interactive-active); - color: var(--token-color-foreground-primary); - } - &.undefined { - background: var(--token-color-surface-interactive-active); - color: var(--token-color-foreground-primary); - } - - .peerings-badge__text { - @extend %body-100-medium; - } +/* Hds::Badge has no stock "dark"/high-contrast color option, but the Peers + Figma design (node 3570:74800) calls for one on the Terminated state. + Approximate it here on top of `@color="neutral"` rather than adding a + bespoke badge implementation for a single state. */ +.peerings-badge--dark { + background-color: var(--token-color-palette-neutral-500); + /* .hds-badge__icon's flight-icon SVG uses fill="currentColor", so setting + `color` here recolors the icon along with the text. */ + color: var(--token-color-foreground-high-contrast); } diff --git a/ui/packages/consul-ui/app/controllers/dc/peers/create.js b/ui/packages/consul-ui/app/controllers/dc/peers/create.js new file mode 100644 index 00000000000..82d06c5edec --- /dev/null +++ b/ui/packages/consul-ui/app/controllers/dc/peers/create.js @@ -0,0 +1,20 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class CreateController extends Controller { + @service router; + + // The Establish peering tab persists the item itself (unlike Generate + // token, which only ever previews one), so its destination depends on + // the item the write actually succeeded with. + @action + onEstablish(item) { + this.router.transitionTo('dc.peers.show', item.Name); + } +} diff --git a/ui/packages/consul-ui/app/controllers/dc/peers/index.js b/ui/packages/consul-ui/app/controllers/dc/peers/index.js index 49372c36286..0047d38aba4 100644 --- a/ui/packages/consul-ui/app/controllers/dc/peers/index.js +++ b/ui/packages/consul-ui/app/controllers/dc/peers/index.js @@ -4,14 +4,5 @@ */ import Controller from '@ember/controller'; -import { inject as service } from '@ember/service'; -export default class DcPeersIndexController extends Controller { - @service router; - - redirectToPeerShow = (modalCloseFn, peerModel) => { - modalCloseFn?.(); - - this.router.transitionTo('dc.peers.show', peerModel.Name); - }; -} +export default class DcPeersIndexController extends Controller {} diff --git a/ui/packages/consul-ui/app/controllers/dc/peers/show/addresses.js b/ui/packages/consul-ui/app/controllers/dc/peers/show/addresses.js new file mode 100644 index 00000000000..09c82b2042c --- /dev/null +++ b/ui/packages/consul-ui/app/controllers/dc/peers/show/addresses.js @@ -0,0 +1,18 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class PeersShowAddressesController extends Controller { + queryParams = ['search']; + + @tracked search = ''; + + @action updateSearch(value) { + this.search = value; + } +} diff --git a/ui/packages/consul-ui/app/models/peer.js b/ui/packages/consul-ui/app/models/peer.js index 0bd7b63b45f..ef9dc6af93b 100644 --- a/ui/packages/consul-ui/app/models/peer.js +++ b/ui/packages/consul-ui/app/models/peer.js @@ -24,6 +24,10 @@ export default class Peer extends Model { @attr('string') ID; @attr('string', { defaultValue: '' }) ServerExternalAddresses; + // only filled in on the "Establish peering" side, while typing the + // token received from the peer that generated it + @attr('string') PeeringToken; + // only the side that establishes will hold this property @attr('string') PeerID; diff --git a/ui/packages/consul-ui/app/routes/dc/peers/create.js b/ui/packages/consul-ui/app/routes/dc/peers/create.js new file mode 100644 index 00000000000..662ac8b87bc --- /dev/null +++ b/ui/packages/consul-ui/app/routes/dc/peers/create.js @@ -0,0 +1,8 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from 'consul-ui/routing/route'; + +export default class CreateRoute extends Route {} diff --git a/ui/packages/consul-ui/app/routes/dc/peers/show/addresses.js b/ui/packages/consul-ui/app/routes/dc/peers/show/addresses.js new file mode 100644 index 00000000000..66e61c084f5 --- /dev/null +++ b/ui/packages/consul-ui/app/routes/dc/peers/show/addresses.js @@ -0,0 +1,14 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from 'consul-ui/routing/route'; + +export default class PeersShowAddressesRoute extends Route { + queryParams = { + search: { + as: 'filter', + }, + }; +} diff --git a/ui/packages/consul-ui/app/search/predicates/peer-address.js b/ui/packages/consul-ui/app/search/predicates/peer-address.js new file mode 100644 index 00000000000..f2109a6bc6c --- /dev/null +++ b/ui/packages/consul-ui/app/search/predicates/peer-address.js @@ -0,0 +1,10 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +// Server addresses (PeerServerAddresses) are plain strings, not records, so +// the only "property" to search on is the address itself. +export default { + Address: (item) => item, +}; diff --git a/ui/packages/consul-ui/app/services/search.js b/ui/packages/consul-ui/app/services/search.js index 79b8630dbd6..cd774c01e57 100644 --- a/ui/packages/consul-ui/app/services/search.js +++ b/ui/packages/consul-ui/app/services/search.js @@ -21,6 +21,7 @@ import policy from 'consul-ui/search/predicates/policy'; import authMethod from 'consul-ui/search/predicates/auth-method'; import nspace from 'consul-ui/search/predicates/nspace'; import peer from 'consul-ui/search/predicates/peer'; +import peerAddress from 'consul-ui/search/predicates/peer-address'; import session from 'consul-ui/search/predicates/session'; const predicates = { @@ -38,6 +39,7 @@ const predicates = { policy: policy, nspace: nspace, peer: peer, + ['peer-address']: peerAddress, session: session, }; diff --git a/ui/packages/consul-ui/app/styles/components.scss b/ui/packages/consul-ui/app/styles/components.scss index 8fb7cc0a175..f00e6e3df8e 100644 --- a/ui/packages/consul-ui/app/styles/components.scss +++ b/ui/packages/consul-ui/app/styles/components.scss @@ -140,6 +140,9 @@ @import 'consul-ui/components/consul/peer'; @import 'consul-ui/components/consul/peer/list'; @import 'consul-ui/components/consul/peer/toolbar'; +@import 'consul-ui/components/consul/peer/exported/list'; +@import 'consul-ui/components/consul/peer/exported/toolbar'; +@import 'consul-ui/components/consul/peer/address/toolbar'; @import 'consul-ui/components/peerings/badge'; @import 'consul-ui/components/consul/node/peer-info'; @import 'consul-ui/components/consul/peer/info'; diff --git a/ui/packages/consul-ui/app/styles/routes.scss b/ui/packages/consul-ui/app/styles/routes.scss index 9252c36c3c3..d6381c56ad9 100644 --- a/ui/packages/consul-ui/app/styles/routes.scss +++ b/ui/packages/consul-ui/app/styles/routes.scss @@ -9,5 +9,6 @@ @import 'routes/dc/kv/index'; @import 'routes/dc/nspaces/index'; @import 'routes/dc/intentions/index'; +@import 'routes/dc/peers/index'; @import 'routes/dc/overview/serverstatus'; @import 'routes/dc/overview/license'; diff --git a/ui/packages/consul-ui/app/styles/routes/dc/peers/index.scss b/ui/packages/consul-ui/app/styles/routes/dc/peers/index.scss new file mode 100644 index 00000000000..5c223c3720c --- /dev/null +++ b/ui/packages/consul-ui/app/styles/routes/dc/peers/index.scss @@ -0,0 +1,45 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* The peer create page's fields (and the Regenerate token modal, which + reuses the same generate form) use Hds::Form::TextInput::Field, so the + global app-view rule that draws a border-bottom under every fieldset + (plus the padding/margin that went with it) shows up as a stray + underline here — spacing between fields instead follows Figma via the + gaps in consul/peer/form/index.scss. */ +html[data-route='dc.peers.create'], +html[data-route='dc.peers.index'] { + .app-view form:not(.filter-bar) fieldset { + border-bottom: none; + padding-bottom: 0; + margin-bottom: 0; + } +} + +/* Figma has no underline below the "Add peer connection" title (the Tabs' + own border reads as the divider instead), so drop the legacy app-view + rule that underlines every page's

    . */ +html[data-route='dc.peers.create'] { + .app-view .title { + border-bottom: none; + } + + /* Every route ending in "create"/"edit" is narrowed app-wide to 1260px + (app/styles/layout.scss), but this page should fill the full content + column like the peers list does — same override dc.kv.create uses. */ + .app-shell__main { + max-width: none; + } +} + +/* Gap between the tab nav and the Imported Services toolbar/table — matches + Figma (24px), same as the Exported Services and Server Addresses tabs + (Consul::Peer::Exported::List / Consul::Peer::Address::List). This tab + reuses the shared Consul::Service::Table/Toolbar (also used outside the + peers show page), so the margin is scoped to this template's own wrapper + rather than added to those shared components. */ +.consul-peer-imported { + margin-top: 24px; +} diff --git a/ui/packages/consul-ui/app/templates/dc/peers/create.hbs b/ui/packages/consul-ui/app/templates/dc/peers/create.hbs new file mode 100644 index 00000000000..0d4e4eccc5e --- /dev/null +++ b/ui/packages/consul-ui/app/templates/dc/peers/create.hbs @@ -0,0 +1,53 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + <:header> +

    + +

    + + <:content> + + + + + {{#if form.Fieldsets}} + + {{/if}} + {{#if form.Actions}} + + {{/if}} + + {{#if form.success}} + {{! The token can't be viewed again once this modal is closed, so + closing it (there's no separate "done" action, per Figma) + takes the user on to the newly-created peer. The filled-in + fields stay visible behind the dimmed backdrop rather than + being replaced by this. }} + + Copy token + + + + + {{/if}} + + + + + +
    +
    diff --git a/ui/packages/consul-ui/app/templates/dc/peers/index.hbs b/ui/packages/consul-ui/app/templates/dc/peers/index.hbs index e223508b5ae..238a6dcf441 100644 --- a/ui/packages/consul-ui/app/templates/dc/peers/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/peers/index.hbs @@ -21,6 +21,10 @@ <:loaded as |loader|> {{#let + (hash + value=(or this.sortBy "Name:asc") + change=(action (mut this.sortBy) value="target.selected") + ) (hash state=(hash value=(if this.state (split this.state ",") undefined) @@ -37,7 +41,7 @@ ) ) loader.data - as |filters items| + as |sort filters items| }} <:header> @@ -45,53 +49,13 @@

    - <:toolbar> - - {{#if (gt items.length 0)}} - - {{/if}} - - <:actions> - - <:header as |api|> - {{did-insert-helper (set this "create" api)}} -

    - Add peer connection -

    - - <:body as |api|> - - {{#if api.opened}} - - - {{did-insert-helper (set this "form" form)}} - - - - {{/if}} - - - <:actions> - - -
    @@ -119,38 +83,32 @@ <:content as |writer|> - - <:header as |modal|> - {{did-insert-helper (set this "regenerate" modal)}} -

    - Regenerate token -

    - - <:body> - {{#if this.item}} + {{#if this.item}} + + Regenerate token + - {{did-insert-helper (set this "regenerateForm" form)}} - + {{#if form.success}} + + {{/if}} - {{/if}} - - <:actions> - - -
    + + + {{/if}} + > + <:toolbar> + {{! Now nested inside collection.Collection (rendered + only while there's at least one matching row), same + as Consul::Partition::List::Table's :toolbar block — + no separate items.length guard needed here. }} + + + diff --git a/ui/packages/consul-ui/app/templates/dc/peers/show/addresses.hbs b/ui/packages/consul-ui/app/templates/dc/peers/show/addresses.hbs index 90e93027464..99397472218 100644 --- a/ui/packages/consul-ui/app/templates/dc/peers/show/addresses.hbs +++ b/ui/packages/consul-ui/app/templates/dc/peers/show/addresses.hbs @@ -4,42 +4,61 @@ }} - {{#if (gt route.model.items.length 0)}} - - {{else}} - - <:header> -

    - {{t "routes.dc.peers.show.addresses.empty.header"}} -

    - - <:body> - {{t "routes.dc.peers.show.addresses.empty.body" htmlSafe=true}} - - <:actions> -
  • - + + + <:toolbar> + -
  • -
  • - -
  • - -
    - {{/if}} -
    \ No newline at end of file + + + + + + <:header> +

    + {{t "routes.dc.peers.show.addresses.empty.header"}} +

    + + <:body> + {{t + "routes.dc.peers.show.addresses.empty.body" + items=route.model.items.length + htmlSafe=true + }} + + <:actions> +
  • + +
  • +
  • + +
  • + +
    +
    +
    + diff --git a/ui/packages/consul-ui/app/templates/dc/peers/show/exported.hbs b/ui/packages/consul-ui/app/templates/dc/peers/show/exported.hbs index 5526d3d48f8..9452abd0ae3 100644 --- a/ui/packages/consul-ui/app/templates/dc/peers/show/exported.hbs +++ b/ui/packages/consul-ui/app/templates/dc/peers/show/exported.hbs @@ -25,113 +25,73 @@ api.data as |partition items| }} - {{#if items.length}} - - {{/if}} - - - {{#if p.data.height}} -
    - {{#if search.data.items.length}} - -
  • - - {{service.Name}} - -
  • -
    - {{else}} - - <:header> -

    - {{t - "routes.dc.peers.show.exported.empty.header" - name=route.model.peer.Name - }} -

    - - <:body> - {{t - "routes.dc.peers.show.exported.empty.body" - items=items.length - name=route.model.peer.Name - htmlSafe=true - }} - - <:actions> -
  • - -
  • -
  • - -
  • - -
    - {{/if}} -
    - {{/if}} -
    - -
    + + + <:toolbar> + + + + + + + <:header> +

    + {{t + "routes.dc.peers.show.exported.empty.header" + name=route.model.peer.Name + }} +

    + + <:body> + {{t + "routes.dc.peers.show.exported.empty.body" + items=items.length + name=route.model.peer.Name + htmlSafe=true + }} + + <:actions> +
  • + +
  • +
  • + +
  • + +
    +
    + {{/let}} diff --git a/ui/packages/consul-ui/app/templates/dc/peers/show/imported.hbs b/ui/packages/consul-ui/app/templates/dc/peers/show/imported.hbs index de36820e342..994675c51cc 100644 --- a/ui/packages/consul-ui/app/templates/dc/peers/show/imported.hbs +++ b/ui/packages/consul-ui/app/templates/dc/peers/show/imported.hbs @@ -56,82 +56,84 @@ as |sort filters items partition nspace| }} - {{#if (gt items.length 0)}} - {{#let (collection items) as |items|}} - - {{/let}} - {{/if}} - - - - - - - <:header> -

    +
    + {{#if (gt items.length 0)}} + {{#let (collection items) as |items|}} + + {{/let}} + {{/if}} + + + + + + + <:header> +

    + {{t + "routes.dc.peers.show.imported.empty.header" + name=route.model.peer.Name + }} +

    + + <:body> {{t - "routes.dc.peers.show.imported.empty.header" + "routes.dc.peers.show.imported.empty.body" + items=items.length name=route.model.peer.Name + htmlSafe=true }} -

    - - <:body> - {{t - "routes.dc.peers.show.imported.empty.body" - items=items.length - name=route.model.peer.Name - htmlSafe=true - }} - - <:actions> -
  • - {{! what's the docs for peering?}} - -
  • -
  • - -
  • - -
    -
    -
    + + <:actions> +
  • + {{! what's the docs for peering?}} + +
  • +
  • + +
  • + + + + +
    {{/let}} diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature index 27136139049..74678dafc9b 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature @@ -1,46 +1,87 @@ @setupApplicationTest Feature: dc / peers / create: Peer Create Token - Scenario: + Background: Given 1 datacenter model with the value "dc-1" - And the url "/v1/peering/token" responds with from yaml + + Scenario: Visiting the Add peer connection page directly + When I visit the peers page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/peers + And I click create + Then the url should be /dc-1/peers/create + And the title should be "Add peer connection - Consul" + + Scenario: Generating a token from the Generate token tab + Given the url "/v1/peering/token" responds with from yaml --- body: PeeringToken: an-encoded-token --- - When I visit the peers page for yaml + When I visit the peerCreate page for yaml --- dc: dc-1 --- - Then the url should be /dc-1/peers - And I click create Then I fill in with yaml --- Name: new-peer --- - When I click ".peer-create-modal .modal-dialog-footer button" + And I submit Then a POST request was made to "/v1/peering/token" from yaml --- body: PeerName: new-peer ServerExternalAddresses: [] --- - Then I see the text "an-encoded-token" in ".consul-peer-form-generate code" - When I click ".consul-peer-form-generate button[type=reset]" - And the url "/v1/peering/token" responds with from yaml + Then I see the text "an-encoded-token" in "#copy-token-modal [data-test-peering-token]" + + Scenario: Establishing a peering from the Establish peering tab + Given the url "/v1/peering/establish" responds with from yaml --- body: - PeeringToken: another-encoded-token + Status: 200 --- + When I visit the peerCreate page for yaml + --- + dc: dc-1 + --- + And I click tabs.initiate Then I fill in with yaml --- - Name: another-new-peer - ServerExternalAddresses: "1.1.1.1:123,1.2.3.4:3202" + Name: new-peer + PeeringToken: an-encoded-token --- - When I click ".peer-create-modal .modal-dialog-footer button" - Then a POST request was made to "/v1/peering/token" from yaml + And I submit + Then a POST request was made to "/v1/peering/establish" from yaml --- body: - PeerName: another-new-peer - ServerExternalAddresses: ["1.1.1.1:123","1.2.3.4:3202"] + PeerName: new-peer + PeeringToken: an-encoded-token + --- + Then the url should be /dc-1/peers/new-peer/imported-services + + Scenario: Cancelling with unsaved input asks for confirmation + When I visit the peerCreate page for yaml + --- + dc: dc-1 + --- + Then I fill in with yaml --- - Then I see the text "another-encoded-token" in ".consul-peer-form-generate code" + Name: new-peer + --- + And I click cancel + Then the url should be /dc-1/peers/create + And I click keepEditing + Then the url should be /dc-1/peers/create + When I click cancel + And I click confirmDiscard + Then the url should be /dc-1/peers + + Scenario: Cancelling an empty form does not ask for confirmation + When I visit the peerCreate page for yaml + --- + dc: dc-1 + --- + And I click cancel + Then the url should be /dc-1/peers diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature deleted file mode 100644 index 6388f824a8a..00000000000 --- a/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature +++ /dev/null @@ -1,31 +0,0 @@ -@setupApplicationTest -Feature: dc / peers / establish: Peer Establish Peering - Scenario: - Given 1 datacenter model with the value "dc-1" - And the url "/v1/peering/token" responds with from yaml - --- - body: - PeeringToken: an-encoded-token - --- - When I visit the peers page for yaml - --- - dc: dc-1 - --- - Then the url should be /dc-1/peers - And I click create - When I click "[data-test-tab=tab_establish-peering] button" - Then I fill in with yaml - --- - Name: new-peer - Token: an-encoded-token - --- - When I click ".peer-create-modal .modal-dialog-footer button" - Then a POST request was made to "/v1/peering/establish" from yaml - --- - body: - PeerName: new-peer - PeeringToken: an-encoded-token - --- - And "[data-notification]" has the "hds-toast" class - And "[data-notification]" has the "hds-alert--color-success" class - And the url should be /dc-1/peers/new-peer/imported-services diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature index ce4d2e3f7f0..7298ad950f0 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature @@ -20,7 +20,7 @@ Feature: dc / peers / regenerate: Regenerate Peer Token --- And I click actions on the peers And I click regenerate on the peers - Then I see the text "an-encoded-token" in ".consul-peer-form-generate code" + Then I see the text "an-encoded-token" in "#regenerate-token-modal [data-test-peering-token]" Scenario: Given 1 datacenter model with the value "datacenter" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/sorting.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/sorting.feature new file mode 100644 index 00000000000..81832544713 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/sorting.feature @@ -0,0 +1,32 @@ +@setupApplicationTest +Feature: dc / peers / sorting + Scenario: Sorting Peers from the toolbar dropdown + Given 1 datacenter model with the value "dc-1" + And 3 peer models from yaml + --- + - Name: z-peer + - Name: b-peer + - Name: a-peer + --- + When I visit the peers page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/peers + Then I see 3 peer models + When I click selected on the sort + When I click options.1.button on the sort + Then I see name on the peers vertically like yaml + --- + - z-peer + - b-peer + - a-peer + --- + When I click selected on the sort + When I click options.0.button on the sort + Then I see name on the peers vertically like yaml + --- + - a-peer + - b-peer + - z-peer + --- diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/sorting-steps.js similarity index 100% rename from ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js rename to ui/packages/consul-ui/tests/acceptance/steps/dc/peers/sorting-steps.js diff --git a/ui/packages/consul-ui/tests/pages.js b/ui/packages/consul-ui/tests/pages.js index 76a29b5eb56..1b94d06bf54 100644 --- a/ui/packages/consul-ui/tests/pages.js +++ b/ui/packages/consul-ui/tests/pages.js @@ -81,6 +81,7 @@ import nspaces from 'consul-ui/tests/pages/dc/nspaces/index'; import nspace from 'consul-ui/tests/pages/dc/nspaces/edit'; import peers from 'consul-ui/tests/pages/dc/peers/index'; import peersShow from 'consul-ui/tests/pages/dc/peers/show'; +import peersCreate from 'consul-ui/tests/pages/dc/peers/create'; // utils const deletable = createDeletable(clickable); @@ -235,8 +236,9 @@ export default { nspace: create( nspace(visitable, submitable, deletable, cancelable, policySelector, roleSelector) ), - peers: create(peers(visitable, creatable, consulPeerList, clickable)), + peers: create(peers(visitable, creatable, consulPeerList, clickable, collection)), peer: create(peersShow(visitable)), + peerCreate: create(peersCreate(visitable, submitable, clickable)), settings: create(settings(visitable, submitable, isPresent)), routingConfig: create(routingConfig(visitable, text)), }; diff --git a/ui/packages/consul-ui/tests/pages/dc/peers/create.js b/ui/packages/consul-ui/tests/pages/dc/peers/create.js new file mode 100644 index 00000000000..c4426c1115f --- /dev/null +++ b/ui/packages/consul-ui/tests/pages/dc/peers/create.js @@ -0,0 +1,22 @@ +/** + * Copyright IBM Corp. 2024, 2026 + * SPDX-License-Identifier: BUSL-1.1 + */ + +export default function (visitable, submitable, clickable) { + return { + visit: visitable('/:dc/peers/create'), + // Scoped: the app shell has its own unrelated [type=submit] element + // (e.g. datacenter/nspace search), so a bare selector matches more + // than one node. + ...submitable({}, '.consul-peer-form'), + tabs: { + generate: clickable('[data-test-tab="generate"] button'), + initiate: clickable('[data-test-tab="initiate"] button'), + }, + cancel: clickable('[data-test-cancel]'), + confirmDiscard: clickable('[data-test-confirm-discard]'), + keepEditing: clickable('[data-test-keep-editing]'), + closeTokenModal: clickable('#copy-token-modal .hds-modal__dismiss'), + }; +} diff --git a/ui/packages/consul-ui/tests/pages/dc/peers/index.js b/ui/packages/consul-ui/tests/pages/dc/peers/index.js index 6060762a7dc..db17e25a6fe 100644 --- a/ui/packages/consul-ui/tests/pages/dc/peers/index.js +++ b/ui/packages/consul-ui/tests/pages/dc/peers/index.js @@ -5,12 +5,20 @@ import tabgroup from 'consul-ui/components/tab-nav/pageobject'; -export default function (visitable, creatable, items, clickable) { +export default function (visitable, creatable, items, clickable, collection) { return creatable({ visit: visitable('/:dc/peers'), peers: items(), sort: { + // In-table column header click-to-sort (Peer name column). name: clickable('.consul-peer-list thead th:nth-child(1) button.hds-table__th-button--sort'), + // Toolbar "A to Z / Z to A / Status" sort dropdown. + selected: clickable('[data-test-sort-control] button', { at: 0 }), + options: collection('[data-test-sort-option]', { + resetScope: true, + testContainer: 'html', + button: clickable(), + }), }, tabs: tabgroup('tab', ['imported-services', 'exported-services', 'server-addresses']), }); diff --git a/ui/packages/consul-ui/translations/routes/en-us.yaml b/ui/packages/consul-ui/translations/routes/en-us.yaml index 4ae60429ac4..ede6c9e7364 100644 --- a/ui/packages/consul-ui/translations/routes/en-us.yaml +++ b/ui/packages/consul-ui/translations/routes/en-us.yaml @@ -170,7 +170,13 @@ dc: addresses: empty: header: No server adddresses. - body:
    There don't seem to be any server addresses for this peer.
    + body: | +
    + {items, select, + 0 {There don't seem to be any server addresses for this peer.} + other {No server addresses were found matching that search.} + } +
    partitions: index: empty: diff --git a/ui/packages/consul-ui/vendor/consul-ui/routes.js b/ui/packages/consul-ui/vendor/consul-ui/routes.js index 8dde7a1f4f4..356c528ae51 100644 --- a/ui/packages/consul-ui/vendor/consul-ui/routes.js +++ b/ui/packages/consul-ui/vendor/consul-ui/routes.js @@ -335,6 +335,13 @@ breadcrumb: { label: 'Peers' }, }, }, + create: { + _options: { + path: '/create', + abilities: ['create peer'], + breadcrumb: { label: 'Add peer connection', parent: 'dc.peers' }, + }, + }, show: { _options: { path: '/:name',