diff --git a/CHANGELOG.md b/CHANGELOG.md index a2f02b18..3640e205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,12 @@ See also the [v0.107.45 GitHub milestone][ms-v0.107.45]. NOTE: Add new changes BELOW THIS COMMENT. --> +### Added + +- Context menu item in the Query Log to add a Client to the Persistent client list ([#6679]). + +[#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679 + diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index ecac7f0d..bc3e459c 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -244,6 +244,7 @@ "allow_this_client": "Allow this client", "block_for_this_client_only": "Block for this client only", "unblock_for_this_client_only": "Unblock for this client only", + "add_persistent_client": "Add as persistent client", "time_table_header": "Time", "date": "Date", "domain_name_table_header": "Domain name", @@ -466,6 +467,7 @@ "form_add_id": "Add identifier", "form_client_name": "Enter client name", "name": "Name", + "client_name": "Client {{id}}", "client_global_settings": "Use global settings", "client_deleted": "Client \"{{key}}\" successfully deleted", "client_added": "Client \"{{key}}\" successfully added", diff --git a/client/src/components/Filters/Modal.js b/client/src/components/Filters/Modal.js index aba53b30..5144a6f2 100644 --- a/client/src/components/Filters/Modal.js +++ b/client/src/components/Filters/Modal.js @@ -13,6 +13,8 @@ ReactModal.setAppElement('#root'); const MODAL_TYPE_TO_TITLE_TYPE_MAP = { [MODAL_TYPE.EDIT_FILTERS]: 'edit', [MODAL_TYPE.ADD_FILTERS]: 'new', + [MODAL_TYPE.EDIT_CLIENT]: 'edit', + [MODAL_TYPE.ADD_CLIENT]: 'new', [MODAL_TYPE.SELECT_MODAL_TYPE]: 'new', [MODAL_TYPE.CHOOSE_FILTERING_LIST]: 'choose', }; diff --git a/client/src/components/Logs/Cells/ClientCell.js b/client/src/components/Logs/Cells/ClientCell.js index d0dd3168..da15efc3 100644 --- a/client/src/components/Logs/Cells/ClientCell.js +++ b/client/src/components/Logs/Cells/ClientCell.js @@ -3,7 +3,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { nanoid } from 'nanoid'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import propTypes from 'prop-types'; import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers'; @@ -25,12 +25,14 @@ const ClientCell = ({ }) => { const { t } = useTranslation(); const dispatch = useDispatch(); + const history = useHistory(); const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual); const isDetailed = useSelector((state) => state.queryLogs.isDetailed); const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual); const [isOptionsOpened, setOptionsOpened] = useState(false); const autoClient = autoClients.find((autoClient) => autoClient.name === client); + const clients = useSelector((state) => 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; @@ -55,6 +57,8 @@ const ClientCell = ({ const isFiltered = checkFiltered(reason); + const clientIds = clients.map((c) => 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, @@ -66,7 +70,6 @@ const ClientCell = ({ const renderBlockingButton = (isFiltered, domain) => { const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; - const clients = useSelector((state) => state.dashboard.clients); const { confirmMessage, @@ -118,6 +121,15 @@ const ClientCell = ({ }, ]; + if (!clientIds.includes(client)) { + BUTTON_OPTIONS.push({ + name: 'add_persistent_client', + onClick: () => { + history.push(`/#clients?clientId=${client}`); + }, + }); + } + const getOptions = (options) => { if (options.length === 0) { return null; diff --git a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js index f4744a5a..3f82ee98 100644 --- a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js +++ b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js @@ -4,6 +4,7 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { Trans, useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; +import { useHistory, useLocation } from 'react-router-dom'; import ReactTable from 'react-table'; import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services'; @@ -39,8 +40,12 @@ const ClientsTable = ({ }) => { const [t] = useTranslation(); const dispatch = useDispatch(); + const location = useLocation(); + const history = useHistory(); const services = useSelector((store) => store?.services); const globalSettings = useSelector((store) => store?.settings.settingsList) || {}; + const params = new URLSearchParams(location.search); + const clientId = params.get('clientId'); const { safesearch } = globalSettings; @@ -48,6 +53,12 @@ const ClientsTable = ({ dispatch(getAllBlockedServices()); dispatch(getBlockedServices()); dispatch(initSettings()); + + if (clientId) { + toggleClientModal({ + type: MODAL_TYPE.ADD_CLIENT, + }); + } }, []); const handleFormAdd = (values) => { @@ -85,11 +96,15 @@ const ClientsTable = ({ } } - if (modalType === MODAL_TYPE.EDIT_FILTERS) { + if (modalType === MODAL_TYPE.EDIT_CLIENT) { handleFormUpdate(config, modalClientName); } else { handleFormAdd(config); } + + if (clientId) { + history.push('/#clients'); + } }; const getOptionsWithLabels = (options) => ( @@ -133,6 +148,14 @@ const ClientsTable = ({ } }; + const handleClose = () => { + toggleClientModal(); + + if (clientId) { + history.push('/#clients'); + } + }; + const columns = [ { Header: t('table_client'), @@ -298,7 +321,7 @@ const ClientsTable = ({ type="button" className="btn btn-icon btn-outline-primary btn-sm mr-2" onClick={() => toggleClientModal({ - type: MODAL_TYPE.EDIT_FILTERS, + type: MODAL_TYPE.EDIT_CLIENT, name: clientName, }) } @@ -371,12 +394,13 @@ const ClientsTable = ({ diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 2df5b655..46c90928 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -147,7 +147,7 @@ let Form = (props) => { useGlobalSettings, useGlobalServices, blockedServicesSchedule, - toggleClientModal, + handleClose, processingAdding, processingUpdating, invalid, @@ -427,7 +427,7 @@ let Form = (props) => { disabled={submitting} onClick={() => { reset(); - toggleClientModal(); + handleClose(); }} > cancel_btn @@ -456,7 +456,7 @@ Form.propTypes = { reset: PropTypes.func.isRequired, change: PropTypes.func.isRequired, submitting: PropTypes.bool.isRequired, - toggleClientModal: PropTypes.func.isRequired, + handleClose: PropTypes.func.isRequired, useGlobalSettings: PropTypes.bool, useGlobalServices: PropTypes.bool, blockedServicesSchedule: PropTypes.object, diff --git a/client/src/components/Settings/Clients/Modal.js b/client/src/components/Settings/Clients/Modal.js index 9c244a21..321e663c 100644 --- a/client/src/components/Settings/Clients/Modal.js +++ b/client/src/components/Settings/Clients/Modal.js @@ -6,7 +6,9 @@ import ReactModal from 'react-modal'; import { MODAL_TYPE } from '../../../helpers/constants'; import Form from './Form'; -const getInitialData = (initial) => { +const getInitialData = ({ + initial, modalType, clientId, clientName, +}) => { if (initial && initial.blocked_services) { const { blocked_services } = initial; const blocked = {}; @@ -21,46 +23,60 @@ const getInitialData = (initial) => { }; } + if (modalType !== MODAL_TYPE.EDIT_CLIENT && clientId) { + return { + ...initial, + name: clientName, + ids: [clientId], + }; + } + return initial; }; -const Modal = (props) => { - const { - isModalOpen, +const Modal = ({ + isModalOpen, + modalType, + currentClientData, + handleSubmit, + handleClose, + processingAdding, + processingUpdating, + tagsOptions, + clientId, + t, +}) => { + const initialData = getInitialData({ + initial: currentClientData, modalType, - currentClientData, - handleSubmit, - toggleClientModal, - processingAdding, - processingUpdating, - tagsOptions, - } = props; - const initialData = getInitialData(currentClientData); + clientId, + clientName: t('client_name', { id: clientId }), + }); return ( toggleClientModal()} + onRequestClose={handleClose} >

- {modalType === MODAL_TYPE.EDIT_FILTERS ? ( + {modalType === MODAL_TYPE.EDIT_CLIENT ? ( client_edit ) : ( client_new )}

-