Pull request 1889: AG-22207 handle rewrite update

Updates #1577

Squashed commit of the following:

commit a07ff51fb3f1eb58ab9a922d7bc11ed1d65ef3a7
Merge: 7db696814 2f515e8d8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 16:50:09 2023 +0300

    Merge branch 'master' into AG-22207

commit 7db696814f2134fd3a3415cbcfa0ffdac1fabda3
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 14:57:09 2023 +0300

    fix changelog

commit bf9458b3f18697ca1fc504c51fa443934f76371f
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 14:48:20 2023 +0300

    changelog

commit bc2bf3f9507957afe63a654334b6e22858d1e41b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 13:28:28 2023 +0300

    client: handle rewrite edit
This commit is contained in:
Ildar Kamalov 2023-06-23 17:10:59 +03:00
parent 2f515e8d8f
commit e7e638443f
17 changed files with 145 additions and 29 deletions

View File

@ -31,7 +31,7 @@ NOTE: Add new changes BELOW THIS COMMENT.
configuration file ([#951]). The UI changes are coming in the upcoming configuration file ([#951]). The UI changes are coming in the upcoming
releases. releases.
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API - The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
([#1577]). and the Web UI ([#1577]).
### Changed ### Changed

View File

@ -478,7 +478,9 @@
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.", "setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.",
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added", "rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted", "rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
"rewrite_updated": "DNS rewrite successfully updated",
"rewrite_add": "Add DNS rewrite", "rewrite_add": "Add DNS rewrite",
"rewrite_edit": "Edit DNS rewrite",
"rewrite_not_found": "No DNS rewrites found", "rewrite_not_found": "No DNS rewrites found",
"rewrite_confirm_delete": "Are you sure you want to delete DNS rewrite for \"{{key}}\"?", "rewrite_confirm_delete": "Are you sure you want to delete DNS rewrite for \"{{key}}\"?",
"rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.", "rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.",

View File

@ -38,6 +38,29 @@ export const addRewrite = (config) => async (dispatch) => {
} }
}; };
export const updateRewriteRequest = createAction('UPDATE_REWRITE_REQUEST');
export const updateRewriteFailure = createAction('UPDATE_REWRITE_FAILURE');
export const updateRewriteSuccess = createAction('UPDATE_REWRITE_SUCCESS');
/**
* @param {Object} config
* @param {string} config.target - current DNS rewrite value
* @param {string} config.update - updated DNS rewrite value
*/
export const updateRewrite = (config) => async (dispatch) => {
dispatch(updateRewriteRequest());
try {
await apiClient.updateRewrite(config);
dispatch(updateRewriteSuccess());
dispatch(toggleRewritesModal());
dispatch(getRewritesList());
dispatch(addSuccessToast(i18next.t('rewrite_updated', { key: config.domain })));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(updateRewriteFailure());
}
};
export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST'); export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST');
export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE'); export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE');
export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS'); export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS');

View File

@ -455,6 +455,8 @@ class Api {
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' }; REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
REWRITE_UPDATE = { path: 'rewrite/update', method: 'PUT' };
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' }; REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
getRewritesList() { getRewritesList() {
@ -470,6 +472,14 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
updateRewrite(config) {
const { path, method } = this.REWRITE_UPDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
deleteRewrite(config) { deleteRewrite(config) {
const { path, method } = this.REWRITE_DELETE; const { path, method } = this.REWRITE_DELETE;
const parameters = { const parameters = {

View File

@ -105,6 +105,7 @@ Form.propTypes = {
submitting: PropTypes.bool.isRequired, submitting: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired, processingAdd: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
initialValues: PropTypes.object,
}; };
export default flow([ export default flow([

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form'; import Form from './Form';
const Modal = (props) => { const Modal = (props) => {
@ -12,6 +13,8 @@ const Modal = (props) => {
toggleRewritesModal, toggleRewritesModal,
processingAdd, processingAdd,
processingDelete, processingDelete,
modalType,
currentRewrite,
} = props; } = props;
return ( return (
@ -24,13 +27,18 @@ const Modal = (props) => {
<div className="modal-content"> <div className="modal-content">
<div className="modal-header"> <div className="modal-header">
<h4 className="modal-title"> <h4 className="modal-title">
<Trans>rewrite_add</Trans> {modalType === MODAL_TYPE.EDIT_REWRITE ? (
<Trans>rewrite_edit</Trans>
) : (
<Trans>rewrite_add</Trans>
)}
</h4> </h4>
<button type="button" className="close" onClick={() => toggleRewritesModal()}> <button type="button" className="close" onClick={() => toggleRewritesModal()}>
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</button> </button>
</div> </div>
<Form <Form
initialValues={{ ...currentRewrite }}
onSubmit={handleSubmit} onSubmit={handleSubmit}
toggleRewritesModal={toggleRewritesModal} toggleRewritesModal={toggleRewritesModal}
processingAdd={processingAdd} processingAdd={processingAdd}
@ -47,6 +55,8 @@ Modal.propTypes = {
toggleRewritesModal: PropTypes.func.isRequired, toggleRewritesModal: PropTypes.func.isRequired,
processingAdd: PropTypes.bool.isRequired, processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired, processingDelete: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
currentRewrite: PropTypes.object,
}; };
export default withTranslation()(Modal); export default withTranslation()(Modal);

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { sortIp } from '../../../helpers/helpers'; import { sortIp } from '../../../helpers/helpers';
import { MODAL_TYPE } from '../../../helpers/constants';
class Table extends Component { class Table extends Component {
cellWrap = ({ value }) => ( cellWrap = ({ value }) => (
@ -31,24 +32,44 @@ class Table extends Component {
maxWidth: 100, maxWidth: 100,
sortable: false, sortable: false,
resizable: false, resizable: false,
Cell: (value) => ( Cell: (value) => {
<div className="logs__row logs__row--center"> const currentRewrite = {
<button answer: value.row.answer,
type="button" domain: value.row.domain,
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm" };
onClick={() => this.props.handleDelete({
answer: value.row.answer, return (
domain: value.row.domain, <div className="logs__row logs__row--center">
}) <button
} type="button"
title={this.props.t('delete_table_action')} className="btn btn-icon btn-outline-primary btn-sm mr-2"
> onClick={() => {
<svg className="icons"> this.props.toggleRewritesModal({
<use xlinkHref="#delete" /> type: MODAL_TYPE.EDIT_REWRITE,
</svg> currentRewrite,
</button> });
</div> }}
), disabled={this.props.processingUpdate}
title={this.props.t('edit_table_action')}
>
<svg className="icons icon12">
<use xlinkHref="#edit" />
</svg>
</button>
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.props.handleDelete(currentRewrite)}
title={this.props.t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
}, },
]; ];
@ -84,7 +105,9 @@ Table.propTypes = {
processing: PropTypes.bool.isRequired, processing: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired, processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired, processingDelete: PropTypes.bool.isRequired,
processingUpdate: PropTypes.bool.isRequired,
handleDelete: PropTypes.func.isRequired, handleDelete: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
}; };
export default withTranslation()(Table); export default withTranslation()(Table);

View File

@ -6,16 +6,13 @@ import Table from './Table';
import Modal from './Modal'; import Modal from './Modal';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
import PageTitle from '../../ui/PageTitle'; import PageTitle from '../../ui/PageTitle';
import { MODAL_TYPE } from '../../../helpers/constants';
class Rewrites extends Component { class Rewrites extends Component {
componentDidMount() { componentDidMount() {
this.props.getRewritesList(); this.props.getRewritesList();
} }
handleSubmit = (values) => {
this.props.addRewrite(values);
};
handleDelete = (values) => { handleDelete = (values) => {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) { if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
@ -23,6 +20,19 @@ class Rewrites extends Component {
} }
}; };
handleSubmit = (values) => {
const { modalType, currentRewrite } = this.props.rewrites;
if (modalType === MODAL_TYPE.EDIT_REWRITE && currentRewrite) {
this.props.updateRewrite({
target: currentRewrite,
update: values,
});
} else {
this.props.addRewrite(values);
}
};
render() { render() {
const { const {
t, t,
@ -36,6 +46,9 @@ class Rewrites extends Component {
processing, processing,
processingAdd, processingAdd,
processingDelete, processingDelete,
processingUpdate,
modalType,
currentRewrite,
} = rewrites; } = rewrites;
return ( return (
@ -54,13 +67,15 @@ class Rewrites extends Component {
processing={processing} processing={processing}
processingAdd={processingAdd} processingAdd={processingAdd}
processingDelete={processingDelete} processingDelete={processingDelete}
processingUpdate={processingUpdate}
handleDelete={this.handleDelete} handleDelete={this.handleDelete}
toggleRewritesModal={toggleRewritesModal}
/> />
<button <button
type="button" type="button"
className="btn btn-success btn-standard mt-3" className="btn btn-success btn-standard mt-3"
onClick={() => toggleRewritesModal()} onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
disabled={processingAdd} disabled={processingAdd}
> >
<Trans>rewrite_add</Trans> <Trans>rewrite_add</Trans>
@ -68,10 +83,13 @@ class Rewrites extends Component {
<Modal <Modal
isModalOpen={isModalOpen} isModalOpen={isModalOpen}
modalType={modalType}
toggleRewritesModal={toggleRewritesModal} toggleRewritesModal={toggleRewritesModal}
handleSubmit={this.handleSubmit} handleSubmit={this.handleSubmit}
processingAdd={processingAdd} processingAdd={processingAdd}
processingDelete={processingDelete} processingDelete={processingDelete}
processingUpdate={processingUpdate}
currentRewrite={currentRewrite}
/> />
</Fragment> </Fragment>
</Card> </Card>
@ -86,6 +104,7 @@ Rewrites.propTypes = {
toggleRewritesModal: PropTypes.func.isRequired, toggleRewritesModal: PropTypes.func.isRequired,
addRewrite: PropTypes.func.isRequired, addRewrite: PropTypes.func.isRequired,
deleteRewrite: PropTypes.func.isRequired, deleteRewrite: PropTypes.func.isRequired,
updateRewrite: PropTypes.func.isRequired,
rewrites: PropTypes.object.isRequired, rewrites: PropTypes.object.isRequired,
}; };

View File

@ -48,6 +48,7 @@ class Table extends Component {
Header: <Trans>list_url_table_header</Trans>, Header: <Trans>list_url_table_header</Trans>,
accessor: 'url', accessor: 'url',
minWidth: 180, minWidth: 180,
// eslint-disable-next-line react/prop-types
Cell: ({ value }) => ( Cell: ({ value }) => (
<div className="logs__row"> <div className="logs__row">
{isValidAbsolutePath(value) ? value {isValidAbsolutePath(value) ? value

View File

@ -32,6 +32,8 @@ const ProtectionTimer = ({
}; };
ProtectionTimer.propTypes = { ProtectionTimer.propTypes = {
protectionDisabledDuration: PropTypes.number,
toggleProtectionSuccess: PropTypes.func.isRequired,
setProtectionTimerTime: PropTypes.func.isRequired, setProtectionTimerTime: PropTypes.func.isRequired,
}; };

View File

@ -27,7 +27,6 @@ import {
} from '../../../helpers/constants'; } from '../../../helpers/constants';
import '../FormButton.css'; import '../FormButton.css';
const getIntervalTitle = (interval, t) => { const getIntervalTitle = (interval, t) => {
switch (interval) { switch (interval) {
case RETENTION_CUSTOM: case RETENTION_CUSTOM:

View File

@ -7,7 +7,6 @@ import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
renderRadioField, renderRadioField,
toNumber, toNumber,

View File

@ -1,3 +1,4 @@
/* eslint-disable react/no-unknown-property */
import React from 'react'; import React from 'react';
import './Icons.css'; import './Icons.css';

View File

@ -3,6 +3,7 @@ import {
getRewritesList, getRewritesList,
addRewrite, addRewrite,
deleteRewrite, deleteRewrite,
updateRewrite,
toggleRewritesModal, toggleRewritesModal,
} from '../actions/rewrites'; } from '../actions/rewrites';
import Rewrites from '../components/Filters/Rewrites'; import Rewrites from '../components/Filters/Rewrites';
@ -17,6 +18,7 @@ const mapDispatchToProps = {
getRewritesList, getRewritesList,
addRewrite, addRewrite,
deleteRewrite, deleteRewrite,
updateRewrite,
toggleRewritesModal, toggleRewritesModal,
}; };

View File

@ -173,6 +173,8 @@ export const MODAL_TYPE = {
ADD_FILTERS: 'ADD_FILTERS', ADD_FILTERS: 'ADD_FILTERS',
EDIT_FILTERS: 'EDIT_FILTERS', EDIT_FILTERS: 'EDIT_FILTERS',
CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST', CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST',
ADD_REWRITE: 'ADD_REWRITE',
EDIT_REWRITE: 'EDIT_REWRITE',
}; };
export const CLIENT_ID = { export const CLIENT_ID = {

View File

@ -845,7 +845,6 @@ export const sortIp = (a, b) => {
} }
}; };
/** /**
* @param {number} filterId * @param {number} filterId
* @returns {string} * @returns {string}

View File

@ -30,7 +30,27 @@ const rewrites = handleActions(
[actions.deleteRewriteFailure]: (state) => ({ ...state, processingDelete: false }), [actions.deleteRewriteFailure]: (state) => ({ ...state, processingDelete: false }),
[actions.deleteRewriteSuccess]: (state) => ({ ...state, processingDelete: false }), [actions.deleteRewriteSuccess]: (state) => ({ ...state, processingDelete: false }),
[actions.toggleRewritesModal]: (state) => { [actions.updateRewriteRequest]: (state) => ({ ...state, processingUpdate: true }),
[actions.updateRewriteFailure]: (state) => ({ ...state, processingUpdate: false }),
[actions.updateRewriteSuccess]: (state) => {
const newState = {
...state,
processingUpdate: false,
};
return newState;
},
[actions.toggleRewritesModal]: (state, { payload }) => {
if (payload) {
const newState = {
...state,
modalType: payload.type || '',
isModalOpen: !state.isModalOpen,
currentRewrite: payload.currentRewrite,
};
return newState;
}
const newState = { const newState = {
...state, ...state,
isModalOpen: !state.isModalOpen, isModalOpen: !state.isModalOpen,
@ -42,7 +62,10 @@ const rewrites = handleActions(
processing: true, processing: true,
processingAdd: false, processingAdd: false,
processingDelete: false, processingDelete: false,
processingUpdate: false,
isModalOpen: false, isModalOpen: false,
modalType: '',
currentRewrite: {},
list: [], list: [],
}, },
); );