Skip to content

Commit

Permalink
ui: Improved main navigation (#7673)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
johncowen authored Apr 21, 2020
1 parent a4857d0 commit 6ffab72
Show file tree
Hide file tree
Showing 23 changed files with 229 additions and 162 deletions.
2 changes: 1 addition & 1 deletion ui-v2/app/adapters/dc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Adapter from './application';

export default Adapter.extend({
requestForFindAll: function(request) {
requestForQuery: function(request) {
return request`
GET /v1/catalog/datacenters
`;
Expand Down
2 changes: 1 addition & 1 deletion ui-v2/app/components/data-source/index.hbs
Original file line number Diff line number Diff line change
@@ -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}}
40 changes: 10 additions & 30 deletions ui-v2/app/components/data-source/index.js
Original file line number Diff line number Diff line change
@@ -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';
/**
Expand All @@ -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: '',

Expand All @@ -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);
Expand All @@ -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)();
Expand Down Expand Up @@ -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);
}
});
}
}
},
Expand Down
11 changes: 11 additions & 0 deletions ui-v2/app/components/hashicorp-consul/index.hbs
Original file line number Diff line number Diff line change
@@ -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'}} />
Expand Down Expand Up @@ -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'}}>
Expand Down Expand Up @@ -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'}}>
Expand Down
10 changes: 8 additions & 2 deletions ui-v2/app/components/hashicorp-consul/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
3 changes: 3 additions & 0 deletions ui-v2/app/controllers/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Controller from '@ember/controller';

export default Controller.extend({});
13 changes: 9 additions & 4 deletions ui-v2/app/helpers/href-mut.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import { hrefTo } from 'consul-ui/helpers/href-to';
import { getOwner } from '@ember/application';

const getRouteParams = function(route, params = {}) {
return route.paramNames.map(function(item) {
return (route.paramNames || []).map(function(item) {
if (typeof params[item] !== 'undefined') {
return params[item];
}
Expand All @@ -13,16 +14,20 @@ const getRouteParams = function(route, params = {}) {
export default Helper.extend({
router: service('router'),
compute([params], hash) {
let current = this.router.currentRoute;
let currentRoute = this.router.currentRoute;
if (currentRoute === null) {
currentRoute = getOwner(this).lookup('route:application');
}
let parent;
let atts = getRouteParams(current, params);
let atts = getRouteParams(currentRoute, params);
// walk up the entire route/s replacing any instances
// of the specified params with the values specified
let current = currentRoute;
while ((parent = current.parent)) {
atts = atts.concat(getRouteParams(parent, params));
current = parent;
}
let route = this.router.currentRoute.name;
let route = currentRoute.name || 'application';
// TODO: this is specific to consul/nspaces
// 'ideally' we could try and do this elsewhere
// not super important though.
Expand Down
66 changes: 53 additions & 13 deletions ui-v2/app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 => {
Expand Down
38 changes: 18 additions & 20 deletions ui-v2/app/routes/dc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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,
});
Expand Down
Loading

0 comments on commit 6ffab72

Please sign in to comment.