mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-17 02:48:28 -07:00
+ client: handle time interval for statistics
This commit is contained in:
parent
7ff27dbb42
commit
011bc3e36b
@ -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"
|
||||||
}
|
}
|
||||||
|
36
client/src/actions/stats.js
Normal file
36
client/src/actions/stats.js
Normal 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());
|
||||||
|
}
|
||||||
|
};
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
71
client/src/components/Settings/StatsConfig/Form.js
Normal file
71
client/src/components/Settings/StatsConfig/Form.js
Normal 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);
|
49
client/src/components/Settings/StatsConfig/index.js
Normal file
49
client/src/components/Settings/StatsConfig/index.js
Normal 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);
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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];
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
27
client/src/reducers/stats.js
Normal file
27
client/src/reducers/stats.js
Normal 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;
|
Loading…
Reference in New Issue
Block a user