mirror of
https://github.com/status-im/consul.git
synced 2025-02-24 03:18:26 +00:00
ui: Improved main navigation (#7673)
* Make datacenter queries use query vs findAll like the rest of the app * Make sure we have an element to pass to isInViewport * Make sure href-mut doesn't error even if the currentRoute === null * More post test cleanup and Safari fix (safari requires http:// URLs) * Reverse order of datasource nspace/dc's and add a namespace source * Rearrange routes/templates/controllers to only use HashicorpConsul once * Add datasources and correct token namespace detection/redirection * Remove old dc findAll adapter method * Add more comments around the 'child route/parent controller' vars
This commit is contained in:
parent
51db157fab
commit
2685c5081b
@ -1,7 +1,7 @@
|
||||
import Adapter from './application';
|
||||
|
||||
export default Adapter.extend({
|
||||
requestForFindAll: function(request) {
|
||||
requestForQuery: function(request) {
|
||||
return request`
|
||||
GET /v1/catalog/datacenters
|
||||
`;
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{#if (eq loading "lazy")}}
|
||||
{{! in order to use intersection observer we need a DOM element on the page}}
|
||||
<data aria-hidden="true" style="width: 0;height: 0;font-size: 0;padding: 0;margin: 0;" />
|
||||
<data id={{guid}} aria-hidden="true" style="width: 0;height: 0;font-size: 0;padding: 0;margin: 0;" />
|
||||
{{/if}}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
|
||||
import Ember from 'ember';
|
||||
/**
|
||||
@ -25,30 +26,6 @@ const replace = function(
|
||||
return set(obj, prop, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @module DataSource
|
||||
*
|
||||
* The DataSource component manages opening and closing data sources via an injectable data service.
|
||||
* Data sources are only opened only if the component is visible in the viewport (using IntersectionObserver).
|
||||
*
|
||||
* Sources returned by the data service should follow an EventTarget/EventSource API.
|
||||
* Management of the caching/usage/counting etc of sources should be done in the data service,
|
||||
* not the component.
|
||||
*
|
||||
* @example ```javascript
|
||||
* <DataSource
|
||||
* src="/dc-1/~nspace/services"
|
||||
* onchange={{action (mut items) value='data'}}
|
||||
* onerror={{action (mut error) value='error'}}
|
||||
* />```
|
||||
*
|
||||
* @param src {string} - An identifier used to determine the source of the data. This is passed
|
||||
* @param loading {string} - Either `eager` or `lazy`, lazy will only load the data once the component
|
||||
* is in the viewport
|
||||
* @param onchange {function=} - An action called when the data changes.
|
||||
* @param onerror {function=} - An action called on error
|
||||
*
|
||||
*/
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
|
||||
@ -67,6 +44,7 @@ export default Component.extend({
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
this._lazyListeners = this.dom.listeners();
|
||||
this.guid = this.dom.guid(this);
|
||||
},
|
||||
willDestroy: function() {
|
||||
this.actions.close.apply(this);
|
||||
@ -78,7 +56,7 @@ export default Component.extend({
|
||||
this._super(...arguments);
|
||||
if (this.loading === 'lazy') {
|
||||
this._lazyListeners.add(
|
||||
this.dom.isInViewport(this.element, inViewport => {
|
||||
this.dom.isInViewport(this.dom.element(`#${this.guid}`), inViewport => {
|
||||
set(this, 'isIntersecting', inViewport || Ember.testing);
|
||||
if (!this.isIntersecting) {
|
||||
this.actions.close.bind(this)();
|
||||
@ -130,11 +108,13 @@ export default Component.extend({
|
||||
if (typeof source.getCurrentEvent === 'function') {
|
||||
const currentEvent = source.getCurrentEvent();
|
||||
if (currentEvent) {
|
||||
try {
|
||||
this.onchange(currentEvent);
|
||||
} catch (err) {
|
||||
error(err);
|
||||
}
|
||||
schedule('afterRender', () => {
|
||||
try {
|
||||
this.onchange(currentEvent);
|
||||
} catch (err) {
|
||||
error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
<header role="banner" data-test-navigation>
|
||||
<a data-test-main-nav-logo href={{href-to 'index'}}><svg width="28" height="27" xmlns="http://www.w3.org/2000/svg"><title>Consul</title><path d="M13.284 16.178a2.876 2.876 0 1 1-.008-5.751 2.876 2.876 0 0 1 .008 5.75zm5.596-1.547a1.333 1.333 0 1 1 0-2.667 1.333 1.333 0 0 1 0 2.667zm4.853 1.249a1.271 1.271 0 1 1 .027-.107c0 .031 0 .067-.027.107zm-.937-3.436a1.333 1.333 0 1 1 .986-1.595c.033.172.033.348 0 .52-.07.53-.465.96-.986 1.075zm4.72 3.29a1.333 1.333 0 1 1-1.076-1.538 1.333 1.333 0 0 1 1.116 1.417.342.342 0 0 0-.027.12h-.013zm-1.08-3.33a1.333 1.333 0 1 1 1.088-1.524c.014.114.014.229 0 .342a1.333 1.333 0 0 1-1.102 1.182h.014zm-.925 7.925a1.333 1.333 0 1 1 .165-.547c-.01.193-.067.38-.165.547zm-.48-12.191a1.333 1.333 0 1 1 .507-1.814c.14.237.198.514.164.787-.038.438-.289.828-.67 1.045v-.018zM13.333 26.667C5.97 26.667 0 20.697 0 13.333 0 5.97 5.97 0 13.333 0c2.929-.01 5.778.955 8.098 2.742L19.8 4.89a10.667 10.667 0 0 0-17.133 8.444 10.667 10.667 0 0 0 17.137 8.471l1.627 2.13a13.218 13.218 0 0 1-8.098 2.733z" fill="#FFF"/></svg></a>
|
||||
<input type="checkbox" name="menu" id="main-nav-toggle" onchange={{action 'change'}} />
|
||||
@ -30,6 +31,11 @@
|
||||
<BlockSlot @name="menu">
|
||||
<li role="separator">
|
||||
Namespaces
|
||||
<DataSource
|
||||
@src="/*/*/namespaces"
|
||||
@onchange={{action (mut nspaces) value="data"}}
|
||||
@loading="lazy"
|
||||
/>
|
||||
</li>
|
||||
{{#each (reject-by 'DeletedAt' nspaces) as |item|}}
|
||||
<li role="none" class={{if (eq nspace.Name item.Name) 'is-active'}}>
|
||||
@ -58,6 +64,11 @@
|
||||
<BlockSlot @name="menu">
|
||||
<li role="separator">
|
||||
Datacenters
|
||||
<DataSource
|
||||
@src="/*/*/datacenters"
|
||||
@onchange={{action (mut dcs) value="data"}}
|
||||
@loading="lazy"
|
||||
/>
|
||||
</li>
|
||||
{{#each dcs as |item|}}
|
||||
<li role="none" data-test-datacenter-picker class={{if (eq dc.Name item.Name) 'is-active'}}>
|
||||
|
@ -38,11 +38,17 @@ export default Component.extend({
|
||||
} else {
|
||||
// TODO: Ideally we wouldn't need to use env() at a component level
|
||||
// transitionTo should probably remove it instead if NSPACES aren't enabled
|
||||
if (this.env.var('CONSUL_NSPACES_ENABLED') && get(token, 'Namespace') !== this.nspace) {
|
||||
if (this.env.var('CONSUL_NSPACES_ENABLED') && get(token, 'Namespace') !== this.nspace.Name) {
|
||||
if (!routeName.startsWith('nspace')) {
|
||||
routeName = `nspace.${routeName}`;
|
||||
}
|
||||
return route.transitionTo(`${routeName}`, `~${get(token, 'Namespace')}`, this.dc.Name);
|
||||
const nspace = get(token, 'Namespace');
|
||||
// you potentially have a new namespace
|
||||
if (typeof nspace !== 'undefined') {
|
||||
return route.transitionTo(`${routeName}`, `~${nspace}`, this.dc.Name);
|
||||
}
|
||||
// you are logging out, just refresh
|
||||
return route.refresh();
|
||||
} else {
|
||||
if (route.routeName === 'dc.acls.index') {
|
||||
return route.transitionTo('dc.acls.tokens.index');
|
||||
|
3
ui-v2/app/controllers/application.js
Normal file
3
ui-v2/app/controllers/application.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default Controller.extend({});
|
@ -10,16 +10,38 @@ const removeLoading = function($from) {
|
||||
};
|
||||
export default Route.extend(WithBlockingActions, {
|
||||
dom: service('dom'),
|
||||
router: service('router'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
repo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
model: function() {
|
||||
return hash({
|
||||
router: this.router,
|
||||
dcs: this.repo.findAll(),
|
||||
nspaces: this.nspacesRepo.findAll(),
|
||||
|
||||
// these properties are added to the controller from route/dc
|
||||
// as we don't have access to the dc and nspace params in the URL
|
||||
// until we get to the route/dc route
|
||||
// permissions also requires the dc param
|
||||
|
||||
// dc: null,
|
||||
// nspace: null
|
||||
// token: null
|
||||
// permissions: null
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
actions: {
|
||||
loading: function(transition, originRoute) {
|
||||
const $root = this.dom.root();
|
||||
let dc = null;
|
||||
if (originRoute.routeName !== 'dc') {
|
||||
const model = this.modelFor('dc') || { dcs: null, dc: { Name: null } };
|
||||
dc = this.repo.getActive(model.dc.Name, model.dcs);
|
||||
if (originRoute.routeName !== 'dc' && originRoute.routeName !== 'application') {
|
||||
const app = this.modelFor('application');
|
||||
const model = this.modelFor('dc') || { dc: { Name: null } };
|
||||
dc = this.repo.getActive(model.dc.Name, app.dcs);
|
||||
}
|
||||
hash({
|
||||
loading: !$root.classList.contains('ember-loading'),
|
||||
@ -50,8 +72,6 @@ export default Route.extend(WithBlockingActions, {
|
||||
error = e.errors[0];
|
||||
error.message = error.title || error.detail || 'Error';
|
||||
}
|
||||
// Try and get the currently attempted dc, whereever that may be
|
||||
const model = this.modelFor('dc') || this.modelFor('nspace.dc');
|
||||
// TODO: Unfortunately ember will not maintain the correct URL
|
||||
// for you i.e. when this happens the URL in your browser location bar
|
||||
// will be the URL where you clicked on the link to come here
|
||||
@ -79,26 +99,46 @@ export default Route.extend(WithBlockingActions, {
|
||||
if (error.status === '') {
|
||||
error.message = 'Error';
|
||||
}
|
||||
// Try and get the currently attempted dc, whereever that may be
|
||||
let model = this.modelFor('dc') || this.modelFor('nspace.dc');
|
||||
if (!model) {
|
||||
const path = new URL(location.href).pathname
|
||||
.substr(this.router.rootURL.length - 1)
|
||||
.split('/')
|
||||
.slice(1, 3);
|
||||
model = {
|
||||
nspace: { Name: 'default' },
|
||||
};
|
||||
if (path[0].startsWith('~')) {
|
||||
model.nspace = {
|
||||
Name: path.shift(),
|
||||
};
|
||||
}
|
||||
model.dc = {
|
||||
Name: path[0],
|
||||
};
|
||||
}
|
||||
const app = this.modelFor('application') || {};
|
||||
const dcs = app.dcs || [model.dc];
|
||||
const nspaces = app.nspaces || [model.nspace];
|
||||
const $root = this.dom.root();
|
||||
hash({
|
||||
error: error,
|
||||
nspace: this.nspacesRepo.getActive(),
|
||||
dc:
|
||||
error.status.toString().indexOf('5') !== 0
|
||||
? this.repo.getActive()
|
||||
: model && model.dc
|
||||
? model.dc
|
||||
? this.repo.getActive(model.dc.Name, dcs)
|
||||
: { Name: 'Error' },
|
||||
dcs: model && model.dcs ? model.dcs : [],
|
||||
dcs: dcs,
|
||||
nspace: model.nspace,
|
||||
nspaces: nspaces,
|
||||
})
|
||||
.then(model => Promise.all([model, this.repo.clearActive()]))
|
||||
.then(([model]) => {
|
||||
removeLoading($root);
|
||||
model.nspaces = [model.nspace];
|
||||
// we can't use setupController as we received an error
|
||||
// so we do it manually instead
|
||||
next(() => {
|
||||
this.controllerFor('error').setProperties(model);
|
||||
this.controllerFor('application').setProperties(model);
|
||||
this.controllerFor('error').setProperties({ error: error });
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
|
@ -28,37 +28,33 @@ export default Route.extend({
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
settingsRepo: service('settings'),
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
const nspacesRepo = this.nspacesRepo;
|
||||
const settingsRepo = this.settingsRepo;
|
||||
const app = this.modelFor('application');
|
||||
return hash({
|
||||
dcs: repo.findAll(),
|
||||
nspaces: nspacesRepo.findAll(),
|
||||
nspace: nspacesRepo.getActive(),
|
||||
token: settingsRepo.findBySlug('token'),
|
||||
nspace: this.nspacesRepo.getActive(),
|
||||
token: this.settingsRepo.findBySlug('token'),
|
||||
dc: this.repo.findBySlug(params.dc, app.dcs),
|
||||
})
|
||||
.then(function(model) {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
dc: repo.findBySlug(params.dc, model.dcs),
|
||||
// if there is only 1 namespace then use that
|
||||
// otherwise find the namespace object that corresponds
|
||||
// to the active one
|
||||
nspace:
|
||||
model.nspaces.length > 1
|
||||
? findActiveNspace(model.nspaces, model.nspace)
|
||||
: model.nspaces.firstObject,
|
||||
app.nspaces.length > 1
|
||||
? findActiveNspace(app.nspaces, model.nspace)
|
||||
: app.nspaces.firstObject,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(function(model) {
|
||||
.then(model => {
|
||||
if (get(model, 'token.SecretID')) {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
// When disabled nspaces is [], so nspace is undefined
|
||||
permissions: nspacesRepo.authorize(params.dc, get(model, 'nspace.Name')),
|
||||
permissions: this.nspacesRepo.authorize(params.dc, get(model, 'nspace.Name')),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@ -67,7 +63,11 @@ export default Route.extend({
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
// the model here is actually required for the entire application
|
||||
// but we need to wait until we are in this route so we know what the dc
|
||||
// and or nspace is if the below changes please revists the comments
|
||||
// in routes/application:model
|
||||
this.controllerFor('application').setProperties(model);
|
||||
},
|
||||
actions: {
|
||||
// TODO: This will eventually be deprecated please see
|
||||
@ -85,15 +85,13 @@ export default Route.extend({
|
||||
// including your permissions for being able to manage namespaces
|
||||
// Potentially we should just do this on every single transition
|
||||
// but then we would need to check to see if nspaces are enabled
|
||||
const controller = this.controllerFor('application');
|
||||
Promise.all([
|
||||
this.nspacesRepo.findAll(),
|
||||
this.nspacesRepo.authorize(
|
||||
get(this.controller, 'dc.Name'),
|
||||
get(this.controller, 'nspace.Name')
|
||||
),
|
||||
this.nspacesRepo.authorize(get(controller, 'dc.Name'), get(controller, 'nspace.Name')),
|
||||
]).then(([nspaces, permissions]) => {
|
||||
if (typeof this.controller !== 'undefined') {
|
||||
this.controller.setProperties({
|
||||
if (typeof controller !== 'undefined') {
|
||||
controller.setProperties({
|
||||
nspaces: nspaces,
|
||||
permissions: permissions,
|
||||
});
|
||||
|
@ -44,17 +44,24 @@ export default Route.extend({
|
||||
nspace: params.nspace,
|
||||
});
|
||||
},
|
||||
afterModel: function(params) {
|
||||
// We need to redirect if someone doesn't specify
|
||||
// the section they want, but not redirect if the 'section' is
|
||||
// specified (i.e. /dc-1/ vs /dc-1/services)
|
||||
// check how many parts are in the URL to figure this out
|
||||
// if there is a better way to do this then would be good to change
|
||||
if (this.router.currentURL.split('/').length < 4) {
|
||||
if (!params.nspace.startsWith('~')) {
|
||||
this.transitionTo('dc.services', params.nspace);
|
||||
|
||||
/**
|
||||
* We need to redirect if someone doesn't specify the section they want,
|
||||
* but not redirect if the 'section' is specified
|
||||
* (i.e. /dc-1/ vs /dc-1/services).
|
||||
*
|
||||
* If the target route of the transition is `nspace.index`, it means that
|
||||
* someone didn't specify a section and thus we forward them on to a
|
||||
* default `.services` subroute. The specific services route we target
|
||||
* depends on whether or not a namespace was specified.
|
||||
*
|
||||
*/
|
||||
afterModel(model, transition) {
|
||||
if (transition.to.name === 'nspace.index') {
|
||||
if (model.nspace.startsWith('~')) {
|
||||
this.transitionTo('nspace.dc.services', model.nspace, model.item.Name);
|
||||
} else {
|
||||
this.transitionTo('nspace.dc.services', params.nspace, params.item.Name);
|
||||
this.transitionTo('dc.services', model.nspace);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,21 +9,16 @@ export default Route.extend({
|
||||
dcRepo: service('repository/dc'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
model: function(params) {
|
||||
const app = this.modelFor('application');
|
||||
return hash({
|
||||
item: this.repo.findAll(),
|
||||
dcs: this.dcRepo.findAll(),
|
||||
nspaces: this.nspacesRepo.findAll(),
|
||||
dc: this.dcRepo.getActive(undefined, app.dcs),
|
||||
nspace: this.nspacesRepo.getActive(),
|
||||
}).then(model => {
|
||||
if (typeof get(model.item, 'client.blocking') === 'undefined') {
|
||||
set(model, 'item.client', { blocking: true });
|
||||
}
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
dc: this.dcRepo.getActive(null, model.dcs),
|
||||
},
|
||||
});
|
||||
return model;
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
|
@ -2,9 +2,14 @@ import Serializer from './application';
|
||||
|
||||
export default Serializer.extend({
|
||||
primaryKey: 'Name',
|
||||
respondForQuery: function(respond, query) {
|
||||
return respond(function(headers, body) {
|
||||
return body;
|
||||
});
|
||||
},
|
||||
normalizePayload: function(payload, id, requestType) {
|
||||
switch (requestType) {
|
||||
case 'findAll':
|
||||
case 'query':
|
||||
return payload.map(item => {
|
||||
return {
|
||||
[this.primaryKey]: item,
|
||||
|
@ -3,10 +3,11 @@ import { get } from '@ember/object';
|
||||
|
||||
export default Service.extend({
|
||||
datacenters: service('repository/dc'),
|
||||
namespaces: service('repository/nspace'),
|
||||
token: service('repository/token'),
|
||||
type: service('data-source/protocols/http/blocking'),
|
||||
source: function(src, configuration) {
|
||||
const [, dc /*nspace*/, , model, ...rest] = src.split('/');
|
||||
const [, , /*nspace*/ dc, model, ...rest] = src.split('/');
|
||||
let find;
|
||||
const repo = this[model];
|
||||
if (typeof repo.reconcile === 'function') {
|
||||
@ -25,6 +26,9 @@ export default Service.extend({
|
||||
case 'datacenters':
|
||||
find = configuration => repo.findAll(configuration);
|
||||
break;
|
||||
case 'namespaces':
|
||||
find = configuration => repo.findAll(configuration);
|
||||
break;
|
||||
case 'token':
|
||||
find = configuration => repo.self(rest[1], dc);
|
||||
break;
|
||||
|
@ -25,6 +25,12 @@ export default Service.extend({
|
||||
},
|
||||
willDestroy: function() {
|
||||
this._listeners.remove();
|
||||
Object.entries(sources || {}).forEach(function([key, item]) {
|
||||
item.close();
|
||||
});
|
||||
cache = null;
|
||||
sources = null;
|
||||
usage = null;
|
||||
},
|
||||
|
||||
open: function(uri, ref) {
|
||||
@ -35,13 +41,10 @@ export default Service.extend({
|
||||
uri = `consul://${uri}`;
|
||||
}
|
||||
if (!sources.has(uri)) {
|
||||
const url = new URL(uri);
|
||||
let pathname = url.pathname;
|
||||
let [providerName, pathname] = uri.split('://');
|
||||
if (pathname.startsWith('//')) {
|
||||
pathname = pathname.substr(2);
|
||||
}
|
||||
const providerName = url.protocol.substr(0, url.protocol.length - 1);
|
||||
|
||||
const provider = this[providerName];
|
||||
|
||||
let configuration = {};
|
||||
|
@ -10,7 +10,7 @@ export default RepositoryService.extend({
|
||||
return modelName;
|
||||
},
|
||||
findAll: function() {
|
||||
return this.store.findAll(this.getModelName()).then(function(items) {
|
||||
return this.store.query(this.getModelName(), {}).then(function(items) {
|
||||
// TODO: Move to view/template
|
||||
return items.sortBy('Name');
|
||||
});
|
||||
|
@ -103,6 +103,9 @@ export default LazyProxyService.extend({
|
||||
cache = createCache(cacheMap);
|
||||
},
|
||||
willDestroy: function() {
|
||||
Object.entries(cacheMap || {}).forEach(function([key, item]) {
|
||||
item.close();
|
||||
});
|
||||
cacheMap = null;
|
||||
cache = null;
|
||||
},
|
||||
|
@ -1,13 +1,15 @@
|
||||
<HeadLayout />
|
||||
{{title 'Consul' separator=' - '}}
|
||||
{{#if (not-eq router.currentRouteName 'application')}}
|
||||
<HashicorpConsul @id="wrapper" @permissions={{permissions}} @dcs={{dcs}} @dc={{or dc dcs.firstObject}} @nspaces={{nspaces}} @nspace={{or nspace nspaces.firstObject}}>
|
||||
{{#if (not loading)}}
|
||||
{{outlet}}
|
||||
{{else}}
|
||||
<HashicorpConsul @id="wrapper" @permissions={{permissions}} @dcs={{dcs}} @dc={{dc}} @nspaces={{nspaces}} @nspace={{nspace}}>
|
||||
<AppView @class="loading show">
|
||||
<BlockSlot @name="content">
|
||||
{{partial 'consul-loading'}}
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
{{/if}}
|
||||
</HashicorpConsul>
|
||||
{{/if}}
|
||||
|
@ -1,3 +1 @@
|
||||
<HashicorpConsul @id="wrapper" @permissions={{permissions}} @dcs={{dcs}} @dc={{dc}} @nspaces={{nspaces}} @nspace={{nspace}}>
|
||||
{{outlet}}
|
||||
</HashicorpConsul>
|
||||
{{outlet}}
|
||||
|
@ -1,21 +1,19 @@
|
||||
<HashicorpConsul @id="wrapper" @permissions={{permissions}} @dcs={{dcs}} @dc={{dc}} @nspaces={{nspaces}} @nspace={{nspace}}>
|
||||
<AppView @class="error show">
|
||||
<BlockSlot @name="header">
|
||||
<h1 data-test-error>
|
||||
<AppView @class="error show">
|
||||
<BlockSlot @name="header">
|
||||
<h1 data-test-error>
|
||||
{{#if error.status }}
|
||||
{{error.status}} ({{error.message}})
|
||||
{{error.status}} ({{error.message}})
|
||||
{{else}}
|
||||
{{error.message}}
|
||||
{{error.message}}
|
||||
{{/if}}
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<p>
|
||||
Consul returned an error.
|
||||
You may have visited a URL that is loading an unknown resource, so you can try going back to the root or try re-submitting your ACL Token/SecretID by going back to ACLs.<br />
|
||||
Try looking in our <a href="{{env 'CONSUL_DOCS_URL'}}" target="_blank">documentation</a>
|
||||
</p>
|
||||
<a data-test-home rel="home" href={{href-to 'index'}}>Go back to root</a>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
</HashicorpConsul>
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<p>
|
||||
Consul returned an error.
|
||||
You may have visited a URL that is loading an unknown resource, so you can try going back to the root or try re-submitting your ACL Token/SecretID by going back to ACLs.<br />
|
||||
Try looking in our <a href="{{env 'CONSUL_DOCS_URL'}}" target="_blank">documentation</a>
|
||||
</p>
|
||||
<a data-test-home rel="home" href={{href-to 'index'}}>Go back to root</a>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
|
@ -1,43 +1,41 @@
|
||||
{{title "Settings"}}
|
||||
<HashicorpConsul @id="wrapper" @permissions={{permissions}} @dcs={{dcs}} @dc={{dc}} @nspaces={{nspaces}} @nspace={{nspace}}>
|
||||
<AppView @class="settings show">
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
Settings
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<div class="notice info">
|
||||
<h3>Local Storage</h3>
|
||||
<AppView @class="settings show">
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
Settings
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<div class="notice info">
|
||||
<h3>Local Storage</h3>
|
||||
<p>
|
||||
These settings are immediately saved to local storage and persisted through browser usage.
|
||||
</p>
|
||||
</div>
|
||||
<form>
|
||||
<fieldset>
|
||||
<h2>Dashboard Links</h2>
|
||||
<p>
|
||||
These settings are immediately saved to local storage and persisted through browser usage.
|
||||
Add a link to the service detail page in the UI to get quick access to a service-wide metrics dashboard. Enter the dashboard URL into the field below. You can use the placeholders <code>{{'{{Datacenter}}'}}</code> and <code>{{'{{Service.Name}}'}}</code> which will be replaced with the name of the datacenter/service currently being viewed.
|
||||
</p>
|
||||
</div>
|
||||
<form>
|
||||
<fieldset>
|
||||
<h2>Dashboard Links</h2>
|
||||
<p>
|
||||
Add a link to the service detail page in the UI to get quick access to a service-wide metrics dashboard. Enter the dashboard URL into the field below. You can use the placeholders <code>{{'{{Datacenter}}'}}</code> and <code>{{'{{Service.Name}}'}}</code> which will be replaced with the name of the datacenter/service currently being viewed.
|
||||
</p>
|
||||
<label class={{concat (if confirming 'confirming') ' type-text'}} id="urls_service">
|
||||
<span>Link template for services</span>
|
||||
<input type="text" name="urls[service]" value={{item.urls.service}} onchange={{action 'change'}} onkeypress={{action 'key'}} onkeydown={{action 'key'}} />
|
||||
<em>e.g. https://grafana.example.com/d/1/consul-service-mesh&orgid=1&datacenter={{'{{Datacenter}}'}}&service-name={{'{{Service.Name}}'}}</em>
|
||||
<label class={{concat (if confirming 'confirming') ' type-text'}} id="urls_service">
|
||||
<span>Link template for services</span>
|
||||
<input type="text" name="urls[service]" value={{item.urls.service}} onchange={{action 'change'}} onkeypress={{action 'key'}} onkeydown={{action 'key'}} />
|
||||
<em>e.g. https://grafana.example.com/d/1/consul-service-mesh&orgid=1&datacenter={{'{{Datacenter}}'}}&service-name={{'{{Service.Name}}'}}</em>
|
||||
</label>
|
||||
</fieldset>
|
||||
{{#if (not (env 'CONSUL_UI_DISABLE_REALTIME'))}}
|
||||
<fieldset data-test-blocking-queries>
|
||||
<h2>Blocking Queries</h2>
|
||||
<p>Keep catalog info up-to-date without refreshing the page. Any changes made to services, nodes and intentions would be reflected in real time.</p>
|
||||
<div class="type-toggle">
|
||||
<label>
|
||||
<input type="checkbox" name="client[blocking]" checked={{if item.client.blocking 'checked'}} onchange={{action 'change'}} />
|
||||
<span>{{if item.client.blocking 'On' 'Off'}}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
{{#if (not (env 'CONSUL_UI_DISABLE_REALTIME'))}}
|
||||
<fieldset data-test-blocking-queries>
|
||||
<h2>Blocking Queries</h2>
|
||||
<p>Keep catalog info up-to-date without refreshing the page. Any changes made to services, nodes and intentions would be reflected in real time.</p>
|
||||
<div class="type-toggle">
|
||||
<label>
|
||||
<input type="checkbox" name="client[blocking]" checked={{if item.client.blocking 'checked'}} onchange={{action 'change'}} />
|
||||
<span>{{if item.client.blocking 'On' 'Off'}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
</form>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
</HashicorpConsul>
|
||||
</div>
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
</form>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
|
@ -2,11 +2,11 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
module('Integration | Adapter | dc', function(hooks) {
|
||||
setupTest(hooks);
|
||||
test('requestForFindAll returns the correct url', function(assert) {
|
||||
test('requestForQuery returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:dc');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/catalog/datacenters`;
|
||||
const actual = adapter.requestForFindAll(client.url);
|
||||
const actual = adapter.requestForQuery(client.url);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
||||
|
@ -3,14 +3,14 @@ import { setupTest } from 'ember-qunit';
|
||||
import { get } from 'consul-ui/tests/helpers/api';
|
||||
module('Integration | Serializer | dc', function(hooks) {
|
||||
setupTest(hooks);
|
||||
test('respondForFindAll 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 request = {
|
||||
url: `/v1/catalog/datacenters`,
|
||||
};
|
||||
return get(request.url).then(function(payload) {
|
||||
const expected = payload;
|
||||
const actual = serializer.respondForFindAll(function(cb) {
|
||||
const actual = serializer.respondForQuery(function(cb) {
|
||||
const headers = {};
|
||||
const body = payload;
|
||||
return cb(headers, body);
|
||||
|
12
ui-v2/tests/unit/controllers/application-test.js
Normal file
12
ui-v2/tests/unit/controllers/application-test.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Controller | application', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.owner.lookup('controller:application');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user