diff --git a/ui/app/controllers/application.js b/ui/app/controllers/application.js new file mode 100644 index 000000000..0516a7090 --- /dev/null +++ b/ui/app/controllers/application.js @@ -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'); + }), +}); diff --git a/ui/app/mixins/with-model-error-handling.js b/ui/app/mixins/with-model-error-handling.js new file mode 100644 index 000000000..585c61595 --- /dev/null +++ b/ui/app/mixins/with-model-error-handling.js @@ -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)); + }, +}); diff --git a/ui/app/router.js b/ui/app/router.js index ee03341de..a63b46fc4 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -35,6 +35,8 @@ Router.map(function() { if (config.environment === 'development') { this.route('freestyle'); } + + this.route('not-found', { path: '/*' }); }); export default Router; diff --git a/ui/app/routes/allocations/allocation.js b/ui/app/routes/allocations/allocation.js new file mode 100644 index 000000000..60eff663b --- /dev/null +++ b/ui/app/routes/allocations/allocation.js @@ -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); diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index 7197f1819..1b6fe6335 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -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); + }, }, }); diff --git a/ui/app/routes/jobs/job.js b/ui/app/routes/jobs/job.js index abe106775..5ae53a122 100644 --- a/ui/app/routes/jobs/job.js +++ b/ui/app/routes/jobs/job.js @@ -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)); }, }); diff --git a/ui/app/routes/nodes/node.js b/ui/app/routes/nodes/node.js index 4e5511637..9571d975a 100644 --- a/ui/app/routes/nodes/node.js +++ b/ui/app/routes/nodes/node.js @@ -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'); }, }); diff --git a/ui/app/routes/not-found.js b/ui/app/routes/not-found.js new file mode 100644 index 000000000..1974ee9ba --- /dev/null +++ b/ui/app/routes/not-found.js @@ -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); + }, +}); diff --git a/ui/app/routes/servers/server.js b/ui/app/routes/servers/server.js new file mode 100644 index 000000000..60eff663b --- /dev/null +++ b/ui/app/routes/servers/server.js @@ -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); diff --git a/ui/app/serializers/agent.js b/ui/app/serializers/agent.js index e3cb04997..f54db7276 100644 --- a/ui/app/serializers/agent.js +++ b/ui/app/serializers/agent.js @@ -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; diff --git a/ui/app/styles/components.scss b/ui/app/styles/components.scss index 50f6ef1c8..2a5e6cb78 100644 --- a/ui/app/styles/components.scss +++ b/ui/app/styles/components.scss @@ -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"; diff --git a/ui/app/styles/components/error-container.scss b/ui/app/styles/components/error-container.scss new file mode 100644 index 000000000..298d7fe52 --- /dev/null +++ b/ui/app/styles/components/error-container.scss @@ -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; + } +} diff --git a/ui/app/templates/application.hbs b/ui/app/templates/application.hbs index dceb76dfa..0dfba7e6e 100644 --- a/ui/app/templates/application.hbs +++ b/ui/app/templates/application.hbs @@ -1,2 +1,19 @@ {{partial "svg-patterns"}} -{{outlet}} +{{#unless error}} + {{outlet}} +{{else}} +