diff --git a/client_v2/src/actions/queryLogs.ts b/client_v2/src/actions/queryLogs.ts index 70dc568a..05bd1ed1 100644 --- a/client_v2/src/actions/queryLogs.ts +++ b/client_v2/src/actions/queryLogs.ts @@ -5,7 +5,11 @@ import apiClient from '../api/Api'; import { normalizeLogs } from '../helpers/helpers'; import { DEFAULT_LOGS_FILTER, QUERY_LOGS_PAGE_LIMIT } from '../helpers/constants'; import { addErrorToast, addSuccessToast } from './toasts'; -import { SearchFormValues } from '../components/Logs'; + +type SearchFormValues = { + search: string; + response_status: string; +}; const getLogsWithParams = async (config: any) => { const { older_than, filter, ...values } = config; diff --git a/client_v2/src/components/Header/Header.css b/client_v2/src/components/Header/Header.css deleted file mode 100644 index 93d45f3e..00000000 --- a/client_v2/src/components/Header/Header.css +++ /dev/null @@ -1,207 +0,0 @@ -.nav-tabs .nav-link.active { - border-color: var(--btn-success-bgcolor); - color: var(--btn-success-bgcolor); - background: transparent; -} - -.nav-tabs .nav-link.active:hover { - border-color: #4b9400; - color: #4b9400; -} - -.nav-icon { - width: 15px; - height: 15px; - margin-right: 6px; - stroke: #9aa0ac; -} - -.nav-tabs .nav-link.active .nav-icon, -.nav-tabs .nav-item.show .nav-icon { - stroke: var(--green-74); -} - -.nav-tabs .nav-link.active:hover .nav-icon, -.nav-tabs .nav-item.show:hover .nav-icon { - stroke: #58a273; -} - -.nav-tabs .nav-link { - width: 100%; - border: 0; - padding: 20px 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: var(--header-bgcolor); - 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; -} - -.header-brand-img { - height: 24px; -} - -.nav-tabs .nav-item.show .nav-link { - color: var(--green-74); - background-color: #fff; - border-bottom-color: var(--green-74); -} - -.header__right { - display: flex; - align-items: center; - justify-content: flex-end; - min-width: 100px; -} - -.header__logout { - display: inline-flex; - align-items: center; - justify-content: center; - width: 25px; - height: 25px; - min-width: 25px; - padding: 2px; - margin-left: 10px; - color: #9aa0ac; -} - -.header__logout:hover, -.header__logout:focus { - color: #6e7687; -} - -.header__logout-icon { - height: 100%; -} - -.header__row { - display: flex; - align-items: center; -} - -.header__container { - width: 100%; - max-width: 1200px; - padding-right: 0.75rem; - padding-left: 0.75rem; - margin-right: auto; - margin-left: auto; -} - -.header__column:last-child { - margin-left: auto; -} - -.nav-tabs { - margin: 0; -} - -@media screen and (min-width: 992px) { - .header { - padding: 0; - } - - .nav-tabs .nav-link { - width: auto; - border-bottom: 1px solid transparent; - font-size: 13px; - white-space: nowrap; - } - - .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-icon { - display: none; - } - - .nav-icon--gray { - color: #9aa0ac; - } - - .nav-icon--white { - color: #fff; - } - - .header-brand-img { - height: 32px; - } - - .header__logout { - width: 35px; - height: 35px; - padding: 5px; - } - - .header__row { - justify-content: space-between; - } - - .header__column:last-child { - margin-left: 0; - } - - .nav-tabs { - margin: 0 -0.75rem; - } -} - -@media screen and (min-width: 1280px) { - .nav-tabs .nav-link { - font-size: 14px; - } - - .nav-icon { - display: block; - } -} - -.dns-status { - padding: 0.35em 0.5em; - line-height: 10px; -} diff --git a/client_v2/src/components/Header/Menu.tsx b/client_v2/src/components/Header/Menu.tsx deleted file mode 100644 index b4ad0712..00000000 --- a/client_v2/src/components/Header/Menu.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import React, { Component } from 'react'; - -import { NavLink } from 'react-router-dom'; - -import enhanceWithClickOutside from 'react-click-outside'; -import classnames from 'classnames'; -import { Trans, withTranslation } from 'react-i18next'; -import { SETTINGS_URLS, FILTERS_URLS, MENU_URLS } from '../../helpers/constants'; - -import Dropdown from '../ui/Dropdown'; - -const MENU_ITEMS = [ - { - route: MENU_URLS.root, - exact: true, - icon: 'dashboard', - text: 'dashboard', - order: 0, - }, - - // Settings dropdown should have visual order 1 - - // Filters dropdown should have visual order 2 - - { - route: MENU_URLS.logs, - icon: 'log', - text: 'query_log', - order: 3, - }, - { - route: MENU_URLS.guide, - icon: 'setup', - text: 'setup_guide', - order: 4, - }, -]; - -const SETTINGS_ITEMS = [ - { - route: SETTINGS_URLS.settings, - text: 'general_settings', - }, - { - route: SETTINGS_URLS.dns, - text: 'dns_settings', - }, - { - route: SETTINGS_URLS.encryption, - text: 'encryption_settings', - }, - { - route: SETTINGS_URLS.clients, - text: 'client_settings', - }, - { - route: SETTINGS_URLS.dhcp, - text: 'dhcp_settings', - }, -]; - -const FILTERS_ITEMS = [ - { - route: FILTERS_URLS.dns_blocklists, - text: 'dns_blocklists', - }, - { - route: FILTERS_URLS.dns_allowlists, - text: 'dns_allowlists', - }, - { - route: FILTERS_URLS.dns_rewrites, - text: 'dns_rewrites', - }, - { - route: FILTERS_URLS.blocked_services, - text: 'blocked_services', - }, - { - route: FILTERS_URLS.custom_rules, - text: 'custom_filtering_rules', - }, -]; - -interface MenuProps { - isMenuOpen: boolean; - closeMenu: (...args: unknown[]) => unknown; - pathname: string; - t?: (...args: unknown[]) => string; -} - -class Menu extends Component { - handleClickOutside = () => { - this.props.closeMenu(); - }; - - closeMenu = () => { - this.props.closeMenu(); - }; - - getActiveClassForDropdown = (URLS: any) => { - const isActivePage = Object.values(URLS) - - .some((item: any) => item === this.props.pathname); - - return isActivePage ? 'active' : ''; - }; - - getNavLink = ({ route, exact, text, order, className, icon }: any) => ( - - {icon && ( - - - - )} - - {text} - - ); - - getDropdown = ({ label, order, URLS, icon, ITEMS }: any) => ( - - {ITEMS.map((item: any) => - this.getNavLink({ - ...item, - order, - className: 'dropdown-item', - }), - )} - - ); - - render() { - const menuClass = classnames({ - 'header__column mobile-menu': true, - - 'mobile-menu--active': this.props.isMenuOpen, - }); - return ( - <> -
- -
- - ); - } -} - -export default withTranslation()(enhanceWithClickOutside(Menu)); diff --git a/client_v2/src/components/Header/index.tsx b/client_v2/src/components/Header/index.tsx deleted file mode 100644 index bb4e0422..00000000 --- a/client_v2/src/components/Header/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useState } from 'react'; - -import { Link, useLocation } from 'react-router-dom'; -import { shallowEqual, useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import classnames from 'classnames'; - -import Menu from './Menu'; - -import { Logo } from '../ui/svg/logo'; -import './Header.css'; -import { RootState } from '../../initialState'; - -const Header = () => { - const [isMenuOpen, setIsMenuOpen] = useState(false); - const { t } = useTranslation(); - - const { protectionEnabled, processing, isCoreRunning, processingProfile, name } = useSelector( - (state: RootState) => state.dashboard, - shallowEqual, - ); - - const { pathname } = useLocation(); - - const toggleMenuOpen = () => { - setIsMenuOpen((isMenuOpen) => !isMenuOpen); - }; - - const closeMenu = () => { - setIsMenuOpen(false); - }; - - const badgeClass = classnames('badge dns-status', { - 'badge-success': protectionEnabled, - 'badge-danger': !protectionEnabled, - }); - - return ( -
-
-
-
- -
- -
-
- - - - {!processing && isCoreRunning && ( - {t(protectionEnabled ? 'on' : 'off')} - )} -
-
- - - -
-
- {!processingProfile && name && ( - - {t('sign_out')} - - )} -
-
-
-
-
- ); -}; - -export default Header; diff --git a/client_v2/src/components/Logs/AnonymizerNotification.tsx b/client_v2/src/components/Logs/AnonymizerNotification.tsx deleted file mode 100644 index ada0636f..00000000 --- a/client_v2/src/components/Logs/AnonymizerNotification.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Trans } from 'react-i18next'; - -import { HashLink as Link } from 'react-router-hash-link'; - -const AnonymizerNotification = () => ( -
- text, - - - link - , - ]}> - anonymizer_notification - -
-); - -export default AnonymizerNotification; diff --git a/client_v2/src/components/Logs/Cells/ClientCell.tsx b/client_v2/src/components/Logs/Cells/ClientCell.tsx deleted file mode 100644 index 30976293..00000000 --- a/client_v2/src/components/Logs/Cells/ClientCell.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React, { useState } from 'react'; -import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import { nanoid } from 'nanoid'; -import classNames from 'classnames'; -import { useTranslation } from 'react-i18next'; - -import { Link, useHistory } from 'react-router-dom'; - -import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers'; -import { BLOCK_ACTIONS } from '../../../helpers/constants'; - -import { toggleBlocking, toggleBlockingForClient } from '../../../actions'; - -import IconTooltip from './IconTooltip'; - -import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell'; -import { toggleClientBlock } from '../../../actions/access'; -import { getBlockClientInfo } from './helpers'; -import { getStats } from '../../../actions/stats'; -import { updateLogs } from '../../../actions/queryLogs'; -import { RootState } from '../../../initialState'; - -interface ClientCellProps { - client: string; - client_id?: string; - client_info?: { - name: string; - whois: { - country?: string; - city?: string; - orgname?: string; - }; - disallowed: boolean; - disallowed_rule: string; - }; - domain: string; - reason: string; -} - -const ClientCell = ({ client, client_id, client_info, domain, reason }: ClientCellProps) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - const history = useHistory(); - - const autoClients = useSelector((state: RootState) => state.dashboard.autoClients, shallowEqual); - - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - - const allowedClients = useSelector((state: RootState) => state.access.allowed_clients, shallowEqual); - const [isOptionsOpened, setOptionsOpened] = useState(false); - - const autoClient = autoClients.find((autoClient: any) => autoClient.name === client); - - const clients = useSelector((state: RootState) => state.dashboard.clients); - const source = autoClient?.source; - const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0; - const clientName = client_info?.name || client_id; - const clientInfo = client_info && { - ...client_info, - whois_info: client_info?.whois, - name: clientName, - }; - - const id = nanoid(); - - const data = { - address: client, - name: clientName, - country: client_info?.whois?.country, - city: client_info?.whois?.city, - network: client_info?.whois?.orgname, - source_label: source, - }; - - const processedData = Object.entries(data); - - const isFiltered = checkFiltered(reason); - - const clientIds = clients.map((c: any) => c.ids).flat(); - - const nameClass = classNames('w-90 o-hidden d-flex flex-column', { - 'mt-2': isDetailed && !client_info?.name && !whoisAvailable, - 'white-space--nowrap': isDetailed, - }); - - const hintClass = classNames('icons mr-4 icon--24 logs__question icon--lightgray', { - 'my-3': isDetailed, - }); - - const renderBlockingButton = (isFiltered: any, domain: any) => { - const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; - - const { - confirmMessage, - buttonKey: blockingClientKey, - lastRuleInAllowlist, - } = getBlockClientInfo( - client, - client_info?.disallowed || false, - client_info?.disallowed_rule || '', - allowedClients, - ); - - const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; - const clientNameBlockingFor = getBlockingClientName(clients, client); - - const onClick = async () => { - await dispatch(toggleBlocking(buttonType, domain)); - await dispatch(getStats()); - setOptionsOpened(false); - }; - - const BUTTON_OPTIONS = [ - { - name: buttonType, - onClick, - className: isFiltered ? 'bg--green' : 'bg--danger', - }, - { - name: blockingForClientKey, - onClick: () => { - dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); - setOptionsOpened(false); - }, - }, - { - name: blockingClientKey, - onClick: async () => { - if (window.confirm(confirmMessage)) { - await dispatch( - toggleClientBlock( - client, - client_info?.disallowed || false, - client_info?.disallowed_rule || '', - ), - ); - await dispatch(updateLogs()); - setOptionsOpened(false); - } - }, - disabled: lastRuleInAllowlist, - }, - ]; - - if (!clientIds.includes(client)) { - BUTTON_OPTIONS.push({ - name: 'add_persistent_client', - onClick: () => { - history.push(`/#clients?clientId=${client}`); - }, - }); - } - - const getOptions = (options: any) => { - if (options.length === 0) { - return null; - } - - return ( - <> - {options.map(({ name, onClick, disabled, className }: any) => ( - - ))} - - ); - }; - - const content = getOptions(BUTTON_OPTIONS); - - const containerClass = classNames('button-action__container', { - 'button-action__container--detailed': isDetailed, - }); - - return ( -
- - {isOptionsOpened && ( - - )} -
- ); - }; - - return ( -
- - -
-
- {renderFormattedClientCell(client, clientInfo, isDetailed, true)} -
- {isDetailed && clientName && !whoisAvailable && ( - - {clientName} - - )} -
- {renderBlockingButton(isFiltered, domain)} -
- ); -}; - -export default ClientCell; diff --git a/client_v2/src/components/Logs/Cells/DateCell.tsx b/client_v2/src/components/Logs/Cells/DateCell.tsx deleted file mode 100644 index 4cf1c233..00000000 --- a/client_v2/src/components/Logs/Cells/DateCell.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; - -import { formatDateTime, formatTime } from '../../../helpers/helpers'; -import { DEFAULT_SHORT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT } from '../../../helpers/constants'; -import { RootState } from '../../../initialState'; - -interface DateCellProps { - time: string; -} - -const DateCell = ({ time }: DateCellProps) => { - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - - if (!time) { - return <>–; - } - - const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT); - - const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS); - - return ( -
-
- {formattedTime} -
- {isDetailed && ( -
- {formattedDate} -
- )} -
- ); -}; - -export default DateCell; diff --git a/client_v2/src/components/Logs/Cells/DomainCell.tsx b/client_v2/src/components/Logs/Cells/DomainCell.tsx deleted file mode 100644 index 3b2d273d..00000000 --- a/client_v2/src/components/Logs/Cells/DomainCell.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import classNames from 'classnames'; -import { useTranslation } from 'react-i18next'; -import { - DEFAULT_SHORT_DATE_FORMAT_OPTIONS, - LONG_TIME_FORMAT, - SCHEME_TO_PROTOCOL_MAP, -} from '../../../helpers/constants'; - -import { captitalizeWords, formatDateTime, formatTime } from '../../../helpers/helpers'; -import { getSourceData } from '../../../helpers/trackers/trackers'; - -import IconTooltip from './IconTooltip'; -import { RootState } from '../../../initialState'; - -interface DomainCellProps { - answer_dnssec: boolean; - client_proto: string; - domain: string; - unicodeName?: string; - time: string; - type: string; - tracker?: { - name: string; - category: string; - }; - ecs?: string; -} - -const DomainCell = ({ - answer_dnssec, - client_proto, - domain, - unicodeName, - time, - tracker, - type, - ecs, -}: DomainCellProps) => { - const { t } = useTranslation(); - - const dnssec_enabled = useSelector((state: RootState) => state.dnsConfig.dnssec_enabled); - - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - - const hasTracker = !!tracker; - - const lockIconClass = classNames('icons icon--24 d-none d-sm-block', { - 'icon--green': answer_dnssec, - 'icon--disabled': !answer_dnssec, - 'my-3': isDetailed, - }); - - const privacyIconClass = classNames('icons mx-2 icon--24 d-none d-sm-block logs__question', { - 'icon--green': hasTracker, - 'icon--disabled': !hasTracker, - 'my-3': isDetailed, - }); - - const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || ''; - const ip = type ? `${t('type_table_header')}: ${type}` : ''; - - let requestDetailsObj: { - time_table_header: string; - date: string; - domain: string; - punycode?: string; - ecs?: string; - type_table_header?: string; - protocol?: string; - } = { - time_table_header: formatTime(time, LONG_TIME_FORMAT), - date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS), - domain, - }; - - if (domain && unicodeName) { - requestDetailsObj = { - ...requestDetailsObj, - domain: unicodeName, - punycode: domain, - }; - } - - if (ecs) { - requestDetailsObj = { - ...requestDetailsObj, - ecs, - }; - } - - requestDetailsObj = { - ...requestDetailsObj, - type_table_header: type, - protocol, - }; - - const sourceData = getSourceData(tracker); - - const knownTrackerDataObj = { - name_table_header: tracker?.name, - category_label: hasTracker && captitalizeWords(tracker.category), - source_label: sourceData && ( - - {sourceData.name} - - ), - }; - - const renderGrid = (content: any, idx: any) => { - const preparedContent = typeof content === 'string' ? t(content) : content; - - const className = classNames('text-truncate o-hidden', { 'overflow-break': preparedContent?.length > 100 }); - - return ( -
- {preparedContent} -
- ); - }; - - const getGrid = (contentObj: any, title: string, className?: string) => [ -
- {t(title)} -
, - -
- {React.Children.map(Object.entries(contentObj), renderGrid)} -
, - ]; - - const requestDetails = getGrid(requestDetailsObj, 'request_details'); - - const renderContent = hasTracker - ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) - : requestDetails; - - const valueClass = classNames('w-100 text-truncate', { - 'px-2 d-flex justify-content-center flex-column': isDetailed, - }); - - const details = [ip, protocol].filter(Boolean).join(', '); - - return ( -
- {dnssec_enabled && ( - - )} - - - -
- {unicodeName ? ( -
- {unicodeName} -
- ) : ( -
- {domain} -
- )} - {details && isDetailed && ( -
- {details} -
- )} -
-
- ); -}; - -export default DomainCell; diff --git a/client_v2/src/components/Logs/Cells/Header.tsx b/client_v2/src/components/Logs/Cells/Header.tsx deleted file mode 100644 index a8f8eb7f..00000000 --- a/client_v2/src/components/Logs/Cells/Header.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; -import classNames from 'classnames'; -import React from 'react'; -import { toggleDetailedLogs } from '../../../actions/queryLogs'; - -import HeaderCell from './HeaderCell'; -import { RootState } from '../../../initialState'; - -const Header = () => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - const disableDetailedMode = () => dispatch(toggleDetailedLogs(false)); - const enableDetailedMode = () => dispatch(toggleDetailedLogs(true)); - - const HEADERS = [ - { - className: 'logs__cell--date', - content: 'time_table_header', - }, - { - className: 'logs__cell--domain', - content: 'request_table_header', - }, - { - className: 'logs__cell--response', - content: 'response_table_header', - }, - { - className: 'logs__cell--client', - - content: ( - <> - {t('client_table_header')} - - { - - - {t('compact')} - - - - - - {t('default')} - - - - - } - - ), - }, - ]; - - return ( -
- {HEADERS.map(HeaderCell)} -
- ); -}; - -export default Header; diff --git a/client_v2/src/components/Logs/Cells/HeaderCell.tsx b/client_v2/src/components/Logs/Cells/HeaderCell.tsx deleted file mode 100644 index 0cb7e46c..00000000 --- a/client_v2/src/components/Logs/Cells/HeaderCell.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -interface HeaderCellProps { - content: string | React.ReactElement; - className?: string; -} - -const HeaderCell = ({ content, className }: HeaderCellProps, idx: any) => { - const { t } = useTranslation(); - - return ( -
- {typeof content === 'string' ? t(content) : content} -
- ); -}; - -export default HeaderCell; diff --git a/client_v2/src/components/Logs/Cells/IconTooltip.css b/client_v2/src/components/Logs/Cells/IconTooltip.css deleted file mode 100644 index 99b1fe4e..00000000 --- a/client_v2/src/components/Logs/Cells/IconTooltip.css +++ /dev/null @@ -1,148 +0,0 @@ -.tooltip-custom__container { - min-width: 150px; - padding: 1rem 1.5rem 1.25rem 1.5rem; - font-size: 16px !important; - box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.2); - border-radius: 4px !important; - pointer-events: auto !important; - background-color: var(--ctrl-bgcolor); - color: var(--mcolor); - z-index: 102; - overflow-y: auto; - max-height: 100%; -} - -.white-space--nowrap { - white-space: nowrap !important; -} - -.overflow-break { - white-space: normal !important; - overflow-wrap: break-word; -} - -@media (max-width: 991.98px) { - .overflow-break-mobile { - white-space: normal !important; - overflow-wrap: break-word; - } -} - -.grid { - display: grid; - grid-template-columns: repeat(2, min-content); - grid-row-gap: 0.5rem; - grid-column-gap: 1rem; -} - -.grid--limited { - grid-template-columns: repeat(2, minmax(0, min-content)); -} - -.grid--gap-bg { - grid-column-gap: 1.5rem; -} - -.grid--title { - font-weight: 600; -} - -.grid--title:not(:first-child) { - padding-top: 1rem; -} - -@media (max-width: 1024px) { - .grid .title--border { - margin-bottom: 4px; - font-weight: 600; - } - - .grid .key-colon { - margin-right: 4px; - color: var(--gray-8); - } - - .grid__row { - display: flex; - align-items: flex-start; - flex-wrap: wrap; - margin-bottom: 2px; - font-size: 14px; - word-break: break-all; - overflow: hidden; - } - - .grid__row .filteringRules__filter, - .grid__row .filteringRules { - margin-bottom: 0; - } -} - -@media (max-width: 767.98px) { - .grid { - grid-template-columns: 35% 55%; - } - - .grid * { - grid-column: 1 / -1; - } - - .grid > :nth-child(even) { - margin: -0.5rem 0 0; - } - - .grid > .key__time_table_header, - .grid > .key__data, - .grid > .key__encryption_status, - .grid > .key__elapsed { - grid-column: 1 / span 1; - } - - .grid > .value__time_table_header, - .grid > .value__data, - .grid > .value__encryption_status, - .grid > .value__elapsed { - grid-column: 2 / span 1; - margin: 0 !important; - } -} - -.grid .key-colon:nth-child(odd)::after { - content: ':'; -} - -.grid__one-row { - grid-template-columns: 15rem; -} - -.grid__flow-column { - grid-auto-flow: column; -} - -.grid-content > * { - justify-content: space-between !important; - width: 100% !important; - overflow: hidden; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; - white-space: nowrap; -} - -.title--border { - padding-top: 1rem; -} - -.title--border:before { - content: ''; - position: absolute; - left: 0; - border-top: 0.5px solid var(--gray-d8) !important; - width: 100%; - margin-top: -0.5rem; -} - -.icon-cross { - position: absolute; - right: 0.5rem; - top: 0.5rem; -} diff --git a/client_v2/src/components/Logs/Cells/IconTooltip.tsx b/client_v2/src/components/Logs/Cells/IconTooltip.tsx deleted file mode 100644 index b968501b..00000000 --- a/client_v2/src/components/Logs/Cells/IconTooltip.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import { Trans } from 'react-i18next'; -import classNames from 'classnames'; -import PopperJS from 'popper.js'; -import { TriggerTypes } from 'react-popper-tooltip'; - -import { processContent } from '../../../helpers/helpers'; - -import Tooltip from '../../ui/Tooltip'; -import 'react-popper-tooltip/dist/styles.css'; -import './IconTooltip.css'; -import { SHOW_TOOLTIP_DELAY } from '../../../helpers/constants'; - -interface IconTooltipProps { - className?: string; - trigger?: TriggerTypes; - triggerClass?: string; - contentItemClass?: string; - columnClass?: string; - tooltipClass?: string; - title?: string; - placement?: PopperJS.Placement; - canShowTooltip?: boolean; - xlinkHref?: string; - content?: React.ReactNode; - renderContent?: React.ReactElement[]; - onVisibilityChange?: (...args: unknown[]) => unknown; - defaultTooltipShown?: boolean; - delayHide?: number; -} - -const IconTooltip = ({ - className, - contentItemClass, - columnClass, - triggerClass, - canShowTooltip = true, - xlinkHref, - title, - placement, - tooltipClass, - content, - trigger, - onVisibilityChange, - defaultTooltipShown, - delayHide, - - renderContent = content - ? React.Children.map( - processContent(content), - - (item, idx) => ( -
- {item || '—'} -
- ), - ) - : null, -}: IconTooltipProps) => { - const tooltipContent = ( - <> - {title && ( -
- {title} -
- )} - -
{renderContent}
- - ); - - const tooltipClassName = classNames('tooltip-custom__container', tooltipClass, { 'd-none': !canShowTooltip }); - - return ( - - {xlinkHref && ( - - - - )} - - ); -}; - -export default IconTooltip; diff --git a/client_v2/src/components/Logs/Cells/ResponseCell.tsx b/client_v2/src/components/Logs/Cells/ResponseCell.tsx deleted file mode 100644 index df7c7e22..00000000 --- a/client_v2/src/components/Logs/Cells/ResponseCell.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { shallowEqual, useSelector } from 'react-redux'; -import classNames from 'classnames'; -import React from 'react'; -import { getRulesToFilterList, formatElapsedMs, getFilterNames, getServiceName } from '../../../helpers/helpers'; -import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants'; - -import IconTooltip from './IconTooltip'; -import { RootState } from '../../../initialState'; - -interface ResponseCellProps { - elapsedMs: string; - originalResponse?: unknown[]; - reason: string; - response: unknown[]; - status: string; - upstream: string; - cached: boolean; - rules?: { - text: string; - filter_list_id: number; - }[]; - service_name?: string; -} - -const ResponseCell = ({ - elapsedMs, - originalResponse, - reason, - response, - status, - upstream, - rules, - service_name, - cached, -}: ResponseCellProps) => { - const { t } = useTranslation(); - - const filters = useSelector((state: RootState) => state.filtering.filters, shallowEqual); - - const whitelistFilters = useSelector((state: RootState) => state.filtering.whitelistFilters, shallowEqual); - - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - - const services = useSelector((store: RootState) => store?.services); - - const formattedElapsedMs = formatElapsedMs(elapsedMs, t); - - const isBlocked = - reason === FILTERED_STATUS.FILTERED_BLACK_LIST || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; - - const isBlockedByResponse = originalResponse.length > 0 && isBlocked; - - const statusLabel = t( - isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason, - ); - - const boldStatusLabel = {statusLabel}; - - const renderResponses = (responseArr: any) => { - if (!responseArr || responseArr.length === 0) { - return ''; - } - - return ( -
- {responseArr.map((response: any) => { - const className = classNames('white-space--nowrap', { - 'overflow-break': response.length > 100, - }); - - return
{`${response}\n`}
; - })} -
- ); - }; - - const COMMON_CONTENT = { - encryption_status: boldStatusLabel, - install_settings_dns: upstream, - ...(cached && { - served_from_cache_label: ( - - - - ), - }), - elapsed: formattedElapsedMs, - response_code: status, - ...(service_name && - services.allServices && { service_name: getServiceName(services.allServices, service_name) }), - ...(rules.length > 0 && { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }), - response_table_header: renderResponses(response), - original_response: renderResponses(originalResponse), - }; - - const content = - rules.length > 0 - ? Object.entries(COMMON_CONTENT) - : Object.entries({ - ...COMMON_CONTENT, - filter: '', - }); - - const getDetailedInfo = (reason: any) => { - switch (reason) { - case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE: - if (!service_name || !services.allServices) { - return formattedElapsedMs; - } - return getServiceName(services.allServices, service_name); - case FILTERED_STATUS.FILTERED_BLACK_LIST: - case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST: - return getFilterNames(rules, filters, whitelistFilters).join(', '); - default: - return formattedElapsedMs; - } - }; - - const detailedInfo = getDetailedInfo(reason); - - return ( -
- - -
-
- {statusLabel} -
- - {isDetailed && ( -
- {detailedInfo} -
- )} -
-
- ); -}; - -export default ResponseCell; diff --git a/client_v2/src/components/Logs/Cells/helpers/index.ts b/client_v2/src/components/Logs/Cells/helpers/index.ts deleted file mode 100644 index b9e14699..00000000 --- a/client_v2/src/components/Logs/Cells/helpers/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import i18next from 'i18next'; - -export const BUTTON_PREFIX = 'btn_'; - -export const getBlockClientInfo = (ip: any, disallowed: any, disallowed_rule: any, allowedClients: any) => { - let confirmMessage; - - if (disallowed) { - confirmMessage = i18next.t('client_confirm_unblock', { ip: disallowed_rule || ip }); - } else { - confirmMessage = `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`; - if (allowedClients.length > 0) { - confirmMessage = confirmMessage.concat(`\n\n${i18next.t('filter_allowlist', { disallowed_rule })}`); - } - } - - const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client'); - const lastRuleInAllowlist = !disallowed && allowedClients === disallowed_rule; - - return { - confirmMessage, - buttonKey, - lastRuleInAllowlist, - }; -}; diff --git a/client_v2/src/components/Logs/Cells/index.tsx b/client_v2/src/components/Logs/Cells/index.tsx deleted file mode 100644 index d62dde2c..00000000 --- a/client_v2/src/components/Logs/Cells/index.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import React, { Dispatch, memo, SetStateAction } from 'react'; -import classNames from 'classnames'; -import { useTranslation } from 'react-i18next'; -import { shallowEqual, useDispatch, useSelector } from 'react-redux'; - -import { - captitalizeWords, - checkFiltered, - getRulesToFilterList, - formatDateTime, - formatElapsedMs, - formatTime, - getBlockingClientName, - getServiceName, - processContent, -} from '../../../helpers/helpers'; -import { - BLOCK_ACTIONS, - DEFAULT_SHORT_DATE_FORMAT_OPTIONS, - FILTERED_STATUS, - FILTERED_STATUS_TO_META_MAP, - LONG_TIME_FORMAT, - QUERY_STATUS_COLORS, - SCHEME_TO_PROTOCOL_MAP, -} from '../../../helpers/constants'; -import { getSourceData } from '../../../helpers/trackers/trackers'; - -import { toggleBlocking, toggleBlockingForClient } from '../../../actions'; - -import DateCell from './DateCell'; - -import DomainCell from './DomainCell'; - -import ResponseCell from './ResponseCell'; - -import ClientCell from './ClientCell'; -import { toggleClientBlock } from '../../../actions/access'; -import { getBlockClientInfo, BUTTON_PREFIX } from './helpers'; -import { updateLogs } from '../../../actions/queryLogs'; - -import '../Logs.css'; -import { RootState } from '../../../initialState'; - -interface RowProps { - style?: object; - rowProps: { - reason: string; - answer_dnssec: boolean; - client: string; - domain: string; - elapsedMs: string; - response: unknown[]; - time: string; - tracker?: { - name: string; - category: string; - }; - upstream: string; - cached: boolean; - type: string; - client_proto: string; - client_id?: string; - ecs?: string; - client_info?: { - name: string; - whois: { - country?: string; - city?: string; - orgname?: string; - }; - disallowed: boolean; - disallowed_rule: string; - }; - rules?: { - text: string; - filter_list_id: number; - }[]; - originalResponse?: unknown[]; - status: string; - service_name?: string; - }; - isSmallScreen: boolean; - setDetailedDataCurrent: Dispatch>; - setButtonType: (...args: unknown[]) => unknown; - setModalOpened: (...args: unknown[]) => unknown; -} - -const Row = memo( - ({ - style, - rowProps, - rowProps: { reason }, - isSmallScreen, - setDetailedDataCurrent, - setButtonType, - setModalOpened, - }: RowProps) => { - const dispatch = useDispatch(); - const { t } = useTranslation(); - - const dnssec_enabled = useSelector((state: RootState) => state.dnsConfig.dnssec_enabled); - - const filters = useSelector((state: RootState) => state.filtering.filters, shallowEqual); - - const whitelistFilters = useSelector((state: RootState) => state.filtering.whitelistFilters, shallowEqual); - - const autoClients = useSelector((state: RootState) => state.dashboard.autoClients, shallowEqual); - - const processingSet = useSelector((state: RootState) => state.access.processingSet); - - const allowedClients = useSelector((state: RootState) => state.access.allowed_clients, shallowEqual); - - const services = useSelector((state: RootState) => state?.services); - - const clients = useSelector((state: RootState) => state.dashboard.clients); - - const onClick = () => { - if (!isSmallScreen) { - return; - } - const { - answer_dnssec, - client, - domain, - elapsedMs, - client_info, - response, - time, - tracker, - upstream, - type, - client_proto, - client_id, - rules, - originalResponse, - status, - service_name, - cached, - } = rowProps; - - const hasTracker = !!tracker; - - const autoClient = autoClients.find((autoClient: any) => autoClient.name === client); - - const source = autoClient?.source; - - const formattedElapsedMs = formatElapsedMs(elapsedMs, t); - const isFiltered = checkFiltered(reason); - - const isBlocked = - reason === FILTERED_STATUS.FILTERED_BLACK_LIST || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; - - const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; - const onToggleBlock = () => { - dispatch(toggleBlocking(buttonType, domain)); - }; - - const isBlockedByResponse = originalResponse.length > 0 && isBlocked; - const requestStatus = t( - isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason, - ); - - const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || ''; - - const sourceData = getSourceData(tracker); - - const { - confirmMessage, - buttonKey: blockingClientKey, - lastRuleInAllowlist, - } = getBlockClientInfo( - client, - client_info?.disallowed || false, - client_info?.disallowed_rule || '', - allowedClients, - ); - - const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; - const clientNameBlockingFor = getBlockingClientName(clients, client); - - const onBlockingForClientClick = () => { - dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); - }; - - const onBlockingClientClick = async () => { - if (window.confirm(confirmMessage)) { - await dispatch( - toggleClientBlock(client, client_info?.disallowed || false, client_info?.disallowed_rule || ''), - ); - await dispatch(updateLogs()); - setModalOpened(false); - } - }; - - const blockButton = ( - <> -
- - - - ); - - const blockForClientButton = ( - - ); - - const blockClientButton = ( - - ); - - const detailedData = { - time_table_header: formatTime(time, LONG_TIME_FORMAT), - - date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS), - encryption_status: isBlocked ?
{requestStatus}
: requestStatus, - ...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && - service_name && - services.allServices && { service_name: getServiceName(services.allServices, service_name) }), - domain, - type_table_header: type, - protocol, - known_tracker: hasTracker && 'title', - table_name: tracker?.name, - category_label: hasTracker && captitalizeWords(tracker.category), - tracker_source: hasTracker && sourceData && ( - - {sourceData.name} - - ), - response_details: 'title', - install_settings_dns: upstream, - ...(cached && { - served_from_cache_label: ( - - - - ), - }), - elapsed: formattedElapsedMs, - ...(rules.length > 0 && { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }), - response_table_header: response?.join('\n'), - response_code: status, - client_details: 'title', - ip_address: client, - name: client_info?.name || client_id, - country: client_info?.whois?.country, - city: client_info?.whois?.city, - network: client_info?.whois?.orgname, - source_label: source, - validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false, - original_response: originalResponse?.join('\n'), - [BUTTON_PREFIX + buttonType]: blockButton, - [BUTTON_PREFIX + blockingForClientKey]: blockForClientButton, - [BUTTON_PREFIX + blockingClientKey]: blockClientButton, - }; - - setDetailedDataCurrent(processContent(detailedData)); - setButtonType(buttonType); - setModalOpened(true); - }; - - const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed); - - const className = classNames( - 'd-flex px-5 logs__row', - `logs__row--${FILTERED_STATUS_TO_META_MAP?.[reason]?.COLOR ?? QUERY_STATUS_COLORS.WHITE}`, - { - 'logs__cell--detailed': isDetailed, - }, - ); - - return ( -
- - - - - - - -
- ); - }, -); - -Row.displayName = 'Row'; - -export default Row; diff --git a/client_v2/src/components/Logs/Disabled.tsx b/client_v2/src/components/Logs/Disabled.tsx deleted file mode 100644 index e9aa9a48..00000000 --- a/client_v2/src/components/Logs/Disabled.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { Fragment } from 'react'; -import { Trans } from 'react-i18next'; - -import { HashLink as Link } from 'react-router-hash-link'; - -import Card from '../ui/Card'; - -const Disabled = () => ( - -
-

- query_log -

-
- - -
- - link - , - ]}> - query_log_disabled - -
-
-
-); - -export default Disabled; diff --git a/client_v2/src/components/Logs/Filters/Form.tsx b/client_v2/src/components/Logs/Filters/Form.tsx deleted file mode 100644 index 634a6a5d..00000000 --- a/client_v2/src/components/Logs/Filters/Form.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useEffect } from 'react'; - -import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; - -import { useHistory } from 'react-router-dom'; -import classNames from 'classnames'; -import { useFormContext } from 'react-hook-form'; -import queryString from 'query-string'; - -import { - DEBOUNCE_FILTER_TIMEOUT, - DEFAULT_LOGS_FILTER, - RESPONSE_FILTER, - RESPONSE_FILTER_QUERIES, -} from '../../../helpers/constants'; -import { setLogsFilter } from '../../../actions/queryLogs'; -import useDebounce from '../../../helpers/useDebounce'; - -import { getLogsUrlParams } from '../../../helpers/helpers'; - -import { SearchField } from './SearchField'; -import { SearchFormValues } from '..'; - -type Props = { - className?: string; - setIsLoading: (value: boolean) => void; -}; - -export const Form = ({ className, setIsLoading }: Props) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - const history = useHistory(); - - const { register, watch, setValue } = useFormContext(); - - const searchValue = watch('search'); - const responseStatusValue = watch('response_status'); - - const [debouncedSearch, setDebouncedSearch] = useDebounce(searchValue.trim(), DEBOUNCE_FILTER_TIMEOUT); - - useEffect(() => { - dispatch( - setLogsFilter({ - response_status: responseStatusValue, - search: debouncedSearch, - }), - ); - - history.replace(`${getLogsUrlParams(debouncedSearch, responseStatusValue)}`); - }, [responseStatusValue, debouncedSearch]); - - useEffect(() => { - if (responseStatusValue && !(responseStatusValue in RESPONSE_FILTER_QUERIES)) { - setValue('response_status', DEFAULT_LOGS_FILTER.response_status); - } - }, [responseStatusValue, setValue]); - - useEffect(() => { - const { search: searchUrlParam } = queryString.parse(history.location.search); - - if (searchUrlParam !== searchValue) { - setValue('search', searchUrlParam ? searchUrlParam.toString() : ''); - } - }, [history.location.search]); - - const onInputClear = async () => { - setIsLoading(true); - history.push(getLogsUrlParams(DEFAULT_LOGS_FILTER.search, responseStatusValue)); - setIsLoading(false); - }; - - const onEnterPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - setDebouncedSearch(searchValue); - } - }; - - return ( -
{ - e.preventDefault(); - }}> -
- setValue('search', val)} - onKeyDown={onEnterPress} - onClear={onInputClear} - placeholder={t('domain_or_client')} - tooltip={t('query_log_strict_search')} - className={classNames('form-control form-control--search form-control--transparent', className)} - /> -
- -
- -
-
- ); -}; diff --git a/client_v2/src/components/Logs/Filters/SearchField.tsx b/client_v2/src/components/Logs/Filters/SearchField.tsx deleted file mode 100644 index 0ee39f26..00000000 --- a/client_v2/src/components/Logs/Filters/SearchField.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { ComponentProps } from 'react'; -import Tooltip from '../../ui/Tooltip'; - -interface Props extends ComponentProps<'input'> { - handleChange: (newValue: string) => void; - onClear: () => void; - tooltip?: string; -} - -export const SearchField = ({ - handleChange, - onClear, - value, - tooltip, - className, - ...rest -}: Props) => { - const handleInputChange = (e: React.ChangeEvent) => { - handleChange(e.target.value); - }; - - const handleBlur = (e: React.FocusEvent) => { - e.target.value = e.target.value.trim(); - handleChange(e.target.value) - } - - return ( - <> -
- - - -
- - {typeof value === 'string' && value.length > 0 && ( -
- - - -
- )} - {tooltip && ( - - - - - - - - )} - - ); -}; diff --git a/client_v2/src/components/Logs/Filters/index.tsx b/client_v2/src/components/Logs/Filters/index.tsx deleted file mode 100644 index b4a332dc..00000000 --- a/client_v2/src/components/Logs/Filters/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; - -import { Form } from './Form'; -import { refreshFilteredLogs } from '../../../actions/queryLogs'; -import { addSuccessToast } from '../../../actions/toasts'; - -interface FiltersProps { - processingGetLogs: boolean; - setIsLoading: (...args: unknown[]) => unknown; -} - -const Filters = ({ setIsLoading }: FiltersProps) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - - const refreshLogs = async () => { - setIsLoading(true); - await dispatch(refreshFilteredLogs()); - dispatch(addSuccessToast('query_log_updated')); - setIsLoading(false); - }; - - return ( -
-

- {t('query_log')} - - -

-
-
- ); -}; - -export default Filters; diff --git a/client_v2/src/components/Logs/InfiniteTable.tsx b/client_v2/src/components/Logs/InfiniteTable.tsx deleted file mode 100644 index e1483640..00000000 --- a/client_v2/src/components/Logs/InfiniteTable.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import throttle from 'lodash/throttle'; - -import Loading from '../ui/Loading'; - -import Header from './Cells/Header'; -import { getLogs } from '../../actions/queryLogs'; - -import Row from './Cells'; - -import { isScrolledIntoView } from '../../helpers/helpers'; -import { QUERY_LOGS_PAGE_LIMIT } from '../../helpers/constants'; -import { RootState } from '../../initialState'; - -interface InfiniteTableProps { - isLoading: boolean; - items: unknown[]; - isSmallScreen: boolean; - currentQuery: string; - setDetailedDataCurrent: Dispatch>; - setButtonType: (...args: unknown[]) => unknown; - setModalOpened: (...args: unknown[]) => unknown; -} - -const InfiniteTable = ({ - isLoading, - items, - isSmallScreen, - currentQuery, - setDetailedDataCurrent, - setButtonType, - setModalOpened, -}: InfiniteTableProps) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - const loader = useRef(null); - const loadingRef = useRef(null); - - const isEntireLog = useSelector((state: RootState) => state.queryLogs.isEntireLog); - - const processingGetLogs = useSelector((state: RootState) => state.queryLogs.processingGetLogs); - const loading = isLoading || processingGetLogs; - - const listener = useCallback(() => { - if (!loadingRef.current && loader.current && isScrolledIntoView(loader.current)) { - dispatch(getLogs(currentQuery)); - } - }, []); - - useEffect(() => { - loadingRef.current = processingGetLogs; - }, [processingGetLogs]); - - useEffect(() => { - listener(); - }, [items.length < QUERY_LOGS_PAGE_LIMIT, isEntireLog]); - - useEffect(() => { - const THROTTLE_TIME = 100; - const throttledListener = throttle(listener, THROTTLE_TIME); - - window.addEventListener('scroll', throttledListener); - return () => { - window.removeEventListener('scroll', throttledListener); - }; - }, []); - - const renderRow = (row: any, idx: any) => ( - - ); - - const isNothingFound = items.length === 0 && !processingGetLogs; - - return ( -
- {loading && } - -
- {isNothingFound ? ( - - ) : ( - <> - {items.map(renderRow)} - {!isEntireLog && ( -
- {t('loading_table_status')} -
- )} - - )} -
- ); -}; - -export default InfiniteTable; diff --git a/client_v2/src/components/Logs/Logs.css b/client_v2/src/components/Logs/Logs.css deleted file mode 100644 index 8ab03da9..00000000 --- a/client_v2/src/components/Logs/Logs.css +++ /dev/null @@ -1,538 +0,0 @@ -:root { - --blue: #e5effd; - --green-pale: rgba(103, 178, 121, 0.1); - --red: rgba(223, 56, 18, 0.05); - --white: #fff; - --yellow: rgba(247, 181, 0, 0.1); - --size-date: 70; - --size-domain: 180; - --size-response: 150; - --size-client: 123; - --gray-216: rgba(216, 216, 216, 0.23); - --gray-4d: #4d4d4d; - --gray-f3: #f3f3f3; - --gray-8: #888; - --gray-3: #333; - --danger: #df3812; - --white80: rgba(255, 255, 255, 0.8); - --btn-block: #c23814; - --btn-block-disabled: #e3b3a6; - --btn-block-active: #a62200; - --btn-unblock: #888888; - --btn-unblock-disabled: #d8d8d8; - --btn-unblock-active: #4d4d4d; - --option-border-radius: 4px; -} - -[data-theme='dark'] { - --red: rgba(223, 56, 18, 0.25); - --green-pale: rgba(103, 178, 121, 0.25); - --yellow: rgba(247, 181, 0, 0.2); -} - -.logs__text { - padding: 0 1px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - font-size: 1rem; - font-family: var(--font-family-sans-serif); - color: var(--logs__text-color); - letter-spacing: 0; - line-height: 1.5rem; -} - -[data-theme='dark'] .logs__text a { - color: var(--gray-f3); -} - -[data-theme='dark'] .logs__text a:hover { - color: var(--gray-f3); -} - -.logs__text--bold { - font-weight: 600; -} - -.logs__time { - font-size: 1rem; - line-height: 1.5; -} - -.detailed-info { - font-size: 0.8rem; - line-height: 1.4; - color: var(--detailed-info-color); -} - -.logs__text--link { - color: #467fcf; -} - -.logs__text--link:hover, -.logs__text--link:focus { - color: #295a9f; -} - -[data-theme='dark'] .logs__text--link, -[data-theme='dark'] .logs__text--link:hover, -[data-theme='dark'] .logs__text--link:focus { - color: var(--gray-f3); -} - -.logs__table .logs__text--client { - padding-right: 32px; -} - -.icon--selected { - background-color: var(--gray-f3); - border: solid 1px var(--gray-d8); - border-radius: 4px; -} - -[data-theme='dark'] .icon--selected { - opacity: 0.75; -} - -.text-pre { - white-space: pre-wrap !important; - overflow-wrap: break-word; - overflow: visible; -} - -.link--green { - color: var(--green79); -} - -.w-90 { - max-width: 90% !important; -} - -.pb-45 { - padding-bottom: 1.25rem !important; -} - -.mh-100 { - max-height: 100% !important; -} - -.icon24 { - width: 24px; - height: 24px; -} - -.icon12 { - width: 12px; - height: 12px; -} - -.cursor--pointer { - cursor: pointer; -} - -.custom-select__arrow--left { - background: var(--white) url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM5YWEwYWMiCiAgICAgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItY2hldnJvbi1kb3duIj4KICAgIDxwb2x5bGluZSBwb2ludHM9IjYgOSAxMiAxNSAxOCA5Ij48L3BvbHlsaW5lPgo8L3N2Zz4K") no-repeat; - background-position: 5px 9px; - background-size: 22px; -} - -.custom-select--logs { - padding: 0.5rem 0.75rem 0.5rem 2rem !important; -} - -.form-control--search { - padding: 0 2.5rem; - height: 2.25rem; - flex-grow: 1; -} - -.form-control--transparent { - background-color: transparent !important; -} - -[data-theme='dark'] .form-control--transparent option { - background-color: var(--card-bgcolor); -} - -.input-group-search { - background-color: transparent; - position: relative; - width: 1.5rem; - height: 1.5rem; - top: 0.4rem; -} - -.input-group-search__icon--magnifier { - left: 2rem; -} - -.input-group-search__icon--cross { - left: -4.5rem; - cursor: pointer; -} - -.input-group-search__icon--tooltip { - left: -4rem; -} - -.form-control--container { - flex: auto; -} - -.field__search { - display: flex; - flex-grow: 1; -} - -@media (max-width: 767.98px) { - .form-control--container { - width: 100%; - flex-direction: column; - } - - .form-control--search { - width: 100%; - } - - .field__select { - margin-top: 1.5rem; - padding-left: 24px; - padding-right: 24px; - } -} - -@media screen and (max-width: 767.98px) { - .logs__table .logs__cell--response, - .logs__table .logs__cell--client { - display: none !important; - } -} - -.logs__refresh { - position: relative; - top: 3px; - display: inline-flex; - align-items: center; - justify-content: center; - width: 2.5rem; - height: 2.5rem; - padding: 0; - margin-left: 0.9375rem; - background-color: transparent; -} - -.logs__cell { - padding: 1rem 1rem 0.5rem 0; -} - -.logs__cell--date { - width: 4.375rem; - flex: var(--size-date) 0 auto; -} - -.logs__cell--domain { - width: 11.25rem; - flex: var(--size-domain) 0 auto; -} - -.logs__cell--response { - width: 9.375rem; - flex: var(--size-response) 0 auto; -} - -.logs__cell--client { - width: 7.6875rem; - flex: var(--size-client) 0 auto; - padding-right: 0; - position: relative; -} - -@media screen and (min-width: 1025px) { - .logs__cell--client { - width: 13rem; - } -} - -.logs__cell--header__container > .logs__cell--header__item { - border-right: 0; - font-size: 1rem; -} - -.logs__cell--header__container > .logs__cell--header__item:last-child { - padding-right: 0; -} - -.button-action__container { - display: flex; - position: absolute; - right: 2px; - bottom: 0.5rem; -} - -@media screen and (max-width: 1024px) { - .button-action__container { - display: none; - } -} - -.button-action__container--detailed { - bottom: 1.3rem; -} - -.button-action { - outline: 0 !important; - background: var(--btn-block); - border-radius: var(--option-border-radius); - font-size: 0.8rem; - color: var(--white); - letter-spacing: 0; - text-align: center; - line-height: 28px; - border: 0; -} - -.button-action--small { - width: fit-content; -} - -.button-action--unblock { - background: var(--btn-unblock); -} - -.button-action--main { - padding: 0 1rem; - display: flex; - align-items: center; -} - -.button-action--with-options { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.button-action:hover { - cursor: pointer; -} - -.button-action--arrow-option { - background: transparent; - border: 0; - display: block; - width: 100%; - padding-top: 0.2rem; - padding-bottom: 0.2rem; - text-align: center; - font-weight: 700; - color: inherit; - cursor: pointer; -} - -.button-action--arrow-option:hover, -.button-action--arrow-option:focus { - outline: none; -} - -.button-action--arrow-option:focus-visible { - outline: 2px solid #295a9f; -} - -.button-action--arrow-option:disabled { - display: none; -} - -.tooltip-custom__container .button-action--arrow-option { - padding-bottom: 0; - text-align: left; - font-weight: 400; -} - -.tooltip-custom__container .button-action--arrow-option:not(:disabled):hover { - background: var(--gray-f3); - overflow: hidden; -} - -[data-theme='dark'] .tooltip-custom__container .button-action--arrow-option:not(:disabled):hover { - background: var(--ctrl-dropdown-bgcolor-focus); -} - -.button-action--arrow-option-container { - overflow: visible; - transform-origin: left; - padding: 1rem 0; -} - -.logs__row { - position: relative; - display: flex; - min-height: 26px; - overflow: hidden; - text-overflow: ellipsis; -} - -.logs__row--icons { - flex-wrap: wrap; -} - -.logs__table .logs__row { - border-bottom: 2px solid var(--gray-216); -} - -.logs__tag { - text-overflow: ellipsis; - overflow: hidden; -} - -/* QUERY_STATUS_COLORS */ -.logs__row--blue { - background-color: var(--logs__row--blue-bgcolor); -} - -[data-theme='dark'] .logs__row--blue .logs__text--link { - color: var(--white); -} - -.logs__row--green { - background-color: var(--green-pale); -} - -.logs__row--red { - background-color: var(--red) !important; -} - -.logs__row--white { - background-color: var(--logs__row--white-bgcolor); -} - -.logs__row--yellow { - background-color: var(--yellow); -} - -.logs__no-data { - color: var(--mcolor); - background-color: var(--logs__table-bgcolor); - pointer-events: none; - font-weight: 600; - text-align: center; - padding-top: 21rem; - display: block; -} - -.logs__loading { - padding: 1rem 0; -} - -.logs__table { - background-color: var(--logs__table-bgcolor); - border: 0; - border-radius: 8px; - min-height: 43rem; - max-width: 100%; - align-items: stretch; - width: 100%; - border-collapse: collapse; - contain: layout; - overflow-x: hidden; - overflow-y: auto; - will-change: scroll-position; -} - -.logs__table .logs__cell--response, -.logs__table .logs__cell--client { - display: flex; -} - -.logs__cell--header__container { - display: flex; -} - -.logs__table > .logs__cell--header__container > .logs__cell--client { - display: flex; - justify-content: space-between; -} - -.logs__table .loading:after { - top: 10%; -} - -.logs__table .loading:before { - min-height: 100%; -} - -.logs__whois { - display: inline; - font-size: 12px; - white-space: nowrap; -} - -.logs__whois::after { - content: '|'; - padding: 0 5px; - opacity: 0.3; -} - -.logs__whois:last-child::after { - content: ''; -} - -.logs__whois-icon.icons { - position: relative; - top: -2px; - width: 12px; - height: 12px; - margin-right: 1px; - opacity: 0.5; -} - -.filteringRules__rule { - margin-bottom: 0; -} - -.filteringRules__filter { - font-style: italic; - font-weight: 400; - margin-bottom: 1rem; -} - -.bg--danger { - color: var(--danger); -} - -.bg--green { - color: var(--green79); -} - -[data-theme='dark'] .logs__question.icon--lightgray { - color: var(--gray-f3); -} - -@media (max-width: 1024px) { - .logs__question { - display: none; - } -} - -.logs__modal { - max-width: 720px; -} - -.logs__modal-wrap { - padding: 1rem 1.5rem; - background-color: var(--card-bgcolor); -} - -.button-action__hidden-trigger { - position: absolute; - top: 0; - right: 0; - width: 1px; - height: 33px; - margin: -1px; - padding: 0; - overflow: hidden; - border: 0; - clip: rect(0 0 0 0); -} - -[data-theme='dark'] .button-action__icon { - color: var(--gray-f3); -} diff --git a/client_v2/src/components/Logs/index.tsx b/client_v2/src/components/Logs/index.tsx deleted file mode 100644 index 8f9013bb..00000000 --- a/client_v2/src/components/Logs/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Trans } from 'react-i18next'; - -import Modal from 'react-modal'; -import { shallowEqual, useDispatch, useSelector } from 'react-redux'; - -import { useHistory } from 'react-router-dom'; -import queryString from 'query-string'; -import classNames from 'classnames'; -import { FormProvider, useForm } from 'react-hook-form'; -import { BLOCK_ACTIONS, DEFAULT_LOGS_FILTER, MEDIUM_SCREEN_SIZE } from '../../helpers/constants'; - -import Loading from '../ui/Loading'; - -import Filters from './Filters'; - -import Disabled from './Disabled'; -import { getFilteringStatus } from '../../actions/filtering'; - -import { getClients } from '../../actions'; -import { getDnsConfig } from '../../actions/dnsConfig'; -import { getAccessList } from '../../actions/access'; -import { getAllBlockedServices } from '../../actions/services'; -import { getLogsConfig, resetFilteredLogs, setFilteredLogs, toggleDetailedLogs } from '../../actions/queryLogs'; - -import InfiniteTable from './InfiniteTable'; -import './Logs.css'; -import { BUTTON_PREFIX } from './Cells/helpers'; - -import AnonymizerNotification from './AnonymizerNotification'; -import { RootState } from '../../initialState'; - -export type SearchFormValues = { - search: string; - response_status: string; -}; - -const processContent = (data: any, _buttonType: string) => - Object.entries(data).map(([key, value]) => { - if (!value) { - return null; - } - - const isTitle = value === 'title'; - const isButton = key.startsWith(BUTTON_PREFIX); - const isBoolean = typeof value === 'boolean'; - const isHidden = isBoolean && value === false; - - let keyClass = 'key-colon'; - - if (isTitle) { - keyClass = 'title--border'; - } - if (isButton || isBoolean) { - keyClass = ''; - } - - return isHidden ? null : ( -
-
- {isButton ? value : key} -
- -
- {isTitle || isButton || isBoolean ? '' : value || '—'} -
-
- ); - }); - -const Logs = () => { - const dispatch = useDispatch(); - const history = useHistory(); - - const { response_status: response_status_url_param, search: search_url_param } = queryString.parse( - history.location.search, - ); - - const { - enabled, - processingGetConfig, - processingGetLogs, - anonymize_client_ip: anonymizeClientIp, - } = useSelector((state: RootState) => state.queryLogs, shallowEqual); - - const filter = useSelector((state: RootState) => state.queryLogs.filter, shallowEqual); - - const logs = useSelector((state: RootState) => state.queryLogs.logs, shallowEqual); - - const search = search_url_param || filter?.search || ''; - const response_status = response_status_url_param || filter?.response_status || ''; - - const formMethods = useForm({ - mode: 'onBlur', - defaultValues: { - search: search || DEFAULT_LOGS_FILTER.search, - response_status: response_status || DEFAULT_LOGS_FILTER.response_status, - }, - }); - - const { watch } = formMethods; - const currentQuery = watch('search'); - - const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth <= MEDIUM_SCREEN_SIZE); - const [detailedDataCurrent, setDetailedDataCurrent] = useState({}); - const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK); - const [isModalOpened, setModalOpened] = useState(false); - const [isLoading, setIsLoading] = useState(false); - - const closeModal = () => setModalOpened(false); - - useEffect(() => { - (async () => { - setIsLoading(true); - await dispatch( - setFilteredLogs({ - search, - response_status, - }), - ); - setIsLoading(false); - })(); - }, [response_status, search]); - - const mediaQuery = window.matchMedia(`(max-width: ${MEDIUM_SCREEN_SIZE}px)`); - const mediaQueryHandler = (e: any) => { - setIsSmallScreen(e.matches); - if (e.matches) { - dispatch(toggleDetailedLogs(false)); - } else { - dispatch(toggleDetailedLogs(true)); - } - }; - - useEffect(() => { - try { - mediaQuery.addEventListener('change', mediaQueryHandler); - } catch (e1) { - try { - // Safari 13.1 do not support mediaQuery.addEventListener('change', handler) - mediaQuery.addListener(mediaQueryHandler); - } catch (e2) { - console.error(e2); - } - } - - (async () => { - setIsLoading(true); - dispatch(getFilteringStatus()); - dispatch(getClients()); - dispatch(getAllBlockedServices()); - try { - await Promise.all([dispatch(getLogsConfig()), dispatch(getDnsConfig()), dispatch(getAccessList())]); - } catch (err) { - console.error(err); - } finally { - setIsLoading(false); - } - })(); - - return () => { - try { - mediaQuery.removeEventListener('change', mediaQueryHandler); - } catch (e1) { - try { - // Safari 13.1 do not support mediaQuery.addEventListener('change', handler) - mediaQuery.removeListener(mediaQueryHandler); - } catch (e2) { - console.error(e2); - } - } - - dispatch(resetFilteredLogs()); - }; - }, []); - - useEffect(() => { - if (!history.location.search) { - (async () => { - setIsLoading(true); - - await dispatch(setFilteredLogs()); - setIsLoading(false); - })(); - } - }, [history.location.search]); - - const renderPage = () => ( - <> - - - - - - - -
- - - - - {processContent(detailedDataCurrent, buttonType)} -
-
- - ); - - return ( - <> - {enabled && ( - <> - {processingGetConfig && } - - {anonymizeClientIp && } - {!processingGetConfig && renderPage()} - - )} - - {!enabled && !processingGetConfig && } - - ); -}; - -export default Logs; diff --git a/client_v2/src/components/Settings/FiltersConfig/index.tsx b/client_v2/src/components/Settings/FiltersConfig/index.tsx deleted file mode 100644 index 944a6f9d..00000000 --- a/client_v2/src/components/Settings/FiltersConfig/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { Trans, useTranslation } from 'react-i18next'; - -import i18next from 'i18next'; -import { toNumber } from '../../../helpers/form'; -import { DAY, FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants'; -import { Checkbox } from '../../ui/Controls/Checkbox'; -import { Select } from '../../ui/Controls/Select'; - -const THREE_DAYS_INTERVAL = DAY * 3; -const SEVEN_DAYS_INTERVAL = DAY * 7; - -const getTitleForInterval = (interval: number) => { - if (interval === 0) { - return i18next.t('disabled'); - } - - if (interval === THREE_DAYS_INTERVAL || interval === SEVEN_DAYS_INTERVAL) { - return i18next.t('interval_days', { count: interval / DAY }); - } - - return i18next.t('interval_hours', { count: interval }); -}; - -export type FormValues = { - enabled: boolean; - interval: number; -}; - -type Props = { - initialValues: FormValues; - setFiltersConfig: (values: FormValues) => void; - processing: boolean; -}; - -export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: Props) => { - const { t } = useTranslation(); - const prevFormValuesRef = useRef(initialValues); - - const { watch, control } = useForm({ - mode: 'onBlur', - defaultValues: initialValues, - }); - - const formValues = watch(); - - useEffect(() => { - const prevFormValues = prevFormValuesRef.current; - - if (JSON.stringify(prevFormValues) !== JSON.stringify(formValues)) { - setFiltersConfig(formValues); - prevFormValuesRef.current = formValues; - } - }, [formValues]); - - const components = { - a: , - }; - - return ( - <> -
-
-
- ( - - )} - /> - -

- filters_block_toggle_hint -

-
-
- -
-
- - ( - - )} - /> -
-
-
- - ); -}; diff --git a/client_v2/src/components/Settings/FormButton.css b/client_v2/src/components/Settings/FormButton.css deleted file mode 100644 index 99d9e3c4..00000000 --- a/client_v2/src/components/Settings/FormButton.css +++ /dev/null @@ -1,11 +0,0 @@ -.form__button { - margin-left: 1.5rem; -} - -@media (max-width: 500px) { - .form__button { - margin-left: 0; - margin-top: 1rem; - display: block; - } -} diff --git a/client_v2/src/components/Settings/LogsConfig/Form.tsx b/client_v2/src/components/Settings/LogsConfig/Form.tsx deleted file mode 100644 index cdb1255d..00000000 --- a/client_v2/src/components/Settings/LogsConfig/Form.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import React, { useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import i18next from 'i18next'; -import { Controller, useForm } from 'react-hook-form'; - -import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; -import { QUERY_LOG_INTERVALS_DAYS, HOUR, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants'; -import '../FormButton.css'; -import { Checkbox } from '../../ui/Controls/Checkbox'; -import { Input } from '../../ui/Controls/Input'; -import { toNumber } from '../../../helpers/form'; -import { Textarea } from '../../ui/Controls/Textarea'; - -const getIntervalTitle = (interval: number) => { - switch (interval) { - case RETENTION_CUSTOM: - return i18next.t('settings_custom'); - case 6 * HOUR: - return i18next.t('interval_6_hour'); - case DAY: - return i18next.t('interval_24_hour'); - default: - return i18next.t('interval_days', { count: interval / DAY }); - } -}; - -export type FormValues = { - enabled: boolean; - anonymize_client_ip: boolean; - interval: number; - customInterval?: number | null; - ignored: string; -}; - -type Props = { - initialValues: Partial; - processing: boolean; - processingReset: boolean; - onSubmit: (values: FormValues) => void; - onReset: () => void; -}; - -export const Form = ({ initialValues, processing, processingReset, onSubmit, onReset }: Props) => { - const { t } = useTranslation(); - - const { - handleSubmit, - watch, - setValue, - control, - formState: { isSubmitting }, - } = useForm({ - mode: 'onBlur', - defaultValues: { - enabled: initialValues.enabled || false, - anonymize_client_ip: initialValues.anonymize_client_ip || false, - interval: initialValues.interval || DAY, - customInterval: initialValues.customInterval || null, - ignored: initialValues.ignored || '', - }, - }); - - const intervalValue = watch('interval'); - const customIntervalValue = watch('customInterval'); - - useEffect(() => { - if (QUERY_LOG_INTERVALS_DAYS.includes(intervalValue)) { - setValue('customInterval', null); - } - }, [intervalValue]); - - const onSubmitForm = (data: FormValues) => { - onSubmit(data); - }; - - const handleIgnoredBlur = (e: React.FocusEvent) => { - const trimmed = trimLinesAndRemoveEmpty(e.target.value); - setValue('ignored', trimmed); - }; - - const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue); - - return ( - -
- ( - - )} - /> -
- -
- ( - - )} - /> -
- -
- query_log_retention -
- -
-
- - - {!QUERY_LOG_INTERVALS_DAYS.includes(intervalValue) && ( -
-
{t('custom_rotation_input')}
- - ( - { - const { value } = e.target; - field.onChange(toNumber(value)); - }} - /> - )} - /> -
- )} - - {QUERY_LOG_INTERVALS_DAYS.map((interval) => ( - - ))} -
-
- - - -
- ignore_domains_desc_query -
- -
- ( -