diff --git a/web/package.json b/web/package.json
index 1aa81cb0a3..97d1be970c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,7 +8,9 @@
"private": true,
"dependencies": {
"axios": "^0.18.0",
+ "immutability-helper": "^2.8.1",
"lodash": "^4.17.10",
+ "moment": "^2.22.2",
"patternfly-react": "^2.13.1",
"prop-types": "^15.6.2",
"react": "^16.4.2",
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 5ca9f9bf86..b2488a93d7 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -25,16 +25,21 @@ import {
Masthead,
Notification,
NotificationDrawer,
+ TimedToastNotification,
+ ToastNotificationList,
} from 'patternfly-react'
+import * as moment from 'moment'
import logo from './images/logo.png'
import { routes } from './routes'
import { fetchConfigErrorsAction } from './actions/configErrors'
import { setTenantAction } from './actions/tenant'
+import { clearError } from './actions/errors'
class App extends React.Component {
static propTypes = {
+ errors: PropTypes.array,
configErrors: PropTypes.array,
info: PropTypes.object,
tenant: PropTypes.object,
@@ -149,6 +154,25 @@ class App extends React.Component {
}
}
+ renderErrors = (errors) => {
+ return (
+
+ {errors.map(error => (
+ {this.props.dispatch(clearError(error.id))}}
+ >
+
+ {error.text} ({error.status})
+ {error.url}
+
+
+ ))}
+
+ )
+ }
+
renderConfigErrors = (configErrors) => {
const { history } = this.props
const errors = []
@@ -207,7 +231,7 @@ class App extends React.Component {
render() {
const { menuCollapsed, showErrors } = this.state
- const { tenant, configErrors } = this.props
+ const { errors, configErrors, tenant } = this.props
return (
@@ -252,6 +276,7 @@ class App extends React.Component {
)}
+ {errors.length > 0 && this.renderErrors(errors)}
{this.renderContent()}
@@ -263,6 +288,7 @@ class App extends React.Component {
// This connect the info state from the store to the info property of the App.
export default withRouter(connect(
state => ({
+ errors: state.errors,
configErrors: state.configErrors,
info: state.info,
tenant: state.tenant
diff --git a/web/src/actions/errors.js b/web/src/actions/errors.js
new file mode 100644
index 0000000000..e113121276
--- /dev/null
+++ b/web/src/actions/errors.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Red Hat, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+export const ADD_ERROR = 'ADD_ERROR'
+export const CLEAR_ERROR = 'CLEAR_ERROR'
+export const CLEAR_ERRORS = 'CLEAR_ERRORS'
+
+let errorId = 0
+
+export const addError = error => ({
+ type: ADD_ERROR,
+ id: errorId++,
+ error
+})
+
+export const addApiError = error => (
+ addError({
+ url: error.request.responseURL,
+ status: error.response.status,
+ text: error.response.statusText,
+ })
+)
+
+export const clearError = id => ({
+ type: CLEAR_ERROR,
+ id
+})
+
+export const clearErrors = () => ({
+ type: CLEAR_ERRORS
+})
diff --git a/web/src/actions/info.js b/web/src/actions/info.js
index 81e9b4a2a0..a28c62651c 100644
--- a/web/src/actions/info.js
+++ b/web/src/actions/info.js
@@ -36,7 +36,10 @@ const fetchInfo = () => dispatch => {
dispatch(fetchInfoRequest())
return API.fetchInfo()
.then(response => dispatch(fetchInfoSuccess(response.data)))
- .catch(error => dispatch(fetchInfoFail(error)))
+ .catch(error => {
+ dispatch(fetchInfoFail(error))
+ setTimeout(() => {dispatch(fetchInfo())}, 5000)
+ })
}
const shouldFetchInfo = state => {
diff --git a/web/src/reducers/errors.js b/web/src/reducers/errors.js
new file mode 100644
index 0000000000..165bb02d0d
--- /dev/null
+++ b/web/src/reducers/errors.js
@@ -0,0 +1,47 @@
+// Copyright 2018 Red Hat, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import update from 'immutability-helper'
+
+import {
+ ADD_ERROR,
+ CLEAR_ERROR,
+ CLEAR_ERRORS,
+ addApiError,
+} from '../actions/errors'
+
+
+export default (state = [], action) => {
+ // Intercept API failure
+ if (action.error && action.type.match(/.*_FETCH_FAIL$/)) {
+ action = addApiError(action.error)
+ }
+ switch (action.type) {
+ case ADD_ERROR:
+ if (state.filter(error => (
+ error.url === action.error.url &&
+ error.status === action.error.status)).length > 0)
+ return state
+ action.error.id = action.id
+ action.error.date = Date.now()
+ return update(state, {$push: [action.error]})
+ case CLEAR_ERROR:
+ return update(state, {$splice: [[state.indexOf(
+ state.filter(item => (item.id === action.id))[0]), 1]]})
+ case CLEAR_ERRORS:
+ return []
+ default:
+ return state
+ }
+}
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 6f287aed55..ab6aab8ec0 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -15,12 +15,14 @@
import { combineReducers } from 'redux'
import configErrors from './configErrors'
+import errors from './errors'
import info from './info'
import tenant from './tenant'
const reducers = {
info,
configErrors,
+ errors,
tenant,
}
diff --git a/web/yarn.lock b/web/yarn.lock
index 2cee873f7e..54e5a3c9da 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -3762,6 +3762,12 @@ ignore@^4.0.2:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+immutability-helper@^2.8.1:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.8.1.tgz#3c5ec05fcd83676bfae7146f319595243ad904f4"
+ dependencies:
+ invariant "^2.2.0"
+
import-lazy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -3857,7 +3863,7 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
+invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@@ -5027,7 +5033,7 @@ moment-timezone@^0.4.0, moment-timezone@^0.4.1:
dependencies:
moment ">= 2.6.0"
-"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1:
+"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1, moment@^2.22.2:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"