You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2018/08/17 19:35:57 UTC

[airavata-django-portal] 01/05: AIRAVATA-2727 AIRAVATA-2638 Global error handling

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 7545f270dd311b80a3d8ac8b1439e0b95054e4fa
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Fri Aug 17 14:29:16 2018 -0400

    AIRAVATA-2727 AIRAVATA-2638 Global error handling
---
 .../admin/static/django_airavata_admin/src/main.js |  10 +-
 django_airavata/apps/api/exceptions.py             |  30 ++
 django_airavata/apps/api/serializers.py            |  12 +-
 .../django_airavata_api/js/errors/ErrorReporter.js |   9 +
 .../js/errors/GlobalErrorHandler.js                |  27 ++
 .../js/errors/UnhandledError.js                    |  19 +
 .../js/errors/UnhandledErrorDispatcher.js          |  18 +
 .../js/errors/UnhandledErrorDisplayList.js         |  21 +
 .../api/static/django_airavata_api/js/index.js     |  12 +-
 .../js/services/ServiceFactory.js                  |  12 +-
 .../django_airavata_api/js/utils/FetchUtils.js     | 133 ++++---
 django_airavata/apps/api/thrift_utils.py           |   8 +-
 django_airavata/apps/api/views.py                  |   4 +-
 django_airavata/settings.py                        |   2 +
 .../common/js/components/NotificationsDisplay.vue  |  75 ++++
 django_airavata/static/common/js/index.js          |   2 +
 .../static/common/js/notifications/Notification.js |  14 +
 .../common/js/notifications/NotificationList.js    |  31 ++
 django_airavata/static/common/package-lock.json    | 424 +++++----------------
 19 files changed, 460 insertions(+), 403 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/main.js b/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
index fa8a27b..46e5ecc 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/main.js
@@ -11,9 +11,14 @@ import ComputeResourcePreferenceDashboard from './components/dashboards/ComputeR
 import BootstrapVue from 'bootstrap-vue'
 import 'bootstrap-vue/dist/bootstrap-vue.css'
 
+import { components } from 'django-airavata-common-ui'
+import { errors } from 'django-airavata-api'
+
 import router from './router';
 import store from './store/store';
 
+errors.GlobalErrorHandler.init();
+
 Vue.config.productionTip = false;
 
 Vue.use(BootstrapVue)
@@ -23,7 +28,7 @@ Vue.use(VueRouter);
 
 export function initializeApacheAiravataDashboard(dashboardName) {
   var template = `
-    <div class="vmain"><Loading/> 
+    <div class="vmain"><notifications-display/><Loading/> 
         <transition name="fade">
             <router-view>
             </router-view>
@@ -40,7 +45,8 @@ export function initializeApacheAiravataDashboard(dashboardName) {
       CredentialStore,
       Loading,
       ComputeResourceDashboard,
-      ComputeResourcePreferenceDashboard
+      ComputeResourcePreferenceDashboard,
+      'notifications-display': components.NotificationsDisplay,
     },
     mounted:function () {
       if (this.$router.currentRoute.path === '/') {
diff --git a/django_airavata/apps/api/exceptions.py b/django_airavata/apps/api/exceptions.py
new file mode 100644
index 0000000..4075189
--- /dev/null
+++ b/django_airavata/apps/api/exceptions.py
@@ -0,0 +1,30 @@
+import json
+import logging
+
+from rest_framework import serializers, status
+from rest_framework.response import Response
+from rest_framework.views import exception_handler
+
+from airavata.api.error.ttypes import AiravataSystemException
+
+log = logging.getLogger(__name__)
+
+
+def custom_exception_handler(exc, context):
+    # Call REST framework's default exception handler first,
+    # to get the standard error response.
+    response = exception_handler(exc, context)
+
+    if isinstance(exc, AiravataSystemException):
+        log.error("AiravataSystemException", exc_info=exc)
+        return Response(
+            {'detail': str(exc)},
+            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+    if isinstance(exc, serializers.ValidationError):
+        # Create a default error message for the validation error
+        response.data['detail'] = "ValidationError: {}".format(
+            json.dumps(response.data))
+
+    log.debug("API exception", exc_info=exc)
+    return response
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index a174565..585f9c6 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -44,7 +44,14 @@ class FullyEncodedHyperlinkedIdentityField(serializers.HyperlinkedIdentityField)
             lookup_value = getattr(obj, self.lookup_field)
         else:
             lookup_value = obj.get(self.lookup_field)
-        encoded_lookup_value = quote(lookup_value, safe="")
+        try:
+            encoded_lookup_value = quote(lookup_value, safe="")
+        except Exception as e:
+            log.warning(
+                "Failed to encode lookup_value [{}] for lookup_field "
+                "[{}] of object [{}]".format(
+                    lookup_value, self.lookup_field, obj))
+            raise
         # Bit of a hack. Django's URL reversing does URL encoding but it doesn't
         # encode all characters including some like '/' that are used in URL
         # mappings.
@@ -451,6 +458,9 @@ class GroupResourceProfileSerializer(
     updatedTime = UTCPosixTimestampDateTimeField(allow_null=True)
     userHasWriteAccess = serializers.SerializerMethodField()
 
+    class Meta:
+        required = ('groupResourceProfileName',)
+
     def update(self, instance, validated_data):
         result = super().update(instance, validated_data)
         result._removed_compute_resource_preferences = []
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js
new file mode 100644
index 0000000..7dc83e2
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js
@@ -0,0 +1,9 @@
+
+class ErrorReporter {
+    reportUnhandledError(unhandledError) {
+        // TODO: send to the server so it can be logged there
+        console.log(JSON.stringify(unhandledError, null, 4));
+    }
+}
+
+export default new ErrorReporter();
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/GlobalErrorHandler.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/GlobalErrorHandler.js
new file mode 100644
index 0000000..02b5278
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/GlobalErrorHandler.js
@@ -0,0 +1,27 @@
+// import StackTrace from 'stacktrace-js'
+
+import UnhandledErrorDispatcher from './UnhandledErrorDispatcher'
+
+class GlobalErrorHandler {
+
+    init() {
+        console.log("Initializing GlobalErrorHandler...");
+        window.onerror = this.handleGlobalError;
+    }
+
+    handleGlobalError(msg, url, lineNo, columnNo, error) {
+        UnhandledErrorDispatcher.reportError({
+            message: msg,
+            error: error,
+            details: {
+                url,
+                lineNo,
+                columnNo
+            }
+        });
+
+        return false;
+    }
+}
+
+export default new GlobalErrorHandler()
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
new file mode 100644
index 0000000..33a80ae
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
@@ -0,0 +1,19 @@
+
+let idSequence = 0;
+class UnhandledError {
+    constructor({message = null, error = null, details = null, suppressDisplay = false, suppressLogging = false}) {
+        this.id = idSequence++;
+        this.message = message;
+        this.error = error;
+        this.details = details;
+        this.suppressDisplay = suppressDisplay;
+        this.suppressLogging = suppressLogging;
+        this.createdDate = new Date();
+    }
+
+    get displayMessage() {
+        return this.error && this.error.message ? this.error.message : this.message;
+    }
+}
+
+export default UnhandledError;
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
new file mode 100644
index 0000000..1a8cee5
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
@@ -0,0 +1,18 @@
+import ErrorReporter from './ErrorReporter'
+import UnhandledError from './UnhandledError'
+import UnhandledErrorList from './UnhandledErrorDisplayList';
+
+class UnhandledErrorDispatcher {
+
+    reportError({ message = null, error = null, details = null, suppressDisplay = false, suppressLogging = false }) {
+        const unhandledError = new UnhandledError({message, error, details, suppressDisplay, suppressLogging});
+        if (!unhandledError.suppressDisplay) {
+            UnhandledErrorList.add(unhandledError);
+        }
+        if (!unhandledError.suppressLogging) {
+            ErrorReporter.reportUnhandledError(unhandledError);
+        }
+    }
+}
+
+export default new UnhandledErrorDispatcher();
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDisplayList.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDisplayList.js
new file mode 100644
index 0000000..411beb9
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDisplayList.js
@@ -0,0 +1,21 @@
+
+class UnhandledErrorDisplayList {
+    constructor() {
+        this.unhandledErrors = [];
+    }
+
+    add(unhandledError) {
+        this.unhandledErrors.push(unhandledError);
+    }
+
+    remove(unhandledError) {
+        const i = this.unhandledErrors.indexOf(unhandledError);
+        this.unhandledErrors.splice(i, 1);
+    }
+
+    get list() {
+        return this.unhandledErrors;
+    }
+}
+
+export default new UnhandledErrorDisplayList();
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 0e3502b..ba494bc 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -1,3 +1,6 @@
+import GlobalErrorHandler from './errors/GlobalErrorHandler'
+import UnhandledError from './errors/UnhandledError'
+import UnhandledErrorDisplayList from './errors/UnhandledErrorDisplayList'
 
 import ApplicationInterfaceDefinition from './models/ApplicationInterfaceDefinition'
 import ApplicationModule from './models/ApplicationModule'
@@ -28,7 +31,6 @@ import ExperimentSearchService from './services/ExperimentSearchService'
 import FullExperimentService from './services/FullExperimentService'
 import ProjectService from './services/ProjectService'
 import GroupService from './services/GroupService'
-import GroupResourceProfileService from './services/GroupResourceProfileService'
 import UserProfileService from './services/UserProfileService'
 import CloudJobSubmissionService from './services/CloudJobSubmissionService'
 import GlobusJobSubmissionService from './services/GlobusJobSubmissionService'
@@ -42,6 +44,12 @@ import ServiceFactory from './services/ServiceFactory'
 import FetchUtils from './utils/FetchUtils'
 import PaginationIterator from './utils/PaginationIterator'
 
+exports.errors = {
+    GlobalErrorHandler,
+    UnhandledError,
+    UnhandledErrorDisplayList,
+}
+
 exports.models = {
     ApplicationInterfaceDefinition,
     ApplicationModule,
@@ -86,7 +94,7 @@ exports.services = {
     GridFTPDataMovementService,
     SCPDataMovementService,
     UnicoreDataMovementService,
-    ServiceFactory
+    ServiceFactory,
 }
 
 exports.utils = {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
index 6ab85b5..7d9d408 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/services/ServiceFactory.js
@@ -159,7 +159,7 @@ class ServiceFactory {
             }
             let pathParamsMapping = parsePathParams(config.url);
             let queryParamsMapping = parseQueryMapping(config.queryParams);
-            serviceObj[functionName] = function (params = {}) {
+            serviceObj[functionName] = function (params = {}, { ignoreErrors } = { ignoreErrors: false }) {
                 let url = config.url;
                 let paramKeys = Object.keys(params);
                 let queryParams = {};
@@ -198,13 +198,13 @@ class ServiceFactory {
                 }
                 switch (config.requestType.toLowerCase()) {
                     case postKey:
-                        return FetchUtils.post(url, bodyParams, queryParams).then(resultHandler);
+                        return FetchUtils.post(url, bodyParams, queryParams, { ignoreErrors }).then(resultHandler);
                     case getKey:
-                        return FetchUtils.get(url, queryParams).then(paginationHandler);
+                        return FetchUtils.get(url, queryParams, { ignoreErrors }).then(paginationHandler);
                     case putKey:
-                        return FetchUtils.put(url, bodyParams).then(resultHandler);
+                        return FetchUtils.put(url, bodyParams, { ignoreErrors }).then(resultHandler);
                     case delKey:
-                        return FetchUtils.delete(url);
+                        return FetchUtils.delete(url, { ignoreErrors });
                 }
             }
         }
@@ -212,4 +212,4 @@ class ServiceFactory {
     }
 }
 
-export default new ServiceFactory(serviceConfiguration);
\ No newline at end of file
+export default new ServiceFactory(serviceConfiguration);
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
index 7395690..f16d382 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
@@ -1,3 +1,5 @@
+import UnhandledErrorDispatcher from "../errors/UnhandledErrorDispatcher";
+
 var count = 0;
 const parseQueryParams = function (url, queryParams = "") {
     if (queryParams && typeof(queryParams) != "string") {
@@ -56,61 +58,31 @@ export default {
         }
         return headers;
     },
-    post: function (url, body, queryParams = "", mediaType = "application/json") {
+    post: function (url, body, queryParams = "", {mediaType = "application/json", ignoreErrors = false } = {}) {
         var headers = this.createHeaders(mediaType)
         // Browsers automatically handle content type for FormData request bodies
         if (body instanceof FormData) {
             headers.delete("Content-Type");
         }
-        console.log("post body", body);
         url = parseQueryParams(url, queryParams);
-        incrementCount();
-        return fetch(url, {
+        return this.processFetch(url, {
             method: 'post',
             body: (body instanceof FormData || typeof body === 'string') ? body : JSON.stringify(body),
             headers: headers,
-            credentials: "same-origin"
-        }).then((response) => {
-            decrementCount();
-            if (response.ok) {
-                return Promise.resolve(response.json())
-            } else {
-                let error = new Error(response.statusText);
-                return response.json().then(json => {
-                    error.data = json;
-                })
-                    .then(() => Promise.reject(error), () => Promise.reject(error));
-            }
-        }, (response) => {
-            decrementCount();
-            return Promise.reject(response);
-        })
+            credentials: "same-origin",
+            ignoreErrors
+        });
     },
-    put: function (url, body, mediaType = "application/json") {
+    put: function (url, body, { mediaType = "application/json", ignoreErrors = false } = {}) {
         var headers = this.createHeaders(mediaType);
-        incrementCount();
-        return fetch(url, {
+        return this.processFetch(url, {
             method: 'put',
             body: (body instanceof FormData || typeof body === 'string') ? body : JSON.stringify(body),
             headers: headers,
             credentials: "same-origin"
-        }).then((response) => {
-            decrementCount();
-            if (response.ok) {
-                return Promise.resolve(response.json())
-            } else {
-                let error = new Error(response.statusText);
-                return response.json().then(json => {
-                    error.data = json;
-                })
-                    .then(() => Promise.reject(error), () => Promise.reject(error));
-            }
-        }, (response) => {
-            decrementCount();
-            return Promise.reject(response);
-        })
+        });
     },
-    get: function (url, queryParams = "", mediaType = "application/json") {
+    get: function (url, queryParams = "", { mediaType = "application/json", ignoreErrors = false } = {}) {
         if (queryParams && typeof(queryParams) != "string") {
             queryParams = Object.keys(queryParams).map(key => encodeURIComponent(key) + "=" + encodeURIComponent(queryParams[key])).join("&")
         }
@@ -118,46 +90,73 @@ export default {
             url = url + "?" + queryParams
         }
         var headers = this.createHeaders(mediaType);
-        incrementCount();
-        return fetch(url, {
+        return this.processFetch(url, {
             method: 'get',
             headers: headers,
             credentials: "same-origin"
-        }).then((response) => {
-            decrementCount();
-            if (response.ok) {
-                return Promise.resolve(response.json())
-            } else {
-                let error = new Error(response.statusText);
-                return response.json().then(json => {
-                    error.data = json;
-                })
-                    .then(() => Promise.reject(error), () => Promise.reject(error));
-            }
-        }, (response) => {
-            decrementCount();
-            return Promise.reject(response);
-        })
+        });
     },
-    delete: function (url) {
+    delete: function (url, { ignoreErrors = false } = {}) {
         var headers = this.createHeaders();
-        incrementCount();
-        return fetch(url, {
+        return this.processFetch(url, {
             method: 'delete',
             headers: headers,
             credentials: "same-origin"
-        }).then((response) => {
+        });
+    },
+    processFetch: function(url, {method = 'get', headers, credentials = 'same-origin', body, ignoreErrors = false}) {
+
+        const fetchConfig = {
+            method,
+            headers,
+            credentials,
+        };
+        if (body) {
+            fetchConfig.body = body;
+        }
+        incrementCount();
+        return fetch(url, fetchConfig).then((response) => {
             decrementCount();
-            // Not expecting a response body
-            if (response.ok && response.status === 204) {
-                return Promise.resolve();
+            if (response.ok) {
+                // No response body
+                if (response.status === 204) {
+                    return Promise.resolve();
+                } else {
+                    return Promise.resolve(response.json())
+                }
             } else {
-                let error = new Error(response.statusText);
                 return response.json().then(json => {
-                    error.data = json;
+                    const error = new Error(json.detail ? json.detail : response.statusText);
+                    error.details = this.createErrorDetails({url, body, status: response.status, responseBody: json})
+                    throw error;
+                }, e => { // In case JSON parsing fails
+                    const error = new Error(response.statusText);
+                    error.details = this.createErrorDetails({url, body, status: response.status})
+                    throw error;
+                });
+            }
+        }, (error) => {
+            decrementCount();
+            error.details = this.createErrorDetails({url, body});
+            throw error;
+        }).catch(error => {
+
+            if (!ignoreErrors) {
+                UnhandledErrorDispatcher.reportError({
+                    message: error.message,
+                    error: error,
+                    details: error.details,
                 })
-                    .then(() => Promise.reject(error), () => Promise.reject(error));
             }
+            throw error;
         })
+    },
+    createErrorDetails: function({url, body, status = null, responseBody = null}={}) {
+        return {
+            url,
+            body,
+            status,
+            response: responseBody
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/django_airavata/apps/api/thrift_utils.py b/django_airavata/apps/api/thrift_utils.py
index bbff521..be59ab9 100644
--- a/django_airavata/apps/api/thrift_utils.py
+++ b/django_airavata/apps/api/thrift_utils.py
@@ -90,8 +90,12 @@ def create_serializer_class(thrift_data_type, enable_date_time_conversion=False)
             for field in thrift_spec:
                 # Don't replace existing attrs to allow subclasses to override
                 if field and field[2] not in attrs:
-                    required = field[2] in meta.required if meta else False
-                    read_only = field[2] in meta.read_only if meta else False
+                    required = (field[2] in meta.required
+                                if meta and hasattr(meta, 'required')
+                                else False)
+                    read_only = (field[2] in meta.read_only
+                                 if meta and hasattr(meta, 'read_only')
+                                 else False)
                     allow_null = not required
                     field_serializer = process_field(
                         field, enable_date_time_conversion, required=required, read_only=read_only,
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index f3b4359..2edcae0 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -693,9 +693,7 @@ class GroupResourceProfileViewSet(APIBackedViewSet):
         group_resource_profile.gatewayId = self.gateway_id
         group_resource_profile_id = self.request.airavata_client.createGroupResourceProfile(authzToken=self.authz_token,
                                                                                             groupResourceProfile=group_resource_profile)
-        group_resource_profile = self.request.airavata_client.getGroupResourceProfile(
-            self.authz_token, group_resource_profile_id)
-        serializer.instance = group_resource_profile
+        group_resource_profile.groupResourceProfileId = group_resource_profile_id
 
     def perform_update(self, serializer):
         grp = serializer.save()
diff --git a/django_airavata/settings.py b/django_airavata/settings.py
index 2748bc7..ed05acf 100644
--- a/django_airavata/settings.py
+++ b/django_airavata/settings.py
@@ -174,6 +174,8 @@ REST_FRAMEWORK = {
     'DEFAULT_PERMISSION_CLASSES': (
         'rest_framework.permissions.IsAuthenticated',
     ),
+    'EXCEPTION_HANDLER':
+        'django_airavata.apps.api.exceptions.custom_exception_handler',
 }
 
 AUTHENTICATION_BACKENDS = [
diff --git a/django_airavata/static/common/js/components/NotificationsDisplay.vue b/django_airavata/static/common/js/components/NotificationsDisplay.vue
new file mode 100644
index 0000000..6a71aac
--- /dev/null
+++ b/django_airavata/static/common/js/components/NotificationsDisplay.vue
@@ -0,0 +1,75 @@
+<template>
+    <div id="notifications-display">
+        <transition-group name="fade" tag="div">
+            <b-alert v-for="error in errors"
+                    :variant="variant(error)" :key="error.id"
+                    show dismissible @dismissed="dismissedError(error)">
+                {{ error.message }}
+            </b-alert>
+            <b-alert v-for="notification in notifications"
+                    :variant="variant(notification)" :key="notification.id"
+                    show dismissible @dismissed="dismissedNotification(notification)">
+                {{ notification.message }}
+            </b-alert>
+        </transition-group>
+    </div>
+</template>
+
+<script>
+
+import { errors } from 'django-airavata-api'
+import Notification from '../notifications/Notification'
+import NotificationList from '../notifications/NotificationList'
+
+export default {
+    name: "notifications-display",
+    data () {
+        return {
+            notifications: NotificationList.list,
+            unhandledErrors: errors.UnhandledErrorDisplayList.list,
+        }
+    },
+    computed: {
+        errors: function() {
+
+            return this.unhandledErrors.map(unhandledError => {
+                return new Notification("UNHANDLED-ERROR-" + unhandledError.id, {
+                    type: "ERROR",
+                    message: unhandledError.displayMessage,
+                    details: unhandledError,
+                    createdDate: unhandledError.createdDate,
+                });
+            })
+        }
+    },
+    methods: {
+        dismissedNotification: function(notification) {
+            NotificationList.remove(notification);
+        },
+        dismissedError: function(error) {
+            errors.UnhandledErrorList.remove(error.details);
+        },
+        variant: function(notification) {
+            if (notification.type === "SUCCESS") {
+                return "success";
+            } else if (notification.type === "ERROR") {
+                return "danger";
+            } else {
+                return "secondary";
+            }
+        }
+    },
+}
+</script>
+
+<style>
+#notifications-display {
+    position: fixed;
+    top: 75px;
+    left: 20vw;
+    width: 60vw;
+    z-index: 10000;
+}
+</style>
+
+
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index 9c9c22f..28a2cfa 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -1,6 +1,7 @@
 import ApplicationCard from './components/ApplicationCard.vue'
 import Autocomplete from './components/Autocomplete.vue'
 import AutocompleteTextInput from './components/AutocompleteTextInput.vue'
+import NotificationsDisplay from './components/NotificationsDisplay.vue'
 import Pager from './components/Pager.vue'
 import ShareButton from './components/ShareButton.vue'
 
@@ -13,6 +14,7 @@ exports.components = {
     ApplicationCard,
     Autocomplete,
     AutocompleteTextInput,
+    NotificationsDisplay,
     ShareButton,
 }
 
diff --git a/django_airavata/static/common/js/notifications/Notification.js b/django_airavata/static/common/js/notifications/Notification.js
new file mode 100644
index 0000000..1b8864e
--- /dev/null
+++ b/django_airavata/static/common/js/notifications/Notification.js
@@ -0,0 +1,14 @@
+
+class Notification {
+    constructor(id, { type = "SUCCESS", message = null, details = null, dismissable = true, duration = 0, createdDate = null }) {
+        this.id = id;
+        this.type = type;
+        this.message = message;
+        this.details = details;
+        this.dismissable = dismissable;
+        this.duration = duration;
+        this.createdDate = createdDate ? createdDate : new Date();
+    }
+}
+
+export default Notification;
diff --git a/django_airavata/static/common/js/notifications/NotificationList.js b/django_airavata/static/common/js/notifications/NotificationList.js
new file mode 100644
index 0000000..09457a7
--- /dev/null
+++ b/django_airavata/static/common/js/notifications/NotificationList.js
@@ -0,0 +1,31 @@
+import { errors } from 'django-airavata-api'
+
+import Notification from './Notification'
+
+let notificationIdSequence = 0;
+
+class NotificationList {
+
+    constructor() {
+        this.notifications = [];
+    }
+
+    add(notification) {
+        this.notifications.push(notification);
+    }
+
+    remove(notification) {
+        const i = this.notifications.indexOf(notification);
+        this.notifications.splice(i, 1);
+    }
+
+    get list() {
+        return this.notifications;
+    }
+
+    getNextId() {
+        return "NOTIFICATION-" + notificationIdSequence++;
+    }
+}
+
+export default new NotificationList();
diff --git a/django_airavata/static/common/package-lock.json b/django_airavata/static/common/package-lock.json
index 564d5ff..664d58a 100644
--- a/django_airavata/static/common/package-lock.json
+++ b/django_airavata/static/common/package-lock.json
@@ -213,10 +213,13 @@
       "dev": true
     },
     "asn1": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
-      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
-      "dev": true
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
     },
     "asn1.js": {
       "version": "4.10.1",
@@ -256,9 +259,9 @@
       }
     },
     "assert-plus": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-      "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
       "dev": true
     },
     "assign-symbols": {
@@ -327,15 +330,15 @@
       }
     },
     "aws-sign2": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-      "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
       "dev": true
     },
     "aws4": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
-      "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==",
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
       "dev": true
     },
     "babel-code-frame": {
@@ -1140,15 +1143,6 @@
         "multicast-dns-service-types": "^1.1.0"
       }
     },
-    "boom": {
-      "version": "2.10.1",
-      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
-      "dev": true,
-      "requires": {
-        "hoek": "2.x.x"
-      }
-    },
     "bootstrap": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz",
@@ -1390,15 +1384,15 @@
       }
     },
     "caniuse-db": {
-      "version": "1.0.30000872",
-      "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000872.tgz",
-      "integrity": "sha1-P25Ttj03N2i/meiWEz1m74nEmZk=",
+      "version": "1.0.30000877",
+      "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000877.tgz",
+      "integrity": "sha512-9RcqvE8HYgdZZzFW6xBmj/CeCaTyCJdUhgkueBCq47AK//w/Yzlg0zcfV1GTlh3jyYEbresGfY2vDEG/AaK/dQ==",
       "dev": true
     },
     "caniuse-lite": {
-      "version": "1.0.30000865",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz",
-      "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==",
+      "version": "1.0.30000877",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000877.tgz",
+      "integrity": "sha512-h04kV/lcuhItU1CZTJOxUEk/9R+1XeJqgc67E+XC8J9TjPM8kzVgOn27ZtRdDUo8O5F8U4QRCzDWJrVym3w3Cg==",
       "dev": true
     },
     "caseless": {
@@ -1654,9 +1648,9 @@
       }
     },
     "commander": {
-      "version": "2.16.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz",
-      "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==",
+      "version": "2.17.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+      "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
       "dev": true
     },
     "commondir": {
@@ -1867,15 +1861,6 @@
         "which": "^1.2.9"
       }
     },
-    "cryptiles": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
-      "dev": true,
-      "requires": {
-        "boom": "2.x.x"
-      }
-    },
     "crypto-browserify": {
       "version": "3.12.0",
       "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@@ -2040,14 +2025,6 @@
       "dev": true,
       "requires": {
         "assert-plus": "^1.0.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        }
       }
     },
     "date-now": {
@@ -2090,13 +2067,12 @@
       "dev": true
     },
     "define-properties": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
-      "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
       "dev": true,
       "requires": {
-        "foreach": "^2.0.5",
-        "object-keys": "^1.0.8"
+        "object-keys": "^1.0.12"
       }
     },
     "define-property": {
@@ -4044,15 +4020,15 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.3.55",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.55.tgz",
-      "integrity": "sha1-8VDhCyC3fZ1Br8yjEu/gw7Gn/c4=",
+      "version": "1.3.58",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz",
+      "integrity": "sha512-AGJxlBEn2wOohxqWZkISVsOjZueKTQljfEODTDSEiMqSpH0S+xzV+/5oEM9AGaqhu7DzrpKOgU7ocQRjj0nJmg==",
       "dev": true
     },
     "elliptic": {
-      "version": "6.4.0",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
-      "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
+      "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
       "dev": true,
       "requires": {
         "bn.js": "^4.4.0",
@@ -4139,9 +4115,9 @@
       }
     },
     "es5-ext": {
-      "version": "0.10.45",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz",
-      "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==",
+      "version": "0.10.46",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz",
+      "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==",
       "dev": true,
       "requires": {
         "es6-iterator": "~2.0.3",
@@ -4714,9 +4690,9 @@
       "dev": true
     },
     "follow-redirects": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz",
-      "integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==",
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.5.tgz",
+      "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==",
       "dev": true,
       "requires": {
         "debug": "^3.1.0"
@@ -4748,12 +4724,6 @@
         "for-in": "^1.0.1"
       }
     },
-    "foreach": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
-      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
-      "dev": true
-    },
     "forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -4761,13 +4731,13 @@
       "dev": true
     },
     "form-data": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
-      "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
+      "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
       "dev": true,
       "requires": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.5",
+        "combined-stream": "1.0.6",
         "mime-types": "^2.1.12"
       }
     },
@@ -5423,14 +5393,6 @@
       "dev": true,
       "requires": {
         "assert-plus": "^1.0.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        }
       }
     },
     "glob": {
@@ -5555,31 +5517,19 @@
       "dev": true
     },
     "har-schema": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
-      "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
       "dev": true
     },
     "har-validator": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
-      "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
+      "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
       "dev": true,
       "requires": {
-        "ajv": "^4.9.1",
-        "har-schema": "^1.0.5"
-      },
-      "dependencies": {
-        "ajv": {
-          "version": "4.11.8",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
-          "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
-          "dev": true,
-          "requires": {
-            "co": "^4.6.0",
-            "json-stable-stringify": "^1.0.1"
-          }
-        }
+        "ajv": "^5.1.0",
+        "har-schema": "^2.0.0"
       }
     },
     "has": {
@@ -5669,18 +5619,6 @@
         "minimalistic-assert": "^1.0.1"
       }
     },
-    "hawk": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
-      "dev": true,
-      "requires": {
-        "boom": "2.x.x",
-        "cryptiles": "2.x.x",
-        "hoek": "2.x.x",
-        "sntp": "1.x.x"
-      }
-    },
     "he": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
@@ -5698,12 +5636,6 @@
         "minimalistic-crypto-utils": "^1.0.1"
       }
     },
-    "hoek": {
-      "version": "2.16.3",
-      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-      "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
-      "dev": true
-    },
     "home-or-tmp": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@@ -5902,12 +5834,12 @@
       }
     },
     "http-signature": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-      "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
       "dev": true,
       "requires": {
-        "assert-plus": "^0.2.0",
+        "assert-plus": "^1.0.0",
         "jsprim": "^1.2.2",
         "sshpk": "^1.7.0"
       }
@@ -6440,15 +6372,6 @@
       "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
       "dev": true
     },
-    "json-stable-stringify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
-      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
-      "dev": true,
-      "requires": {
-        "jsonify": "~0.0.0"
-      }
-    },
     "json-stringify-safe": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -6467,12 +6390,6 @@
       "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
       "dev": true
     },
-    "jsonify": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
-      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
-      "dev": true
-    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -6483,14 +6400,6 @@
         "extsprintf": "1.3.0",
         "json-schema": "0.2.3",
         "verror": "1.10.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        }
       }
     },
     "killable": {
@@ -7009,9 +6918,9 @@
       "dev": true
     },
     "neo-async": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz",
-      "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==",
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz",
+      "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==",
       "dev": true
     },
     "next-tick": {
@@ -7036,9 +6945,9 @@
       "dev": true
     },
     "node-gyp": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.7.0.tgz",
-      "integrity": "sha512-qDQE/Ft9xXP6zphwx4sD0t+VhwV7yFaloMpfbL2QnnDZcyaiakWlLdtFGGQfTAwpFHdpbRhRxVhIHN1OKAjgbg==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+      "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
       "dev": true,
       "requires": {
         "fstream": "^1.0.0",
@@ -7048,43 +6957,13 @@
         "nopt": "2 || 3",
         "npmlog": "0 || 1 || 2 || 3 || 4",
         "osenv": "0",
-        "request": ">=2.9.0 <2.82.0",
+        "request": "^2.87.0",
         "rimraf": "2",
         "semver": "~5.3.0",
         "tar": "^2.0.0",
         "which": "1"
       },
       "dependencies": {
-        "request": {
-          "version": "2.81.0",
-          "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
-          "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
-          "dev": true,
-          "requires": {
-            "aws-sign2": "~0.6.0",
-            "aws4": "^1.2.1",
-            "caseless": "~0.12.0",
-            "combined-stream": "~1.0.5",
-            "extend": "~3.0.0",
-            "forever-agent": "~0.6.1",
-            "form-data": "~2.1.1",
-            "har-validator": "~4.2.1",
-            "hawk": "~3.1.3",
-            "http-signature": "~1.1.0",
-            "is-typedarray": "~1.0.0",
-            "isstream": "~0.1.2",
-            "json-stringify-safe": "~5.0.1",
-            "mime-types": "~2.1.7",
-            "oauth-sign": "~0.8.1",
-            "performance-now": "^0.2.0",
-            "qs": "~6.4.0",
-            "safe-buffer": "^5.0.1",
-            "stringstream": "~0.0.4",
-            "tough-cookie": "~2.3.0",
-            "tunnel-agent": "^0.6.0",
-            "uuid": "^3.0.0"
-          }
-        },
         "semver": {
           "version": "5.3.0",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
@@ -7125,9 +7004,9 @@
       }
     },
     "node-sass": {
-      "version": "4.9.2",
-      "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz",
-      "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==",
+      "version": "4.9.3",
+      "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.3.tgz",
+      "integrity": "sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==",
       "dev": true,
       "requires": {
         "async-foreach": "^0.1.3",
@@ -7143,7 +7022,7 @@
         "meow": "^3.7.0",
         "mkdirp": "^0.5.1",
         "nan": "^2.10.0",
-        "node-gyp": "^3.3.1",
+        "node-gyp": "^3.8.0",
         "npmlog": "^4.0.0",
         "request": "2.87.0",
         "sass-graph": "^2.2.4",
@@ -7392,12 +7271,12 @@
       }
     },
     "original": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/original/-/original-1.0.1.tgz",
-      "integrity": "sha512-IEvtB5vM5ULvwnqMxWBLxkS13JIEXbakizMSo3yoPNPCIWzg8TG3Usn/UhXoZFM/m+FuEA20KdzPSFq/0rS+UA==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+      "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
       "dev": true,
       "requires": {
-        "url-parse": "~1.4.0"
+        "url-parse": "^1.4.3"
       }
     },
     "os-browserify": {
@@ -7578,9 +7457,9 @@
       "dev": true
     },
     "path-parse": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
-      "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
       "dev": true
     },
     "path-to-regexp": {
@@ -7622,9 +7501,9 @@
       }
     },
     "performance-now": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
-      "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
       "dev": true
     },
     "pify": {
@@ -7656,14 +7535,14 @@
       }
     },
     "popper.js": {
-      "version": "1.14.3",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz",
-      "integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU="
+      "version": "1.14.4",
+      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz",
+      "integrity": "sha1-juwdj/AqWjoVLdQ0FKFce3n9abY="
     },
     "portfinder": {
-      "version": "1.0.13",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz",
-      "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=",
+      "version": "1.0.16",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.16.tgz",
+      "integrity": "sha512-icBXCFQxzlK2PMepOM0QeEdPPFSLAaXXeuKOv5AClJlMy1oVCBrkDGJ12IZYesI/BF8mpeVco3vRCmgeBb4+hw==",
       "dev": true,
       "requires": {
         "async": "^1.5.2",
@@ -8418,9 +8297,9 @@
       "dev": true
     },
     "qs": {
-      "version": "6.4.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
-      "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
       "dev": true
     },
     "query-string": {
@@ -8452,9 +8331,9 @@
       "dev": true
     },
     "randomatic": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz",
-      "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz",
+      "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==",
       "dev": true,
       "requires": {
         "is-number": "^4.0.0",
@@ -8782,70 +8661,6 @@
         "tough-cookie": "~2.3.3",
         "tunnel-agent": "^0.6.0",
         "uuid": "^3.1.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        },
-        "aws-sign2": {
-          "version": "0.7.0",
-          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-          "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
-          "dev": true
-        },
-        "form-data": {
-          "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
-          "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
-          "dev": true,
-          "requires": {
-            "asynckit": "^0.4.0",
-            "combined-stream": "1.0.6",
-            "mime-types": "^2.1.12"
-          }
-        },
-        "har-schema": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
-          "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
-          "dev": true
-        },
-        "har-validator": {
-          "version": "5.0.3",
-          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
-          "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
-          "dev": true,
-          "requires": {
-            "ajv": "^5.1.0",
-            "har-schema": "^2.0.0"
-          }
-        },
-        "http-signature": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-          "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
-          "dev": true,
-          "requires": {
-            "assert-plus": "^1.0.0",
-            "jsprim": "^1.2.2",
-            "sshpk": "^1.7.0"
-          }
-        },
-        "performance-now": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-          "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
-          "dev": true
-        },
-        "qs": {
-          "version": "6.5.2",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
-          "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
-          "dev": true
-        }
       }
     },
     "require-directory": {
@@ -9323,15 +9138,6 @@
         }
       }
     },
-    "sntp": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
-      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
-      "dev": true,
-      "requires": {
-        "hoek": "2.x.x"
-      }
-    },
     "sockjs": {
       "version": "0.3.19",
       "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
@@ -9507,14 +9313,6 @@
         "jsbn": "~0.1.0",
         "safer-buffer": "^2.0.2",
         "tweetnacl": "~0.14.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        }
       }
     },
     "static-extend": {
@@ -9615,12 +9413,6 @@
         "safe-buffer": "~5.1.0"
       }
     },
-    "stringstream": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
-      "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
-      "dev": true
-    },
     "strip-ansi": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -9664,15 +9456,15 @@
       },
       "dependencies": {
         "ajv": {
-          "version": "6.5.2",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz",
-          "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==",
+          "version": "6.5.3",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz",
+          "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==",
           "dev": true,
           "requires": {
             "fast-deep-equal": "^2.0.1",
             "fast-json-stable-stringify": "^2.0.0",
             "json-schema-traverse": "^0.4.1",
-            "uri-js": "^4.2.1"
+            "uri-js": "^4.2.2"
           }
         },
         "fast-deep-equal": {
@@ -9688,9 +9480,9 @@
           "dev": true
         },
         "schema-utils": {
-          "version": "0.4.5",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz",
-          "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==",
+          "version": "0.4.7",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
+          "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
           "dev": true,
           "requires": {
             "ajv": "^6.1.0",
@@ -10145,9 +9937,9 @@
       "dev": true
     },
     "validate-npm-package-license": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
-      "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
       "dev": true,
       "requires": {
         "spdx-correct": "^3.0.0",
@@ -10175,14 +9967,6 @@
         "assert-plus": "^1.0.0",
         "core-util-is": "1.0.2",
         "extsprintf": "^1.2.0"
-      },
-      "dependencies": {
-        "assert-plus": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
-          "dev": true
-        }
       }
     },
     "vm-browserify": {
@@ -10308,15 +10092,15 @@
       },
       "dependencies": {
         "ajv": {
-          "version": "6.5.2",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz",
-          "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==",
+          "version": "6.5.3",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz",
+          "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==",
           "dev": true,
           "requires": {
             "fast-deep-equal": "^2.0.1",
             "fast-json-stable-stringify": "^2.0.0",
             "json-schema-traverse": "^0.4.1",
-            "uri-js": "^4.2.1"
+            "uri-js": "^4.2.2"
           }
         },
         "camelcase": {