ui: Adds basic support for partition exports to Service listings (#11702)

Also:

* ui: Add cross partition linking and rollout BucketList (#11712)

* ui: Add exported service partition to the source filter menu (#11727)
This commit is contained in:
John Cowen 2021-12-06 11:06:33 +00:00 committed by GitHub
parent a90a65c9d8
commit 442df6d27d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 286 additions and 100 deletions

3
.changelog/11702.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Adds basic support for showing Services exported from another partition.
```

View File

@ -0,0 +1,44 @@
# Consul::Bucket::List
A presentational component for rendering a list of Consul 'buckets'
(a single partition and/or a single namepace).
Please note this is not your usual "scrollable list component" more a list of
'buckets' that make up a partition / namespace combination.
If only a the namespace is different to the currently selected namespace, then
the namespace will be displayed, whereas if the partition is different it will
show both the partition and namespace (as a namespace called 'team-1' in
`partition-1` is different to a namespace called 'team-1' in `partition-2`)
If you don't need the nspace only support for the view you are building then
omit the `@nspace` argument.
At the time of writing, this is not currently used across the entire UI
(specifically in intentions and maybe other areas) but eventually should be.
```hbs preview-template
<DataSource
@src={{uri "/partition/default/dc-1/gateways/for-service/service-name"}} as |source|>
<Consul::Bucket::List
@item={{object-at 0 source.data}}
@nspace={{'nspace'}}
@partition={{'partition'}}
/>
</DataSource>
```
## Arguments
| Argument/Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `item` | `array` | | A Consul object that could have both a `Partition` and a `Namespace` property |
| `nspace` | `string` | | The name of the current namespace |
| `partition` | `string` | | The name of the current partition |
## See
- [Template Source Code](./index.hbs)
---

View File

@ -0,0 +1,39 @@
{{#if (and @partition (can 'use partitions'))}}
{{#if (not-eq @item.Partition @partition)}}
<dl class="consul-bucket-list">
<dt
class="partition"
{{tooltip}}
>
Admin Partition
</dt>
<dd>
{{@item.Partition}}
</dd>
<dt
class="nspace"
{{tooltip}}
>
Namespace
</dt>
<dd>
{{@item.Namespace}}
</dd>
</dl>
{{/if}}
{{else if (and @nspace (can 'use nspace'))}}
{{#if (not-eq @item.Namespace @nspace)}}
<dl>
<dt
class="nspace"
{{tooltip}}
>
Namespace
</dt>
<dd>
{{@item.Namespace}}
</dd>
</dl>
{{/if}}
{{/if}}

View File

@ -0,0 +1,26 @@
%consul-bucket-list {
& {
@extend %horizontal-kv-list;
}
.partition::before {
@extend %with-user-team-mask, %as-pseudo;
}
.nspace::before {
@extend %with-folder-outline-mask, %as-pseudo;
}
/* potential for some sort of %composite-kv thing here */
.partition + dd::after {
display: inline-block;
content: '/';
margin: 0 3px;
/*TODO: In isolation this is not needed */
margin-right: 6px;
}
.partition + dd + .nspace {
margin-left: 0 !important;
}
/**/
}
.consul-bucket-list {
@extend %consul-bucket-list;
}

View File

@ -0,0 +1,28 @@
# Consul::Service::List
A presentational component for rendering a list of Consul Services.
```hbs preview-template
<DataSource
@src={{uri "/partition/default/dc-1/gateways/for-service/service-name"}} as |source|>
<Consul::Service::List
@items={{source.data}}
@nspace={{'nspace'}}
@partition={{'partition'}}
/>
</DataSource>
```
## Arguments
| Argument/Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `array` | | An array of Consul Services |
| `nspace` | `string` | | The name of the current namespace |
| `partition` | `string` | | The name of the current partition |
## See
- [Template Source Code](./index.hbs)
---

View File

@ -25,7 +25,18 @@
</dd> </dd>
</dl> </dl>
{{#if (gt item.InstanceCount 0)}} {{#if (gt item.InstanceCount 0)}}
<a data-test-service-name href={{href-to "dc.services.show.index" item.Name}}> <a
data-test-service-name
href={{href-to "dc.services.show.index" item.Name
params=(if (not-eq item.Partition @partition)
(hash
partition=item.Partition
nspace=item.Namespace
)
(hash)
)
}}
>
{{item.Name}} {{item.Name}}
</a> </a>
{{else}} {{else}}
@ -35,20 +46,6 @@
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="details"> <BlockSlot @name="details">
{{#if (and nspace (env 'CONSUL_NSPACES_ENABLED'))}}
{{#if (not-eq item.Namespace nspace)}}
<dl class="nspace">
<dt>
<Tooltip>
Namespace
</Tooltip>
</dt>
<dd>
{{item.Namespace}}
</dd>
</dl>
{{/if}}
{{/if}}
<Consul::Kind @item={{item}} /> <Consul::Kind @item={{item}} />
<Consul::ExternalSource @item={{item}} /> <Consul::ExternalSource @item={{item}} />
{{#if (and (not-eq item.InstanceCount 0) (and (not-eq item.Kind 'terminating-gateway') (not-eq item.Kind 'ingress-gateway'))) }} {{#if (and (not-eq item.InstanceCount 0) (and (not-eq item.Kind 'terminating-gateway') (not-eq item.Kind 'ingress-gateway'))) }}
@ -87,6 +84,11 @@
{{/if}} {{/if}}
</dl> </dl>
{{/if}} {{/if}}
<Consul::Bucket::List
@item={{item}}
@nspace={{@nspace}}
@partition={{@partition}}
/>
<TagList @item={{item}} /> <TagList @item={{item}} />
</BlockSlot> </BlockSlot>
</ListCollection> </ListCollection>

View File

@ -140,11 +140,33 @@ as |key value|}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="options"> <BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
{{#let
(reject-by 'Partition' @partition @partitions)
as |nonDefaultPartitions|}}
{{#if (gt nonDefaultPartitions.length 0)}}
<Optgroup
@label={{t 'common.brand.consul'}}
>
{{#each @partitions as |partition|}}
<Option class="partition" @value={{partition}} @selected={{contains partition @filter.source.value}}>
{{partition}}
</Option>
{{/each}}
</Optgroup>
{{/if}}
{{/let}}
{{#if (gt @sources.length 0)}}
<Optgroup
@label={{t 'common.search.integrations'}}
>
{{#each @sources as |source|}} {{#each @sources as |source|}}
<Option class={{source}} @value={{source}} @selected={{contains source @filter.source.value}}> <Option class={{source}} @value={{source}} @selected={{contains source @filter.source.value}}>
{{t (concat "common.brand." source)}} {{t (concat "common.brand." source)}}
</Option> </Option>
{{/each}} {{/each}}
</Optgroup>
{{/if}}
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
</search.Select> </search.Select>

View File

@ -5,40 +5,26 @@
<ul> <ul>
{{#each @items as |item|}} {{#each @items as |item|}}
<li> <li>
<div class="header"> <div class="header">
<p> <p>
{{item.DestinationName}} {{item.DestinationName}}
</p> </p>
</div> </div>
<div class="detail"> <div class="detail">
{{#if (can 'use partitions')}}
{{#if (not-eq item.DestinationType 'prepared_query')}} {{#if (not-eq item.DestinationType 'prepared_query')}}
<dl class="partition"> <Consul::Bucket::List
<dt @item={{hash
{{tooltip}} Namespace=(or item.DestinationNamespace @nspace)
> Partition=(or item.DestinationPartition @partition)
Admin Partition }}
</dt> @partition={{@partition}}
<dd> @nspace={{@nspace}}
{{or item.DestinationPartition 'default'}} />
</dd>
</dl>
{{/if}}
{{/if}}
{{#if (can 'use nspaces')}}
{{#if (not-eq item.DestinationType 'prepared_query')}}
<dl class="nspace">
<dt
{{tooltip}}
>
Namespace
</dt>
<dd>
{{or item.DestinationNamespace 'default'}}
</dd>
</dl>
{{/if}}
{{/if}} {{/if}}
{{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}} {{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}}
<dl class="datacenter"> <dl class="datacenter">
<dt <dt
@ -51,6 +37,7 @@
</dd> </dd>
</dl> </dl>
{{/if}} {{/if}}
{{#if item.LocalBindSocketPath}} {{#if item.LocalBindSocketPath}}
<dl class="local-bind-socket-path"> <dl class="local-bind-socket-path">
<dt> <dt>
@ -73,23 +60,26 @@
</dd> </dd>
</dl> </dl>
{{else}} {{else}}
{{#if (gt item.LocalBindPort 0)}} {{#if (gt item.LocalBindPort 0)}}
{{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}} {{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}}
<dl class="local-bind-address"> <dl class="local-bind-address">
<dt> <dt>
Address Address
</dt> </dt>
<dd> <dd>
<CopyButton <CopyButton
@value={{combinedAddress}} @value={{combinedAddress}}
@name="Address" @name="Address"
/> />
{{combinedAddress}} {{combinedAddress}}
</dd> </dd>
</dl> </dl>
{{/let}} {{/let}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</div> </div>
</li> </li>
{{/each}} {{/each}}

View File

@ -24,27 +24,21 @@ as |item index|>
{{/if}} {{/if}}
</dd> </dd>
</dl> </dl>
{{#if (and (can 'use partitions') (not-eq item.Partition @partition))}}
<dl class="partition">
<dt
{{tooltip}}
>
Admin Partition
</dt>
<dd>
{{item.Partition}}
</dd>
</dl>
{{/if}}
<a <a
data-test-service-name data-test-service-name
href={{if (and (can 'use nspaces') (not-eq item.Namespace @nspace)) href={{href-to "dc.services.show" item.Name
(href-to 'dc.services.show' @dc item.Name params=(if (not-eq item.Partition @partition)
params=(hash (hash
partition=item.Partition
nspace=item.Namespace nspace=item.Namespace
) )
(if (not-eq item.Namespace @nspace)
(hash
nspace=item.Namespace
)
(hash)
)
) )
(href-to 'dc.services.show' item.Name)
}} }}
> >
{{item.Name}} {{item.Name}}
@ -56,18 +50,11 @@ as |item index|>
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="details"> <BlockSlot @name="details">
{{#if (and (can 'use nspaces') (not-eq item.Namespace @nspace))}} <Consul::Bucket::List
<dl class="nspace"> @item={{item}}
<dt @nspace={{@nspace}}
{{tooltip}} @partition={{@partition}}
> />
Namespace
</dt>
<dd>
{{item.Namespace}}
</dd>
</dl>
{{/if}}
{{#each item.GatewayConfig.Addresses as |address|}} {{#each item.GatewayConfig.Addresses as |address|}}
<dl> <dl>
<dt> <dt>

View File

@ -21,6 +21,8 @@
margin-right: 10px; margin-right: 10px;
} }
/* TODO: Consider moving these to their specific search bard componets or */
/* even their own search bar sub menu components */
%popover-select .value-passing button::before { %popover-select .value-passing button::before {
@extend %with-check-circle-fill-mask, %as-pseudo; @extend %with-check-circle-fill-mask, %as-pseudo;
color: rgb(var(--tone-green-500)); color: rgb(var(--tone-green-500));
@ -37,12 +39,16 @@
@extend %with-minus-square-fill-mask, %as-pseudo; @extend %with-minus-square-fill-mask, %as-pseudo;
color: rgb(var(--tone-gray-400)); color: rgb(var(--tone-gray-400));
} }
%popover-select.type-source li button { %popover-select.type-source li:not(.partition) button {
text-transform: capitalize; text-transform: capitalize;
} }
%popover-select.type-source li.aws button { %popover-select.type-source li.aws button {
text-transform: uppercase; text-transform: uppercase;
} }
%popover-select.type-source li.partition button::before {
@extend %with-user-team-mask, %as-pseudo;
color: rgb(var(--tone-gray-500));
}
%popover-select .aws button::before { %popover-select .aws button::before {
@extend %with-logo-aws-color-icon, %as-pseudo; @extend %with-logo-aws-color-icon, %as-pseudo;
} }
@ -68,3 +74,4 @@
%popover-select .terraform button::before { %popover-select .terraform button::before {
@extend %with-logo-terraform-color-icon, %as-pseudo; @extend %with-logo-terraform-color-icon, %as-pseudo;
} }
/**/

View File

@ -20,6 +20,9 @@ export default {
'not-registered': (item, value) => item.InstanceCount === 0, 'not-registered': (item, value) => item.InstanceCount === 0,
}, },
source: (item, values) => { source: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0; return (
setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0 ||
values.includes(item.Partition)
);
}, },
}; };

View File

@ -15,11 +15,17 @@ export const Collection = class Collection {
} }
get ExternalSources() { get ExternalSources() {
const sources = this.items.reduce(function(prev, item) { const items = this.items.reduce(function(prev, item) {
return prev.concat(item.ExternalSources || []); return prev.concat(item.ExternalSources || []);
}, []); }, []);
// unique, non-empty values, alpha sort // unique, non-empty values, alpha sort
return [...new Set(sources)].filter(Boolean).sort(); return [...new Set(items)].filter(Boolean).sort();
}
// TODO: Think about when this/collections is worthwhile using and explain
// when and when not somewhere in the docs
get Partitions() {
// unique, non-empty values, alpha sort
return [...new Set(this.items.map(item => item.Partition))].sort();
} }
}; };
export default class Service extends Model { export default class Service extends Model {

View File

@ -75,6 +75,7 @@
@import 'consul-ui/components/consul/loader'; @import 'consul-ui/components/consul/loader';
@import 'consul-ui/components/consul/tomography/graph'; @import 'consul-ui/components/consul/tomography/graph';
@import 'consul-ui/components/consul/discovery-chain'; @import 'consul-ui/components/consul/discovery-chain';
@import 'consul-ui/components/consul/bucket/list';
@import 'consul-ui/components/consul/upstream/list'; @import 'consul-ui/components/consul/upstream/list';
@import 'consul-ui/components/consul/upstream-instance/list'; @import 'consul-ui/components/consul/upstream-instance/list';
@import 'consul-ui/components/consul/health-check/list'; @import 'consul-ui/components/consul/health-check/list';

View File

@ -53,7 +53,10 @@ as |route|>
(reject-by 'Kind' 'connect-proxy' api.data) (reject-by 'Kind' 'connect-proxy' api.data)
as |sort filters items|}} (or route.params.partition route.model.user.token.Partition 'default')
(or route.params.nspace route.model.user.token.Namespace 'default')
as |sort filters items partition nspace|}}
<AppView> <AppView>
<BlockSlot @name="header"> <BlockSlot @name="header">
@ -63,9 +66,12 @@ as |sort filters items|}}
<label for="toolbar-toggle"></label> <label for="toolbar-toggle"></label>
</BlockSlot> </BlockSlot>
<BlockSlot @name="toolbar"> <BlockSlot @name="toolbar">
{{#if (gt items.length 0) }} {{#if (gt items.length 0) }}
{{#let (collection items) as |items|}}
<Consul::Service::SearchBar <Consul::Service::SearchBar
@sources={{get (collection items) 'ExternalSources'}} @sources={{get items 'ExternalSources'}}
@partitions={{get items 'Partitions'}}
@partition={{partition}}
@search={{search}} @search={{search}}
@onsearch={{action (mut search) value="target.value"}} @onsearch={{action (mut search) value="target.value"}}
@ -75,7 +81,8 @@ as |sort filters items|}}
@filter={{filters}} @filter={{filters}}
/> />
{{/if}} {{/let}}
{{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="content"> <BlockSlot @name="content">
<DataCollection <DataCollection
@ -88,6 +95,7 @@ as |sort filters items|}}
<collection.Collection> <collection.Collection>
<Consul::Service::List <Consul::Service::List
@items={{collection.items}} @items={{collection.items}}
@partition={{partition}}
> >
</Consul::Service::List> </Consul::Service::List>
</collection.Collection> </collection.Collection>
@ -115,10 +123,20 @@ as |sort filters items|}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="actions"> <BlockSlot @name="actions">
<li class="docs-link"> <li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/services" rel="noopener noreferrer" target="_blank">Documentation on services</a> <Action
@href="{{env 'CONSUL_DOCS_URL'}}/commands/services"
@external={{true}}
>
Documentation on services
</Action>
</li> </li>
<li class="learn-link"> <li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/services" rel="noopener noreferrer" target="_blank">Read the guide</a> <Action
@href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/getting-started/services"
@external={{true}}
>
Read the guide
</Action>
</li> </li>
</BlockSlot> </BlockSlot>
</EmptyState> </EmptyState>

View File

@ -20,12 +20,14 @@ as |route|>
) )
) )
(or route.params.partition route.model.user.token.Partition 'default')
(or route.params.nspace route.model.user.token.Namespace 'default')
route.params.dc route.params.dc
route.params.nspace
route.model.proxy route.model.proxy
route.model.proxy.Service.Proxy.Upstreams route.model.proxy.Service.Proxy.Upstreams
as |sort filters dc nspace proxy items|}} as |sort filters partition nspace dc proxy items|}}
{{#if (gt items.length 0)}} {{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" /> <input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar <Consul::UpstreamInstance::SearchBar

View File

@ -69,7 +69,8 @@ as |route|>
as |collection|> as |collection|>
<collection.Collection> <collection.Collection>
<Consul::Service::List <Consul::Service::List
@nspace={{nspace}} @nspace={{or route.params.nspace route.model.user.token.Namespace 'default'}}
@partition={{or route.params.partition route.model.user.token.Partition 'default'}}
@items={{collection.items}} @items={{collection.items}}
> >
</Consul::Service::List> </Consul::Service::List>

View File

@ -42,11 +42,12 @@ as |route|>
) )
) )
route.params.nspace (or route.params.partition route.model.user.token.Partition 'default')
(or route.params.nspace route.model.user.token.Namespace 'default')
route.params.dc route.params.dc
loader.data loader.data
as |sort filters nspace dc items|}} as |sort filters partition nspace dc items|}}
{{#if (gt items.length 0)}} {{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" /> <input type="checkbox" id="toolbar-toggle" />
<Consul::Upstream::SearchBar <Consul::Upstream::SearchBar
@ -74,6 +75,7 @@ as |route|>
@items={{collection.items}} @items={{collection.items}}
@dc={{dc}} @dc={{dc}}
@nspace={{nspace}} @nspace={{nspace}}
@partition={{partition}}
> >
</Consul::Upstream::List> </Consul::Upstream::List>
</collection.Collection> </collection.Collection>

View File

@ -53,7 +53,7 @@ ${typeof location.search.ns !== 'undefined' ? `
"Namespace": "${location.search.ns}", "Namespace": "${location.search.ns}",
` : ``} ` : ``}
${typeof location.search.partition !== 'undefined' ? ` ${typeof location.search.partition !== 'undefined' ? `
"Partition": "${location.search.partition}", "Partition": "${fake.helpers.randomize([env('CONSUL_PARTITION_EXPORTER', location.search.partition), location.search.partition])}",
` : ``} ` : ``}
"Tags":[ "Tags":[
${ ${
@ -132,6 +132,7 @@ ${range(env('CONSUL_UPSTREAM_COUNT', 10)).map((item, j) => `
"Datacenter": "${fake.address.countryCode().toLowerCase()} ${ i % 2 ? "west" : "east"}-${j}", "Datacenter": "${fake.address.countryCode().toLowerCase()} ${ i % 2 ? "west" : "east"}-${j}",
"DestinationName": "${fake.hacker.noun()}", "DestinationName": "${fake.hacker.noun()}",
"DestinationNamespace": "${fake.hacker.noun()}", "DestinationNamespace": "${fake.hacker.noun()}",
"DestinationPartition": "${fake.hacker.noun()}",
"DestinationType": "${fake.helpers.randomize(['service', 'prepared_query'])}", "DestinationType": "${fake.helpers.randomize(['service', 'prepared_query'])}",
${fake.random.number({min: 1, max: 10}) > 5 ? ` ${fake.random.number({min: 1, max: 10}) > 5 ? `
"LocalBindAddress": "${fake.internet.ip()}", "LocalBindAddress": "${fake.internet.ip()}",

View File

@ -17,6 +17,9 @@ ${i === 1 ? `
` : ` ` : `
"Namespace": "${fake.hacker.noun()}-ns-${i}", "Namespace": "${fake.hacker.noun()}-ns-${i}",
`} `}
${typeof location.search.partition !== 'undefined' ? `
"Partition": "${fake.helpers.randomize([env('CONSUL_PARTITION_EXPORTER', location.search.partition), location.search.partition])}",
` : ``}
"Tags": [ "Tags": [
${ ${
range(env('CONSUL_TAG_COUNT', fake.random.number(10))).map( range(env('CONSUL_TAG_COUNT', fake.random.number(10))).map(

View File

@ -47,7 +47,7 @@ ${typeof location.search.ns !== 'undefined' ? `
"Namespace": "${location.search.ns}", "Namespace": "${location.search.ns}",
` : ``} ` : ``}
${typeof location.search.partition !== 'undefined' ? ` ${typeof location.search.partition !== 'undefined' ? `
"Partition": "${location.search.ns}", "Partition": "${fake.helpers.randomize([env('CONSUL_PARTITION_EXPORTER', location.search.partition), location.search.partition])}",
` : ``} ` : ``}
"Tags": [ "Tags": [
${ ${

View File

@ -46,6 +46,7 @@ search:
critical: Failing critical: Failing
in-mesh: In service mesh in-mesh: In service mesh
not-in-mesh: Not in service mesh not-in-mesh: Not in service mesh
integrations: Integrations
sort: sort:
alpha: alpha:
asc: A to Z asc: A to Z
@ -61,4 +62,4 @@ sort:
desc: Shortest to longest desc: Shortest to longest
status: status:
asc: Unhealthy to Healthy asc: Unhealthy to Healthy
desc: Healthy to Unhealthy desc: Healthy to Unhealthy