Merge pull request #3377 from hashicorp/b-ui-gracefully-handle-403s
Gracefully handle 403s in the UI
This commit is contained in:
commit
c04d9020c2
|
@ -1,5 +1,6 @@
|
|||
import Ember from 'ember';
|
||||
import RESTAdapter from 'ember-data/adapters/rest';
|
||||
import codesForError from '../utils/codes-for-error';
|
||||
|
||||
const { get, computed, inject } = Ember;
|
||||
|
||||
|
@ -21,8 +22,12 @@ export default RESTAdapter.extend({
|
|||
|
||||
findAll() {
|
||||
return this._super(...arguments).catch(error => {
|
||||
if (error.code === '501' || (error.errors && error.errors.findBy('status', '501'))) {
|
||||
// Feature is not implemented in this version of Nomad
|
||||
const errorCodes = codesForError(error);
|
||||
|
||||
const isNotAuthorized = errorCodes.includes('403');
|
||||
const isNotImplemented = errorCodes.includes('501');
|
||||
|
||||
if (isNotAuthorized || isNotImplemented) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Ember from 'ember';
|
||||
import codesForError from '../utils/codes-for-error';
|
||||
|
||||
const { Controller, computed, inject, run, observer } = Ember;
|
||||
|
||||
|
@ -12,19 +13,11 @@ export default Controller.extend({
|
|||
}),
|
||||
|
||||
errorCodes: computed('error', function() {
|
||||
const error = this.get('error');
|
||||
const codes = [error.code];
|
||||
return codesForError(this.get('error'));
|
||||
}),
|
||||
|
||||
if (error.errors) {
|
||||
error.errors.forEach(err => {
|
||||
codes.push(err.status);
|
||||
});
|
||||
}
|
||||
|
||||
return codes
|
||||
.compact()
|
||||
.uniq()
|
||||
.map(code => '' + code);
|
||||
is403: computed('errorCodes.[]', function() {
|
||||
return this.get('errorCodes').includes('403');
|
||||
}),
|
||||
|
||||
is404: computed('errorCodes.[]', function() {
|
||||
|
|
|
@ -11,10 +11,13 @@ export default Route.extend({
|
|||
|
||||
actions: {
|
||||
didTransition() {
|
||||
this.controllerFor('application').set('error', null);
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
|
||||
willTransition() {
|
||||
this.controllerFor('application').set('error', null);
|
||||
},
|
||||
|
||||
error(error) {
|
||||
this.controllerFor('application').set('error', error);
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ export default Route.extend({
|
|||
|
||||
model({ job_id }) {
|
||||
return this.get('store')
|
||||
.find('job', job_id)
|
||||
.findRecord('job', job_id, { reload: true })
|
||||
.then(job => {
|
||||
return job.get('allocations').then(() => job);
|
||||
})
|
||||
|
|
|
@ -28,7 +28,7 @@ export default ApplicationSerializer.extend({
|
|||
},
|
||||
|
||||
normalizeResponse(store, typeClass, hash, ...args) {
|
||||
return this._super(store, typeClass, hash.Members, ...args);
|
||||
return this._super(store, typeClass, hash.Members || [], ...args);
|
||||
},
|
||||
|
||||
normalizeSingleResponse(store, typeClass, hash, id, ...args) {
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
{{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>
|
||||
{{else if is403}}
|
||||
<h1 class="title is-spaced">Not Authorized</h1>
|
||||
{{#if token.secret}}
|
||||
<p class="subtitle">Your {{#link-to "settings.tokens"}}ACL token{{/link-to}} does not provide the required permissions. Contact your administrator if this is an error.</p>
|
||||
{{else}}
|
||||
<p class="subtitle">Provide an {{#link-to "settings.tokens"}}ACL token{{/link-to}} with requisite permissions to view this.</p>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<h1 class="title is-spaced">Error</h1>
|
||||
<p class="subtitle">Something went wrong.</p>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Returns an array of error codes as strings for an Ember error object
|
||||
export default function codesForError(error) {
|
||||
const codes = [error.code];
|
||||
|
||||
if (error.errors) {
|
||||
error.errors.forEach(err => {
|
||||
codes.push(err.status);
|
||||
});
|
||||
}
|
||||
|
||||
return codes
|
||||
.compact()
|
||||
.uniq()
|
||||
.map(code => '' + code);
|
||||
}
|
|
@ -11,7 +11,7 @@ moduleForAcceptance('Acceptance | application errors ', {
|
|||
});
|
||||
|
||||
test('transitioning away from an error page resets the global error', function(assert) {
|
||||
server.pretender.get('/v1/nodes', () => [403, {}, null]);
|
||||
server.pretender.get('/v1/nodes', () => [500, {}, null]);
|
||||
|
||||
visit('/nodes');
|
||||
|
||||
|
@ -25,3 +25,32 @@ test('transitioning away from an error page resets the global error', function(a
|
|||
assert.notOk(find('.error-message'), 'Application is no longer in an error state');
|
||||
});
|
||||
});
|
||||
|
||||
test('the 403 error page links to the ACL tokens page', function(assert) {
|
||||
const job = server.db.jobs[0];
|
||||
|
||||
server.pretender.get(`/v1/job/${job.id}`, () => [403, {}, null]);
|
||||
|
||||
visit(`/jobs/${job.id}`);
|
||||
|
||||
andThen(() => {
|
||||
assert.ok(find('.error-message'), 'Error message is shown');
|
||||
assert.equal(
|
||||
find('.error-message .title').textContent,
|
||||
'Not Authorized',
|
||||
'Error message is for 403'
|
||||
);
|
||||
});
|
||||
|
||||
andThen(() => {
|
||||
click('.error-message a');
|
||||
});
|
||||
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
'/settings/tokens',
|
||||
'Error message contains a link to the tokens page'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue