+ client: handle DNS rewrites

This commit is contained in:
Ildar Kamalov
2019-07-22 15:32:12 +03:00
parent 70b8cf6ec8
commit e95aae5744
20 changed files with 631 additions and 70 deletions

View File

@@ -13,6 +13,12 @@
overflow: hidden;
}
.logs__row--column {
flex-direction: column;
align-items: flex-start;
overflow: hidden;
}
.logs__row .list-unstyled {
margin-bottom: 0;
overflow: hidden;

View File

@@ -5,6 +5,7 @@ import { saveAs } from 'file-saver/FileSaver';
import escapeRegExp from 'lodash/escapeRegExp';
import endsWith from 'lodash/endsWith';
import { Trans, withNamespaces } from 'react-i18next';
import { HashLink as Link } from 'react-router-hash-link';
import { formatTime, getClientName } from '../../helpers/helpers';
import { getTrackerData } from '../../helpers/trackers/trackers';
@@ -125,6 +126,7 @@ class Logs extends Component {
const rule = row && row.original && row.original.rule;
const { filterId } = row.original;
const { filters } = this.props.filtering;
const isRewrite = reason && reason === 'Rewrite';
let filterName = '';
if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
@@ -161,14 +163,16 @@ class Logs extends Component {
const isRenderTooltip = reason === 'NotFilteredWhiteList';
return (
<div className="logs__row">
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
<ul className="list-unstyled">{liNodes}</ul>
{this.renderTooltip(isRenderTooltip, rule, filterName)}
</div>
);
}
return (
<div className="logs__row">
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
<span><Trans>empty_response_status</Trans></span>
{this.renderTooltip(isFiltered, rule, filterName)}
</div>
@@ -197,6 +201,7 @@ class Logs extends Component {
Cell: (row) => {
const { reason } = row.original;
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
const isRewrite = reason && reason === 'Rewrite';
const clientName = getClientName(dashboard.clients, row.value)
|| getClientName(dashboard.autoClients, row.value);
let client;
@@ -207,6 +212,21 @@ class Logs extends Component {
client = row.value;
}
if (isRewrite) {
return (
<Fragment>
<div className="logs__row">
{client}
</div>
<div className="logs__action">
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
<Trans>configure</Trans>
</Link>
</div>
</Fragment>
);
}
return (
<Fragment>
<div className="logs__row">
@@ -261,6 +281,10 @@ class Logs extends Component {
return {
className: 'green',
};
} else if (rowInfo.original.reason === 'Rewrite') {
return {
className: 'blue',
};
}
return {

View File

@@ -0,0 +1,89 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, required, domain, answer } from '../../../../helpers/form';
const Form = (props) => {
const {
t,
handleSubmit,
reset,
pristine,
submitting,
toggleRewritesModal,
processingAdd,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<Field
id="domain"
name="domain"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_domain')}
validate={[required, domain]}
/>
</div>
<div className="form__group">
<Field
id="answer"
name="answer"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_answer')}
validate={[required, answer]}
/>
</div>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting || processingAdd}
onClick={() => {
reset();
toggleRewritesModal();
}}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || pristine || processingAdd}
>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({
form: 'rewritesForm',
enableReinitialize: true,
}),
])(Form);

View File

@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import ReactModal from 'react-modal';
import Form from './Form';
const Modal = (props) => {
const {
isModalOpen,
handleSubmit,
toggleRewritesModal,
processingAdd,
processingDelete,
} = props;
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleRewritesModal()}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
<Trans>Add DNS rewrite</Trans>
</h4>
<button type="button" className="close" onClick={() => toggleRewritesModal()}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
onSubmit={handleSubmit}
toggleRewritesModal={toggleRewritesModal}
processingAdd={processingAdd}
processingDelete={processingDelete}
/>
</div>
</ReactModal>
);
};
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
};
export default withNamespaces()(Modal);

View File

@@ -0,0 +1,87 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { withNamespaces } from 'react-i18next';
class Table extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
columns = [
{
Header: 'Domain',
accessor: 'domain',
Cell: this.cellWrap,
},
{
Header: 'Answer',
accessor: 'answer',
Cell: this.cellWrap,
},
{
Header: this.props.t('actions_table_header'),
accessor: 'actions',
maxWidth: 100,
Cell: value => (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() =>
this.props.handleDelete({
answer: value.row.answer,
domain: value.row.domain,
})
}
title={this.props.t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
),
},
];
render() {
const {
t, list, processing, processingAdd, processingDelete,
} = this.props;
return (
<ReactTable
data={list || []}
columns={this.columns}
loading={processing || processingAdd || processingDelete}
className="-striped -highlight card-table-overflow"
showPagination={true}
defaultPageSize={10}
minRows={5}
previousText={t('previous_btn')}
nextText={t('next_btn')}
loadingText={t('loading_table_status')}
pageText={t('page_table_footer_text')}
ofText={t('of_table_footer_text')}
rowsText={t('rows_table_footer_text')}
noDataText={t('rewrite_not_found')}
/>
);
}
}
Table.propTypes = {
t: PropTypes.func.isRequired,
list: PropTypes.array.isRequired,
processing: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
handleDelete: PropTypes.func.isRequired,
};
export default withNamespaces()(Table);

View File

@@ -0,0 +1,83 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Table from './Table';
import Modal from './Modal';
import Card from '../../../ui/Card';
class Rewrites extends Component {
handleSubmit = (values) => {
this.props.addRewrite(values);
};
handleDelete = (values) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
this.props.deleteRewrite(values);
}
};
render() {
const {
t,
rewrites,
toggleRewritesModal,
} = this.props;
const {
list,
isModalOpen,
processing,
processingAdd,
processingDelete,
} = rewrites;
return (
<Card
id="rewrites"
title={t('dns_rewrites')}
subtitle={t('rewrite_desc')}
bodyType="card-body box-body--settings"
>
<Fragment>
<Table
list={list}
processing={processing}
processingAdd={processingAdd}
processingDelete={processingDelete}
handleDelete={this.handleDelete}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleRewritesModal()}
disabled={processingAdd}
>
<Trans>rewrite_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
toggleRewritesModal={toggleRewritesModal}
handleSubmit={this.handleSubmit}
processingAdd={processingAdd}
processingDelete={processingDelete}
/>
</Fragment>
</Card>
);
}
}
Rewrites.propTypes = {
t: PropTypes.func.isRequired,
getRewritesList: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
addRewrite: PropTypes.func.isRequired,
deleteRewrite: PropTypes.func.isRequired,
rewrites: PropTypes.object.isRequired,
};
export default withNamespaces()(Rewrites);

View File

@@ -4,12 +4,14 @@ import { withNamespaces } from 'react-i18next';
import Upstream from './Upstream';
import Access from './Access';
import Rewrites from './Rewrites';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
class Dns extends Component {
componentDidMount() {
this.props.getAccessList();
this.props.getRewritesList();
}
render() {
@@ -18,9 +20,14 @@ class Dns extends Component {
dashboard,
settings,
access,
rewrites,
setAccessList,
testUpstream,
setUpstream,
getRewritesList,
addRewrite,
deleteRewrite,
toggleRewritesModal,
} = this.props;
return (
@@ -39,6 +46,13 @@ class Dns extends Component {
testUpstream={testUpstream}
/>
<Access access={access} setAccessList={setAccessList} />
<Rewrites
rewrites={rewrites}
getRewritesList={getRewritesList}
addRewrite={addRewrite}
deleteRewrite={deleteRewrite}
toggleRewritesModal={toggleRewritesModal}
/>
</Fragment>
)}
</Fragment>
@@ -54,6 +68,11 @@ Dns.propTypes = {
getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired,
rewrites: PropTypes.object.isRequired,
getRewritesList: PropTypes.func.isRequired,
addRewrite: PropTypes.func.isRequired,
deleteRewrite: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import './Card.css';
const Card = props => (
<div className={props.type ? `card ${props.type}` : 'card'}>
<div className={props.type ? `card ${props.type}` : 'card'} id={props.id ? props.id : ''}>
{props.title &&
<div className="card-header with-border">
<div className="card-inner">
@@ -30,6 +30,7 @@ const Card = props => (
);
Card.propTypes = {
id: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
bodyType: PropTypes.string,

View File

@@ -15,3 +15,7 @@
.rt-tr-group .green {
background-color: #f1faf3;
}
.rt-tr-group .blue {
background-color: #ecf7ff;
}