Merge pull request #3300 from hashicorp/f-ui-404-pages
404 pages for the UI
This commit is contained in:
commit
ab72eb164f
35
ui/app/controllers/application.js
Normal file
35
ui/app/controllers/application.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
const { Controller, computed } = Ember;
|
||||
|
||||
export default Controller.extend({
|
||||
error: null,
|
||||
|
||||
errorStr: computed('error', function() {
|
||||
return this.get('error').toString();
|
||||
}),
|
||||
|
||||
errorCodes: computed('error', function() {
|
||||
const error = this.get('error');
|
||||
const codes = [error.code];
|
||||
|
||||
if (error.errors) {
|
||||
error.errors.forEach(err => {
|
||||
codes.push(err.status);
|
||||
});
|
||||
}
|
||||
|
||||
return codes
|
||||
.compact()
|
||||
.uniq()
|
||||
.map(code => '' + code);
|
||||
}),
|
||||
|
||||
is404: computed('errorCodes.[]', function() {
|
||||
return this.get('errorCodes').includes('404');
|
||||
}),
|
||||
|
||||
is500: computed('errorCodes.[]', function() {
|
||||
return this.get('errorCodes').includes('500');
|
||||
}),
|
||||
});
|
10
ui/app/mixins/with-model-error-handling.js
Normal file
10
ui/app/mixins/with-model-error-handling.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Ember from 'ember';
|
||||
import notifyError from 'nomad-ui/utils/notify-error';
|
||||
|
||||
const { Mixin } = Ember;
|
||||
|
||||
export default Mixin.create({
|
||||
model() {
|
||||
return this._super(...arguments).catch(notifyError(this));
|
||||
},
|
||||
});
|
|
@ -35,6 +35,8 @@ Router.map(function() {
|
|||
if (config.environment === 'development') {
|
||||
this.route('freestyle');
|
||||
}
|
||||
|
||||
this.route('not-found', { path: '/*' });
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
|
6
ui/app/routes/allocations/allocation.js
Normal file
6
ui/app/routes/allocations/allocation.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Ember from 'ember';
|
||||
import WithModelErrorHandling from 'nomad-ui/mixins/with-model-error-handling';
|
||||
|
||||
const { Route } = Ember;
|
||||
|
||||
export default Route.extend(WithModelErrorHandling);
|
|
@ -3,9 +3,19 @@ import Ember from 'ember';
|
|||
const { Route } = Ember;
|
||||
|
||||
export default Route.extend({
|
||||
resetController(controller, isExiting) {
|
||||
if (isExiting) {
|
||||
controller.set('error', null);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
didTransition() {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
|
||||
error(error) {
|
||||
this.controllerFor('application').set('error', error);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Ember from 'ember';
|
||||
import notifyError from 'nomad-ui/utils/notify-error';
|
||||
|
||||
const { Route, inject } = Ember;
|
||||
|
||||
|
@ -10,6 +11,7 @@ export default Route.extend({
|
|||
.find('job', job_id)
|
||||
.then(job => {
|
||||
return job.get('allocations').then(() => job);
|
||||
});
|
||||
})
|
||||
.catch(notifyError(this));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import Ember from 'ember';
|
||||
import notifyError from 'nomad-ui/utils/notify-error';
|
||||
|
||||
const { Route, inject } = Ember;
|
||||
|
||||
export default Route.extend({
|
||||
store: inject.service(),
|
||||
|
||||
model() {
|
||||
return this._super(...arguments).catch(notifyError(this));
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
if (model.get('isPartial')) {
|
||||
if (model && model.get('isPartial')) {
|
||||
return model.reload().then(node => node.get('allocations'));
|
||||
}
|
||||
return model.get('allocations');
|
||||
return model && model.get('allocations');
|
||||
},
|
||||
});
|
||||
|
|
11
ui/app/routes/not-found.js
Normal file
11
ui/app/routes/not-found.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
const { Route, Error: EmberError } = Ember;
|
||||
|
||||
export default Route.extend({
|
||||
model() {
|
||||
const err = new EmberError('Page not found');
|
||||
err.code = '404';
|
||||
this.controllerFor('application').set('error', err);
|
||||
},
|
||||
});
|
6
ui/app/routes/servers/server.js
Normal file
6
ui/app/routes/servers/server.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Ember from 'ember';
|
||||
import WithModelErrorHandling from 'nomad-ui/mixins/with-model-error-handling';
|
||||
|
||||
const { Route } = Ember;
|
||||
|
||||
export default Route.extend(WithModelErrorHandling);
|
|
@ -1,4 +1,5 @@
|
|||
import ApplicationSerializer from './application';
|
||||
import { AdapterError } from 'ember-data/adapters/errors';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
attrs: {
|
||||
|
@ -8,6 +9,16 @@ export default ApplicationSerializer.extend({
|
|||
},
|
||||
|
||||
normalize(typeHash, hash) {
|
||||
if (!hash) {
|
||||
// It's unusual to throw an adapter error from a serializer,
|
||||
// but there is no single server end point so the serializer
|
||||
// acts like the API in this case.
|
||||
const error = new AdapterError([{ status: '404' }]);
|
||||
|
||||
error.message = 'Requested Agent was not found in set of available Agents';
|
||||
throw error;
|
||||
}
|
||||
|
||||
hash.ID = hash.Name;
|
||||
hash.Datacenter = hash.Tags && hash.Tags.dc;
|
||||
hash.Region = hash.Tags && hash.Tags.region;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import "./components/boxed-section";
|
||||
@import "./components/breadcrumbs";
|
||||
@import "./components/empty-message";
|
||||
@import "./components/error-container";
|
||||
@import "./components/gutter";
|
||||
@import "./components/inline-definitions";
|
||||
@import "./components/job-diff";
|
||||
|
|
22
ui/app/styles/components/error-container.scss
Normal file
22
ui/app/styles/components/error-container.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
.error-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 25vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: $grey-lighter;
|
||||
|
||||
.error-message {
|
||||
max-width: 600px;
|
||||
|
||||
.title,
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.error-stack-trace {
|
||||
border: 1px solid $grey-light;
|
||||
border-radius: $radius;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,19 @@
|
|||
{{partial "svg-patterns"}}
|
||||
{{outlet}}
|
||||
{{#unless error}}
|
||||
{{outlet}}
|
||||
{{else}}
|
||||
<div class="error-container">
|
||||
<div class="error-message">
|
||||
{{#if is500}}
|
||||
<h1 class="title is-spaced">Server Error</h1>
|
||||
<p class="subtitle">A server error prevented data from being sent to the client.</p>
|
||||
{{else if is404}}
|
||||
<h1 class="title is-spaced">Not Found</h1>
|
||||
<p class="subtitle">What you're looking for couldn't be found. It either doesn't exist or you are not authorized to see it.</p>
|
||||
{{/if}}
|
||||
{{#if (eq config.environment "development")}}
|
||||
<pre class="error-stack-trace"><code>{{errorStr}}</code></pre>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
|
7
ui/app/utils/notify-error.js
Normal file
7
ui/app/utils/notify-error.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
// An error handler to provide to a promise catch to set an error
|
||||
// on the application controller.
|
||||
export default function notifyError(route) {
|
||||
return error => {
|
||||
route.controllerFor('application').set('error', error);
|
||||
};
|
||||
}
|
|
@ -183,3 +183,24 @@ test('each recent event should list the time, type, and description of the event
|
|||
'Event message'
|
||||
);
|
||||
});
|
||||
|
||||
test('when the allocation is not found, an error message is shown, but the URL persists', function(
|
||||
assert
|
||||
) {
|
||||
visit('/allocations/not-a-real-allocation');
|
||||
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
server.pretender.handledRequests.findBy('status', 404).url,
|
||||
'/v1/allocation/not-a-real-allocation',
|
||||
'A request to the non-existent allocation is made'
|
||||
);
|
||||
assert.equal(currentURL(), '/allocations/not-a-real-allocation', 'The URL persists');
|
||||
assert.ok(find('.error-message'), 'Error message is shown');
|
||||
assert.equal(
|
||||
find('.error-message .title').textContent,
|
||||
'Not Found',
|
||||
'Error message is for 404'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -181,3 +181,24 @@ test('each allocation should link to the job the allocation belongs to', functio
|
|||
test('/nodes/:id should list all attributes for the node', function(assert) {
|
||||
assert.ok(find('.attributes-table'), 'Attributes table is on the page');
|
||||
});
|
||||
|
||||
test('when the node is not found, an error message is shown, but the URL persists', function(
|
||||
assert
|
||||
) {
|
||||
visit('/nodes/not-a-real-node');
|
||||
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
server.pretender.handledRequests.findBy('status', 404).url,
|
||||
'/v1/node/not-a-real-node',
|
||||
'A request to the non-existent node is made'
|
||||
);
|
||||
assert.equal(currentURL(), '/nodes/not-a-real-node', 'The URL persists');
|
||||
assert.ok(find('.error-message'), 'Error message is shown');
|
||||
assert.equal(
|
||||
find('.error-message .title').textContent,
|
||||
'Not Found',
|
||||
'Error message is for 404'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -260,3 +260,24 @@ test('the active deployment section can be expanded to show task groups and allo
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('when the job is not found, an error message is shown, but the URL persists', function(
|
||||
assert
|
||||
) {
|
||||
visit('/jobs/not-a-real-job');
|
||||
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
server.pretender.handledRequests.findBy('status', 404).url,
|
||||
'/v1/job/not-a-real-job',
|
||||
'A request to the non-existent job is made'
|
||||
);
|
||||
assert.equal(currentURL(), '/jobs/not-a-real-job', 'The URL persists');
|
||||
assert.ok(find('.error-message'), 'Error message is shown');
|
||||
assert.equal(
|
||||
find('.error-message .title').textContent,
|
||||
'Not Found',
|
||||
'Error message is for 404'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Ember from 'ember';
|
||||
import { findAll, currentURL, visit } from 'ember-native-dom-helpers';
|
||||
import { find, findAll, currentURL, visit } from 'ember-native-dom-helpers';
|
||||
import { test } from 'qunit';
|
||||
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
|
||||
|
||||
|
@ -42,3 +42,19 @@ test('the active server should be denoted in the table', function(assert) {
|
|||
'Active server matches current route'
|
||||
);
|
||||
});
|
||||
|
||||
test('when the server is not found, an error message is shown, but the URL persists', function(
|
||||
assert
|
||||
) {
|
||||
visit('/servers/not-a-real-server');
|
||||
|
||||
andThen(() => {
|
||||
assert.equal(currentURL(), '/servers/not-a-real-server', 'The URL persists');
|
||||
assert.ok(find('.error-message'), 'Error message is shown');
|
||||
assert.equal(
|
||||
find('.error-message .title').textContent,
|
||||
'Not Found',
|
||||
'Error message is for 404'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue