ui: Overview UI Routing (#12493)
This PR adds routes and an initial landing page for the Cluster Overview page
This commit is contained in:
parent
7fbfab669c
commit
e711b920c4
|
@ -0,0 +1,11 @@
|
|||
import BaseAbility from './base';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class HcpAbility extends BaseAbility {
|
||||
@service('env') env;
|
||||
|
||||
get is() {
|
||||
return false;
|
||||
// return this.env.var('CONSUL_HCP');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import BaseAbility from './base';
|
||||
|
||||
export default class LicenseAbility extends BaseAbility {
|
||||
resource = 'operator';
|
||||
segmented = false;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import BaseAbility from './base';
|
||||
|
||||
export default class OverviewAbility extends BaseAbility {
|
||||
get canAccess() {
|
||||
return ['read services', 'read nodes', 'read license']
|
||||
.some(item => this.permissions.can(item))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import BaseAbility from './base';
|
||||
|
||||
export default class RaftAbility extends BaseAbility {
|
||||
resource = 'operator';
|
||||
segmented = false;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import BaseAbility from './base';
|
||||
|
||||
export default class ServiceAbility extends BaseAbility {
|
||||
export default class ZerviceAbility extends BaseAbility {
|
||||
resource = 'service';
|
||||
|
||||
get isLinkable() {
|
|
@ -107,6 +107,20 @@
|
|||
@nspaces={{this.nspaces}}
|
||||
@onchange={{action (mut this.nspaces) value="data"}}
|
||||
/>
|
||||
{{#if (can 'access overview')}}
|
||||
<li
|
||||
data-test-main-nav-overview
|
||||
class={{class-map
|
||||
(array 'is-active' (is-href 'dc.show' @dc.Name))
|
||||
}}
|
||||
>
|
||||
<Action
|
||||
@href={{href-to 'dc.show' @dc.Name}}
|
||||
>
|
||||
Overview
|
||||
</Action>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (can "read services")}}
|
||||
<li data-test-main-nav-services class={{if (is-href 'dc.services' @dc.Name) 'is-active'}}>
|
||||
<a href={{href-to 'dc.services' @dc.Name}}>Services</a>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import Helper from 'ember-can/helpers/can';
|
||||
|
||||
export default class extends Helper {
|
||||
_addAbilityObserver(ability, propertyName) {
|
||||
if(!this.isDestroyed && !this.isDestroying) {
|
||||
super._addAbilityObserver(...arguments);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ import { get } from '@ember/object';
|
|||
|
||||
import { camelize } from '@ember/string';
|
||||
export const is = (helper, [abilityString, model], properties) => {
|
||||
let { abilityName, propertyName } = helper.can.parse(abilityString);
|
||||
let ability = helper.can.abilityFor(abilityName, model, properties);
|
||||
let { abilityName, propertyName } = helper.abilities.parse(abilityString);
|
||||
let ability = helper.abilities.abilityFor(abilityName, model, properties);
|
||||
|
||||
if(typeof ability.getCharacteristicProperty === 'function') {
|
||||
propertyName = ability.getCharacteristicProperty(propertyName);
|
||||
|
@ -17,8 +17,8 @@ export const is = (helper, [abilityString, model], properties) => {
|
|||
|
||||
return get(ability, propertyName);
|
||||
}
|
||||
export default Helper.extend({
|
||||
export default class extends Helper {
|
||||
compute([abilityString, model], properties) {
|
||||
return is(this, [abilityString, model], properties);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Helper from 'ember-can/helpers/can';
|
||||
import { is } from 'consul-ui/helpers/is';
|
||||
import Helper from './can';
|
||||
import { is } from './is';
|
||||
|
||||
export default Helper.extend({
|
||||
export default class extends Helper {
|
||||
compute([abilityString, model], properties) {
|
||||
switch(true) {
|
||||
case abilityString.startsWith('can '):
|
||||
|
@ -10,5 +10,5 @@ export default Helper.extend({
|
|||
return is(this, [abilityString.substr(3), model], properties);
|
||||
}
|
||||
throw new Error(`${abilityString} is not supported by the 'test' helper.`);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Service from 'ember-can/services/can';
|
||||
|
||||
export default class CanService extends Service {
|
||||
export default class AbilitiesService extends Service {
|
||||
parse(str) {
|
||||
// It's nicer to talk about SSO but technically its part of the authMethod
|
||||
// ability, we probably only need 'use SSO' but if we need more, reasses
|
||||
// ability, we probably only need 'use SSO' but if we need more, reassess
|
||||
// the `replace`
|
||||
return super.parse(str.replace('use SSO', ' use authMethods'));
|
||||
return super.parse(str.replace('use SSO', 'use authMethods').replace('service', 'zervice'));
|
||||
}
|
||||
}
|
|
@ -59,7 +59,7 @@ const REQUIRED_PERMISSIONS = [
|
|||
];
|
||||
export default class PermissionService extends RepositoryService {
|
||||
@service('env') env;
|
||||
@service('can') _can;
|
||||
@service('abilities') _can;
|
||||
|
||||
// TODO: move this to the store, if we want it to use ember-data
|
||||
// currently this overwrites an inherited permissions service (this service)
|
||||
|
|
|
@ -47,7 +47,7 @@ as |source|>
|
|||
|
||||
{{! redirect if we aren't on a URL with dc information }}
|
||||
{{#if (eq route.currentName 'index')}}
|
||||
{{did-insert (route-action 'replaceWith' 'dc.services.index'
|
||||
{{did-insert (route-action 'replaceWith' 'dc.show'
|
||||
(hash
|
||||
dc=(env 'CONSUL_DATACENTER_LOCAL')
|
||||
)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
<AppView>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
<route.Title @title={{compute (fn route.t 'title')}} />
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="toolbar">
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<TabNav @items={{
|
||||
compact
|
||||
(array
|
||||
(hash
|
||||
label=(compute (fn route.t 'serverstatus.title'))
|
||||
href=(href-to "dc.show.serverstatus")
|
||||
selected=(is-href "dc.show.serverstatus")
|
||||
)
|
||||
(hash
|
||||
label=(compute (fn route.t 'health.title'))
|
||||
href=(href-to 'dc.show.health')
|
||||
selected=(is-href 'dc.show.health')
|
||||
)
|
||||
(if (and (can 'read license') (not (is 'hcp')))
|
||||
(hash
|
||||
label=(compute (fn route.t 'license.title'))
|
||||
href=(href-to 'dc.show.license')
|
||||
selected=(is-href 'dc.show.license')
|
||||
)
|
||||
)
|
||||
'')
|
||||
}}/>
|
||||
</BlockSlot>
|
||||
|
||||
</AppView>
|
||||
</Route>
|
|
@ -92,7 +92,7 @@
|
|||
"deepmerge": "^4.2.2",
|
||||
"ember-assign-helper": "^0.3.0",
|
||||
"ember-auto-import": "^1.5.3",
|
||||
"ember-can": "^3.0.0",
|
||||
"ember-can": "^4.1.0",
|
||||
"ember-changeset-conditional-validations": "^0.6.0",
|
||||
"ember-changeset-validations": "~3.9.0",
|
||||
"ember-cli": "~3.20.2",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / error: Recovering from a dc 500 error
|
||||
Background:
|
||||
Feature: dc / error
|
||||
Scenario: Recovering from a dc 500 error
|
||||
Given 2 datacenter models from yaml
|
||||
---
|
||||
- dc-1
|
||||
|
@ -23,12 +23,7 @@ Feature: dc / error: Recovering from a dc 500 error
|
|||
Then the url should be /dc-500/services
|
||||
And the title should be "Consul"
|
||||
Then I see status on the error like "500"
|
||||
Scenario: Clicking the back to root button
|
||||
Given the url "/v1/internal/ui/services" responds with a 200 status
|
||||
When I click home
|
||||
Then I see 3 service models
|
||||
Scenario: Choosing a different dc from the dc menu
|
||||
Given the url "/v1/internal/ui/services" responds with a 200 status
|
||||
And the url "/v1/internal/ui/services" responds with a 200 status
|
||||
When I click dc on the navigation
|
||||
And I click dcs.0.name on the navigation
|
||||
Then I see 3 service models
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Feature: dc / forwarding
|
||||
In order to arrive at a useful page when only specifying a dc in the url
|
||||
As a user
|
||||
I should be redirected to the services page for the dc
|
||||
I should be redirected to the overview page for the dc
|
||||
@notNamespaceable
|
||||
Scenario: Arriving at the datacenter index page with no other url info
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
|
@ -10,4 +10,4 @@ Feature: dc / forwarding
|
|||
---
|
||||
dc: datacenter
|
||||
---
|
||||
Then the url should be /datacenter/services
|
||||
Then the url should be /datacenter/overview/server-status
|
||||
|
|
|
@ -4,4 +4,4 @@ Feature: index-forwarding
|
|||
Scenario: Arriving at the index page when there is only one datacenter
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
When I visit the index page
|
||||
Then the url should be /dc1/services
|
||||
Then the url should be /dc1/overview/server-status
|
||||
|
|
|
@ -10,8 +10,7 @@ Feature: page-navigation
|
|||
---
|
||||
dc: dc1
|
||||
---
|
||||
Then the url should be /dc1/services
|
||||
Then a GET request was made to "/v1/internal/ui/services?dc=dc1&ns=@namespace"
|
||||
Then the url should be /dc1/overview/server-status
|
||||
Scenario: Clicking [Link] in the navigation takes me to [URL]
|
||||
When I visit the services page for yaml
|
||||
---
|
||||
|
|
|
@ -4,16 +4,19 @@ Feature: token-header
|
|||
In order to authenticate with tokens
|
||||
As a user
|
||||
I need to be able to specify a ACL token AND/OR leave it blank to authenticate with the API
|
||||
Scenario: Arriving at the index page having not set a token previously
|
||||
Scenario: Arriving at the service page having not set a token previously
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
When I visit the index page
|
||||
When I visit the services page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
---
|
||||
Then the url should be /dc1/services
|
||||
And a GET request was made to "/v1/internal/ui/services?dc=dc1&ns=@namespace" from yaml
|
||||
---
|
||||
headers:
|
||||
X-Consul-Token: ''
|
||||
---
|
||||
Scenario: Set the token to [Token] and then navigate to the index page
|
||||
Scenario: Set the token to [Token] and then navigate to the service page
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
And the url "/v1/acl/tokens" responds with a 403 status
|
||||
When I visit the tokens page for yaml
|
||||
|
@ -27,7 +30,10 @@ Feature: token-header
|
|||
SecretID: [Token]
|
||||
---
|
||||
And I click submit on the authdialog.form
|
||||
When I visit the index page
|
||||
When I visit the services page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
---
|
||||
Then the url should be /dc1/services
|
||||
And a GET request was made to "/v1/internal/ui/services?dc=dc1&ns=@namespace" from yaml
|
||||
---
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
dc:
|
||||
show:
|
||||
title: Cluster Overview
|
||||
serverstatus:
|
||||
title: Server status
|
||||
health:
|
||||
title: Health
|
||||
license:
|
||||
title: License
|
||||
nodes:
|
||||
show:
|
||||
healthchecks:
|
||||
|
|
|
@ -7,9 +7,34 @@
|
|||
index: {
|
||||
_options: {
|
||||
path: '/',
|
||||
redirect: '../services',
|
||||
redirect: '../show/serverstatus',
|
||||
},
|
||||
},
|
||||
show: {
|
||||
_options: {
|
||||
path: '/overview',
|
||||
redirect: './serverstatus',
|
||||
abilities: ['access overview']
|
||||
},
|
||||
serverstatus: {
|
||||
_options: {
|
||||
path: '/server-status',
|
||||
abilities: ['access overview', 'read raft']
|
||||
},
|
||||
},
|
||||
health: {
|
||||
_options: {
|
||||
path: '/health',
|
||||
abilities: ['access overview']
|
||||
},
|
||||
},
|
||||
license: {
|
||||
_options: {
|
||||
path: '/license',
|
||||
abilities: ['access overview', 'read licence']
|
||||
},
|
||||
}
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
index: {
|
||||
|
|
952
ui/yarn.lock
952
ui/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue