+ client: handle time interval for statistics

This commit is contained in:
Ildar Kamalov 2019-08-08 13:43:06 +03:00
parent 7ff27dbb42
commit 011bc3e36b
11 changed files with 251 additions and 4 deletions

View File

@ -361,5 +361,10 @@
"encryption_certificates_source_path": "Set a certificates file path", "encryption_certificates_source_path": "Set a certificates file path",
"encryption_certificates_source_content":"Paste the certificates contents", "encryption_certificates_source_content":"Paste the certificates contents",
"encryption_key_source_path": "Set a private key file", "encryption_key_source_path": "Set a private key file",
"encryption_key_source_content": "Paste the private key contents" "encryption_key_source_content": "Paste the private key contents",
"stats_params": "Statistics configuration",
"config_successfully_saved": "Configuration successfully saved",
"interval_24_hour": "24 hours",
"interval_days": "{{value}} days",
"time_period": "Time period"
} }

View File

@ -0,0 +1,36 @@
import { createAction } from 'redux-actions';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
const apiClient = new Api();
export const getStatsConfigRequest = createAction('GET_LOGS_CONFIG_REQUEST');
export const getStatsConfigFailure = createAction('GET_LOGS_CONFIG_FAILURE');
export const getStatsConfigSuccess = createAction('GET_LOGS_CONFIG_SUCCESS');
export const getStatsConfig = () => async (dispatch) => {
dispatch(getStatsConfigRequest());
try {
const data = await apiClient.getStatsInfo();
dispatch(getStatsConfigSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getStatsConfigFailure());
}
};
export const setStatsConfigRequest = createAction('SET_STATS_CONFIG_REQUEST');
export const setStatsConfigFailure = createAction('SET_STATS_CONFIG_FAILURE');
export const setStatsConfigSuccess = createAction('SET_STATS_CONFIG_SUCCESS');
export const setStatsConfig = config => async (dispatch) => {
dispatch(setStatsConfigRequest());
try {
await apiClient.setStatsConfig(config);
dispatch(addSuccessToast('config_successfully_saved'));
dispatch(setStatsConfigSuccess(config));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setStatsConfigFailure());
}
};

View File

@ -527,4 +527,22 @@ export default class Api {
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
// Settings for statistics
STATS_INFO = { path: 'stats_info', method: 'GET' };
STATS_CONFIG = { path: 'stats_config', method: 'POST' };
getStatsInfo() {
const { path, method } = this.STATS_INFO;
return this.makeRequest(path, method);
}
setStatsConfig(data) {
const { path, method } = this.STATS_CONFIG;
const config = {
data,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
} }

View File

@ -104,3 +104,8 @@
min-width: 23px; min-width: 23px;
padding: 5px; padding: 5px;
} }
.custom-control-label,
.custom-control-label:before {
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
}

View File

@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderRadioField, toNumber } from '../../../helpers/form';
import { STATS_INTERVALS } from '../../../helpers/constants';
const getIntervalFields = (processing, t, handleChange, toNumber) =>
STATS_INTERVALS.map((interval) => {
const title = interval === 1
? t('interval_24_hour')
: t('interval_days', { value: interval });
return (
<Field
key={interval}
name="interval"
type="radio"
component={renderRadioField}
value={interval}
placeholder={title}
onChange={handleChange}
normalize={toNumber}
disabled={processing}
/>
);
});
const Form = (props) => {
const {
handleSubmit, handleChange, processing, t,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<label className="form__label" htmlFor="server_name">
<Trans>time_period</Trans>
</label>
</div>
<div className="col-12">
<div className="form__group">
<div className="custom-controls-stacked">
{getIntervalFields(processing, t, handleChange, toNumber)}
</div>
</div>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({
form: 'logConfigForm',
}),
])(Form);

View File

@ -0,0 +1,49 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import debounce from 'lodash/debounce';
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
import Form from './Form';
import Card from '../../ui/Card';
class StatsConfig extends Component {
handleFormChange = debounce((values) => {
this.props.setStatsConfig(values);
}, DEBOUNCE_TIMEOUT);
render() {
const {
t,
interval,
processing,
} = this.props;
return (
<Card
title={t('stats_params')}
bodyType="card-body box-body--settings"
>
<div className="form">
<Form
initialValues={{
interval,
}}
onSubmit={this.handleFormChange}
onChange={this.handleFormChange}
processing={processing}
/>
</div>
</Card>
);
}
}
StatsConfig.propTypes = {
interval: PropTypes.number.isRequired,
processing: PropTypes.bool.isRequired,
setStatsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(StatsConfig);

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next'; import { withNamespaces, Trans } from 'react-i18next';
import Services from './Services'; import Services from './Services';
import StatsConfig from './StatsConfig';
import Checkbox from '../ui/Checkbox'; import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
@ -37,6 +38,7 @@ class Settings extends Component {
componentDidMount() { componentDidMount() {
this.props.initSettings(this.settings); this.props.initSettings(this.settings);
this.props.getBlockedServices(); this.props.getBlockedServices();
this.props.getStatsConfig();
} }
renderSettings = (settings) => { renderSettings = (settings) => {
@ -62,7 +64,12 @@ class Settings extends Component {
render() { render() {
const { const {
settings, services, setBlockedServices, t, settings,
services,
setBlockedServices,
setStatsConfig,
stats,
t,
} = this.props; } = this.props;
return ( return (
<Fragment> <Fragment>
@ -78,6 +85,13 @@ class Settings extends Component {
</div> </div>
</Card> </Card>
</div> </div>
<div className="col-md-12">
<StatsConfig
interval={stats.interval}
processing={stats.setConfigProcessing}
setStatsConfig={setStatsConfig}
/>
</div>
<div className="col-md-12"> <div className="col-md-12">
<Services <Services
services={services} services={services}
@ -97,6 +111,8 @@ Settings.propTypes = {
settings: PropTypes.object, settings: PropTypes.object,
settingsList: PropTypes.object, settingsList: PropTypes.object,
toggleSetting: PropTypes.func, toggleSetting: PropTypes.func,
getStatsConfig: PropTypes.func,
setStatsConfig: PropTypes.func,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -1,13 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { initSettings, toggleSetting } from '../actions'; import { initSettings, toggleSetting } from '../actions';
import { getBlockedServices, setBlockedServices } from '../actions/services'; import { getBlockedServices, setBlockedServices } from '../actions/services';
import { getStatsConfig, setStatsConfig } from '../actions/stats';
import Settings from '../components/Settings'; import Settings from '../components/Settings';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { settings, services } = state; const { settings, services, stats } = state;
const props = { const props = {
settings, settings,
services, services,
stats,
}; };
return props; return props;
}; };
@ -17,6 +19,8 @@ const mapDispatchToProps = {
toggleSetting, toggleSetting,
getBlockedServices, getBlockedServices,
setBlockedServices, setBlockedServices,
getStatsConfig,
setStatsConfig,
}; };
export default connect( export default connect(

View File

@ -260,3 +260,5 @@ export const FILTERED_STATUS = {
FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService', FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService',
REWRITE: 'Rewrite', REWRITE: 'Rewrite',
}; };
export const STATS_INTERVALS = [1, 7, 30, 90];

View File

@ -11,6 +11,7 @@ import clients from './clients';
import access from './access'; import access from './access';
import rewrites from './rewrites'; import rewrites from './rewrites';
import services from './services'; import services from './services';
import stats from './stats';
const settings = handleActions({ const settings = handleActions({
[actions.initSettingsRequest]: state => ({ ...state, processing: true }), [actions.initSettingsRequest]: state => ({ ...state, processing: true }),
@ -218,6 +219,14 @@ const dashboard = handleActions({
clients: [], clients: [],
autoClients: [], autoClients: [],
topStats: [], topStats: [],
stats: {
dns_queries: '',
blocked_filtering: '',
replaced_safebrowsing: '',
replaced_parental: '',
replaced_safesearch: '',
avg_processing_time: '',
},
}); });
const queryLogs = handleActions({ const queryLogs = handleActions({
@ -230,7 +239,11 @@ const queryLogs = handleActions({
[actions.downloadQueryLogRequest]: state => ({ ...state, logsDownloading: true }), [actions.downloadQueryLogRequest]: state => ({ ...state, logsDownloading: true }),
[actions.downloadQueryLogFailure]: state => ({ ...state, logsDownloading: false }), [actions.downloadQueryLogFailure]: state => ({ ...state, logsDownloading: false }),
[actions.downloadQueryLogSuccess]: state => ({ ...state, logsDownloading: false }), [actions.downloadQueryLogSuccess]: state => ({ ...state, logsDownloading: false }),
}, { getLogsProcessing: false, logsDownloading: false }); }, {
getLogsProcessing: false,
logsDownloading: false,
logs: [],
});
const filtering = handleActions({ const filtering = handleActions({
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }), [actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
@ -426,6 +439,7 @@ export default combineReducers({
access, access,
rewrites, rewrites,
services, services,
stats,
loadingBar: loadingBarReducer, loadingBar: loadingBarReducer,
form: formReducer, form: formReducer,
}); });

View File

@ -0,0 +1,27 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/stats';
const stats = handleActions({
[actions.getStatsConfigRequest]: state => ({ ...state, getConfigProcessing: true }),
[actions.getStatsConfigFailure]: state => ({ ...state, getConfigProcessing: false }),
[actions.getStatsConfigSuccess]: (state, { payload }) => ({
...state,
interval: payload.interval,
getConfigProcessing: false,
}),
[actions.setStatsConfigRequest]: state => ({ ...state, setConfigProcessing: true }),
[actions.setStatsConfigFailure]: state => ({ ...state, setConfigProcessing: false }),
[actions.setStatsConfigSuccess]: (state, { payload }) => ({
...state,
interval: payload.interval,
setConfigProcessing: false,
}),
}, {
getConfigProcessing: false,
setConfigProcessing: false,
interval: 1,
});
export default stats;