From 9ac60494055a5a5f432039448ee081b6a48ede00 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Fri, 20 Sep 2019 15:05:10 +0300 Subject: [PATCH] + client: server side pagination for the query logs --- client/src/__locales/en.json | 2 +- client/src/actions/queryLogs.js | 9 +- client/src/api/Api.js | 10 +- client/src/components/Logs/Logs.css | 20 ++ client/src/components/Logs/index.js | 258 ++++++++++++------ client/src/components/ui/Popover.css | 1 + client/src/components/ui/Tooltip.css | 20 ++ .../components/ui/svg/help-circle-gray.svg | 1 + client/src/containers/Logs.js | 3 +- client/src/helpers/constants.js | 44 +++ client/src/helpers/helpers.js | 3 + client/src/reducers/queryLogs.js | 51 +++- 12 files changed, 323 insertions(+), 99 deletions(-) create mode 100644 client/src/components/ui/svg/help-circle-gray.svg diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index e4e1c1af..d3700869 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -163,7 +163,6 @@ "show_filtered_type": "Show filtered", "no_logs_found": "No logs found", "refresh_btn": "Refresh", - "last_dns_queries": "Last 5000 DNS queries", "previous_btn": "Previous", "next_btn": "Next", "loading_table_status": "Loading...", @@ -182,6 +181,7 @@ "query_log_enable": "Enable log", "query_log_configuration": "Logs configuration", "query_log_disabled": "The query log is disabled and can be configured in the <0>settings", + "query_log_strict_search": "Use double quotes for strict search", "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js index c68ddf15..f36bc3d1 100644 --- a/client/src/actions/queryLogs.js +++ b/client/src/actions/queryLogs.js @@ -4,15 +4,18 @@ import apiClient from '../api/Api'; import { addErrorToast, addSuccessToast } from './index'; import { normalizeLogs } from '../helpers/helpers'; +export const setLogsPagination = createAction('LOGS_PAGINATION'); + 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) => { +export const getLogs = config => async (dispatch) => { dispatch(getLogsRequest()); try { - const logs = normalizeLogs(await apiClient.getQueryLog()); - dispatch(getLogsSuccess(logs)); + const { filter, lastRowTime: older_than } = config; + const logs = normalizeLogs(await apiClient.getQueryLog({ filter, older_than })); + dispatch(getLogsSuccess({ logs, ...config })); } catch (error) { dispatch(addErrorToast({ error })); dispatch(getLogsFailure(error)); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index b7a7d045..9cd0e650 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -482,14 +482,18 @@ class Api { } // Query log - GET_QUERY_LOG = { path: 'querylog', method: 'GET' }; + GET_QUERY_LOG = { path: 'querylog', method: 'POST' }; QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' }; QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' }; QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' }; - getQueryLog() { + getQueryLog(data) { const { path, method } = this.GET_QUERY_LOG; - return this.makeRequest(path, method); + const config = { + data, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, config); } getQueryLogInfo() { diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index 931e8694..5a79ed85 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -107,6 +107,11 @@ border: 1px solid rgba(0, 40, 100, 0.12); } +.logs__table .rt-thead.-filters select { + background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center; + background-size: 8px 10px; +} + .logs__table .rt-thead.-filters input:focus, .logs__table .rt-thead.-filters select:focus { border-color: #1991eb; @@ -130,6 +135,21 @@ overflow: hidden; } +.logs__input-wrap { + position: relative; +} + +.logs__notice { + position: absolute; + z-index: 1; + top: 8px; + right: 10px; + margin-top: 3px; + font-size: 12px; + text-align: left; + color: #a5a5a5; +} + .logs__whois { display: inline; } diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js index 994a4e18..eeadb6fb 100644 --- a/client/src/components/Logs/index.js +++ b/client/src/components/Logs/index.js @@ -5,9 +5,14 @@ import escapeRegExp from 'lodash/escapeRegExp'; import endsWith from 'lodash/endsWith'; import { Trans, withNamespaces } from 'react-i18next'; import { HashLink as Link } from 'react-router-hash-link'; +import debounce from 'lodash/debounce'; -import { formatTime, formatDateTime } from '../../helpers/helpers'; -import { SERVICES, FILTERED_STATUS } from '../../helpers/constants'; +import { + formatTime, + formatDateTime, + isValidQuestionType, +} from '../../helpers/helpers'; +import { SERVICES, FILTERED_STATUS, DEBOUNCE_TIMEOUT } from '../../helpers/constants'; import { getTrackerData } from '../../helpers/trackers/trackers'; import { formatClientCell } from '../../helpers/formatClientCell'; @@ -16,8 +21,12 @@ import Card from '../ui/Card'; import Loading from '../ui/Loading'; import PopoverFiltered from '../ui/PopoverFilter'; import Popover from '../ui/Popover'; +import Tooltip from '../ui/Tooltip'; import './Logs.css'; +const TABLE_FIRST_PAGE = 0; +const TABLE_DEFAULT_PAGE_SIZE = 50; +const INITIAL_REQUEST_DATA = ['', {}, TABLE_FIRST_PAGE, TABLE_DEFAULT_PAGE_SIZE]; const FILTERED_REASON = 'Filtered'; const RESPONSE_FILTER = { ALL: 'all', @@ -26,26 +35,36 @@ const RESPONSE_FILTER = { class Logs extends Component { componentDidMount() { - this.getLogs(); + this.getLogs(...INITIAL_REQUEST_DATA); this.props.getFilteringStatus(); this.props.getClients(); this.props.getLogsConfig(); } - componentDidUpdate(prevProps) { - // get logs when queryLog becomes enabled - if (this.props.queryLogs.enabled && !prevProps.queryLogs.enabled) { - this.props.getLogs(); - } - } - - getLogs = () => { - // get logs on initialization if queryLogIsEnabled + getLogs = (lastRowTime, filter, page, pageSize) => { if (this.props.queryLogs.enabled) { - this.props.getLogs(); + this.props.getLogs({ + lastRowTime, filter, page, pageSize, + }); } }; + refreshLogs = (lastRowTime, filter, page, pageSize, refreshLogs = true) => { + this.props.getLogs({ + lastRowTime, filter, page, pageSize, refreshLogs, + }); + }; + + handleLogsFiltering = debounce((lastRowTime, filter, page, pageSize, filtered) => { + this.props.getLogs({ + lastRowTime, + filter, + page, + pageSize, + filtered, + }); + }, DEBOUNCE_TIMEOUT); + renderTooltip = (isFiltered, rule, filter, service) => isFiltered && ; @@ -215,8 +234,64 @@ class Logs extends Component { ); }; - renderLogs(logs) { - const { t } = this.props; + getFilterInput = ({ filter, onChange }) => ( + +
+ onChange(event.target.value)} + value={filter ? filter.value : ''} + /> + + + +
+
+ ); + + getFilters = (filtered) => { + const filteredObj = filtered.reduce((acc, cur) => ({ ...acc, [cur.id]: cur.value }), {}); + const { + domain, client, type, response, + } = filteredObj; + + return { + domain: domain || '', + client: client || '', + question_type: isValidQuestionType(type) ? type.toUpperCase() : '', + response_status: response === RESPONSE_FILTER.FILTERED ? response : '', + }; + }; + + fetchData = (state) => { + const { + filtered, pageSize, page, pages, + } = state; + const { allLogs } = this.props.queryLogs; + const isLastPage = pages && (page + 1 >= pages); + const isFiltersPresent = filtered.length > 0; + const filter = this.getFilters(filtered); + + if (isFiltersPresent) { + this.handleLogsFiltering('', filter, page, pageSize, true); + } else if (isLastPage) { + const lastRow = allLogs[allLogs.length - 1]; + const lastRowTime = (lastRow && lastRow.time) || ''; + this.getLogs(lastRowTime, filter, page, pageSize); + } else { + this.props.setLogsPagination({ page, pageSize }); + } + }; + + renderLogs() { + const { queryLogs, dashboard, t } = this.props; + const { processingClients } = dashboard; + const { + processingGetLogs, processingGetConfig, logs, pages, + } = queryLogs; + const isLoading = processingGetLogs || processingClients || processingGetConfig; + const columns = [ { Header: t('time_table_header'), @@ -230,6 +305,7 @@ class Logs extends Component { accessor: 'domain', minWidth: 180, Cell: this.getDomainCell, + Filter: this.getFilterInput, }, { Header: t('type_table_header'), @@ -251,7 +327,7 @@ class Logs extends Component { }, Filter: ({ filter, onChange }) => (