Merge pull request #20 in DNS/adguard-dns from feature/315 to master
* commit 'ded02d112c0e5b6d9585ec5506f24746abffdff3': Add console error Fix timeout Handle settings errors Show toast on failed request Fix clear interval Add alert on failed requests
This commit is contained in:
commit
ae50a2f827
|
@ -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": {
|
"dom-serializer": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
|
||||||
|
@ -9048,6 +9053,11 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"nanoid": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-BAnxAdaihzMoszwhqRy8FPOX+dijs7esUEUYTIQ1KsOSKmCVNYnitAMmBDFxYzA6VQYvuUKw7o2K1AcMBTGzIg=="
|
||||||
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
|
@ -12948,6 +12958,17 @@
|
||||||
"prop-types": "^15.5.6"
|
"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": {
|
"read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
|
"nanoid": "^1.2.3",
|
||||||
"prop-types": "^15.6.1",
|
"prop-types": "^15.6.1",
|
||||||
"react": "^16.4.0",
|
"react": "^16.4.0",
|
||||||
"react-click-outside": "^3.0.1",
|
"react-click-outside": "^3.0.1",
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"react-table": "^6.8.6",
|
"react-table": "^6.8.6",
|
||||||
|
"react-transition-group": "^2.4.0",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-actions": "^2.4.0",
|
"redux-actions": "^2.4.0",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
|
|
|
@ -6,39 +6,54 @@ import Api from '../api/Api';
|
||||||
|
|
||||||
const apiClient = new 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 toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
|
||||||
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||||
|
|
||||||
export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||||
|
let successMessage = '';
|
||||||
|
try {
|
||||||
|
// TODO move setting keys to constants
|
||||||
switch (settingKey) {
|
switch (settingKey) {
|
||||||
case 'filtering':
|
case 'filtering':
|
||||||
if (status) {
|
if (status) {
|
||||||
|
successMessage = 'Disabled filtering';
|
||||||
await apiClient.disableFiltering();
|
await apiClient.disableFiltering();
|
||||||
} else {
|
} else {
|
||||||
|
successMessage = 'Enabled filtering';
|
||||||
await apiClient.enableFiltering();
|
await apiClient.enableFiltering();
|
||||||
}
|
}
|
||||||
dispatch(toggleSettingStatus({ settingKey }));
|
dispatch(toggleSettingStatus({ settingKey }));
|
||||||
break;
|
break;
|
||||||
case 'safebrowsing':
|
case 'safebrowsing':
|
||||||
if (status) {
|
if (status) {
|
||||||
|
successMessage = 'Disabled safebrowsing';
|
||||||
await apiClient.disableSafebrowsing();
|
await apiClient.disableSafebrowsing();
|
||||||
} else {
|
} else {
|
||||||
|
successMessage = 'Enabled safebrowsing';
|
||||||
await apiClient.enableSafebrowsing();
|
await apiClient.enableSafebrowsing();
|
||||||
}
|
}
|
||||||
dispatch(toggleSettingStatus({ settingKey }));
|
dispatch(toggleSettingStatus({ settingKey }));
|
||||||
break;
|
break;
|
||||||
case 'parental':
|
case 'parental':
|
||||||
if (status) {
|
if (status) {
|
||||||
|
successMessage = 'Disabled parental control';
|
||||||
await apiClient.disableParentalControl();
|
await apiClient.disableParentalControl();
|
||||||
} else {
|
} else {
|
||||||
|
successMessage = 'Enabled parental control';
|
||||||
await apiClient.enableParentalControl();
|
await apiClient.enableParentalControl();
|
||||||
}
|
}
|
||||||
dispatch(toggleSettingStatus({ settingKey }));
|
dispatch(toggleSettingStatus({ settingKey }));
|
||||||
break;
|
break;
|
||||||
case 'safesearch':
|
case 'safesearch':
|
||||||
if (status) {
|
if (status) {
|
||||||
|
successMessage = 'Disabled safe search';
|
||||||
await apiClient.disableSafesearch();
|
await apiClient.disableSafesearch();
|
||||||
} else {
|
} else {
|
||||||
|
successMessage = 'Enabled safe search';
|
||||||
await apiClient.enableSafesearch();
|
await apiClient.enableSafesearch();
|
||||||
}
|
}
|
||||||
dispatch(toggleSettingStatus({ settingKey }));
|
dispatch(toggleSettingStatus({ settingKey }));
|
||||||
|
@ -46,6 +61,10 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
dispatch(addSuccessToast(successMessage));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initSettingsRequest = createAction('SETTINGS_INIT_REQUEST');
|
export const initSettingsRequest = createAction('SETTINGS_INIT_REQUEST');
|
||||||
|
@ -73,7 +92,7 @@ export const initSettings = settingsList => async (dispatch) => {
|
||||||
};
|
};
|
||||||
dispatch(initSettingsSuccess({ settingsList: newSettingsList }));
|
dispatch(initSettingsSuccess({ settingsList: newSettingsList }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(initSettingsFailure());
|
dispatch(initSettingsFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -88,7 +107,7 @@ export const getDnsStatus = () => async (dispatch) => {
|
||||||
const dnsStatus = await apiClient.getGlobalStatus();
|
const dnsStatus = await apiClient.getGlobalStatus();
|
||||||
dispatch(dnsStatusSuccess(dnsStatus));
|
dispatch(dnsStatusSuccess(dnsStatus));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(initSettingsFailure());
|
dispatch(initSettingsFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -103,7 +122,7 @@ export const enableDns = () => async (dispatch) => {
|
||||||
await apiClient.startGlobalFiltering();
|
await apiClient.startGlobalFiltering();
|
||||||
dispatch(enableDnsSuccess());
|
dispatch(enableDnsSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(enableDnsFailure());
|
dispatch(enableDnsFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -118,8 +137,8 @@ export const disableDns = () => async (dispatch) => {
|
||||||
await apiClient.stopGlobalFiltering();
|
await apiClient.stopGlobalFiltering();
|
||||||
dispatch(disableDnsSuccess());
|
dispatch(disableDnsSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(disableDnsFailure(error));
|
||||||
dispatch(disableDnsFailure());
|
dispatch(addErrorToast({ error }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -139,7 +158,7 @@ export const getStats = () => async (dispatch) => {
|
||||||
|
|
||||||
dispatch(getStatsSuccess(processedStats));
|
dispatch(getStatsSuccess(processedStats));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(getStatsFailure());
|
dispatch(getStatsFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -150,19 +169,19 @@ export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
|
||||||
|
|
||||||
export const getTopStats = () => async (dispatch, getState) => {
|
export const getTopStats = () => async (dispatch, getState) => {
|
||||||
dispatch(getTopStatsRequest());
|
dispatch(getTopStatsRequest());
|
||||||
try {
|
|
||||||
const state = getState();
|
|
||||||
const timer = setInterval(async () => {
|
const timer = setInterval(async () => {
|
||||||
|
const state = getState();
|
||||||
if (state.dashboard.isCoreRunning) {
|
if (state.dashboard.isCoreRunning) {
|
||||||
|
clearInterval(timer);
|
||||||
|
try {
|
||||||
const stats = await apiClient.getGlobalStatsTop();
|
const stats = await apiClient.getGlobalStatsTop();
|
||||||
dispatch(getTopStatsSuccess(stats));
|
dispatch(getTopStatsSuccess(stats));
|
||||||
clearInterval(timer);
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(getTopStatsFailure(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
dispatch(getTopStatsFailure());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||||
|
@ -171,19 +190,19 @@ export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||||
|
|
||||||
export const getLogs = () => async (dispatch, getState) => {
|
export const getLogs = () => async (dispatch, getState) => {
|
||||||
dispatch(getLogsRequest());
|
dispatch(getLogsRequest());
|
||||||
try {
|
|
||||||
const state = getState();
|
|
||||||
const timer = setInterval(async () => {
|
const timer = setInterval(async () => {
|
||||||
|
const state = getState();
|
||||||
if (state.dashboard.isCoreRunning) {
|
if (state.dashboard.isCoreRunning) {
|
||||||
|
clearInterval(timer);
|
||||||
|
try {
|
||||||
const logs = normalizeLogs(await apiClient.getQueryLog());
|
const logs = normalizeLogs(await apiClient.getQueryLog());
|
||||||
dispatch(getLogsSuccess(logs));
|
dispatch(getLogsSuccess(logs));
|
||||||
clearInterval(timer);
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(getLogsFailure(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
dispatch(getLogsFailure());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
|
export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
|
||||||
|
@ -202,7 +221,7 @@ export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
|
||||||
await toggleMethod();
|
await toggleMethod();
|
||||||
dispatch(toggleLogStatusSuccess());
|
dispatch(toggleLogStatusSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(toggleLogStatusFailure());
|
dispatch(toggleLogStatusFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -217,7 +236,7 @@ export const setRules = rules => async (dispatch) => {
|
||||||
await apiClient.setRules(rules);
|
await apiClient.setRules(rules);
|
||||||
dispatch(setRulesSuccess());
|
dispatch(setRulesSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(setRulesFailure());
|
dispatch(setRulesFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -232,7 +251,7 @@ export const getFilteringStatus = () => async (dispatch) => {
|
||||||
const status = await apiClient.getFilteringStatus();
|
const status = await apiClient.getFilteringStatus();
|
||||||
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
|
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(getFilteringStatusFailure());
|
dispatch(getFilteringStatusFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -258,7 +277,7 @@ export const toggleFilterStatus = url => async (dispatch, getState) => {
|
||||||
dispatch(toggleFilterSuccess(url));
|
dispatch(toggleFilterSuccess(url));
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(toggleFilterFailure());
|
dispatch(toggleFilterFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -274,7 +293,7 @@ export const refreshFilters = () => async (dispatch) => {
|
||||||
dispatch(refreshFiltersSuccess);
|
dispatch(refreshFiltersSuccess);
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(refreshFiltersFailure());
|
dispatch(refreshFiltersFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -292,7 +311,7 @@ export const getStatsHistory = () => async (dispatch) => {
|
||||||
const normalizedHistory = normalizeHistory(statsHistory);
|
const normalizedHistory = normalizeHistory(statsHistory);
|
||||||
dispatch(getStatsHistorySuccess(normalizedHistory));
|
dispatch(getStatsHistorySuccess(normalizedHistory));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(getStatsHistoryFailure());
|
dispatch(getStatsHistoryFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -308,7 +327,7 @@ export const addFilter = url => async (dispatch) => {
|
||||||
dispatch(addFilterSuccess(url));
|
dispatch(addFilterSuccess(url));
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(addFilterFailure());
|
dispatch(addFilterFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -325,7 +344,7 @@ export const removeFilter = url => async (dispatch) => {
|
||||||
dispatch(removeFilterSuccess(url));
|
dispatch(removeFilterSuccess(url));
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(removeFilterFailure());
|
dispatch(removeFilterFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -344,7 +363,7 @@ export const downloadQueryLog = () => async (dispatch) => {
|
||||||
data = await apiClient.downloadQueryLog();
|
data = await apiClient.downloadQueryLog();
|
||||||
dispatch(downloadQueryLogSuccess());
|
dispatch(downloadQueryLogSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(downloadQueryLogFailure());
|
dispatch(downloadQueryLogFailure());
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
@ -361,7 +380,7 @@ export const setUpstream = url => async (dispatch) => {
|
||||||
await apiClient.setUpstream(url);
|
await apiClient.setUpstream(url);
|
||||||
dispatch(setUpstreamSuccess());
|
dispatch(setUpstreamSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(setUpstreamFailure());
|
dispatch(setUpstreamFailure());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,12 +6,17 @@ export default class Api {
|
||||||
baseUrl = 'control';
|
baseUrl = 'control';
|
||||||
|
|
||||||
async makeRequest(path, method = 'POST', config) {
|
async makeRequest(path, method = 'POST', config) {
|
||||||
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
url: `${this.baseUrl}/${path}`,
|
url: `${this.baseUrl}/${path}`,
|
||||||
method,
|
method,
|
||||||
...config,
|
...config,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(`${this.baseUrl}/${path} | ${error.response.data} | ${error.response.status}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global methods
|
// Global methods
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Settings from '../../containers/Settings';
|
||||||
import Filters from '../../containers/Filters';
|
import Filters from '../../containers/Filters';
|
||||||
import Logs from '../../containers/Logs';
|
import Logs from '../../containers/Logs';
|
||||||
import Footer from '../ui/Footer';
|
import Footer from '../ui/Footer';
|
||||||
|
import Toasts from '../Toasts';
|
||||||
|
|
||||||
import Status from '../ui/Status';
|
import Status from '../ui/Status';
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ class App extends Component {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<Toasts />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
|
@ -60,6 +62,7 @@ App.propTypes = {
|
||||||
enableDns: PropTypes.func,
|
enableDns: PropTypes.func,
|
||||||
dashboard: PropTypes.object,
|
dashboard: PropTypes.object,
|
||||||
isCoreRunning: PropTypes.bool,
|
isCoreRunning: PropTypes.bool,
|
||||||
|
error: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -26,7 +26,6 @@ class Dashboard extends Component {
|
||||||
dashboard.processingStatsHistory ||
|
dashboard.processingStatsHistory ||
|
||||||
dashboard.processingTopStats;
|
dashboard.processingTopStats;
|
||||||
|
|
||||||
const disableButton = <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={() => this.props.disableDns()}>Disable DNS</button>;
|
|
||||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.componentDidMount()}>Refresh statistics</button>;
|
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.componentDidMount()}>Refresh statistics</button>;
|
||||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.componentDidMount()}></button>;
|
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.componentDidMount()}></button>;
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ class Dashboard extends Component {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<PageTitle title="Dashboard">
|
<PageTitle title="Dashboard">
|
||||||
<div className="page-title__actions">
|
<div className="page-title__actions">
|
||||||
{disableButton}
|
|
||||||
{refreshFullButton}
|
{refreshFullButton}
|
||||||
</div>
|
</div>
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
.toasts {
|
||||||
|
position: fixed;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 10;
|
||||||
|
width: 345px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(236, 53, 53, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast--success {
|
||||||
|
background-color: rgba(90, 173, 99, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast__content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast__dismiss {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-enter {
|
||||||
|
opacity: 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-exit {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-exit-active {
|
||||||
|
opacity: 0.01;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class Toast extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const timeout = this.props.type === 'error' ? 30000 : 5000;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.props.removeToast(this.props.id);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={`toast toast--${this.props.type}`}>
|
||||||
|
<p className="toast__content">
|
||||||
|
{this.props.message}
|
||||||
|
</p>
|
||||||
|
<button className="toast__dismiss" onClick={() => this.props.removeToast(this.props.id)}>
|
||||||
|
<svg stroke="#fff" fill="none" width="20" height="20" strokeWidth="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
message: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
removeToast: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toast;
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||||
|
import * as actionCreators from '../../actions';
|
||||||
|
import Toast from './Toast';
|
||||||
|
|
||||||
|
import './Toast.css';
|
||||||
|
|
||||||
|
const Toasts = props => (
|
||||||
|
<TransitionGroup className="toasts">
|
||||||
|
{props.toasts.notices && props.toasts.notices.map((toast) => {
|
||||||
|
const { id } = toast;
|
||||||
|
return (
|
||||||
|
<CSSTransition
|
||||||
|
key={id}
|
||||||
|
timeout={500}
|
||||||
|
classNames="toast"
|
||||||
|
>
|
||||||
|
<Toast removeToast={props.removeToast} {...toast} />
|
||||||
|
</CSSTransition>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TransitionGroup>
|
||||||
|
);
|
||||||
|
|
||||||
|
Toasts.propTypes = {
|
||||||
|
toasts: PropTypes.object,
|
||||||
|
removeToast: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const { toasts } = state;
|
||||||
|
const props = { toasts };
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
actionCreators,
|
||||||
|
)(Toasts);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
|
import nanoid from 'nanoid';
|
||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
@ -172,9 +173,38 @@ const filtering = handleActions({
|
||||||
userRules: '',
|
userRules: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const toasts = handleActions({
|
||||||
|
[actions.addErrorToast]: (state, { payload }) => {
|
||||||
|
const errorToast = {
|
||||||
|
id: nanoid(),
|
||||||
|
message: payload.error.toString(),
|
||||||
|
type: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = { ...state, notices: [...state.notices, errorToast] };
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
[actions.addSuccessToast]: (state, { payload }) => {
|
||||||
|
const successToast = {
|
||||||
|
id: nanoid(),
|
||||||
|
message: payload,
|
||||||
|
type: 'success',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = { ...state, notices: [...state.notices, successToast] };
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
[actions.removeToast]: (state, { payload }) => {
|
||||||
|
const filtered = state.notices.filter(notice => notice.id !== payload);
|
||||||
|
const newState = { ...state, notices: filtered };
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
}, { notices: [] });
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
dashboard,
|
dashboard,
|
||||||
queryLogs,
|
queryLogs,
|
||||||
filtering,
|
filtering,
|
||||||
|
toasts,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue