Initial commit
This commit is contained in:
9
client/src/__tests__/App.test.js
Normal file
9
client/src/__tests__/App.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from '../components/App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
367
client/src/actions/index.js
Normal file
367
client/src/actions/index.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import round from 'lodash/round';
|
||||
|
||||
import { normalizeHistory, normalizeFilteringStatus } from '../helpers/helpers';
|
||||
import Api from '../api/Api';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
export const initSettingsRequest = createAction('SETTINGS_INIT_REQUEST');
|
||||
export const initSettingsFailure = createAction('SETTINGS_INIT_FAILURE');
|
||||
export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS');
|
||||
|
||||
export const initSettings = settingsList => async (dispatch) => {
|
||||
dispatch(initSettingsRequest());
|
||||
try {
|
||||
const filteringStatus = await apiClient.getFilteringStatus();
|
||||
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
|
||||
const parentalStatus = await apiClient.getParentalStatus();
|
||||
const safesearchStatus = await apiClient.getSafesearchStatus();
|
||||
const {
|
||||
filtering,
|
||||
safebrowsing,
|
||||
parental,
|
||||
safesearch,
|
||||
} = settingsList;
|
||||
const newSettingsList = {
|
||||
filtering: { ...filtering, enabled: filteringStatus.enabled },
|
||||
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
|
||||
parental: { ...parental, enabled: parentalStatus.enabled },
|
||||
safesearch: { ...safesearch, enabled: safesearchStatus.enabled },
|
||||
};
|
||||
dispatch(initSettingsSuccess({ settingsList: newSettingsList }));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(initSettingsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
|
||||
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
|
||||
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
||||
|
||||
export const getDnsStatus = () => async (dispatch) => {
|
||||
dispatch(dnsStatusRequest());
|
||||
try {
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(initSettingsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const enableDnsRequest = createAction('ENABLE_DNS_REQUEST');
|
||||
export const enableDnsFailure = createAction('ENABLE_DNS_FAILURE');
|
||||
export const enableDnsSuccess = createAction('ENABLE_DNS_SUCCESS');
|
||||
|
||||
export const enableDns = () => async (dispatch) => {
|
||||
dispatch(enableDnsRequest());
|
||||
try {
|
||||
await apiClient.startGlobalFiltering();
|
||||
dispatch(enableDnsSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(enableDnsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const disableDnsRequest = createAction('DISABLE_DNS_REQUEST');
|
||||
export const disableDnsFailure = createAction('DISABLE_DNS_FAILURE');
|
||||
export const disableDnsSuccess = createAction('DISABLE_DNS_SUCCESS');
|
||||
|
||||
export const disableDns = () => async (dispatch) => {
|
||||
dispatch(disableDnsRequest());
|
||||
try {
|
||||
await apiClient.stopGlobalFiltering();
|
||||
dispatch(disableDnsSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(disableDnsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getStatsRequest = createAction('GET_STATS_REQUEST');
|
||||
export const getStatsFailure = createAction('GET_STATS_FAILURE');
|
||||
export const getStatsSuccess = createAction('GET_STATS_SUCCESS');
|
||||
|
||||
export const getStats = () => async (dispatch) => {
|
||||
dispatch(getStatsRequest());
|
||||
try {
|
||||
const stats = await apiClient.getGlobalStats();
|
||||
|
||||
const processedStats = {
|
||||
...stats,
|
||||
avg_processing_time: round(stats.avg_processing_time, 2),
|
||||
};
|
||||
|
||||
dispatch(getStatsSuccess(processedStats));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getStatsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
|
||||
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
|
||||
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
|
||||
|
||||
export const getTopStats = () => async (dispatch, getState) => {
|
||||
dispatch(getTopStatsRequest());
|
||||
try {
|
||||
const state = getState();
|
||||
const timer = setInterval(async () => {
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
const stats = await apiClient.getGlobalStatsTop();
|
||||
dispatch(getTopStatsSuccess(stats));
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getTopStatsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||
|
||||
export const getLogs = () => async (dispatch, getState) => {
|
||||
dispatch(getLogsRequest());
|
||||
try {
|
||||
const state = getState();
|
||||
const timer = setInterval(async () => {
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
const logs = await apiClient.getQueryLog();
|
||||
dispatch(getLogsSuccess(logs));
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getLogsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
|
||||
export const toggleLogStatusFailure = createAction('TOGGLE_LOGS_FAILURE');
|
||||
export const toggleLogStatusSuccess = createAction('TOGGLE_LOGS_SUCCESS');
|
||||
|
||||
export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
|
||||
dispatch(toggleLogStatusRequest());
|
||||
let toggleMethod;
|
||||
if (queryLogEnabled) {
|
||||
toggleMethod = apiClient.disableQueryLog.bind(apiClient);
|
||||
} else {
|
||||
toggleMethod = apiClient.enableQueryLog.bind(apiClient);
|
||||
}
|
||||
try {
|
||||
await toggleMethod();
|
||||
dispatch(toggleLogStatusSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(toggleLogStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setRulesRequest = createAction('SET_RULES_REQUEST');
|
||||
export const setRulesFailure = createAction('SET_RULES_FAILURE');
|
||||
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
|
||||
|
||||
export const setRules = rules => async (dispatch) => {
|
||||
dispatch(setRulesRequest());
|
||||
try {
|
||||
await apiClient.setRules(rules);
|
||||
dispatch(setRulesSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(setRulesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQUEST');
|
||||
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
|
||||
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
|
||||
|
||||
export const getFilteringStatus = () => async (dispatch) => {
|
||||
dispatch(getFilteringStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getFilteringStatus();
|
||||
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getFilteringStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleFilterRequest = createAction('FILTER_ENABLE_REQUEST');
|
||||
export const toggleFilterFailure = createAction('FILTER_ENABLE_FAILURE');
|
||||
export const toggleFilterSuccess = createAction('FILTER_ENABLE_SUCCESS');
|
||||
|
||||
export const toggleFilterStatus = url => async (dispatch, getState) => {
|
||||
dispatch(toggleFilterRequest());
|
||||
const state = getState();
|
||||
const { filters } = state.filtering;
|
||||
const filter = filters.filter(filter => filter.url === url)[0];
|
||||
const { enabled } = filter;
|
||||
let toggleStatusMethod;
|
||||
if (enabled) {
|
||||
toggleStatusMethod = apiClient.disableFilter.bind(apiClient);
|
||||
} else {
|
||||
toggleStatusMethod = apiClient.enableFilter.bind(apiClient);
|
||||
}
|
||||
try {
|
||||
await toggleStatusMethod(url);
|
||||
dispatch(toggleFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(toggleFilterFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
|
||||
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
||||
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
||||
|
||||
export const refreshFilters = () => async (dispatch) => {
|
||||
dispatch(refreshFiltersRequest);
|
||||
try {
|
||||
await apiClient.refreshFilters();
|
||||
dispatch(refreshFiltersSuccess);
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(refreshFiltersFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const handleRulesChange = createAction('HANDLE_RULES_CHANGE');
|
||||
|
||||
export const getStatsHistoryRequest = createAction('GET_STATS_HISTORY_REQUEST');
|
||||
export const getStatsHistoryFailure = createAction('GET_STATS_HISTORY_FAILURE');
|
||||
export const getStatsHistorySuccess = createAction('GET_STATS_HISTORY_SUCCESS');
|
||||
|
||||
export const getStatsHistory = () => async (dispatch) => {
|
||||
dispatch(getStatsHistoryRequest());
|
||||
try {
|
||||
const statsHistory = await apiClient.getGlobalStatsHistory();
|
||||
const normalizedHistory = normalizeHistory(statsHistory);
|
||||
dispatch(getStatsHistorySuccess(normalizedHistory));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getStatsHistoryFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
|
||||
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
|
||||
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
||||
|
||||
export const addFilter = url => async (dispatch) => {
|
||||
dispatch(addFilterRequest());
|
||||
try {
|
||||
await apiClient.addFilter(url);
|
||||
dispatch(addFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addFilterFailure());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const removeFilterRequest = createAction('ADD_FILTER_REQUEST');
|
||||
export const removeFilterFailure = createAction('ADD_FILTER_FAILURE');
|
||||
export const removeFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
||||
|
||||
export const removeFilter = url => async (dispatch) => {
|
||||
dispatch(removeFilterRequest());
|
||||
try {
|
||||
await apiClient.removeFilter(url);
|
||||
dispatch(removeFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(removeFilterFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
|
||||
|
||||
export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST');
|
||||
export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
|
||||
export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
|
||||
|
||||
// TODO create some common flasher with all server errors
|
||||
export const downloadQueryLog = () => async (dispatch) => {
|
||||
let data;
|
||||
dispatch(downloadQueryLogRequest());
|
||||
try {
|
||||
data = await apiClient.downloadQueryLog();
|
||||
dispatch(downloadQueryLogSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(downloadQueryLogFailure());
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
|
||||
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||
export const setUpstreamSuccess = createAction('SET_UPSTREAM_SUCCESS');
|
||||
|
||||
export const setUpstream = url => async (dispatch) => {
|
||||
dispatch(setUpstreamRequest());
|
||||
try {
|
||||
await apiClient.setUpstream(url);
|
||||
dispatch(setUpstreamSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(setUpstreamFailure());
|
||||
}
|
||||
};
|
||||
256
client/src/api/Api.js
Normal file
256
client/src/api/Api.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import axios from 'axios';
|
||||
import startOfToday from 'date-fns/start_of_today';
|
||||
import endOfToday from 'date-fns/end_of_today';
|
||||
import dateFormat from 'date-fns/format';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Global methods
|
||||
GLOBAL_RESTART = { path: 'restart', method: 'POST' };
|
||||
GLOBAL_START = { path: 'start', method: 'POST' };
|
||||
GLOBAL_STATS = { path: 'stats', method: 'GET' };
|
||||
GLOBAL_STATS_HISTORY = { path: 'stats_history', method: 'GET' };
|
||||
GLOBAL_STATUS = { path: 'status', method: 'GET' };
|
||||
GLOBAL_STOP = { path: 'stop', method: 'POST' };
|
||||
GLOBAL_STATS_TOP = { path: 'stats_top', method: 'GET' };
|
||||
GLOBAL_QUERY_LOG = { path: 'querylog', method: 'GET' };
|
||||
GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' };
|
||||
GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstream_dns', method: 'POST' };
|
||||
|
||||
restartGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_RESTART;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
startGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_START;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
stopGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_STOP;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getGlobalStats() {
|
||||
const { path, method } = this.GLOBAL_STATS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getGlobalStatsHistory() {
|
||||
const { path, method } = this.GLOBAL_STATS_HISTORY;
|
||||
const format = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||
const todayStart = dateFormat(startOfToday(), format);
|
||||
const todayEnd = dateFormat(endOfToday(), format);
|
||||
|
||||
const config = {
|
||||
params: {
|
||||
start_time: todayStart,
|
||||
end_time: todayEnd,
|
||||
time_unit: 'hours',
|
||||
},
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
getGlobalStatus() {
|
||||
const { path, method } = this.GLOBAL_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getGlobalStatsTop() {
|
||||
const { path, method } = this.GLOBAL_STATS_TOP;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getQueryLog() {
|
||||
const { path, method } = this.GLOBAL_QUERY_LOG;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
downloadQueryLog() {
|
||||
const { path, method } = this.GLOBAL_QUERY_LOG;
|
||||
const queryString = '?download=1';
|
||||
return this.makeRequest(path + queryString, method);
|
||||
}
|
||||
|
||||
enableQueryLog() {
|
||||
const { path, method } = this.GLOBAL_QUERY_LOG_ENABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
disableQueryLog() {
|
||||
const { path, method } = this.GLOBAL_QUERY_LOG_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setUpstream(url) {
|
||||
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: url,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
// Filtering
|
||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
||||
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
|
||||
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
|
||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'PUT' };
|
||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'DELETE' };
|
||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'PUT' };
|
||||
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
|
||||
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
|
||||
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
||||
|
||||
getFilteringStatus() {
|
||||
const { path, method } = this.FILTERING_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
enableFiltering() {
|
||||
const { path, method } = this.FILTERING_ENABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
disableFiltering() {
|
||||
const { path, method } = this.FILTERING_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// TODO find out when to use force parameter
|
||||
refreshFilters() {
|
||||
const { path, method } = this.FILTERING_REFRESH;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
addFilter(url) {
|
||||
const { path, method } = this.FILTERING_ADD_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
removeFilter(url) {
|
||||
const { path, method } = this.FILTERING_REMOVE_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
setRules(rules) {
|
||||
const { path, method } = this.FILTERING_SET_RULES;
|
||||
const parameters = {
|
||||
data: rules,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
enableFilter(url) {
|
||||
const { path, method } = this.FILTERING_ENABLE_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
disableFilter(url) {
|
||||
const { path, method } = this.FILTERING_DISABLE_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
// Parental
|
||||
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
|
||||
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };
|
||||
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' };
|
||||
|
||||
getParentalStatus() {
|
||||
const { path, method } = this.PARENTAL_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
enableParentalControl() {
|
||||
const { path, method } = this.PARENTAL_ENABLE;
|
||||
const parameter = 'sensitivity=TEEN'; // this parameter TEEN is hardcoded
|
||||
const config = {
|
||||
data: parameter,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
disableParentalControl() {
|
||||
const { path, method } = this.PARENTAL_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Safebrowsing
|
||||
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' };
|
||||
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' };
|
||||
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' };
|
||||
|
||||
getSafebrowsingStatus() {
|
||||
const { path, method } = this.SAFEBROWSING_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
enableSafebrowsing() {
|
||||
const { path, method } = this.SAFEBROWSING_ENABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
disableSafebrowsing() {
|
||||
const { path, method } = this.SAFEBROWSING_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Safesearch
|
||||
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' };
|
||||
SAFESEARCH_ENABLE = { path: 'safesearch/enable', method: 'POST' };
|
||||
SAFESEARCH_DISABLE = { path: 'safesearch/disable', method: 'POST' };
|
||||
|
||||
getSafesearchStatus() {
|
||||
const { path, method } = this.SAFESEARCH_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
enableSafesearch() {
|
||||
const { path, method } = this.SAFESEARCH_ENABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
disableSafesearch() {
|
||||
const { path, method } = this.SAFESEARCH_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
}
|
||||
19
client/src/components/App/index.css
Normal file
19
client/src/components/App/index.css
Normal file
@@ -0,0 +1,19 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.container--wrap {
|
||||
min-height: calc(100vh - 160px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.container--wrap {
|
||||
min-height: calc(100vh - 117px);
|
||||
}
|
||||
}
|
||||
65
client/src/components/App/index.js
Normal file
65
client/src/components/App/index.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { HashRouter, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import 'react-table/react-table.css';
|
||||
import '../ui/Tabler.css';
|
||||
import '../ui/ReactTable.css';
|
||||
import './index.css';
|
||||
|
||||
import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Filters from '../../containers/Filters';
|
||||
import Logs from '../../containers/Logs';
|
||||
import Footer from '../ui/Footer';
|
||||
|
||||
import Status from '../ui/Status';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsStatus();
|
||||
}
|
||||
|
||||
handleStatusChange = () => {
|
||||
this.props.enableDns();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
return (
|
||||
<HashRouter hashType='noslash'>
|
||||
<Fragment>
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap">
|
||||
{!dashboard.processing && !dashboard.isCoreRunning &&
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status handleStatusChange={this.handleStatusChange} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!dashboard.processing && dashboard.isCoreRunning &&
|
||||
<Fragment>
|
||||
<Route path="/" exact component={Dashboard} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/filters" component={Filters} />
|
||||
<Route path="/logs" component={Logs} />
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
<Footer />
|
||||
</Fragment>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
getDnsStatus: PropTypes.func,
|
||||
enableDns: PropTypes.func,
|
||||
dashboard: PropTypes.object,
|
||||
isCoreRunning: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default App;
|
||||
34
client/src/components/Dashboard/BlockedDomains.js
Normal file
34
client/src/components/Dashboard/BlockedDomains.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Clients = props => (
|
||||
<Card title="Top blocked domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topBlockedDomains, (value, prop) => (
|
||||
{ ip: prop, domain: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Domain name',
|
||||
accessor: 'domain',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No domains found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Clients.propTypes = {
|
||||
topBlockedDomains: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Clients;
|
||||
34
client/src/components/Dashboard/Clients.js
Normal file
34
client/src/components/Dashboard/Clients.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Clients = props => (
|
||||
<Card title="Top clients" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topClients, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Request count',
|
||||
accessor: 'count',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No clients found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Clients.propTypes = {
|
||||
topClients: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Clients;
|
||||
92
client/src/components/Dashboard/Counters.js
Normal file
92
client/src/components/Dashboard/Counters.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
|
||||
const Counters = props => (
|
||||
<Card title="General counters" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<table className="table card-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
DNS Queries
|
||||
<Tooltip text="A number of DNS quieries processed in the last 3 minutes" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.dnsQueries}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked by filters
|
||||
<Tooltip text="A number of DNS requests blocked by filters" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.blockedFiltering}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked malware/phishing
|
||||
<Tooltip text="A number of DNS requests blocked" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.replacedSafebrowsing}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked adult websites
|
||||
<Tooltip text="A number of adult websites blocked" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.replacedParental}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Enforced safe search
|
||||
<Tooltip text="A number of DNS requests to search engines for which Safe Search was enforced" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.replacedSafesearch}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Average processing time
|
||||
<Tooltip text="Average time in milliseconds on processing a DNS request" />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
{props.avgProcessingTime}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Counters.propTypes = {
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
blockedFiltering: PropTypes.number.isRequired,
|
||||
replacedSafebrowsing: PropTypes.number.isRequired,
|
||||
replacedParental: PropTypes.number.isRequired,
|
||||
replacedSafesearch: PropTypes.number.isRequired,
|
||||
avgProcessingTime: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Counters;
|
||||
34
client/src/components/Dashboard/QueriedDomains.js
Normal file
34
client/src/components/Dashboard/QueriedDomains.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const QueriedDomains = props => (
|
||||
<Card title="Top queried domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topQueriedDomains, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Request count',
|
||||
accessor: 'count',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No domains found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
QueriedDomains.propTypes = {
|
||||
topQueriedDomains: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default QueriedDomains;
|
||||
61
client/src/components/Dashboard/Statistics.js
Normal file
61
client/src/components/Dashboard/Statistics.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Statistics = props => (
|
||||
<Card title="Statistics" subtitle="Today" bodyType="card-graph" refresh={props.refreshButton}>
|
||||
{props.history ?
|
||||
<ResponsiveLine
|
||||
data={props.history}
|
||||
margin={{
|
||||
top: 50,
|
||||
right: 40,
|
||||
bottom: 80,
|
||||
left: 80,
|
||||
}}
|
||||
minY="auto"
|
||||
stacked={false}
|
||||
curve='monotoneX'
|
||||
axisBottom={{
|
||||
orient: 'bottom',
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: -45,
|
||||
legend: 'time',
|
||||
legendOffset: 50,
|
||||
legendPosition: 'center',
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: 'left',
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: 'count',
|
||||
legendOffset: -40,
|
||||
legendPosition: 'center',
|
||||
}}
|
||||
enableArea={true}
|
||||
dotSize={10}
|
||||
dotColor="inherit:darker(0.3)"
|
||||
dotBorderWidth={2}
|
||||
dotBorderColor="#ffffff"
|
||||
dotLabel="y"
|
||||
dotLabelYOffset={-12}
|
||||
animate={true}
|
||||
motionStiffness={90}
|
||||
motionDamping={15}
|
||||
/>
|
||||
:
|
||||
<h2 className="text-muted">Empty data</h2>
|
||||
}
|
||||
</Card>
|
||||
);
|
||||
|
||||
Statistics.propTypes = {
|
||||
history: PropTypes.array.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Statistics;
|
||||
103
client/src/components/Dashboard/index.js
Normal file
103
client/src/components/Dashboard/index.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import Statistics from './Statistics';
|
||||
import Counters from './Counters';
|
||||
import Clients from './Clients';
|
||||
import QueriedDomains from './QueriedDomains';
|
||||
import BlockedDomains from './BlockedDomains';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Loading from '../ui/Loading';
|
||||
|
||||
class Dashboard extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getStats();
|
||||
this.props.getStatsHistory();
|
||||
this.props.getTopStats();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const dashboardProcessing =
|
||||
dashboard.processing ||
|
||||
dashboard.processingStats ||
|
||||
dashboard.processingStatsHistory ||
|
||||
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.props.getStats()}>Refresh statistics</button>;
|
||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.props.getStats()}></button>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title="Dashboard">
|
||||
<div className="page-title__actions">
|
||||
{disableButton}
|
||||
{refreshFullButton}
|
||||
</div>
|
||||
</PageTitle>
|
||||
{dashboardProcessing && <Loading />}
|
||||
{!dashboardProcessing &&
|
||||
<div className="row row-cards">
|
||||
{dashboard.statsHistory &&
|
||||
<div className="col-lg-12">
|
||||
<Statistics
|
||||
history={dashboard.statsHistory}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className="col-lg-6">
|
||||
{dashboard.stats &&
|
||||
<Counters
|
||||
refreshButton={refreshButton}
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
blockedFiltering={dashboard.stats.blocked_filtering}
|
||||
replacedSafebrowsing={dashboard.stats.replaced_safebrowsing}
|
||||
replacedParental={dashboard.stats.replaced_parental}
|
||||
replacedSafesearch={dashboard.stats.replaced_safesearch}
|
||||
avgProcessingTime={dashboard.stats.avg_processing_time}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{dashboard.topStats &&
|
||||
<Fragment>
|
||||
<div className="col-lg-6">
|
||||
<Clients
|
||||
refreshButton={refreshButton}
|
||||
topClients={dashboard.topStats.top_clients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<QueriedDomains
|
||||
refreshButton={refreshButton}
|
||||
topQueriedDomains={dashboard.topStats.top_queried_domains}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<BlockedDomains
|
||||
refreshButton={refreshButton}
|
||||
topBlockedDomains={dashboard.topStats.top_blocked_domains}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
getStats: PropTypes.func,
|
||||
getStatsHistory: PropTypes.func,
|
||||
getTopStats: PropTypes.func,
|
||||
disableDns: PropTypes.func,
|
||||
dashboard: PropTypes.object,
|
||||
isCoreRunning: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
13
client/src/components/Filters/Filters.css
Normal file
13
client/src/components/Filters/Filters.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.remove-icon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.remove-icon:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
67
client/src/components/Filters/UserRules.js
Normal file
67
client/src/components/Filters/UserRules.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
export default class UserRules extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleRulesChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleRulesSubmit();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card
|
||||
title="Custom filtering rules"
|
||||
subtitle="Enter one rule on a line. You can use either adblock rules or hosts files syntax."
|
||||
>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea className="form-control" value={this.props.userRules} onChange={this.handleChange} />
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
Apply...
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<div className="list leading-loose">
|
||||
Examples:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> - block access to the example.org domain
|
||||
and all its subdomains
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> - unblock access to the example.org
|
||||
domain and all its subdomains
|
||||
</li>
|
||||
<li>
|
||||
<code>example.org 127.0.0.1</code> - AdGuard DNS will now return
|
||||
127.0.0.1 address for the example.org domain (but not its subdomains).
|
||||
</li>
|
||||
<li>
|
||||
<code>! Here goes a comment</code> - just a comment
|
||||
</li>
|
||||
<li>
|
||||
<code># Also a comment</code> - just a comment
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserRules.propTypes = {
|
||||
userRules: PropTypes.string,
|
||||
handleRulesChange: PropTypes.func,
|
||||
handleRulesSubmit: PropTypes.func,
|
||||
};
|
||||
130
client/src/components/Filters/index.js
Normal file
130
client/src/components/Filters/index.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from '../ui/Modal';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import UserRules from './UserRules';
|
||||
import './Filters.css';
|
||||
|
||||
class Filters extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
handleRulesChange = (value) => {
|
||||
this.props.handleRulesChange({ userRules: value });
|
||||
};
|
||||
|
||||
handleRulesSubmit = () => {
|
||||
this.props.setRules(this.props.filtering.userRules);
|
||||
};
|
||||
|
||||
renderCheckbox = (row) => {
|
||||
const { url } = row.original;
|
||||
const { filters } = this.props.filtering;
|
||||
const filter = filters.filter(filter => filter.url === url)[0];
|
||||
return (
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" className="checkbox__input" onChange={() => this.props.toggleFilterStatus(filter.url)} checked={filter.enabled}/>
|
||||
<span className="checkbox__label"/>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
columns = [{
|
||||
Header: 'Enabled',
|
||||
accessor: 'enabled',
|
||||
Cell: this.renderCheckbox,
|
||||
width: 90,
|
||||
className: 'text-center',
|
||||
}, {
|
||||
Header: 'Filter name',
|
||||
accessor: 'name',
|
||||
}, {
|
||||
Header: 'Host file URL',
|
||||
accessor: 'url',
|
||||
}, {
|
||||
Header: 'Rules count',
|
||||
accessor: 'rulesCount',
|
||||
className: 'text-center',
|
||||
}, {
|
||||
Header: 'Last time update',
|
||||
accessor: 'lastUpdated',
|
||||
className: 'text-center',
|
||||
}, {
|
||||
Header: 'Actions',
|
||||
accessor: 'url',
|
||||
Cell: ({ value }) => (<span className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>),
|
||||
className: 'text-center',
|
||||
width: 75,
|
||||
sortable: false,
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const { filters, userRules } = this.props.filtering;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title="Filters" />
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card
|
||||
title="Blocking filters and hosts files"
|
||||
subtitle="AdGuard DNS understands basic adblock rules and hosts files syntax."
|
||||
>
|
||||
<ReactTable
|
||||
data={filters}
|
||||
columns={this.columns}
|
||||
showPagination={false}
|
||||
noDataText="No filters added"
|
||||
minRows={4} // TODO find out what to show if rules.length is 0
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}>Add filter</button>
|
||||
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}>Check updates</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="col-md-12">
|
||||
<UserRules
|
||||
userRules={userRules}
|
||||
handleRulesChange={this.handleRulesChange}
|
||||
handleRulesSubmit={this.handleRulesSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={this.props.filtering.isFilteringModalOpen}
|
||||
toggleModal={this.props.toggleFilteringModal}
|
||||
addFilter={this.props.addFilter}
|
||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
||||
title="New filter subscription"
|
||||
inputDescription="Enter valid URL or file path of the filter into field above. You will be subscribed to that filter."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Filters.propTypes = {
|
||||
setRules: PropTypes.func,
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
filtering: PropTypes.shape({
|
||||
userRules: PropTypes.string,
|
||||
filters: PropTypes.array,
|
||||
isFilteringModalOpen: PropTypes.bool.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
}),
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
refreshFilters: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default Filters;
|
||||
113
client/src/components/Header/Header.css
Normal file
113
client/src/components/Header/Header.css
Normal file
@@ -0,0 +1,113 @@
|
||||
.nav-tabs .nav-link.active {
|
||||
border-color: #66b574;
|
||||
color: #66b574;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover {
|
||||
border-color: #58a273;
|
||||
color: #58a273;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-right: 6px;
|
||||
stroke: #9aa0ac;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active .nav-icon {
|
||||
stroke: #66b574;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover .nav-icon {
|
||||
stroke: #58a273;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
padding: 5px 0;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
position: fixed;
|
||||
z-index: 103;
|
||||
top: 0;
|
||||
right: calc(100% + 5px);
|
||||
display: block;
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
transition: transform 0.3s ease;
|
||||
background-color: #fff;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mobile-menu--active {
|
||||
transform: translateX(255px);
|
||||
box-shadow: 15px 0 50px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link--back {
|
||||
height: 63px;
|
||||
padding: 20px 0 21px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link--account {
|
||||
max-width: 160px;
|
||||
font-size: 0.9rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-version {
|
||||
padding: 16px 0;
|
||||
font-size: 0.85rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.header-brand-img {
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
width: auto;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
position: static;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-shadow: none;
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.mobile-menu--active {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.nav-version {
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dns-status {
|
||||
padding: 0.35em 0.5em;
|
||||
line-height: 10px;
|
||||
}
|
||||
69
client/src/components/Header/Menu.js
Normal file
69
client/src/components/Header/Menu.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import enhanceWithClickOutside from 'react-click-outside';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class Menu extends Component {
|
||||
handleClickOutside = () => {
|
||||
this.props.closeMenu();
|
||||
};
|
||||
|
||||
toggleMenu = () => {
|
||||
this.props.toggleMenuOpen();
|
||||
};
|
||||
|
||||
render() {
|
||||
const menuClass = classnames({
|
||||
'col-lg mobile-menu': true,
|
||||
'mobile-menu--active': this.props.isMenuOpen,
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={menuClass}>
|
||||
<ul className="nav nav-tabs border-0 flex-column flex-lg-row">
|
||||
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
|
||||
<div className="nav-link nav-link--back">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg>
|
||||
Back
|
||||
</div>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/" exact={true} className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg>
|
||||
Dashboard
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/settings" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg>
|
||||
Settings
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/filters" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg>
|
||||
Filters
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/logs" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg>
|
||||
Query Log
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Menu.propTypes = {
|
||||
isMenuOpen: PropTypes.bool,
|
||||
closeMenu: PropTypes.func,
|
||||
toggleMenuOpen: PropTypes.func,
|
||||
};
|
||||
|
||||
export default enhanceWithClickOutside(Menu);
|
||||
17
client/src/components/Header/Version.js
Normal file
17
client/src/components/Header/Version.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function Version(props) {
|
||||
const { dnsVersion, dnsAddress, dnsPort } = props;
|
||||
return (
|
||||
<div className="nav-version">
|
||||
v.{dnsVersion} / address: {dnsAddress}:{dnsPort}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Version.propTypes = {
|
||||
dnsVersion: PropTypes.string,
|
||||
dnsAddress: PropTypes.string,
|
||||
dnsPort: PropTypes.number,
|
||||
};
|
||||
75
client/src/components/Header/index.js
Normal file
75
client/src/components/Header/index.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Menu from './Menu';
|
||||
import Version from './Version';
|
||||
import logo from './logo.svg';
|
||||
import './Header.css';
|
||||
|
||||
class Header extends Component {
|
||||
state = {
|
||||
isMenuOpen: false,
|
||||
isDropdownOpen: false,
|
||||
};
|
||||
|
||||
toggleMenuOpen = () => {
|
||||
this.setState(prevState => ({ isMenuOpen: !prevState.isMenuOpen }));
|
||||
};
|
||||
|
||||
closeMenu = () => {
|
||||
this.setState({ isMenuOpen: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
'badge-success': dashboard.isCoreRunning,
|
||||
'badge-danger': !dashboard.isCoreRunning,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="container">
|
||||
<div className="row align-items-center">
|
||||
<div className="header-toggler d-lg-none ml-2 ml-lg-0 collapsed" onClick={this.toggleMenuOpen}>
|
||||
<span className="header-toggler-icon"></span>
|
||||
</div>
|
||||
<div className="col col-lg-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!dashboard.proccessing &&
|
||||
<span className={badgeClass}>
|
||||
{dashboard.isCoreRunning ? 'ON' : 'OFF'}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
location={this.props.location}
|
||||
isMenuOpen={this.state.isMenuOpen}
|
||||
toggleMenuOpen={this.toggleMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...this.props.dashboard }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
dashboard: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Header;
|
||||
1
client/src/components/Header/logo.svg
Normal file
1
client/src/components/Header/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 118 26" xmlns="http://www.w3.org/2000/svg"><path fill="#232323" d="M92.535 18.314l-.897-2.259h-4.47l-.849 2.259h-3.034L88.13 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.468 3.949h2.904L89.435 9.88zm-6.607 4.095c0 .693-.117 1.324-.35 1.893a4.115 4.115 0 0 1-1.004 1.463 4.63 4.63 0 0 1-1.574.95c-.614.228-1.297.341-2.047.341-.761 0-1.447-.113-2.056-.34a4.468 4.468 0 0 1-1.55-.951 4.126 4.126 0 0 1-.978-1.463 5.038 5.038 0 0 1-.343-1.893V6.809H75.7v6.939c0 .314.041.612.123.893.081.282.206.534.375.756.169.222.392.398.669.528s.612.195 1.003.195c.392 0 .726-.065 1.003-.195a1.83 1.83 0 0 0 .677-.528 2.1 2.1 0 0 0 .376-.756c.076-.281.114-.58.114-.893v-6.94h2.79v7.167zm-11.446 3.64a8.898 8.898 0 0 1-1.982.715 10.43 10.43 0 0 1-2.472.276c-.924 0-1.775-.146-2.553-.439a5.895 5.895 0 0 1-2.006-1.235 5.63 5.63 0 0 1-1.314-1.909c-.315-.742-.473-1.568-.473-2.478 0-.92.16-1.755.482-2.502a5.567 5.567 0 0 1 1.33-1.91 5.893 5.893 0 0 1 1.99-1.21 7.044 7.044 0 0 1 2.463-.423c.913 0 1.762.138 2.545.414.783.277 1.419.648 1.908 1.114l-1.762 1.998a3.05 3.05 0 0 0-1.076-.772c-.446-.2-.952-.3-1.517-.3-.49 0-.941.09-1.354.268a3.256 3.256 0 0 0-1.077.747 3.39 3.39 0 0 0-.71 1.138 3.977 3.977 0 0 0-.253 1.438c0 .53.077 1.018.229 1.463.152.444.378.826.677 1.145.299.32.669.569 1.11.748.44.178.943.268 1.508.268.326 0 .636-.025.93-.073.294-.05.566-.128.816-.236v-2.096h-2.203V11.52h4.764v6.094zm46.107-5.086c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.208-.544 5.955 5.955 0 0 0-1.394-.163h-1.387v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-55.226 0c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.207-.544 5.955 5.955 0 0 0-1.395-.163H51.3v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-11.86 5.785l-.897-2.259h-4.47l-.848 2.259h-3.034L40.19 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.467 3.949h2.903L41.496 9.88zm61.203 8.434l-2.496-4.566h-.946v4.566h-2.74V6.809h4.404c.555 0 1.096.057 1.623.17.528.114 1 .306 1.42.577.418.271.752.629 1.003 1.073.25.444.375.996.375 1.657 0 .78-.212 1.436-.636 1.966-.425.531-1.012.91-1.762 1.138l3.018 4.924h-3.263zm-.114-7.979c0-.27-.057-.49-.171-.658a1.172 1.172 0 0 0-.44-.39 1.919 1.919 0 0 0-.604-.187 4.469 4.469 0 0 0-.645-.049H99.24v2.681h1.321c.228 0 .462-.018.701-.056.24-.038.457-.106.653-.204.196-.097.356-.238.481-.422s.188-.422.188-.715z"/><path fill="#68bc71" d="M12.651 0C8.697 0 3.927.93 0 2.977c0 4.42-.054 15.433 12.651 22.958C25.357 18.41 25.303 7.397 25.303 2.977 21.376.93 16.606 0 12.651 0z"/><path fill="#67b279" d="M12.638 25.927C-.054 18.403 0 7.396 0 2.977 3.923.932 8.687.002 12.638 0v25.927z"/><path fill="#fff" d="M12.19 17.305l7.65-10.311c-.56-.45-1.052-.133-1.323.113h-.01l-6.379 6.636-2.403-2.892c-1.147-1.325-2.705-.314-3.07-.047l5.535 6.5"/></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
122
client/src/components/Logs/index.js
Normal file
122
client/src/components/Logs/index.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { saveAs } from 'file-saver/FileSaver';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import Loading from '../ui/Loading';
|
||||
import { normalizeLogs } from '../../helpers/helpers';
|
||||
|
||||
|
||||
const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
|
||||
|
||||
class Logs extends Component {
|
||||
componentDidMount() {
|
||||
// get logs on initialization if queryLogIsEnabled
|
||||
if (this.props.dashboard.queryLogEnabled) {
|
||||
this.props.getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// get logs when queryLog becomes enabled
|
||||
if (this.props.dashboard.queryLogEnabled && !prevProps.dashboard.queryLogEnabled) {
|
||||
this.props.getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
renderLogs(logs) {
|
||||
const columns = [{
|
||||
Header: 'Time',
|
||||
accessor: 'time',
|
||||
maxWidth: 150,
|
||||
}, {
|
||||
Header: 'Domain name',
|
||||
accessor: 'domain',
|
||||
}, {
|
||||
Header: 'Type',
|
||||
accessor: 'type',
|
||||
maxWidth: 100,
|
||||
}, {
|
||||
Header: 'Response',
|
||||
accessor: 'response',
|
||||
Cell: (row) => {
|
||||
const responses = row.value;
|
||||
if (responses.length > 0) {
|
||||
const liNodes = responses.map((response, index) =>
|
||||
(<li key={index}>{response}</li>));
|
||||
return (<ul className="list-unstyled">{liNodes}</ul>);
|
||||
}
|
||||
return 'Empty';
|
||||
},
|
||||
}];
|
||||
|
||||
if (logs) {
|
||||
const normalizedLogs = normalizeLogs(logs);
|
||||
return (<ReactTable
|
||||
data={normalizedLogs}
|
||||
columns={columns}
|
||||
showPagination={false}
|
||||
minRows={7}
|
||||
noDataText="No logs found"
|
||||
defaultSorted={[
|
||||
{
|
||||
id: 'time',
|
||||
desc: true,
|
||||
},
|
||||
]}
|
||||
/>);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
handleDownloadButton = async (e) => {
|
||||
e.preventDefault();
|
||||
const data = await this.props.downloadQueryLog();
|
||||
const jsonStr = JSON.stringify(data);
|
||||
const dataBlob = new Blob([jsonStr], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
|
||||
};
|
||||
|
||||
renderButtons(queryLogEnabled) {
|
||||
return (<div className="card-actions-top">
|
||||
<button
|
||||
className="btn btn-success btn-standart mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
>{queryLogEnabled ? 'Disable log' : 'Enable log'}</button>
|
||||
{queryLogEnabled &&
|
||||
<button
|
||||
className="btn btn-primary btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleDownloadButton}
|
||||
>Download log file</button> }
|
||||
</div>);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { queryLogs, dashboard } = this.props;
|
||||
const { queryLogEnabled } = dashboard;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title="Query Log" subtitle="DNS queries log" />
|
||||
<Card>
|
||||
{this.renderButtons(queryLogEnabled)}
|
||||
{queryLogEnabled && queryLogs.processing && <Loading />}
|
||||
{queryLogEnabled && !queryLogs.processing &&
|
||||
this.renderLogs(queryLogs.logs)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Logs.propTypes = {
|
||||
getLogs: PropTypes.func,
|
||||
queryLogs: PropTypes.object,
|
||||
dashboard: PropTypes.object,
|
||||
toggleLogStatus: PropTypes.func,
|
||||
downloadQueryLog: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Logs;
|
||||
12
client/src/components/Settings/Settings.css
Normal file
12
client/src/components/Settings/Settings.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.form__group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form__group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-standart {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
52
client/src/components/Settings/Upstream.js
Normal file
52
client/src/components/Settings/Upstream.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
export default class Upstream extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleUpstreamChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleUpstreamSubmit();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card
|
||||
title="Upstream DNS servers"
|
||||
subtitle="If you keep this field empty, AdGuard will use <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> as an upstream."
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<form>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={this.props.upstream}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstream: PropTypes.string,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
handleUpstreamSubmit: PropTypes.func,
|
||||
};
|
||||
100
client/src/components/Settings/index.js
Normal file
100
client/src/components/Settings/index.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Upstream from './Upstream';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import './Settings.css';
|
||||
|
||||
export default class Settings extends Component {
|
||||
settings = {
|
||||
filtering: {
|
||||
enabled: false,
|
||||
title: 'Block domains using filters and hosts files',
|
||||
subtitle: 'You can setup blocking rules in the <a href="#filters">Filters</a> settings.',
|
||||
},
|
||||
safebrowsing: {
|
||||
enabled: false,
|
||||
title: 'Use AdGuard browsing security web service',
|
||||
subtitle: 'AdGuard DNS will check if domain is blacklisted by the browsing security web service (sb.adtidy.org). It will use privacy-safe lookup API to do the check.',
|
||||
},
|
||||
parental: {
|
||||
enabled: false,
|
||||
title: 'Use AdGuard parental control web service',
|
||||
subtitle: 'AdGuard DNS will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.',
|
||||
},
|
||||
safesearch: {
|
||||
enabled: false,
|
||||
title: 'Enforce safe search',
|
||||
subtitle: 'AdGuard DNS can enforce safe search in the major search engines: Google, Bing, Yandex.',
|
||||
},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.initSettings(this.settings);
|
||||
}
|
||||
|
||||
handleUpstreamChange = (value) => {
|
||||
this.props.handleUpstreamChange({ upstream: value });
|
||||
};
|
||||
|
||||
handleUpstreamSubmit = () => {
|
||||
this.props.setUpstream(this.props.settings.upstream);
|
||||
};
|
||||
|
||||
renderSettings = (settings) => {
|
||||
if (Object.keys(settings).length > 0) {
|
||||
return Object.keys(settings).map((key) => {
|
||||
const setting = settings[key];
|
||||
const { enabled } = setting;
|
||||
return (<Checkbox
|
||||
key={key}
|
||||
{...settings[key]}
|
||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||
/>);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div>No settings</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { settings, upstream } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title="Settings" />
|
||||
{settings.processing && <Loading />}
|
||||
{!settings.processing &&
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card title="General settings" bodyType="card-body box-body--settings">
|
||||
<div className="form">
|
||||
{this.renderSettings(settings.settingsList)}
|
||||
</div>
|
||||
</Card>
|
||||
<Upstream
|
||||
upstream={upstream}
|
||||
handleUpstreamChange={this.handleUpstreamChange}
|
||||
handleUpstreamSubmit={this.handleUpstreamSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
initSettings: PropTypes.func,
|
||||
settings: PropTypes.object,
|
||||
settingsList: PropTypes.object,
|
||||
toggleSetting: PropTypes.func,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
setUpstream: PropTypes.func,
|
||||
upstream: PropTypes.string,
|
||||
};
|
||||
50
client/src/components/ui/Card.css
Normal file
50
client/src/components/ui/Card.css
Normal file
@@ -0,0 +1,50 @@
|
||||
.card-header {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.6rem 1.5rem;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
margin: 4px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-table-overflow {
|
||||
overflow-y: auto;
|
||||
max-height: 280px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-actions-top {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-graph {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.card-body--status {
|
||||
padding: 2.5rem 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-refresh {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiM0NjdmY2YiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
|
||||
}
|
||||
|
||||
.card-refresh:hover,
|
||||
.card-refresh:not(:disabled):not(.disabled):active,
|
||||
.card-refresh:focus:active {
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
|
||||
}
|
||||
40
client/src/components/ui/Card.js
Normal file
40
client/src/components/ui/Card.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Card.css';
|
||||
|
||||
const Card = props => (
|
||||
<div className="card">
|
||||
{ props.title &&
|
||||
<div className="card-header with-border">
|
||||
<div className="card-inner">
|
||||
<div className="card-title">
|
||||
{props.title}
|
||||
</div>
|
||||
|
||||
{props.subtitle &&
|
||||
<div className="card-subtitle" dangerouslySetInnerHTML={{ __html: props.subtitle }} />
|
||||
}
|
||||
</div>
|
||||
|
||||
{props.refresh &&
|
||||
<div className="card-options">
|
||||
{props.refresh}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
<div className={props.bodyType ? props.bodyType : 'card-body'}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Card.propTypes = {
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
bodyType: PropTypes.string,
|
||||
refresh: PropTypes.node,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default Card;
|
||||
93
client/src/components/ui/Checkbox.css
Normal file
93
client/src/components/ui/Checkbox.css
Normal file
@@ -0,0 +1,93 @@
|
||||
.checkbox {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.checkbox--single {
|
||||
display: block;
|
||||
margin: 2px auto 6px;
|
||||
}
|
||||
|
||||
.checkbox--single .checkbox__label:before {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.checkbox--settings .checkbox__label:before {
|
||||
top: 2px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.checkbox--settings .checkbox__label-title {
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkbox__label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox__label:before {
|
||||
content: "";
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
margin-right: 10px;
|
||||
background-color: #e2e2e2;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 12px 10px;
|
||||
border-radius: 3px;
|
||||
transition: 0.3s ease box-shadow;
|
||||
}
|
||||
|
||||
.checkbox__label .checkbox__label-text {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.checkbox__label .checkbox__label-text .md__paragraph {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.checkbox__input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.checkbox__input:checked+.checkbox__label:before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
|
||||
}
|
||||
|
||||
.checkbox__input:focus+.checkbox__label:before {
|
||||
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
|
||||
}
|
||||
|
||||
.checkbox__label-text {
|
||||
max-width: 515px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-title {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-subtitle {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
color: rgba(74, 74, 74, 0.7);
|
||||
}
|
||||
38
client/src/components/ui/Checkbox.js
Normal file
38
client/src/components/ui/Checkbox.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Checkbox.css';
|
||||
|
||||
class Checkbox extends Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
enabled,
|
||||
handleChange,
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="form__group">
|
||||
<label className="checkbox checkbox--settings">
|
||||
<span className="checkbox__marker"/>
|
||||
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text">
|
||||
<span className="checkbox__label-title">{title}</span>
|
||||
<span className="checkbox__label-subtitle" dangerouslySetInnerHTML={{ __html: subtitle }}/>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Checkbox.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
handleChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
37
client/src/components/ui/Footer.js
Normal file
37
client/src/components/ui/Footer.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class Footer extends Component {
|
||||
getYear = () => {
|
||||
const today = new Date();
|
||||
return today.getFullYear();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="container">
|
||||
<div className="row align-items-center flex-row-reverse">
|
||||
<div className="col-12 col-lg-auto ml-lg-auto">
|
||||
<ul className="list-inline list-inline-dots text-center mb-0">
|
||||
<li className="list-inline-item">
|
||||
<a href="https://adguard.com/welcome.html" target="_blank" rel="noopener noreferrer">Homepage</a>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<a href="https://github.com/AdguardTeam/" target="_blank" rel="noopener noreferrer">Github</a>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<a href="https://adguard.com/privacy.html" target="_blank" rel="noopener noreferrer">Privacy Policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
|
||||
© AdGuard {this.getYear()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
53
client/src/components/ui/Loading.css
Normal file
53
client/src/components/ui/Loading.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.loading {
|
||||
position: relative;
|
||||
z-index: 101;
|
||||
opacity: 0;
|
||||
animation: opacity 0.2s linear 0.2s forwards;
|
||||
}
|
||||
|
||||
.loading:before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2047.6%2047.6%22%20height%3D%22100%25%22%20width%3D%22100%25%22%3E%3Cpath%20opacity%3D%22.235%22%20fill%3D%22%23979797%22%20d%3D%22M44.4%2011.9l-5.2%203c1.5%202.6%202.4%205.6%202.4%208.9%200%209.8-8%2017.8-17.8%2017.8-6.6%200-12.3-3.6-15.4-8.9l-5.2%203C7.3%2042.8%2015%2047.6%2023.8%2047.6c13.1%200%2023.8-10.7%2023.8-23.8%200-4.3-1.2-8.4-3.2-11.9z%22%2F%3E%3Cpath%20fill%3D%22%2366b574%22%20d%3D%22M3.2%2035.7C0%2030.2-.8%2023.8.8%2017.6%202.5%2011.5%206.4%206.4%2011.9%203.2%2017.4%200%2023.8-.8%2030%20.8c6.1%201.6%2011.3%205.6%2014.4%2011.1l-5.2%203c-2.4-4.1-6.2-7.1-10.8-8.3C23.8%205.4%2019%206%2014.9%208.4s-7.1%206.2-8.3%2010.8c-1.2%204.6-.6%209.4%201.8%2013.5l-5.2%203z%22%2F%3E%3C%2Fsvg%3E");
|
||||
will-change: transform;
|
||||
animation: clockwise 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes opacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes clockwise {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
9
client/src/components/ui/Loading.js
Normal file
9
client/src/components/ui/Loading.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Loading.css';
|
||||
|
||||
const Loading = () => (
|
||||
<div className="loading"></div>
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
40
client/src/components/ui/Modal.css
Normal file
40
client/src/components/ui/Modal.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.ReactModal__Overlay {
|
||||
-webkit-perspective: 600;
|
||||
perspective: 600;
|
||||
opacity: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--after-open {
|
||||
opacity: 1;
|
||||
transition: opacity 150ms ease-out;
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
-webkit-transform: scale(0.5) rotateX(-30deg);
|
||||
transform: scale(0.5) rotateX(-30deg);
|
||||
}
|
||||
|
||||
.ReactModal__Content--after-open {
|
||||
-webkit-transform: scale(1) rotateX(0deg);
|
||||
transform: scale(1) rotateX(0deg);
|
||||
transition: all 150ms ease-in;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ReactModal__Content--before-close {
|
||||
-webkit-transform: scale(0.5) rotateX(30deg);
|
||||
transform: scale(0.5) rotateX(30deg);
|
||||
transition: all 150ms ease-in;
|
||||
}
|
||||
|
||||
.ReactModal__Content.modal-dialog {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
112
client/src/components/ui/Modal.js
Normal file
112
client/src/components/ui/Modal.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactModal from 'react-modal';
|
||||
import classnames from 'classnames';
|
||||
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
|
||||
import './Modal.css';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
export default class Modal extends Component {
|
||||
state = {
|
||||
url: '',
|
||||
isUrlValid: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
isUrlValid = url => {
|
||||
return R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
};
|
||||
|
||||
handleUrlChange = async (e) => {
|
||||
const { value: url } = e.currentTarget;
|
||||
if (this.isUrlValid(url)) {
|
||||
this.setState(...this.state, { url, isUrlValid: true });
|
||||
} else {
|
||||
this.setState(...this.state, { url, isUrlValid: false });
|
||||
}
|
||||
};
|
||||
|
||||
handleNext = () => {
|
||||
this.props.addFilter(this.state.url);
|
||||
setTimeout(() => {
|
||||
if (this.props.isFilterAdded) {
|
||||
this.props.toggleModal();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
toggleModal,
|
||||
title,
|
||||
inputDescription,
|
||||
} = this.props;
|
||||
const { isUrlValid, url } = this.state;
|
||||
const inputClass = classnames({
|
||||
'form-control mb-2': true,
|
||||
'is-invalid': url.length > 0 && !isUrlValid,
|
||||
'is-valid': url.length > 0 && isUrlValid,
|
||||
});
|
||||
|
||||
const renderBody = () => {
|
||||
if (!this.props.isFilterAdded) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input type="text" className={inputClass} placeholder="Enter URL or path" onChange={this.handleUrlChange}/>
|
||||
{inputDescription &&
|
||||
<div className="description">
|
||||
{inputDescription}
|
||||
</div>}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="description">
|
||||
Url added successfully
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isValidForSubmit = !(url.length > 0 && isUrlValid);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={ isOpen }
|
||||
onRequestClose={toggleModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{title}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={toggleModal}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{ renderBody()}
|
||||
</div>
|
||||
{
|
||||
!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={toggleModal}>Cancel</button>
|
||||
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}>Add filter</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Modal.propTypes = {
|
||||
toggleModal: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
inputDescription: PropTypes.string,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
};
|
||||
10
client/src/components/ui/PageTitle.css
Normal file
10
client/src/components/ui/PageTitle.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.page-subtitle {
|
||||
margin-left: 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.page-title__actions {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
margin-left: 20px;
|
||||
}
|
||||
22
client/src/components/ui/PageTitle.js
Normal file
22
client/src/components/ui/PageTitle.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './PageTitle.css';
|
||||
|
||||
const PageTitle = props => (
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">
|
||||
{props.title}
|
||||
{props.subtitle && <span className="page-subtitle">{props.subtitle}</span>}
|
||||
{props.children}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
PageTitle.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
4
client/src/components/ui/ReactTable.css
Normal file
4
client/src/components/ui/ReactTable.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.ReactTable .rt-th,
|
||||
.ReactTable .rt-td {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
23
client/src/components/ui/Status.js
Normal file
23
client/src/components/ui/Status.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Status = props => (
|
||||
<div className="status">
|
||||
<Card bodyType="card-body card-body--status">
|
||||
<div className="h4 font-weight-light mb-4">
|
||||
You are currently not using AdGuard DNS
|
||||
</div>
|
||||
<button className="btn btn-success" onClick={props.handleStatusChange}>
|
||||
Enable AdGuard DNS
|
||||
</button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
Status.propTypes = {
|
||||
handleStatusChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
16243
client/src/components/ui/Tabler.css
Normal file
16243
client/src/components/ui/Tabler.css
Normal file
File diff suppressed because one or more lines are too long
49
client/src/components/ui/Tooltip.css
Normal file
49
client/src/components/ui/Tooltip.css
Normal file
@@ -0,0 +1,49 @@
|
||||
.tooltip-custom {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 5px;
|
||||
background-image: url("./svg/help-circle.svg");
|
||||
background-size: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip-custom:before {
|
||||
content: attr(data-tooltip);
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: calc(100% + 12px);
|
||||
left: calc(50% - 103px);
|
||||
width: 206px;
|
||||
padding: 10px 15px;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: #585965;
|
||||
border-radius: 3px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tooltip-custom:after {
|
||||
content: "";
|
||||
position: relative;
|
||||
top: -9px;
|
||||
left: calc(50% - 6px);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid #585965;
|
||||
}
|
||||
|
||||
.tooltip-custom:hover:before,
|
||||
.tooltip-custom:hover:after {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
14
client/src/components/ui/Tooltip.js
Normal file
14
client/src/components/ui/Tooltip.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Tooltip.css';
|
||||
|
||||
const Tooltip = props => (
|
||||
<div data-tooltip={props.text} className="tooltip-custom"></div>
|
||||
);
|
||||
|
||||
Tooltip.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
1
client/src/components/ui/svg/help-circle.svg
Normal file
1
client/src/components/ui/svg/help-circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
1
client/src/components/ui/svg/trash-2.svg
Normal file
1
client/src/components/ui/svg/trash-2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/></svg>
|
||||
|
After Width: | Height: | Size: 340 B |
1
client/src/components/ui/svg/x.svg
Normal file
1
client/src/components/ui/svg/x.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
||||
|
After Width: | Height: | Size: 227 B |
16
client/src/configureStore.js
Normal file
16
client/src/configureStore.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
const middlewares = [
|
||||
thunk,
|
||||
];
|
||||
|
||||
export default function configureStore(reducer, initialState) {
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const store = createStore(reducer, initialState, compose(
|
||||
applyMiddleware(...middlewares),
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f,
|
||||
));
|
||||
/* eslint-enable */
|
||||
return store;
|
||||
}
|
||||
14
client/src/containers/App.js
Normal file
14
client/src/containers/App.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actionCreators from '../actions';
|
||||
import App from '../components/App';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { dashboard } = state;
|
||||
const props = { dashboard };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(App);
|
||||
14
client/src/containers/Dashboard.js
Normal file
14
client/src/containers/Dashboard.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actionCreators from '../actions';
|
||||
import Dashboard from '../components/Dashboard';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { dashboard } = state;
|
||||
const props = { dashboard };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Dashboard);
|
||||
14
client/src/containers/Filters.js
Normal file
14
client/src/containers/Filters.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actionCreators from '../actions';
|
||||
import Filters from '../components/Filters';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { filtering } = state;
|
||||
const props = { filtering };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Filters);
|
||||
14
client/src/containers/Header.js
Normal file
14
client/src/containers/Header.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actionCreators from '../actions';
|
||||
import Header from '../components/Header';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { dashboard } = state;
|
||||
const props = { dashboard };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Header);
|
||||
20
client/src/containers/Logs.js
Normal file
20
client/src/containers/Logs.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getLogs, toggleLogStatus, downloadQueryLog } from '../actions';
|
||||
import Logs from '../components/Logs';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { queryLogs, dashboard } = state;
|
||||
const props = { queryLogs, dashboard };
|
||||
return props;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getLogs,
|
||||
toggleLogStatus,
|
||||
downloadQueryLog,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Logs);
|
||||
21
client/src/containers/Settings.js
Normal file
21
client/src/containers/Settings.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { initSettings, toggleSetting, handleUpstreamChange, setUpstream } from '../actions';
|
||||
import Settings from '../components/Settings';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { settings } = state;
|
||||
const props = { settings };
|
||||
return props;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initSettings,
|
||||
toggleSetting,
|
||||
handleUpstreamChange,
|
||||
setUpstream,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Settings);
|
||||
1
client/src/helpers/constants.js
Normal file
1
client/src/helpers/constants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/;
|
||||
65
client/src/helpers/helpers.js
Normal file
65
client/src/helpers/helpers.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import dateParse from 'date-fns/parse';
|
||||
import dateFormat from 'date-fns/format';
|
||||
import startOfToday from 'date-fns/start_of_today';
|
||||
import addHours from 'date-fns/add_hours';
|
||||
import round from 'lodash/round';
|
||||
|
||||
const formatTime = (time) => {
|
||||
const parsedTime = dateParse(time);
|
||||
return dateFormat(parsedTime, 'HH:mm:ss');
|
||||
};
|
||||
|
||||
export const normalizeLogs = logs => logs.map((log) => {
|
||||
const {
|
||||
time,
|
||||
question,
|
||||
answer: response,
|
||||
} = log;
|
||||
const { host: domain, type } = question;
|
||||
const responsesArray = response ? response.map((response) => {
|
||||
const { value, type, ttl } = response;
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}) : [];
|
||||
return {
|
||||
time: formatTime(time),
|
||||
domain,
|
||||
type,
|
||||
response: responsesArray,
|
||||
};
|
||||
});
|
||||
|
||||
export const normalizeHistory = history => Object.keys(history).map((key) => {
|
||||
const id = key.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase());
|
||||
|
||||
const today = startOfToday();
|
||||
|
||||
const data = history[key].map((item, index) => {
|
||||
const formatHour = dateFormat(addHours(today, index), 'HH:mm');
|
||||
const roundValue = round(item, 2);
|
||||
|
||||
return {
|
||||
x: formatHour,
|
||||
y: roundValue,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
export const normalizeFilteringStatus = (filteringStatus) => {
|
||||
const { enabled, filters, user_rules: userRules } = filteringStatus;
|
||||
const newFilters = filters ? filters.map((filter) => {
|
||||
const {
|
||||
url, enabled, last_updated: lastUpdated = Date.now(), name = 'Default name', rules_count: rulesCount = 0,
|
||||
} = filter;
|
||||
|
||||
return {
|
||||
url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
|
||||
};
|
||||
}) : [];
|
||||
const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
|
||||
return { enabled, userRules: newUserRules, filters: newFilters };
|
||||
};
|
||||
15
client/src/index.js
Normal file
15
client/src/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import './components/App/index.css';
|
||||
import App from './containers/App';
|
||||
import configureStore from './configureStore';
|
||||
import reducers from './reducers';
|
||||
|
||||
const store = configureStore(reducers, {}); // set initial state
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
180
client/src/reducers/index.js
Normal file
180
client/src/reducers/index.js
Normal file
@@ -0,0 +1,180 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
import * as actions from '../actions';
|
||||
|
||||
const settings = handleActions({
|
||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.initSettingsFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.initSettingsSuccess]: (state, { payload }) => {
|
||||
const { settingsList } = payload;
|
||||
const newState = { ...state, settingsList, processing: false };
|
||||
return newState;
|
||||
},
|
||||
[actions.toggleSettingStatus]: (state, { payload }) => {
|
||||
const { settingsList } = state;
|
||||
const { settingKey } = payload;
|
||||
|
||||
const setting = settingsList[settingKey];
|
||||
|
||||
const newSetting = { ...setting, enabled: !setting.enabled };
|
||||
const newSettingsList = { ...settingsList, [settingKey]: newSetting };
|
||||
return { ...state, settingsList: newSettingsList };
|
||||
},
|
||||
[actions.setUpstreamRequest]: state => ({ ...state, processingUpstream: true }),
|
||||
[actions.setUpstreamFailure]: state => ({ ...state, processingUpstream: false }),
|
||||
[actions.setUpstreamSuccess]: state => ({ ...state, processingUpstream: false }),
|
||||
[actions.handleUpstreamChange]: (state, { payload }) => {
|
||||
const { upstream } = payload;
|
||||
return { ...state, upstream };
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
processingUpstream: true,
|
||||
upstream: '',
|
||||
});
|
||||
|
||||
const dashboard = handleActions({
|
||||
[actions.dnsStatusRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.dnsStatusFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.dnsStatusSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
version,
|
||||
running,
|
||||
dns_port: dnsPort,
|
||||
dns_address: dnsAddress,
|
||||
querylog_enabled: queryLogEnabled,
|
||||
} = payload;
|
||||
const newState = {
|
||||
...state,
|
||||
isCoreRunning: running,
|
||||
processing: false,
|
||||
dnsVersion: version,
|
||||
dnsPort,
|
||||
dnsAddress,
|
||||
queryLogEnabled,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.enableDnsRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.enableDnsFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.enableDnsSuccess]: (state) => {
|
||||
const newState = { ...state, isCoreRunning: !state.isCoreRunning, processing: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.disableDnsRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.disableDnsFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.disableDnsSuccess]: (state) => {
|
||||
const newState = { ...state, isCoreRunning: !state.isCoreRunning, processing: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getStatsRequest]: state => ({ ...state, processingStats: true }),
|
||||
[actions.getStatsFailure]: state => ({ ...state, processingStats: false }),
|
||||
[actions.getStatsSuccess]: (state, { payload }) => {
|
||||
const newState = { ...state, stats: payload, processingStats: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getTopStatsRequest]: state => ({ ...state, processingTopStats: true }),
|
||||
[actions.getTopStatsFailure]: state => ({ ...state, processingTopStats: false }),
|
||||
[actions.getTopStatsSuccess]: (state, { payload }) => {
|
||||
const newState = { ...state, topStats: payload, processingTopStats: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getStatsHistoryRequest]: state => ({ ...state, processingStatsHistory: true }),
|
||||
[actions.getStatsHistoryFailure]: state => ({ ...state, processingStatsHistory: false }),
|
||||
[actions.getStatsHistorySuccess]: (state, { payload }) => {
|
||||
const newState = { ...state, statsHistory: payload, processingStatsHistory: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.toggleLogStatusRequest]: state => ({ ...state, logStatusProcessing: true }),
|
||||
[actions.toggleLogStatusFailure]: state => ({ ...state, logStatusProcessing: false }),
|
||||
[actions.toggleLogStatusSuccess]: (state) => {
|
||||
const { queryLogEnabled } = state;
|
||||
return ({ ...state, queryLogEnabled: !queryLogEnabled, logStatusProcessing: false });
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
isCoreRunning: false,
|
||||
processingTopStats: true,
|
||||
processingStats: true,
|
||||
logStatusProcessing: false,
|
||||
});
|
||||
|
||||
const queryLogs = handleActions({
|
||||
[actions.getLogsRequest]: state => ({ ...state, getLogsProcessing: true }),
|
||||
[actions.getLogsFailure]: state => ({ ...state, getLogsProcessing: false }),
|
||||
[actions.getLogsSuccess]: (state, { payload }) => {
|
||||
const newState = { ...state, logs: payload, getLogsProcessing: false };
|
||||
return newState;
|
||||
},
|
||||
[actions.downloadQueryLogRequest]: state => ({ ...state, logsDownloading: true }),
|
||||
[actions.downloadQueryLogFailure]: state => ({ ...state, logsDownloading: false }),
|
||||
[actions.downloadQueryLogSuccess]: state => ({ ...state, logsDownloading: false }),
|
||||
}, { getLogsProcessing: false, logsDownloading: false });
|
||||
|
||||
const filtering = handleActions({
|
||||
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
|
||||
[actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
|
||||
[actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
|
||||
|
||||
[actions.handleRulesChange]: (state, { payload }) => {
|
||||
const { userRules } = payload;
|
||||
return { ...state, userRules };
|
||||
},
|
||||
|
||||
[actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
|
||||
[actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
|
||||
[actions.getFilteringStatusSuccess]: (state, { payload }) => {
|
||||
const { status } = payload;
|
||||
const { filters, userRules } = status;
|
||||
const newState = {
|
||||
...state, filters, userRules, processingFilters: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.addFilterRequest]: state =>
|
||||
({ ...state, processingAddFilter: true, isFilterAdded: false }),
|
||||
[actions.addFilterFailure]: (state) => {
|
||||
const newState = { ...state, processingAddFilter: false, isFilterAdded: false };
|
||||
return newState;
|
||||
},
|
||||
[actions.addFilterSuccess]: state =>
|
||||
({ ...state, processingAddFilter: false, isFilterAdded: true }),
|
||||
|
||||
[actions.toggleFilteringModal]: (state) => {
|
||||
const newState = {
|
||||
...state,
|
||||
isFilteringModalOpen: !state.isFilteringModalOpen,
|
||||
isFilterAdded: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.toggleFilterRequest]: state => ({ ...state, processingFilters: true }),
|
||||
[actions.toggleFilterFailure]: state => ({ ...state, processingFilters: false }),
|
||||
[actions.toggleFilterSuccess]: state => ({ ...state, processingFilters: false }),
|
||||
|
||||
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
|
||||
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
|
||||
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
|
||||
}, {
|
||||
isFilteringModalOpen: false,
|
||||
processingFilters: false,
|
||||
processingRules: false,
|
||||
filters: [],
|
||||
userRules: '',
|
||||
});
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
dashboard,
|
||||
queryLogs,
|
||||
filtering,
|
||||
});
|
||||
Reference in New Issue
Block a user