mirror of https://github.com/status-im/consul.git
ui: Add peer searching and sorting (#13634)
* ui: Add peer searching and sorting Initial name search and sort only, more to come here * Remove old peerings::search component * Use @model peers * ui: Peer listing with dc/ns/partition/name based unique IDs and polling deletion (#13648) * ui: Add peer repo with listing datasource * ui: Use data-loader component to use the data-source * ui: Remove ember-data REST things and Route.model hook * 10 second not 1 second poll * Fill out Datacenter and Partition * route > routeName * Faker randomised mocks for peering endpoint * ui: Adds initial peer detail page plus address tab (#13651)
This commit is contained in:
parent
ea33bc249c
commit
27e50ae925
|
@ -0,0 +1,19 @@
|
|||
import BaseAbility from 'consul-ui/abilities/base';
|
||||
|
||||
export default class PeerAbility extends BaseAbility {
|
||||
resource = 'operator';
|
||||
segmented = false;
|
||||
|
||||
get isLinkable() {
|
||||
return this.canDelete;
|
||||
}
|
||||
get canDelete() {
|
||||
// TODO: Need to confirm these states
|
||||
return ![
|
||||
'DELETING',
|
||||
'TERMINATED',
|
||||
'UNDEFINED'
|
||||
].includes(this.item.State);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import JSONAPIAdapter from '@ember-data/adapter/json-api';
|
||||
|
||||
export default class PeerAdapter extends JSONAPIAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
pathForType(_modelName) {
|
||||
return 'peerings';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<ListCollection
|
||||
class="consul-peer-address-list"
|
||||
...attributes
|
||||
@items={{@items}}
|
||||
as |item|>
|
||||
<BlockSlot @name="header">
|
||||
<p>
|
||||
{{item}}
|
||||
<CopyButton
|
||||
@value={{item}}
|
||||
@name="Address"
|
||||
/>
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
|
@ -0,0 +1,30 @@
|
|||
# Consul::Peer::SearchBar
|
||||
|
||||
Searchbar tailored for searching Peers. Follows our more generic
|
||||
'*::SearchBar' component interface.
|
||||
|
||||
```hbs preview-template
|
||||
<Consul::Peer::SearchBar
|
||||
@search={{this.search}}
|
||||
@onsearch={{fn (mut this.search) value="target.value"}}
|
||||
|
||||
@sort={{hash
|
||||
value='Name:asc'
|
||||
change=(noop)
|
||||
}}
|
||||
|
||||
@filter={{hash
|
||||
searchproperty=(hash
|
||||
value=(array)
|
||||
change=(noop)
|
||||
default=(array)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## See
|
||||
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -0,0 +1,98 @@
|
|||
<SearchBar
|
||||
class="consul-peer-search-bar"
|
||||
...attributes
|
||||
@filter={{@filter}}
|
||||
>
|
||||
<: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|}}
|
||||
<search.RemoveFilter
|
||||
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
|
||||
>
|
||||
<dl>
|
||||
<dt>{{key}}</dt>
|
||||
<dd>{{value}}</dd>
|
||||
</dl>
|
||||
</search.RemoveFilter>
|
||||
{{/let}}
|
||||
|
||||
</:status>
|
||||
<:search as |search|>
|
||||
<search.Search
|
||||
@onsearch={{action @onsearch}}
|
||||
@value={{@search}}
|
||||
@placeholder={{t "common.search.search"}}
|
||||
>
|
||||
<search.Select
|
||||
class="type-search-properties"
|
||||
@position="right"
|
||||
@onchange={{action @filter.searchproperty.change}}
|
||||
@multiple={{true}}
|
||||
@required={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{t "common.search.searchproperty"}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
{{#each @filter.searchproperty.default as |prop|}}
|
||||
<Option @value={{prop}} @selected={{includes prop @filter.searchproperty.value}}>
|
||||
{{t (concat "common.consul." (lowercase prop))}}
|
||||
</Option>
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
</search.Search>
|
||||
</:search>
|
||||
<:sort as |search|>
|
||||
<search.Select
|
||||
class="type-sort"
|
||||
data-test-sort-control
|
||||
@position="right"
|
||||
@onchange={{action @sort.change}}
|
||||
@multiple={{false}}
|
||||
@required={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{#let (from-entries (array
|
||||
(array "Name:asc" (t "common.sort.alpha.asc"))
|
||||
(array "Name:desc" (t "common.sort.alpha.desc"))
|
||||
))
|
||||
as |selectable|
|
||||
}}
|
||||
{{get selectable @sort.value}}
|
||||
{{/let}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
<Optgroup @label={{t "common.consul.name"}}>
|
||||
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
|
||||
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
|
||||
</Optgroup>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
</:sort>
|
||||
</SearchBar>
|
|
@ -1,28 +0,0 @@
|
|||
<div class="peerings-search">
|
||||
<div class="peerings-search__input">
|
||||
<label for="peer-search" class="peerings-search__input__label">
|
||||
<svg width="16" height="16" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.33334 7.33334H7.80667L7.62 7.15334C8.29592 6.36935 8.6674 5.36846 8.66667 4.33334C8.66667 3.47628 8.41252 2.63848 7.93637 1.92586C7.46022 1.21325 6.78344 0.657837 5.99163 0.329857C5.19982 0.00187757 4.32853 -0.0839369 3.48794 0.0832657C2.64736 0.250468 1.87523 0.663178 1.26921 1.26921C0.663178 1.87523 0.250468 2.64736 0.0832657 3.48794C-0.0839369 4.32853 0.00187757 5.19982 0.329857 5.99163C0.657837 6.78344 1.21325 7.46022 1.92586 7.93637C2.63848 8.41252 3.47628 8.66667 4.33334 8.66667C5.40667 8.66667 6.39333 8.27334 7.15334 7.62L7.33334 7.80667V8.33334L10.6667 11.66L11.66 10.6667L8.33334 7.33334ZM4.33334 7.33334C2.67334 7.33334 1.33334 5.99334 1.33334 4.33334C1.33334 2.67334 2.67334 1.33334 4.33334 1.33334C5.99334 1.33334 7.33334 2.67334 7.33334 4.33334C7.33334 5.99334 5.99334 7.33334 4.33334 7.33334Z"
|
||||
fill="#8E96A3" />
|
||||
</svg>
|
||||
</label>
|
||||
<input id="peer-search" placeholder="Search" class="peerings-search__input__input" value="{{@search}}" {{on "input"
|
||||
(pick "target.value" @onSearch)}} {{on-key "Escape" (fn @onSearch "" )}} />
|
||||
{{#if @search}}
|
||||
<button
|
||||
type="button"
|
||||
class="peerings-search__input__clear-button"
|
||||
{{on "click" (fn @onSearch "" )}}
|
||||
>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.7803 4.28033C13.0732 3.98744 13.0732 3.51256 12.7803 3.21967C12.4874 2.92678 12.0126 2.92678 11.7197 3.21967L8 6.93934L4.28033 3.21967C3.98744 2.92678 3.51256 2.92678 3.21967 3.21967C2.92678 3.51256 2.92678 3.98744 3.21967 4.28033L6.93934 8L3.21967 11.7197C2.92678 12.0126 2.92678 12.4874 3.21967 12.7803C3.51256 13.0732 3.98744 13.0732 4.28033 12.7803L8 9.06066L11.7197 12.7803C12.0126 13.0732 12.4874 13.0732 12.7803 12.7803C13.0732 12.4874 13.0732 12.0126 12.7803 11.7197L9.06066 8L12.7803 4.28033Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
.peerings-search {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
background: rgb(var(--gray-010));
|
||||
|
||||
.peerings-search__input {
|
||||
position: relative;
|
||||
border-width: 1px;
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
|
||||
.peerings-search__input__label {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.peerings-search__input__input {
|
||||
padding: 8px 32px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgb(var(--gray-300));
|
||||
}
|
||||
|
||||
.peerings-search__input__clear-button {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class PeersController extends Controller {
|
||||
queryParams = ['filter'];
|
||||
|
||||
@tracked filter = '';
|
||||
|
||||
get peers() {
|
||||
return this.model.peers;
|
||||
}
|
||||
|
||||
get filteredPeers() {
|
||||
const { peers, filter } = this;
|
||||
|
||||
if (filter) {
|
||||
const filterRegex = new RegExp(`${filter}`, 'gi');
|
||||
|
||||
return peers.filter(peer => peer.Name.match(filterRegex));
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
@action handleSearchChanged(newSearchTerm) {
|
||||
this.filter = newSearchTerm;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
|
||||
export default class Peer extends Model {
|
||||
@attr('string') uri;
|
||||
@attr() meta;
|
||||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Partition;
|
||||
|
||||
@attr('string') Name;
|
||||
@attr('string') State;
|
||||
@attr('string') CreateIndex;
|
||||
@attr('string') ModifyIndex;
|
||||
@attr('number') ImportedServiceCount;
|
||||
@attr('number') ExportedServiceCount;
|
||||
@attr() PeerServerAddresses;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class PeersRoute extends Route {
|
||||
model() {
|
||||
return this.store.findAll('peer').then(peers => {
|
||||
return {
|
||||
peers,
|
||||
loadPeers: this.loadPeers,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@action loadPeers() {
|
||||
return this.store.findAll('peer');
|
||||
}
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
searchproperty: {
|
||||
as: 'searchproperty',
|
||||
empty: [['Name']],
|
||||
},
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
Name: item => item.Name,
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
import JSONAPISerializer from '@ember-data/serializer/json-api';
|
||||
|
||||
export default class PeerSerializer extends JSONAPISerializer {
|
||||
keyForAttribute(key) {
|
||||
return key.capitalize();
|
||||
}
|
||||
|
||||
normalizeFindAllResponse(store, primaryModelClass, payload, id, requestType) {
|
||||
const data = payload.map(peering => {
|
||||
return {
|
||||
type: 'peer',
|
||||
id: peering.ID,
|
||||
attributes: {
|
||||
...peering,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return super.normalizeFindAllResponse(store, primaryModelClass, { data }, id, requestType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
export default class PeerService extends RepositoryService {
|
||||
|
||||
getModelName() {
|
||||
return 'peer';
|
||||
}
|
||||
|
||||
@dataSource('/:partition/:ns/:dc/peers')
|
||||
async fetchAll({ dc, ns, partition }, { uri }, request) {
|
||||
return (await request`
|
||||
GET /v1/peerings
|
||||
|
||||
${{
|
||||
partition,
|
||||
}}
|
||||
`)(
|
||||
(headers, body, cache) => {
|
||||
return {
|
||||
meta: {
|
||||
version: 2,
|
||||
interval: 10000,
|
||||
uri: uri,
|
||||
},
|
||||
body: body.map(item => {
|
||||
return cache(
|
||||
{
|
||||
...item,
|
||||
Datacenter: dc,
|
||||
Partition: partition,
|
||||
},
|
||||
uri => uri`peer:///${partition}/${ns}/${dc}/peer/${item.Name}`
|
||||
);
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@dataSource('/:partition/:ns/:dc/peer/:name')
|
||||
async fetchOne({partition, ns, dc, name}, { uri }, request) {
|
||||
if (name === '') {
|
||||
return this.create({
|
||||
Datacenter: dc,
|
||||
Namespace: '',
|
||||
Partition: partition,
|
||||
});
|
||||
}
|
||||
return (await request`
|
||||
GET /v1/peering/${name}
|
||||
|
||||
${{
|
||||
partition,
|
||||
}}
|
||||
`)((headers, body, cache) => {
|
||||
return {
|
||||
meta: {
|
||||
version: 2,
|
||||
interval: 10000,
|
||||
uri: uri,
|
||||
},
|
||||
body: cache(
|
||||
{
|
||||
...body,
|
||||
Datacenter: dc,
|
||||
Partition: partition,
|
||||
},
|
||||
uri => uri`peer:///${partition}/${ns}/${dc}/peer/${body.Name}`
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import role from 'consul-ui/search/predicates/role';
|
|||
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';
|
||||
|
||||
const predicates = {
|
||||
intention: intention,
|
||||
|
@ -30,6 +31,7 @@ const predicates = {
|
|||
role: role,
|
||||
policy: policy,
|
||||
nspace: nspace,
|
||||
peer: peer,
|
||||
};
|
||||
|
||||
export default class SearchService extends Service {
|
||||
|
|
|
@ -10,6 +10,7 @@ import role from 'consul-ui/sort/comparators/role';
|
|||
import policy from 'consul-ui/sort/comparators/policy';
|
||||
import authMethod from 'consul-ui/sort/comparators/auth-method';
|
||||
import nspace from 'consul-ui/sort/comparators/nspace';
|
||||
import peer from 'consul-ui/sort/comparators/peer';
|
||||
import node from 'consul-ui/sort/comparators/node';
|
||||
|
||||
// returns an array of Property:asc, Property:desc etc etc
|
||||
|
@ -39,6 +40,7 @@ const comparators = {
|
|||
role: role(options),
|
||||
policy: policy(options),
|
||||
nspace: nspace(options),
|
||||
peer: peer(options),
|
||||
node: node(options),
|
||||
};
|
||||
export default class SortService extends Service {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default ({ properties }) => key => {
|
||||
return properties(['Name'])(key);
|
||||
};
|
|
@ -105,7 +105,6 @@
|
|||
@import 'consul-ui/components/topology-metrics/stats';
|
||||
@import 'consul-ui/components/topology-metrics/status';
|
||||
@import 'consul-ui/components/peerings/badge';
|
||||
@import 'consul-ui/components/peerings/search';
|
||||
@import 'consul-ui/components/consul/node/peer-info';
|
||||
@import 'consul-ui/components/consul/intention/list/table';
|
||||
@import 'consul-ui/components/consul/service/peer-info';
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
<DataLoader @src={{
|
||||
uri '/${partition}/${nspace}/${dc}/peer/${name}'
|
||||
(hash
|
||||
partition=route.params.partition
|
||||
nspace=route.params.nspace
|
||||
dc=route.params.dc
|
||||
name=route.params.name
|
||||
)
|
||||
}}
|
||||
as |loader|>
|
||||
|
||||
<BlockSlot @name="error">
|
||||
<AppError
|
||||
@error={{loader.error}}
|
||||
@login={{route.model.app.login.open}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="loaded">
|
||||
{{#let
|
||||
|
||||
route.params.dc
|
||||
route.params.partition
|
||||
route.params.nspace
|
||||
|
||||
loader.data
|
||||
as |dc partition nspace item|}}
|
||||
<AppView>
|
||||
<BlockSlot @name="breadcrumbs">
|
||||
<ol>
|
||||
<li><a data-test-back href={{href-to 'dc.peers'}}>All Peers</a></li>
|
||||
</ol>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
<route.Title
|
||||
@title={{item.Name}}
|
||||
/>
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<TabNav @items={{
|
||||
compact
|
||||
(array
|
||||
(hash
|
||||
label="Addresses"
|
||||
href=(href-to "dc.peers.edit.addresses")
|
||||
selected=(is-href "dc.peers.edit.addresses")
|
||||
)
|
||||
)
|
||||
}}/>
|
||||
<Outlet
|
||||
@name={{routeName}}
|
||||
@model={{assign (hash
|
||||
items=item.PeerServerAddresses
|
||||
) route.model}}
|
||||
as |o|>
|
||||
{{outlet}}
|
||||
</Outlet>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</DataLoader>
|
||||
</Route>
|
|
@ -0,0 +1,7 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
<Consul::Peer::Address::List
|
||||
@items={{route.model.items}}
|
||||
/>
|
||||
</Route>
|
|
@ -0,0 +1,6 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
{{did-insert (route-action 'replaceWith' 'dc.peers.edit.addresses')}}
|
||||
</Route>
|
||||
|
|
@ -1,8 +1,43 @@
|
|||
<Route @name={{route}} as |route|>
|
||||
<Watcher @watch={{this.model.loadPeers}} as |w|>
|
||||
{{did-insert w.fns.start}}
|
||||
{{will-destroy w.fns.stop}}
|
||||
</Watcher>
|
||||
<Route @name={{routeName}} as |route|>
|
||||
<DataLoader
|
||||
@src={{
|
||||
uri '/${partition}/${nspace}/${dc}/peers'
|
||||
(hash
|
||||
partition=route.params.partition
|
||||
nspace=route.params.nspace
|
||||
dc=route.params.dc
|
||||
)}}
|
||||
as |loader|>
|
||||
|
||||
<BlockSlot @name="error">
|
||||
<AppError
|
||||
@error={{loader.error}}
|
||||
@login={{route.model.app.login.open}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
|
||||
<BlockSlot @name="loaded">
|
||||
{{#let
|
||||
|
||||
(hash
|
||||
value=(or sortBy "Name:asc")
|
||||
change=(action (mut sortBy) value="target.selected")
|
||||
)
|
||||
|
||||
(hash
|
||||
searchproperty=(hash
|
||||
value=(if (not-eq searchproperty undefined)
|
||||
(split searchproperty ',')
|
||||
searchProperties
|
||||
)
|
||||
change=(action (mut searchproperty) value="target.selectedItems")
|
||||
default=searchProperties
|
||||
)
|
||||
)
|
||||
|
||||
loader.data
|
||||
|
||||
as |sort filters items|}}
|
||||
<AppView>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
|
@ -10,52 +45,103 @@
|
|||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="toolbar">
|
||||
<Peerings::Search @search={{this.filter}} @onSearch={{this.handleSearchChanged}} />
|
||||
{{#if (gt items.length 0)}}
|
||||
<Consul::Peer::SearchBar
|
||||
@search={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
|
||||
@sort={{sort}}
|
||||
|
||||
@filter={{filters}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
{{#if this.filteredPeers.length}}
|
||||
<ListCollection @items={{this.filteredPeers}} as |item index|>
|
||||
<BlockSlot @name="header">
|
||||
<p>{{item.Name}}</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="details">
|
||||
<div class="peers__list__peer-detail">
|
||||
<Peerings::Badge @peering={{item}} />
|
||||
<Peerings::ServiceCount @peering={{item}} @kind="imported"/>
|
||||
<Peerings::ServiceCount @peering={{item}} @kind="exported"/>
|
||||
</div>
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
||||
{{else}}
|
||||
{{!-- TODO: do we need to check permissions here or will we receive an error automatically? --}}
|
||||
<EmptyState @login={{route.model.app.login.open}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Peers</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
Peering allows an admin partition in one datacenter to communicate with a partition in a different
|
||||
datacenter. There don't seem to be any peers for this admin partition, or you may not have
|
||||
<code>peering:read</code> permissions to
|
||||
access this view.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
{{!-- what's the docs for peering?--}}
|
||||
<a href="https://www.consul.io/docs/agent/kv" rel="noopener noreferrer" target="_blank">
|
||||
Documentation on Peers
|
||||
</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="https://learn.hashicorp.com/consul/getting-started/kv" rel="noopener noreferrer" target="_blank">
|
||||
Take the tutorial
|
||||
</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
<DataCollection
|
||||
@type="peer"
|
||||
@sort={{sort.value}}
|
||||
@filters={{filters}}
|
||||
@search={{search}}
|
||||
@items={{items}}
|
||||
as |collection|>
|
||||
<collection.Collection>
|
||||
|
||||
<ListCollection
|
||||
@items={{collection.items}}
|
||||
@linkable="linkable peer"
|
||||
as |item index|>
|
||||
<BlockSlot @name="header">
|
||||
<p>{{item.Name}}</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="details">
|
||||
<div class="peers__list__peer-detail">
|
||||
<Peerings::Badge @peering={{item}} />
|
||||
<Peerings::ServiceCount @peering={{item}} @kind="imported"/>
|
||||
<Peerings::ServiceCount @peering={{item}} @kind="exported"/>
|
||||
</div>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions" as |Actions|>
|
||||
{{#if (can 'delete peer' item=item)}}
|
||||
|
||||
<Actions as |Action|>
|
||||
{{#if true}}
|
||||
<Action data-test-edit-action @href={{href-to 'dc.peers.edit' item.Name}}>
|
||||
<BlockSlot @name="label">
|
||||
View
|
||||
</BlockSlot>
|
||||
</Action>
|
||||
{{/if}}
|
||||
</Actions>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
||||
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
{{!-- TODO: do we need to check permissions here or will we receive an error automatically? --}}
|
||||
<EmptyState
|
||||
@login={{route.model.app.login.open}}
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<h2>
|
||||
{{#if (gt items.length 0)}}
|
||||
No peers found
|
||||
{{else}}
|
||||
Welcome to Peers
|
||||
{{/if}}
|
||||
</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
{{#if (gt items.length 0)}}
|
||||
No peers where found matching that search, or you may not have access to view the peers you are searching for.
|
||||
{{else}}
|
||||
Peering allows an admin partition in one datacenter to communicate with a partition in a different
|
||||
datacenter. There don't seem to be any peers for this admin partition, or you may not have
|
||||
<code>peering:read</code> permissions to
|
||||
access this view.
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
{{!-- what's the docs for peering?--}}
|
||||
<a href="https://www.consul.io/docs/agent/kv" rel="noopener noreferrer" target="_blank">
|
||||
Documentation on Peers
|
||||
</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="https://learn.hashicorp.com/consul/getting-started/kv" rel="noopener noreferrer" target="_blank">
|
||||
Take the tutorial
|
||||
</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</collection.Empty>
|
||||
</DataCollection>
|
||||
</BlockSlot>
|
||||
|
||||
</AppView>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</DataLoader>
|
||||
</Route>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"ID": "${fake.random.uuid()}",
|
||||
"Name": "${location.pathname.get(2)}",
|
||||
"State": "${fake.helpers.randomize([
|
||||
'ACTIVE',
|
||||
'PENDING',
|
||||
'ESTABLISHING',
|
||||
'FAILING',
|
||||
'DELETING',
|
||||
'TERMINATED',
|
||||
'UNDEFINED'
|
||||
])}",
|
||||
"PeerID": "${fake.random.uuid()}",
|
||||
"PeerServerName": "${fake.internet.domainName()}",
|
||||
"PeerServerAddresses": [
|
||||
${
|
||||
range(
|
||||
env('CONSUL_PEER_ADDRESS_COUNT', Math.floor((Math.random() * 10) + 1))
|
||||
).map(i => `
|
||||
"${fake.internet.ip()}:${fake.random.number({min: 0, max: 65535})}"
|
||||
`)}
|
||||
],
|
||||
"CreateIndex": 89,
|
||||
"ModifyIndex": 89
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"PeeringToken": "eyJDQSI6bnVsbCwiU2V"
|
||||
}
|
|
@ -1,65 +1,26 @@
|
|||
[
|
||||
${
|
||||
range(
|
||||
env('CONSUL_PEER_COUNT', Math.floor((Math.random() * 10) + 1))
|
||||
).map(i => `
|
||||
|
||||
{
|
||||
"ID": "2ccc588f-efc4-0a7c-1a73-c25cfcf34b94",
|
||||
"Name": "web",
|
||||
"State": "ACTIVE",
|
||||
"ImportedServiceCount": 10,
|
||||
"ExportedServiceCount": 3,
|
||||
"CreateIndex": 18,
|
||||
"ModifyIndex": 18
|
||||
},
|
||||
{
|
||||
"ID": "a25cdcc4-9e09-5276-bcd7-e2e4743ca687",
|
||||
"Name": "billing",
|
||||
"State": "PENDING",
|
||||
"ImportedServiceCount": 5,
|
||||
"ExportedServiceCount": 2,
|
||||
"ID": "${fake.random.uuid()}-${i}",
|
||||
"Name": "${fake.hacker.noun()}-peer-${i}",
|
||||
"State": "${fake.helpers.randomize([
|
||||
'ACTIVE',
|
||||
'PENDING',
|
||||
'ESTABLISHING',
|
||||
'FAILING',
|
||||
'DELETING',
|
||||
'TERMINATED',
|
||||
'UNDEFINED'
|
||||
])}",
|
||||
"ImportedServiceCount": ${fake.random.number({min: 0, max: 4000})},
|
||||
"ExportedServiceCount": ${fake.random.number({min: 0, max: 4000})},
|
||||
"CreateIndex": 16,
|
||||
"ModifyIndex": 16
|
||||
},
|
||||
{
|
||||
"ID": "a25cdcc4-9e09-5276-bcd7-e2e4743ca688",
|
||||
"Name": "peer-1",
|
||||
"State": "ESTABLISHING",
|
||||
"ImportedServiceCount": 2,
|
||||
"ExportedServiceCount": 4,
|
||||
"CreateIndex": 16,
|
||||
"ModifyIndex": 16
|
||||
},
|
||||
{
|
||||
"ID": "2ccc588f-efc4-0a7c-1a73-c25cfcf34b95",
|
||||
"Name": "db",
|
||||
"State": "FAILING",
|
||||
"ImportedServiceCount": 4,
|
||||
"ExportedServiceCount": 3,
|
||||
"CreateIndex": 19,
|
||||
"ModifyIndex": 19
|
||||
},
|
||||
{
|
||||
"ID": "2ccc588f-efc4-0a7c-1a73-c25cfcf34b98",
|
||||
"Name": "legacy deleted",
|
||||
"State": "DELETING",
|
||||
"ImportedServiceCount": 2,
|
||||
"ExportedServiceCount": 4,
|
||||
"CreateIndex": 20,
|
||||
"ModifyIndex": 20
|
||||
},
|
||||
{
|
||||
"ID": "2ccc588f-efc4-0a7c-1a73-c25cfcf34b96",
|
||||
"Name": "legacy",
|
||||
"State": "TERMINATED",
|
||||
"ImportedServiceCount": 0,
|
||||
"ExportedServiceCount": 0,
|
||||
"CreateIndex": 20,
|
||||
"ModifyIndex": 20
|
||||
},
|
||||
{
|
||||
"ID": "2ccc588f-efc4-0a7c-1a73-c25cfcf34b97",
|
||||
"Name": "legacy undefined",
|
||||
"ImportedServiceCount": 0,
|
||||
"ExportedServiceCount": 0,
|
||||
"State": "UNDEFINED",
|
||||
"CreateIndex": 20,
|
||||
"ModifyIndex": 20
|
||||
}
|
||||
|
||||
`)}
|
||||
]
|
||||
|
|
|
@ -123,7 +123,6 @@
|
|||
"ember-in-viewport": "^3.8.1",
|
||||
"ember-inflector": "^4.0.1",
|
||||
"ember-intl": "^5.5.1",
|
||||
"ember-keyboard": "^7.0.1",
|
||||
"ember-load-initializers": "^2.1.1",
|
||||
"ember-math-helpers": "^2.4.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
|
|
|
@ -48,6 +48,11 @@ module('Unit | Ability | *', function(hooks) {
|
|||
ID: bool ? 'not-default' : 'default',
|
||||
};
|
||||
break;
|
||||
case 'peer':
|
||||
ability.item = {
|
||||
State: bool ? 'ACTIVE' : 'DELETING',
|
||||
};
|
||||
break;
|
||||
case 'kv':
|
||||
// TODO: We currently hardcode KVs to always be true
|
||||
assert.equal(true, ability[`can${perm}`], `Expected ${item}.can${perm} to be true`);
|
||||
|
|
|
@ -41,6 +41,16 @@
|
|||
path: '/',
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
_options: {
|
||||
path: '/:name'
|
||||
},
|
||||
addresses: {
|
||||
_options: {
|
||||
path: '/addresses',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
|
|
65
ui/yarn.lock
65
ui/yarn.lock
|
@ -2944,13 +2944,6 @@ ansi-styles@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
|
||||
integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
|
||||
|
||||
ansi-to-html@^0.6.15:
|
||||
version "0.6.15"
|
||||
resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.15.tgz#ac6ad4798a00f6aa045535d7f6a9cb9294eebea7"
|
||||
integrity sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ==
|
||||
dependencies:
|
||||
entities "^2.0.0"
|
||||
|
||||
ansi-to-html@^0.6.6:
|
||||
version "0.6.14"
|
||||
resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8"
|
||||
|
@ -6654,27 +6647,6 @@ ember-cli-htmlbars@^6.0.0:
|
|||
strip-bom "^4.0.0"
|
||||
walk-sync "^2.2.0"
|
||||
|
||||
ember-cli-htmlbars@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-6.0.1.tgz#5487831d477e61682bc867fd138808269e5d2152"
|
||||
integrity sha512-IDsl9uty+MXtMfp/BUTEc/Q36EmlHYj8ZdPekcoRa8hmdsigHnK4iokfaB7dJFktlf6luruei+imv7JrJrBQPQ==
|
||||
dependencies:
|
||||
"@ember/edition-utils" "^1.2.0"
|
||||
babel-plugin-ember-template-compilation "^1.0.0"
|
||||
babel-plugin-htmlbars-inline-precompile "^5.3.0"
|
||||
broccoli-debug "^0.6.5"
|
||||
broccoli-persistent-filter "^3.1.2"
|
||||
broccoli-plugin "^4.0.3"
|
||||
ember-cli-version-checker "^5.1.2"
|
||||
fs-tree-diff "^2.0.1"
|
||||
hash-for-dep "^1.5.1"
|
||||
heimdalljs-logger "^0.1.10"
|
||||
json-stable-stringify "^1.0.1"
|
||||
semver "^7.3.4"
|
||||
silent-error "^1.1.1"
|
||||
strip-bom "^4.0.0"
|
||||
walk-sync "^2.2.0"
|
||||
|
||||
ember-cli-inject-live-reload@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-inject-live-reload/-/ember-cli-inject-live-reload-2.0.2.tgz#95edb543b386239d35959e5ea9579f5382976ac7"
|
||||
|
@ -6873,22 +6845,6 @@ ember-cli-typescript@^4.0.0, ember-cli-typescript@^4.1.0:
|
|||
stagehand "^1.0.0"
|
||||
walk-sync "^2.2.0"
|
||||
|
||||
ember-cli-typescript@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.1.0.tgz#460eb848564e29d64f2b36b2a75bbe98172b72a4"
|
||||
integrity sha512-wEZfJPkjqFEZAxOYkiXsDrJ1HY75e/6FoGhQFg8oNFJeGYpIS/3W0tgyl1aRkSEEN1NRlWocDubJ4aZikT+RTA==
|
||||
dependencies:
|
||||
ansi-to-html "^0.6.15"
|
||||
broccoli-stew "^3.0.0"
|
||||
debug "^4.0.0"
|
||||
execa "^4.0.0"
|
||||
fs-extra "^9.0.1"
|
||||
resolve "^1.5.0"
|
||||
rsvp "^4.8.1"
|
||||
semver "^7.3.2"
|
||||
stagehand "^1.0.0"
|
||||
walk-sync "^2.2.0"
|
||||
|
||||
ember-cli-uglify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-uglify/-/ember-cli-uglify-3.0.0.tgz#8819665b2cc5fe70e3ba9fe7a94645209bc42fd6"
|
||||
|
@ -7291,16 +7247,6 @@ ember-intl@^5.5.1:
|
|||
mkdirp "^1.0.4"
|
||||
silent-error "^1.1.1"
|
||||
|
||||
ember-keyboard@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-keyboard/-/ember-keyboard-7.0.1.tgz#6cf336efd4ea6cb69ec93d20fb0b819bd7241a9d"
|
||||
integrity sha512-MKK9/3yzn30ekmFAQO7z+okCQa7Z5wCSI5m7lR3EL2dMIeRd/9eeLhbQNCU00Slx+GjwsGyCEWPqIQmekFJxpQ==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.26.6"
|
||||
ember-cli-htmlbars "^6.0.1"
|
||||
ember-modifier "^2.1.2 || ^3.0.0"
|
||||
ember-modifier-manager-polyfill "^1.2.0"
|
||||
|
||||
ember-load-initializers@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.2.tgz#8a47a656c1f64f9b10cecdb4e22a9d52ad9c7efa"
|
||||
|
@ -7359,17 +7305,6 @@ ember-modifier@^2.1.0, ember-modifier@^2.1.1:
|
|||
ember-destroyable-polyfill "^2.0.2"
|
||||
ember-modifier-manager-polyfill "^1.2.0"
|
||||
|
||||
"ember-modifier@^2.1.2 || ^3.0.0":
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-3.2.7.tgz#f2d35b7c867cbfc549e1acd8d8903c5ecd02ea4b"
|
||||
integrity sha512-ezcPQhH8jUfcJQbbHji4/ZG/h0yyj1jRDknfYue/ypQS8fM8LrGcCMo0rjDZLzL1Vd11InjNs3BD7BdxFlzGoA==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.26.6"
|
||||
ember-cli-normalize-entity-name "^1.0.0"
|
||||
ember-cli-string-utils "^1.1.0"
|
||||
ember-cli-typescript "^5.0.0"
|
||||
ember-compatibility-helpers "^1.2.5"
|
||||
|
||||
ember-named-blocks-polyfill@^0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ember-named-blocks-polyfill/-/ember-named-blocks-polyfill-0.2.4.tgz#f5f30711ee89244927b55aae7fa9630edaadc974"
|
||||
|
|
Loading…
Reference in New Issue