diff --git a/client/package-lock.json b/client/package-lock.json index 3309c809..a40c8624 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3849,6 +3849,11 @@ } } }, + "dom-helpers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", + "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -9048,6 +9053,11 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.2.3.tgz", + "integrity": "sha512-BAnxAdaihzMoszwhqRy8FPOX+dijs7esUEUYTIQ1KsOSKmCVNYnitAMmBDFxYzA6VQYvuUKw7o2K1AcMBTGzIg==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -12948,6 +12958,17 @@ "prop-types": "^15.5.6" } }, + "react-transition-group": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz", + "integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/client/package.json b/client/package.json index 19260f52..ce8e436d 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "date-fns": "^1.29.0", "file-saver": "^1.3.8", "lodash": "^4.17.10", + "nanoid": "^1.2.3", "prop-types": "^15.6.1", "react": "^16.4.0", "react-click-outside": "^3.0.1", @@ -23,6 +24,7 @@ "react-redux": "^5.0.7", "react-router-dom": "^4.2.2", "react-table": "^6.8.6", + "react-transition-group": "^2.4.0", "redux": "^4.0.0", "redux-actions": "^2.4.0", "redux-thunk": "^2.3.0", diff --git a/client/src/actions/index.js b/client/src/actions/index.js index daa1cc49..27d57f39 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -6,45 +6,64 @@ import Api from '../api/Api'; const apiClient = new Api(); +export const addErrorToast = createAction('ADD_ERROR_TOAST'); +export const addSuccessToast = createAction('ADD_SUCCESS_TOAST'); +export const removeToast = createAction('REMOVE_TOAST'); + export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE'); export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW'); export const toggleSetting = (settingKey, status) => async (dispatch) => { - switch (settingKey) { - case 'filtering': - if (status) { - await apiClient.disableFiltering(); - } else { - await apiClient.enableFiltering(); - } - dispatch(toggleSettingStatus({ settingKey })); - break; - case 'safebrowsing': - if (status) { - await apiClient.disableSafebrowsing(); - } else { - await apiClient.enableSafebrowsing(); - } - dispatch(toggleSettingStatus({ settingKey })); - break; - case 'parental': - if (status) { - await apiClient.disableParentalControl(); - } else { - await apiClient.enableParentalControl(); - } - dispatch(toggleSettingStatus({ settingKey })); - break; - case 'safesearch': - if (status) { - await apiClient.disableSafesearch(); - } else { - await apiClient.enableSafesearch(); - } - dispatch(toggleSettingStatus({ settingKey })); - break; - default: - break; + let successMessage = ''; + try { + // TODO move setting keys to constants + switch (settingKey) { + case 'filtering': + if (status) { + successMessage = 'Disabled filtering'; + await apiClient.disableFiltering(); + } else { + successMessage = 'Enabled filtering'; + await apiClient.enableFiltering(); + } + dispatch(toggleSettingStatus({ settingKey })); + break; + case 'safebrowsing': + if (status) { + successMessage = 'Disabled safebrowsing'; + await apiClient.disableSafebrowsing(); + } else { + successMessage = 'Enabled safebrowsing'; + await apiClient.enableSafebrowsing(); + } + dispatch(toggleSettingStatus({ settingKey })); + break; + case 'parental': + if (status) { + successMessage = 'Disabled parental control'; + await apiClient.disableParentalControl(); + } else { + successMessage = 'Enabled parental control'; + await apiClient.enableParentalControl(); + } + dispatch(toggleSettingStatus({ settingKey })); + break; + case 'safesearch': + if (status) { + successMessage = 'Disabled safe search'; + await apiClient.disableSafesearch(); + } else { + successMessage = 'Enabled safe search'; + await apiClient.enableSafesearch(); + } + dispatch(toggleSettingStatus({ settingKey })); + break; + default: + break; + } + dispatch(addSuccessToast(successMessage)); + } catch (error) { + dispatch(addErrorToast({ error })); } }; @@ -73,7 +92,7 @@ export const initSettings = settingsList => async (dispatch) => { }; dispatch(initSettingsSuccess({ settingsList: newSettingsList })); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(initSettingsFailure()); } }; @@ -88,7 +107,7 @@ export const getDnsStatus = () => async (dispatch) => { const dnsStatus = await apiClient.getGlobalStatus(); dispatch(dnsStatusSuccess(dnsStatus)); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(initSettingsFailure()); } }; @@ -103,7 +122,7 @@ export const enableDns = () => async (dispatch) => { await apiClient.startGlobalFiltering(); dispatch(enableDnsSuccess()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(enableDnsFailure()); } }; @@ -118,8 +137,8 @@ export const disableDns = () => async (dispatch) => { await apiClient.stopGlobalFiltering(); dispatch(disableDnsSuccess()); } catch (error) { - console.error(error); - dispatch(disableDnsFailure()); + dispatch(disableDnsFailure(error)); + dispatch(addErrorToast({ error })); } }; @@ -139,7 +158,7 @@ export const getStats = () => async (dispatch) => { dispatch(getStatsSuccess(processedStats)); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(getStatsFailure()); } }; @@ -150,19 +169,19 @@ export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS'); export const getTopStats = () => async (dispatch, getState) => { dispatch(getTopStatsRequest()); - try { + const timer = setInterval(async () => { const state = getState(); - const timer = setInterval(async () => { - if (state.dashboard.isCoreRunning) { + if (state.dashboard.isCoreRunning) { + clearInterval(timer); + try { const stats = await apiClient.getGlobalStatsTop(); dispatch(getTopStatsSuccess(stats)); - clearInterval(timer); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getTopStatsFailure(error)); } - }, 100); - } catch (error) { - console.error(error); - dispatch(getTopStatsFailure()); - } + } + }, 100); }; export const getLogsRequest = createAction('GET_LOGS_REQUEST'); @@ -171,19 +190,19 @@ export const getLogsSuccess = createAction('GET_LOGS_SUCCESS'); export const getLogs = () => async (dispatch, getState) => { dispatch(getLogsRequest()); - try { + const timer = setInterval(async () => { const state = getState(); - const timer = setInterval(async () => { - if (state.dashboard.isCoreRunning) { + if (state.dashboard.isCoreRunning) { + clearInterval(timer); + try { const logs = normalizeLogs(await apiClient.getQueryLog()); dispatch(getLogsSuccess(logs)); - clearInterval(timer); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getLogsFailure(error)); } - }, 100); - } catch (error) { - console.error(error); - dispatch(getLogsFailure()); - } + } + }, 100); }; export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST'); @@ -202,7 +221,7 @@ export const toggleLogStatus = queryLogEnabled => async (dispatch) => { await toggleMethod(); dispatch(toggleLogStatusSuccess()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(toggleLogStatusFailure()); } }; @@ -217,7 +236,7 @@ export const setRules = rules => async (dispatch) => { await apiClient.setRules(rules); dispatch(setRulesSuccess()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(setRulesFailure()); } }; @@ -232,7 +251,7 @@ export const getFilteringStatus = () => async (dispatch) => { const status = await apiClient.getFilteringStatus(); dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) })); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(getFilteringStatusFailure()); } }; @@ -258,7 +277,7 @@ export const toggleFilterStatus = url => async (dispatch, getState) => { dispatch(toggleFilterSuccess(url)); dispatch(getFilteringStatus()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(toggleFilterFailure()); } }; @@ -274,7 +293,7 @@ export const refreshFilters = () => async (dispatch) => { dispatch(refreshFiltersSuccess); dispatch(getFilteringStatus()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(refreshFiltersFailure()); } }; @@ -292,7 +311,7 @@ export const getStatsHistory = () => async (dispatch) => { const normalizedHistory = normalizeHistory(statsHistory); dispatch(getStatsHistorySuccess(normalizedHistory)); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(getStatsHistoryFailure()); } }; @@ -308,7 +327,7 @@ export const addFilter = url => async (dispatch) => { dispatch(addFilterSuccess(url)); dispatch(getFilteringStatus()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(addFilterFailure()); } }; @@ -325,7 +344,7 @@ export const removeFilter = url => async (dispatch) => { dispatch(removeFilterSuccess(url)); dispatch(getFilteringStatus()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(removeFilterFailure()); } }; @@ -344,7 +363,7 @@ export const downloadQueryLog = () => async (dispatch) => { data = await apiClient.downloadQueryLog(); dispatch(downloadQueryLogSuccess()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(downloadQueryLogFailure()); } return data; @@ -361,7 +380,7 @@ export const setUpstream = url => async (dispatch) => { await apiClient.setUpstream(url); dispatch(setUpstreamSuccess()); } catch (error) { - console.error(error); + dispatch(addErrorToast({ error })); dispatch(setUpstreamFailure()); } }; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 1ba54271..8cec8a05 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -6,12 +6,17 @@ export default class Api { baseUrl = 'control'; async makeRequest(path, method = 'POST', config) { - const response = await axios({ - url: `${this.baseUrl}/${path}`, - method, - ...config, - }); - return response.data; + try { + const response = await axios({ + url: `${this.baseUrl}/${path}`, + method, + ...config, + }); + return response.data; + } catch (error) { + console.error(error); + throw new Error(`${this.baseUrl}/${path} | ${error.response.data} | ${error.response.status}`); + } } // Global methods diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index 7bdad801..1b48620c 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -13,6 +13,7 @@ import Settings from '../../containers/Settings'; import Filters from '../../containers/Filters'; import Logs from '../../containers/Logs'; import Footer from '../ui/Footer'; +import Toasts from '../Toasts'; import Status from '../ui/Status'; @@ -49,6 +50,7 @@ class App extends Component { }