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

View File

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

View File

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