ui: Ensure dc selector correctly shows the currently selected dc (#11380)
* ui: Ensure dc selector correctly shows the currently selected dc * ui: Restrict access to non-default partitions in non-primaries (#11420) This PR restricts access via the UI to only the default partition when in a non-primary datacenter i.e. you can only have multiple (non-default) partitions in the primary datacenter.
This commit is contained in:
parent
d764bac6af
commit
4dd7e34c96
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
ui: Ensure dc selector correctly shows the currently selected dc
|
||||||
|
```
|
|
@ -1,4 +1,4 @@
|
||||||
{{#if (can "choose partitions")}}
|
{{#if (can "choose partitions" dc=@dc)}}
|
||||||
<li
|
<li
|
||||||
class="partitions"
|
class="partitions"
|
||||||
data-test-partition-menu
|
data-test-partition-menu
|
||||||
|
@ -49,5 +49,12 @@
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</PopoverMenu>
|
</PopoverMenu>
|
||||||
</li>
|
</li>
|
||||||
|
{{else}}
|
||||||
|
<li
|
||||||
|
class="partition"
|
||||||
|
aria-label="Admin Partition"
|
||||||
|
>
|
||||||
|
{{@partition}}
|
||||||
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,10 @@ export default class PartitionAbility extends BaseAbility {
|
||||||
}
|
}
|
||||||
|
|
||||||
get canChoose() {
|
get canChoose() {
|
||||||
return this.canUse;
|
if(typeof this.dc === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.canUse && this.dc.Primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canUse() {
|
get canUse() {
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'choose partitions')}}
|
{{#if (can 'choose partitions' dc=@dc)}}
|
||||||
<label data-test-source-partition class="type-select{{if item.error.SourcePartition ' has-error'}}">
|
<label data-test-source-partition class="type-select{{if item.error.SourcePartition ' has-error'}}">
|
||||||
<span>Source Partition</span>
|
<span>Source Partition</span>
|
||||||
<PowerSelectWithCreate
|
<PowerSelectWithCreate
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'choose partitions')}}
|
{{#if (can 'choose partitions' dc=@dc)}}
|
||||||
<label data-test-destination-partition class="type-select{{if item.error.DestinationPartition ' has-error'}}">
|
<label data-test-destination-partition class="type-select{{if item.error.DestinationPartition ' has-error'}}">
|
||||||
<span>Destination Partition</span>
|
<span>Destination Partition</span>
|
||||||
<PowerSelectWithCreate
|
<PowerSelectWithCreate
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
>
|
>
|
||||||
<DataForm
|
<DataForm
|
||||||
@type="intention"
|
@type="intention"
|
||||||
@dc={{@dc}}
|
@dc={{@dc.Name}}
|
||||||
@nspace={{@nspace}}
|
@nspace={{@nspace}}
|
||||||
@partition={{@partition}}
|
@partition={{@partition}}
|
||||||
@autofill={{@autofill}}
|
@autofill={{@autofill}}
|
||||||
|
@ -77,7 +77,7 @@ as |api|>
|
||||||
@src={{uri '/${partition}/*/${dc}/services'
|
@src={{uri '/${partition}/*/${dc}/services'
|
||||||
(hash
|
(hash
|
||||||
partition=@partition
|
partition=@partition
|
||||||
dc=@dc
|
dc=@dc.Name
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{action this.createServices item}}
|
@onchange={{action this.createServices item}}
|
||||||
|
@ -88,7 +88,7 @@ as |api|>
|
||||||
@src={{uri '/${partition}/*/${dc}/namespaces'
|
@src={{uri '/${partition}/*/${dc}/namespaces'
|
||||||
(hash
|
(hash
|
||||||
partition=@partition
|
partition=@partition
|
||||||
dc=@dc
|
dc=@dc.Name
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{action this.createNspaces item}}
|
@onchange={{action this.createNspaces item}}
|
||||||
|
@ -99,7 +99,7 @@ as |api|>
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri '/*/*/${dc}/partitions'
|
@src={{uri '/*/*/${dc}/partitions'
|
||||||
(hash
|
(hash
|
||||||
dc=@dc
|
dc=@dc.Name
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{action this.createPartitions item}}
|
@onchange={{action this.createPartitions item}}
|
||||||
|
@ -114,6 +114,7 @@ as |api|>
|
||||||
>
|
>
|
||||||
<Consul::Intention::Form::Fieldsets
|
<Consul::Intention::Form::Fieldsets
|
||||||
@nspaces={{this.nspaces}}
|
@nspaces={{this.nspaces}}
|
||||||
|
@dc={{@dc}}
|
||||||
@partitions={{this.partitions}}
|
@partitions={{this.partitions}}
|
||||||
@services={{this.services}}
|
@services={{this.services}}
|
||||||
@SourceName={{this.SourceName}}
|
@SourceName={{this.SourceName}}
|
||||||
|
|
|
@ -33,11 +33,18 @@
|
||||||
{{#each (sort-by 'Name' @dcs) as |item|}}
|
{{#each (sort-by 'Name' @dcs) as |item|}}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test-datacenter-picker
|
data-test-datacenter-picker
|
||||||
class={{concat (if (eq @dc.Name item.Name) 'is-active') (if item.Local ' is-local') }}
|
class={{concat
|
||||||
|
(if (eq @dc.Name item.Name) 'is-active')
|
||||||
|
(if item.Local ' is-local')
|
||||||
|
(if item.Primary ' is-primary')
|
||||||
|
}}
|
||||||
@href={{href-to '.' params=(hash dc=item.Name)}}
|
@href={{href-to '.' params=(hash dc=item.Name)}}
|
||||||
>
|
>
|
||||||
<BlockSlot @name="label">
|
<BlockSlot @name="label">
|
||||||
{{item.Name}}
|
{{item.Name}}
|
||||||
|
{{#if item.Primary}}
|
||||||
|
<span>Primary</span>
|
||||||
|
{{/if}}
|
||||||
{{#if item.Local}}
|
{{#if item.Local}}
|
||||||
<span>Local</span>
|
<span>Local</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.hashicorp-consul {
|
%hashicorp-consul {
|
||||||
[role='banner'] nav .dcs {
|
[role='banner'] nav .dcs {
|
||||||
@extend %main-nav-vertical-hoisted;
|
@extend %main-nav-vertical-hoisted;
|
||||||
left: 100px;
|
left: 100px;
|
||||||
|
@ -27,3 +27,6 @@
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hashicorp-consul {
|
||||||
|
@extend %hashicorp-consul;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
@import './skin';
|
@import './skin';
|
||||||
@import './layout';
|
@import './layout';
|
||||||
/* things that should look like nav buttons */
|
/* things that should look like nav buttons */
|
||||||
|
|
||||||
|
/* items are single things that look like button */
|
||||||
|
/* but aren't clickable */
|
||||||
|
%main-nav-vertical > ul > li[aria-label] {
|
||||||
|
@extend %main-nav-vertical-item;
|
||||||
|
}
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/* actual clickable button-y things plus states */
|
||||||
%main-nav-vertical > ul > li > a {
|
%main-nav-vertical > ul > li > a {
|
||||||
@extend %main-nav-vertical-action;
|
@extend %main-nav-vertical-action;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +30,8 @@
|
||||||
%main-nav-vertical > ul > li > label {
|
%main-nav-vertical > ul > li > label {
|
||||||
@extend %main-nav-vertical-action;
|
@extend %main-nav-vertical-action;
|
||||||
}
|
}
|
||||||
|
/**/
|
||||||
|
|
||||||
%main-nav-vertical .popover-menu {
|
%main-nav-vertical .popover-menu {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,15 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
%main-nav-vertical li.partitions,
|
%main-nav-vertical li.partitions,
|
||||||
%main-nav-vertical li.nspaces,
|
%main-nav-vertical li.partition,
|
||||||
%main-nav-vertical li.dcs {
|
%main-nav-vertical li.nspaces {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
padding: 0 26px;
|
padding: 0 26px;
|
||||||
}
|
}
|
||||||
|
%main-nav-vertical li.dcs {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
// TODO: We no longer have the rule that menu-panel buttons only contain two
|
// TODO: We no longer have the rule that menu-panel buttons only contain two
|
||||||
// items, left and right aligned. We should remove this and look to use
|
// items, left and right aligned. We should remove this and look to use
|
||||||
// align-self for anything that needs right aligning instead.
|
// align-self for anything that needs right aligning instead.
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
%main-nav-vertical-action {
|
%main-nav-vertical-action {
|
||||||
color: rgb(var(--tone-gray-800));
|
color: rgb(var(--tone-gray-800));
|
||||||
}
|
}
|
||||||
|
%main-nav-vertical-item,
|
||||||
%main-nav-vertical-action-intent,
|
%main-nav-vertical-action-intent,
|
||||||
%main-nav-vertical-action-active {
|
%main-nav-vertical-action-active {
|
||||||
color: rgb(var(--tone-gray-999));
|
color: rgb(var(--tone-gray-999));
|
||||||
|
@ -40,13 +41,16 @@
|
||||||
background-color: rgb(var(--tone-gray-150));
|
background-color: rgb(var(--tone-gray-150));
|
||||||
border-color: rgb(var(--tone-gray-999));
|
border-color: rgb(var(--tone-gray-999));
|
||||||
}
|
}
|
||||||
|
%main-nav-vertical li[aria-label]::before,
|
||||||
%main-nav-vertical .popover-menu[aria-label]::before {
|
%main-nav-vertical .popover-menu[aria-label]::before {
|
||||||
|
color: rgb(var(--tone-gray-700));
|
||||||
content: attr(aria-label);
|
content: attr(aria-label);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: -0.5rem;
|
margin-top: -0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
%main-nav-vertical .is-local span:last-of-type {
|
%main-nav-vertical .is-primary span,
|
||||||
|
%main-nav-vertical .is-local span {
|
||||||
@extend %pill-200;
|
@extend %pill-200;
|
||||||
color: rgb(var(--tone-gray-000));
|
color: rgb(var(--tone-gray-000));
|
||||||
background-color: rgb(var(--tone-gray-500));
|
background-color: rgb(var(--tone-gray-500));
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{{will-destroy this.disconnect}}
|
{{will-destroy this.disconnect}}
|
||||||
<section
|
<section
|
||||||
{{did-insert (fn this.attributeChanged 'element')}}
|
{{did-insert (fn this.attributeChanged 'element')}}
|
||||||
|
{{did-update (fn this.attributeChanged 'model' @model)}}
|
||||||
class="outlet"
|
class="outlet"
|
||||||
data-outlet={{@name}}
|
data-outlet={{@name}}
|
||||||
data-route={{this.routeName}}
|
data-route={{this.routeName}}
|
||||||
|
|
|
@ -22,6 +22,8 @@ export default class Outlet extends Component {
|
||||||
@tracked previousState;
|
@tracked previousState;
|
||||||
@tracked endTransition;
|
@tracked endTransition;
|
||||||
|
|
||||||
|
@tracked route;
|
||||||
|
|
||||||
get model() {
|
get model() {
|
||||||
return this.args.model || {};
|
return this.args.model || {};
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,9 @@ export default class Outlet extends Component {
|
||||||
this.setAppRoute(this.router.currentRouteName);
|
this.setAppRoute(this.router.currentRouteName);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'model':
|
||||||
|
this.route._model = this.args.model;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,8 @@ import Helper from '@ember/component/helper';
|
||||||
import { getOwner } from '@ember/application';
|
import { getOwner } from '@ember/application';
|
||||||
|
|
||||||
export default class CachedHelper extends Helper {
|
export default class CachedHelper extends Helper {
|
||||||
compute([model], hash) {
|
compute([model, params], hash) {
|
||||||
return () => {
|
const container = getOwner(this);
|
||||||
const container = getOwner(this);
|
return container.lookup(`service:repository/${model}`).cached(params);
|
||||||
return container.lookup(`service:repository/${model}`).cached(hash);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default class Datacenter extends Model {
|
||||||
@attr('string') uid;
|
@attr('string') uid;
|
||||||
@attr('string') Name;
|
@attr('string') Name;
|
||||||
@attr('boolean') Local;
|
@attr('boolean') Local;
|
||||||
|
@attr('boolean') Primary;
|
||||||
@attr('string') DefaultACLPolicy;
|
@attr('string') DefaultACLPolicy;
|
||||||
|
|
||||||
@attr('boolean', { defaultValue: () => true }) MeshEnabled;
|
@attr('boolean', { defaultValue: () => true }) MeshEnabled;
|
||||||
|
|
|
@ -1,31 +1,42 @@
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import Serializer from './application';
|
import Serializer from './application';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/dc';
|
||||||
|
|
||||||
|
import {
|
||||||
|
HEADERS_SYMBOL,
|
||||||
|
HEADERS_DEFAULT_ACL_POLICY as DEFAULT_ACL_POLICY,
|
||||||
|
} from 'consul-ui/utils/http/consul';
|
||||||
export default class DcSerializer extends Serializer {
|
export default class DcSerializer extends Serializer {
|
||||||
@service('env') env;
|
@service('env') env;
|
||||||
|
|
||||||
primaryKey = 'Name';
|
primaryKey = PRIMARY_KEY;
|
||||||
|
slugKey = SLUG_KEY;
|
||||||
|
|
||||||
|
// datacenters come in as an array of plain strings. Convert to objects
|
||||||
|
// instead and collect all the other datacenter info from other places and
|
||||||
|
// add it to each datacenter object
|
||||||
respondForQuery(respond, query) {
|
respondForQuery(respond, query) {
|
||||||
return respond(function(headers, body) {
|
return super.respondForQuery(
|
||||||
return {
|
cb => respond((headers, body) => {
|
||||||
body,
|
body = body.map(item => ({
|
||||||
headers,
|
Datacenter: '',
|
||||||
};
|
[this.slugKey]: item,
|
||||||
});
|
}));
|
||||||
}
|
body = cb(headers, body);
|
||||||
|
headers = body[HEADERS_SYMBOL];
|
||||||
|
|
||||||
normalizePayload(payload, id, requestType) {
|
const Local = this.env.var('CONSUL_DATACENTER_LOCAL');
|
||||||
switch (requestType) {
|
const Primary = this.env.var('CONSUL_DATACENTER_PRIMARY');
|
||||||
case 'query':
|
const DefaultACLPolicy = headers[DEFAULT_ACL_POLICY.toLowerCase()];
|
||||||
return payload.body.map(item => {
|
|
||||||
return {
|
return body.map(item => ({
|
||||||
Local: this.env.var('CONSUL_DATACENTER_LOCAL') === item,
|
...item,
|
||||||
[this.primaryKey]: item,
|
Local: item.Name === Local,
|
||||||
DefaultACLPolicy: payload.headers['x-consul-default-acl-policy'],
|
Primary: item.Name === Primary,
|
||||||
};
|
DefaultACLPolicy: DefaultACLPolicy,
|
||||||
});
|
}));
|
||||||
}
|
}),
|
||||||
return payload;
|
query
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,10 @@ export default class RepositoryService extends Service {
|
||||||
return this.store.peekRecord(this.getModelName(), id);
|
return this.store.peekRecord(this.getModelName(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peekAll() {
|
||||||
|
return this.store.peekAll(this.getModelName());
|
||||||
|
}
|
||||||
|
|
||||||
cached(params) {
|
cached(params) {
|
||||||
const entries = Object.entries(params);
|
const entries = Object.entries(params);
|
||||||
return this.store.peekAll(this.getModelName()).filter(item => {
|
return this.store.peekAll(this.getModelName()).filter(item => {
|
||||||
|
|
|
@ -15,8 +15,10 @@ export default class DiscoveryChainService extends RepositoryService {
|
||||||
|
|
||||||
@dataSource('/:partition/:ns/:dc/discovery-chain/:id')
|
@dataSource('/:partition/:ns/:dc/discovery-chain/:id')
|
||||||
findBySlug(params, configuration = {}) {
|
findBySlug(params, configuration = {}) {
|
||||||
const datacenter = this.dcs.peekOne(params.dc);
|
// peekAll and find is fine here as datacenter count should be relatively
|
||||||
if (datacenter !== null && !get(datacenter, 'MeshEnabled')) {
|
// low, and DCs are the top bucket (when talking dc's partitions, nspaces)
|
||||||
|
const datacenter = this.dcs.peekAll().findBy('Name', params.dc);
|
||||||
|
if (typeof datacenter !== 'undefined' && !get(datacenter, 'MeshEnabled')) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return super.findBySlug(...arguments).catch(e => {
|
return super.findBySlug(...arguments).catch(e => {
|
||||||
|
@ -24,7 +26,7 @@ export default class DiscoveryChainService extends RepositoryService {
|
||||||
const body = (get(e, 'errors.firstObject.detail') || '').trim();
|
const body = (get(e, 'errors.firstObject.detail') || '').trim();
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case '500':
|
case '500':
|
||||||
if (datacenter !== null && body.endsWith(ERROR_MESH_DISABLED)) {
|
if (typeof datacenter !== 'undefined' && body.endsWith(ERROR_MESH_DISABLED)) {
|
||||||
set(datacenter, 'MeshEnabled', false);
|
set(datacenter, 'MeshEnabled', false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -135,6 +135,7 @@ export default class RoutletService extends Service {
|
||||||
const outlet = outlets.get(keys[key]);
|
const outlet = outlets.get(keys[key]);
|
||||||
if (typeof outlet !== 'undefined') {
|
if (typeof outlet !== 'undefined') {
|
||||||
route._model = outlet.model;
|
route._model = outlet.model;
|
||||||
|
outlet.route = route;
|
||||||
// TODO: Try to avoid the double computation bug
|
// TODO: Try to avoid the double computation bug
|
||||||
schedule('afterRender', () => {
|
schedule('afterRender', () => {
|
||||||
outlet.routeName = route.args.name;
|
outlet.routeName = route.args.name;
|
||||||
|
|
|
@ -73,39 +73,45 @@ as |dcs|>
|
||||||
{{! Once we have a list of DCs make sure the DC we are asking for exists }}
|
{{! Once we have a list of DCs make sure the DC we are asking for exists }}
|
||||||
{{! If not use the DC that the UI is running in }}
|
{{! If not use the DC that the UI is running in }}
|
||||||
{{#let
|
{{#let
|
||||||
|
|
||||||
(or
|
(or
|
||||||
|
|
||||||
(get (object-at 0 (cached-model
|
(if nofound.dc
|
||||||
'dc'
|
(object-at 0 (cached-model
|
||||||
(hash
|
'dc'
|
||||||
Name=notfound.dc
|
(hash
|
||||||
|
Name=notfound.dc
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)) 'Name')
|
)
|
||||||
|
)
|
||||||
|
|
||||||
(get (object-at 0 (cached-model
|
(object-at 0 (cached-model
|
||||||
'dc'
|
'dc'
|
||||||
(hash
|
(hash
|
||||||
Name=route.params.dc
|
Name=route.params.dc
|
||||||
)
|
)
|
||||||
)) 'Name')
|
)
|
||||||
|
)
|
||||||
|
|
||||||
(env "CONSUL_DATACENTER_LOCAL")
|
(hash
|
||||||
|
Name=(env "CONSUL_DATACENTER_LOCAL")
|
||||||
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dcs.data
|
dcs.data
|
||||||
|
|
||||||
as |dc dcs|}}
|
as |dc dcs|}}
|
||||||
|
{{#if (and (gt dc.Name.length 0) dcs nspace partition)}}
|
||||||
{{#if (and (gt dc.length 0) dcs nspace partition)}}
|
|
||||||
|
|
||||||
{{! figure out our current DC and convert it to a model }}
|
{{! figure out our current DC and convert it to a model }}
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri '/${partition}/*/${dc}/datacenter/${name}'
|
@src={{uri '/${partition}/*/${dc}/datacenter/${name}'
|
||||||
(hash
|
(hash
|
||||||
dc=dc
|
dc=dc.Name
|
||||||
partition=partition
|
partition=partition
|
||||||
name=dc
|
name=dc.Name
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
as |dc|>
|
as |dc|>
|
||||||
|
|
|
@ -45,7 +45,7 @@ as |item|}}
|
||||||
<BlockSlot @name="content">
|
<BlockSlot @name="content">
|
||||||
<Consul::Intention::Form
|
<Consul::Intention::Form
|
||||||
@item={{item}}
|
@item={{item}}
|
||||||
@dc={{route.params.dc}}
|
@dc={{route.model.dc}}
|
||||||
@nspace={{route.params.nspace}}
|
@nspace={{route.params.nspace}}
|
||||||
@partition={{route.params.partition}}
|
@partition={{route.params.partition}}
|
||||||
@onsubmit={{route-action 'transitionTo' 'dc.intentions.index'
|
@onsubmit={{route-action 'transitionTo' 'dc.intentions.index'
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<Route
|
<Route
|
||||||
@name={{routeName}}
|
@name={{routeName}}
|
||||||
as |route|>
|
as |route|>
|
||||||
|
|
||||||
<DataLoader
|
<DataLoader
|
||||||
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
|
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
|
||||||
(hash
|
(hash
|
||||||
|
@ -69,7 +68,7 @@ as |items item dc|}}
|
||||||
{{! and use this to set MeshEnabled on the Datacenter }}
|
{{! and use this to set MeshEnabled on the Datacenter }}
|
||||||
{{! if once chain is set, i.e. we've checked this dc we remove the DataSource }}
|
{{! if once chain is set, i.e. we've checked this dc we remove the DataSource }}
|
||||||
{{! which will mark it for closure, which possibly could be reopened if }}
|
{{! which will mark it for closure, which possibly could be reopened if }}
|
||||||
{{! the user clicks the routing/disco-chain tab}}
|
{{! the user clicks the routing/disco-chain tab}}
|
||||||
{{#if (not chain)}}
|
{{#if (not chain)}}
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri '/${partition}/${nspace}/${dc}/discovery-chain/${name}'
|
@src={{uri '/${partition}/${nspace}/${dc}/discovery-chain/${name}'
|
||||||
|
@ -83,6 +82,7 @@ as |items item dc|}}
|
||||||
@onchange={{action (mut chain) value="data"}}
|
@onchange={{action (mut chain) value="data"}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{did-insert (set this 'chain' undefined) route.params.dc}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#let
|
{{#let
|
||||||
(hash
|
(hash
|
||||||
|
|
|
@ -2,10 +2,11 @@ import { get } from '@ember/object';
|
||||||
|
|
||||||
export default function(foreignKey, nspaceKey, partitionKey, hash = JSON.stringify) {
|
export default function(foreignKey, nspaceKey, partitionKey, hash = JSON.stringify) {
|
||||||
return function(primaryKey, slugKey, foreignKeyValue, nspaceValue, partitionValue) {
|
return function(primaryKey, slugKey, foreignKeyValue, nspaceValue, partitionValue) {
|
||||||
if (foreignKeyValue == null || foreignKeyValue.length < 1) {
|
|
||||||
throw new Error('Unable to create fingerprint, missing foreignKey value');
|
|
||||||
}
|
|
||||||
return function(item) {
|
return function(item) {
|
||||||
|
foreignKeyValue = foreignKeyValue == null ? item[foreignKey] : foreignKeyValue;
|
||||||
|
if (foreignKeyValue == null) {
|
||||||
|
throw new Error('Unable to create fingerprint, missing foreignKey value');
|
||||||
|
}
|
||||||
const slugKeys = slugKey.split(',');
|
const slugKeys = slugKey.split(',');
|
||||||
const slugValues = slugKeys.map(function(slugKey) {
|
const slugValues = slugKeys.map(function(slugKey) {
|
||||||
if (get(item, slugKey) == null || get(item, slugKey).length < 1) {
|
if (get(item, slugKey) == null || get(item, slugKey).length < 1) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
export const HEADERS_PARTITION = 'X-Consul-Partition';
|
export const HEADERS_PARTITION = 'X-Consul-Partition';
|
||||||
export const HEADERS_NAMESPACE = 'X-Consul-Namespace';
|
export const HEADERS_NAMESPACE = 'X-Consul-Namespace';
|
||||||
export const HEADERS_DATACENTER = 'X-Consul-Datacenter';
|
export const HEADERS_DATACENTER = 'X-Consul-Datacenter';
|
||||||
|
export const HEADERS_DEFAULT_ACL_POLICY = 'X-Consul-Default-Acl-Policy';
|
||||||
export const HEADERS_INDEX = 'X-Consul-Index';
|
export const HEADERS_INDEX = 'X-Consul-Index';
|
||||||
export const HEADERS_TOKEN = 'X-Consul-Token';
|
export const HEADERS_TOKEN = 'X-Consul-Token';
|
||||||
export const HEADERS_DIGEST = 'X-Consul-ContentHash';
|
export const HEADERS_DIGEST = 'X-Consul-ContentHash';
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
"*":
|
"*":
|
||||||
headers:
|
headers:
|
||||||
response:
|
response:
|
||||||
x-consul-default-acl-policy: ${env('CONSUL_ACL_POLICY', fake.helpers.randomize(['allow', 'deny']))}
|
X-Consul-Default-Acl-Policy: ${env('CONSUL_ACL_POLICY', fake.helpers.randomize(['allow', 'deny']))}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
Feature: dc / services / instances / gateway: Show Gateway Service Instance
|
Feature: dc / services / instances / gateway: Show Gateway Service Instance
|
||||||
Scenario: A Gateway Service instance
|
Scenario: A Gateway Service instance
|
||||||
Given 1 datacenter model with the value "dc1"
|
Given 1 datacenter model with the value "dc1"
|
||||||
Given 1 proxy model from yaml
|
Given 1 proxy model from yaml
|
||||||
---
|
---
|
||||||
- ServiceProxy:
|
- ServiceProxy:
|
||||||
DestinationServiceName: service-1
|
DestinationServiceName: service-1
|
||||||
DestinationServiceID: ~
|
DestinationServiceID: ~
|
||||||
---
|
---
|
||||||
And 1 instance model from yaml
|
And 1 instance model from yaml
|
||||||
---
|
---
|
||||||
|
@ -36,7 +36,7 @@ Feature: dc / services / instances / gateway: Show Gateway Service Instance
|
||||||
When I click addresses on the tabs
|
When I click addresses on the tabs
|
||||||
And I see addressesIsSelected on the tabs
|
And I see addressesIsSelected on the tabs
|
||||||
And I see 2 of the addresses object
|
And I see 2 of the addresses object
|
||||||
And I see address on the addresses like yaml
|
And I see address on the addresses vertically like yaml
|
||||||
---
|
---
|
||||||
- 127.0.0.1:8080
|
- 127.0.0.1:8080
|
||||||
- 92.68.0.0:8081
|
- 92.68.0.0:8081
|
||||||
|
|
|
@ -44,18 +44,19 @@ Feature: dc / services / show-routing: Show Routing for Service
|
||||||
---
|
---
|
||||||
And I don't see routing on the tabs
|
And I don't see routing on the tabs
|
||||||
And I don't see the "[data-test-error]" element
|
And I don't see the "[data-test-error]" element
|
||||||
# FIXME: Does one dc not having connect and another having connect ever actually happen
|
# Not entirely sure if having one dc not having connect
|
||||||
# And I visit the service page for yaml
|
# and another having connect ever actually happen
|
||||||
# ---
|
And I visit the service page for yaml
|
||||||
# dc: dc2
|
---
|
||||||
# service: service-1
|
dc: dc2
|
||||||
# ---
|
service: service-1
|
||||||
# And I see routing on the tabs
|
---
|
||||||
# And I visit the service page for yaml
|
And I see routing on the tabs
|
||||||
# ---
|
And I visit the service page for yaml
|
||||||
# dc: dc1
|
---
|
||||||
# service: service-0
|
dc: dc1
|
||||||
# ---
|
service: service-0
|
||||||
# Then a GET request wasn't made to "/v1/discovery-chain/service-0?dc=dc1&ns=@namespace"
|
---
|
||||||
# And I don't see routing on the tabs
|
Then a GET request wasn't made to "/v1/discovery-chain/service-0?dc=dc1&ns=@namespace"
|
||||||
# And I don't see the "[data-test-error]" element
|
And I don't see routing on the tabs
|
||||||
|
And I don't see the "[data-test-error]" element
|
||||||
|
|
|
@ -1,24 +1,43 @@
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
import { get } from 'consul-ui/tests/helpers/api';
|
import { get } from 'consul-ui/tests/helpers/api';
|
||||||
|
import {
|
||||||
|
HEADERS_DEFAULT_ACL_POLICY as DEFAULT_ACL_POLICY,
|
||||||
|
} from 'consul-ui/utils/http/consul';
|
||||||
module('Integration | Serializer | dc', function(hooks) {
|
module('Integration | Serializer | dc', function(hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
test('respondForQuery returns the correct data for list endpoint', function(assert) {
|
test('respondForQuery returns the correct data for list endpoint', function(assert) {
|
||||||
const serializer = this.owner.lookup('serializer:dc');
|
const serializer = this.owner.lookup('serializer:dc');
|
||||||
|
let env = this.owner.lookup('service:env');
|
||||||
|
env = env.var.bind(env);
|
||||||
const request = {
|
const request = {
|
||||||
url: `/v1/catalog/datacenters`,
|
url: `/v1/catalog/datacenters`,
|
||||||
};
|
};
|
||||||
return get(request.url).then(function(payload) {
|
return get(request.url).then(function(payload) {
|
||||||
const expected = {
|
const ALLOW = 'allow';
|
||||||
body: payload,
|
const expected = payload.map(item => (
|
||||||
headers: {},
|
{
|
||||||
};
|
Name: item,
|
||||||
|
Datacenter: '',
|
||||||
|
Local: item === env('CONSUL_DATACENTER_LOCAL'),
|
||||||
|
Primary: item === env('CONSUL_DATACENTER_PRIMARY'),
|
||||||
|
DefaultACLPolicy: ALLOW
|
||||||
|
}
|
||||||
|
))
|
||||||
const actual = serializer.respondForQuery(function(cb) {
|
const actual = serializer.respondForQuery(function(cb) {
|
||||||
const headers = {};
|
const headers = {
|
||||||
const body = payload;
|
[DEFAULT_ACL_POLICY]: ALLOW
|
||||||
return cb(headers, body);
|
};
|
||||||
|
return cb(headers, payload);
|
||||||
|
}, {
|
||||||
|
dc: '*',
|
||||||
|
});
|
||||||
|
actual.forEach((item, i) => {
|
||||||
|
assert.equal(actual[i].Name, expected[i].Name);
|
||||||
|
assert.equal(actual[i].Local, expected[i].Local);
|
||||||
|
assert.equal(actual[i].Primary, expected[i].Primary);
|
||||||
|
assert.equal(actual[i].DefaultACLPolicy, expected[i].DefaultACLPolicy);
|
||||||
});
|
});
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,12 +21,10 @@ test('findAll returns the correct data for list endpoint', function(assert) {
|
||||||
return service.findAll();
|
return service.findAll();
|
||||||
},
|
},
|
||||||
function performAssertion(actual, expected) {
|
function performAssertion(actual, expected) {
|
||||||
assert.deepEqual(
|
actual.forEach((item, i) => {
|
||||||
actual,
|
assert.equal(actual[i].Name, item.Name);
|
||||||
expected(function(payload) {
|
assert.equal(item.Local, i === 0);
|
||||||
return payload.map((item, i) => ({ Name: item, Local: i === 0 ? true : false }));
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ module('Unit | Utility | create fingerprinter', function() {
|
||||||
const fingerprint = createFingerprinter('Datacenter', 'Namespace', 'Partition');
|
const fingerprint = createFingerprinter('Datacenter', 'Namespace', 'Partition');
|
||||||
[undefined, null].forEach(function(item) {
|
[undefined, null].forEach(function(item) {
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
fingerprint('uid', 'ID', item);
|
fingerprint('uid', 'ID', item)({Datacenter: item});
|
||||||
}, /missing foreignKey/);
|
}, /missing foreignKey/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue