diff --git a/ui/core/app/components/error/message.hbs b/ui/core/app/components/error/message.hbs
new file mode 100644
index 000000000..7cc59f8c9
--- /dev/null
+++ b/ui/core/app/components/error/message.hbs
@@ -0,0 +1,4 @@
+
+ {{t this.description}}
+ {{t "actions.help"}}
+
\ No newline at end of file
diff --git a/ui/core/app/components/error/message.js b/ui/core/app/components/error/message.js
new file mode 100644
index 000000000..c949ba822
--- /dev/null
+++ b/ui/core/app/components/error/message.js
@@ -0,0 +1,104 @@
+import Message from 'rose/components/rose/message';
+
+/*
+ * Helpful error booleans are attached based on the error status code:
+ *
+ * - 401 - `error.isUnauthenticated`: the session isn't authenticated
+ * - 403 - `error.isForbidden`: the session is authenticated but does not have
+ * permission to perform the requested action
+ * - 404 - `error.isNotFound`: the requested resource could not be found
+ * - 500 - `error.isServer`: an internal server error occurred
+ *
+ * For an unknown error state, i.e error state not matching to the above defined list:
+ * - unknown - `error.isUnknown`: an error occurred, but we don't know which or
+ * we don't distinguish it yet
+*/
+
+const status_messages = {
+ '401' : {
+ icon: 'disabled',
+ title: 'errors.titles.unauthenticated-error',
+ subtitle: 'errors.subtitles.unauthenticated-error',
+ description: 'errors.descriptions.unauthenticated-error',
+ },
+ '403' : {
+ icon: 'disabled',
+ title: 'errors.titles.forbidden-error',
+ subtitle: 'errors.subtitles.forbidden-error',
+ description: 'errors.descriptions.forbidden-error',
+ },
+ '404' : {
+ icon: 'help-circle-outline',
+ title: 'errors.titles.notfound-error',
+ subtitle: 'errors.subtitles.notfound-error',
+ description: 'errors.descriptions.notfound-error',
+ },
+ '500' : {
+ icon: 'alert-circle-outline',
+ title: 'errors.titles.server-error',
+ subtitle: 'errors.subtitles.server-error',
+ description: 'errors.descriptions.server-error',
+ },
+ 'unknown' : {
+ icon: 'alert-circle-outline',
+ title: 'errors.titles.unknown-error',
+ subtitle: 'errors.subtitles.unknown-error',
+ description: 'errors.descriptions.unknown-error',
+ }
+};
+
+export default class ErrorMessageComponent extends Message {
+ // =methods
+
+ /**
+ * Returns an icon name based on error status.
+ * @return {string}
+ */
+ get icon() {
+ return this.statusMessage.icon;
+ }
+
+ /**
+ * Returns a message title based on error status.
+ * @return {string}
+ */
+ get title() {
+ return this.statusMessage.title;
+ }
+
+ /**
+ * Returns a message subtitle of error status prefixed with 'Error'.
+ * @return {string}
+ */
+ get subtitle() {
+ return this.statusMessage.subtitle;
+ }
+
+ /**
+ * Returns an error description based on error status.
+ * @return {string}
+ */
+ get description() {
+ return this.statusMessage.description;
+ }
+
+ /**
+ * Returns an route string for help text in message.
+ * @return {string}
+ */
+ get helpRoute() {
+ return 'index'
+ }
+
+ /**
+ * Returns an object with message details based on error status.
+ * Return message for `unkown` error when error status isn't part of
+ * predefined message set.
+ * @return {object}
+ */
+ get statusMessage() {
+ let status = this.status;
+ if(!status_messages[status]) { status = 'unknown'; }
+ return status_messages[status];
+ }
+}
\ No newline at end of file
diff --git a/ui/core/app/routes/application.js b/ui/core/app/routes/application.js
index 0a6c58271..1ee2b2ce2 100644
--- a/ui/core/app/routes/application.js
+++ b/ui/core/app/routes/application.js
@@ -50,8 +50,6 @@ export default class ApplicationRoute extends Route.extend(
if (!Ember.testing) later(location, location.reload, 150);
}
- // =actions
-
/**
* Delegates invalidation to the session service.
*/
diff --git a/ui/core/app/routes/scopes/scope.js b/ui/core/app/routes/scopes/scope.js
index 820f7caf2..def116b51 100644
--- a/ui/core/app/routes/scopes/scope.js
+++ b/ui/core/app/routes/scopes/scope.js
@@ -33,4 +33,5 @@ export default class ScopesScopeRoute extends Route {
);
});
}
+
}
diff --git a/ui/core/app/templates/scopes/scope/error.hbs b/ui/core/app/templates/scopes/scope/error.hbs
new file mode 100644
index 000000000..417feb0ea
--- /dev/null
+++ b/ui/core/app/templates/scopes/scope/error.hbs
@@ -0,0 +1,6 @@
+
+ {{#each @model.errors as |error|}}
+ {{title error.status}}
+
+ {{/each}}
+
\ No newline at end of file
diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js
index 7f2ea5ba0..846da6b99 100644
--- a/ui/core/mirage/config.js
+++ b/ui/core/mirage/config.js
@@ -1,6 +1,6 @@
import config from '../config/environment';
import { authHandler, deauthHandler } from './route-handlers/auth';
-//import { Response } from 'miragejs';
+import { Response } from 'miragejs';
export default function() {
@@ -288,4 +288,18 @@ export default function() {
}
return target.update(updatedAttrs);
});
+
+ /* Uncomment the following line and the Response import above
+ * Then change the response code to simulate error responses.
+ * this.get('/scopes', () => new Response(505));
+ *
+ * Update error payload to simulate specific error responses.
+ */
+ // this.get('/host-catalogs', () => new Response(404));
+ // this.get('/scopes/:id', () => new Response(505, {}, {
+ // errors: [{
+ // status: 505,
+ // message: 'HTTP version not supported.',
+ // }]
+ // }));
}
diff --git a/ui/core/tests/integration/components/error/message-test.js b/ui/core/tests/integration/components/error/message-test.js
new file mode 100644
index 000000000..811a0a138
--- /dev/null
+++ b/ui/core/tests/integration/components/error/message-test.js
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | error/message', function(hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.equal(this.element.textContent.trim(), '');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.equal(this.element.textContent.trim(), 'template block text');
+ });
+});
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index 301ac40a2..756b98b98 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -162,6 +162,12 @@ errors:
server-error: Server error
unknown-error: Something went wrong
authentication-failed: Authentication Failed
+ subtitles:
+ unauthenticated-error: Error 401
+ forbidden-error: Error 403
+ notfound-error: Error 404
+ server-error: Error 500
+ unknown-error: Error
descriptions:
unauthenticated-error: You are not authenticated. Please authenticate and try again later.
forbidden-error: You must be granted permissions to view this data. Ask your administrator if you think you should have access.