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 Ember from 'ember';
|
||||||
import RESTAdapter from 'ember-data/adapters/rest';
|
import RESTAdapter from 'ember-data/adapters/rest';
|
||||||
|
import codesForError from '../utils/codes-for-error';
|
||||||
|
|
||||||
const { get, computed, inject } = Ember;
|
const { get, computed, inject } = Ember;
|
||||||
|
|
||||||
|
@ -21,8 +22,12 @@ export default RESTAdapter.extend({
|
||||||
|
|
||||||
findAll() {
|
findAll() {
|
||||||
return this._super(...arguments).catch(error => {
|
return this._super(...arguments).catch(error => {
|
||||||
if (error.code === '501' || (error.errors && error.errors.findBy('status', '501'))) {
|
const errorCodes = codesForError(error);
|
||||||
// Feature is not implemented in this version of Nomad
|
|
||||||
|
const isNotAuthorized = errorCodes.includes('403');
|
||||||
|
const isNotImplemented = errorCodes.includes('501');
|
||||||
|
|
||||||
|
if (isNotAuthorized || isNotImplemented) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import codesForError from '../utils/codes-for-error';
|
||||||
|
|
||||||
const { Controller, computed, inject, run, observer } = Ember;
|
const { Controller, computed, inject, run, observer } = Ember;
|
||||||
|
|
||||||
|
@ -12,19 +13,11 @@ export default Controller.extend({
|
||||||
}),
|
}),
|
||||||
|
|
||||||
errorCodes: computed('error', function() {
|
errorCodes: computed('error', function() {
|
||||||
const error = this.get('error');
|
return codesForError(this.get('error'));
|
||||||
const codes = [error.code];
|
}),
|
||||||
|
|
||||||
if (error.errors) {
|
is403: computed('errorCodes.[]', function() {
|
||||||
error.errors.forEach(err => {
|
return this.get('errorCodes').includes('403');
|
||||||
codes.push(err.status);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return codes
|
|
||||||
.compact()
|
|
||||||
.uniq()
|
|
||||||
.map(code => '' + code);
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
is404: computed('errorCodes.[]', function() {
|
is404: computed('errorCodes.[]', function() {
|
||||||
|
|
|
@ -11,10 +11,13 @@ export default Route.extend({
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
didTransition() {
|
didTransition() {
|
||||||
this.controllerFor('application').set('error', null);
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
willTransition() {
|
||||||
|
this.controllerFor('application').set('error', null);
|
||||||
|
},
|
||||||
|
|
||||||
error(error) {
|
error(error) {
|
||||||
this.controllerFor('application').set('error', error);
|
this.controllerFor('application').set('error', error);
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default Route.extend({
|
||||||
|
|
||||||
model({ job_id }) {
|
model({ job_id }) {
|
||||||
return this.get('store')
|
return this.get('store')
|
||||||
.find('job', job_id)
|
.findRecord('job', job_id, { reload: true })
|
||||||
.then(job => {
|
.then(job => {
|
||||||
return job.get('allocations').then(() => job);
|
return job.get('allocations').then(() => job);
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default ApplicationSerializer.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
normalizeResponse(store, typeClass, hash, ...args) {
|
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) {
|
normalizeSingleResponse(store, typeClass, hash, id, ...args) {
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
{{else if is404}}
|
{{else if is404}}
|
||||||
<h1 class="title is-spaced">Not Found</h1>
|
<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>
|
<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}}
|
{{else}}
|
||||||
<h1 class="title is-spaced">Error</h1>
|
<h1 class="title is-spaced">Error</h1>
|
||||||
<p class="subtitle">Something went wrong.</p>
|
<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) {
|
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');
|
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');
|
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