+ client: split settings page into several pages
This commit is contained in:
parent
1d09ff0562
commit
cf53653cfa
|
@ -96,6 +96,10 @@
|
||||||
"no_servers_specified": "No servers specified",
|
"no_servers_specified": "No servers specified",
|
||||||
"no_settings": "No settings",
|
"no_settings": "No settings",
|
||||||
"general_settings": "General settings",
|
"general_settings": "General settings",
|
||||||
|
"dns_settings": "DNS settings",
|
||||||
|
"encryption_settings": "Encryption settings",
|
||||||
|
"dhcp_settings": "DHCP settings",
|
||||||
|
"clients_settings": "Clients settings",
|
||||||
"upstream_dns": "Upstream DNS servers",
|
"upstream_dns": "Upstream DNS servers",
|
||||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream.",
|
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream.",
|
||||||
"test_upstream_btn": "Test upstreams",
|
"test_upstream_btn": "Test upstreams",
|
||||||
|
|
|
@ -13,6 +13,12 @@ import Header from '../../containers/Header';
|
||||||
import Dashboard from '../../containers/Dashboard';
|
import Dashboard from '../../containers/Dashboard';
|
||||||
import Settings from '../../containers/Settings';
|
import Settings from '../../containers/Settings';
|
||||||
import Filters from '../../containers/Filters';
|
import Filters from '../../containers/Filters';
|
||||||
|
|
||||||
|
import Dns from '../../containers/Dns';
|
||||||
|
import Encryption from '../../containers/Encryption';
|
||||||
|
import Dhcp from '../../containers/Dhcp';
|
||||||
|
import Clients from '../../containers/Clients';
|
||||||
|
|
||||||
import Logs from '../../containers/Logs';
|
import Logs from '../../containers/Logs';
|
||||||
import SetupGuide from '../../containers/SetupGuide';
|
import SetupGuide from '../../containers/SetupGuide';
|
||||||
import Toasts from '../Toasts';
|
import Toasts from '../Toasts';
|
||||||
|
@ -41,7 +47,7 @@ class App extends Component {
|
||||||
|
|
||||||
handleUpdate = () => {
|
handleUpdate = () => {
|
||||||
this.props.getUpdate();
|
this.props.getUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
setLanguage = () => {
|
setLanguage = () => {
|
||||||
const { processing, language } = this.props.dashboard;
|
const { processing, language } = this.props.dashboard;
|
||||||
|
@ -55,19 +61,17 @@ class App extends Component {
|
||||||
i18n.on('languageChanged', (lang) => {
|
i18n.on('languageChanged', (lang) => {
|
||||||
this.props.changeLanguage(lang);
|
this.props.changeLanguage(lang);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, encryption } = this.props;
|
const { dashboard, encryption } = this.props;
|
||||||
const updateAvailable =
|
const updateAvailable =
|
||||||
!dashboard.processingVersions &&
|
!dashboard.processingVersions && dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||||
dashboard.isCoreRunning &&
|
|
||||||
dashboard.isUpdateAvailable;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HashRouter hashType='noslash'>
|
<HashRouter hashType="noslash">
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{updateAvailable &&
|
{updateAvailable && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<UpdateTopline
|
<UpdateTopline
|
||||||
url={dashboard.announcementUrl}
|
url={dashboard.announcementUrl}
|
||||||
|
@ -78,29 +82,33 @@ class App extends Component {
|
||||||
/>
|
/>
|
||||||
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
{!encryption.processing &&
|
{!encryption.processing && (
|
||||||
<EncryptionTopline notAfter={encryption.not_after} />
|
<EncryptionTopline notAfter={encryption.not_after} />
|
||||||
}
|
)}
|
||||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||||
<Route component={Header} />
|
<Route component={Header} />
|
||||||
<div className="container container--wrap">
|
<div className="container container--wrap">
|
||||||
{!dashboard.processing && !dashboard.isCoreRunning &&
|
{!dashboard.processing && !dashboard.isCoreRunning && (
|
||||||
<div className="row row-cards">
|
<div className="row row-cards">
|
||||||
<div className="col-lg-12">
|
<div className="col-lg-12">
|
||||||
<Status handleStatusChange={this.handleStatusChange} />
|
<Status handleStatusChange={this.handleStatusChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
{!dashboard.processing && dashboard.isCoreRunning &&
|
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Route path="/" exact component={Dashboard} />
|
<Route path="/" exact component={Dashboard} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Route path="/settings" component={Settings} />
|
||||||
|
<Route path="/dns" component={Dns} />
|
||||||
|
<Route path="/encryption" component={Encryption} />
|
||||||
|
<Route path="/dhcp" component={Dhcp} />
|
||||||
|
<Route path="/clients" component={Clients} />
|
||||||
<Route path="/filters" component={Filters} />
|
<Route path="/filters" component={Filters} />
|
||||||
<Route path="/logs" component={Logs} />
|
<Route path="/logs" component={Logs} />
|
||||||
<Route path="/guide" component={SetupGuide} />
|
<Route path="/guide" component={SetupGuide} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
<Toasts />
|
<Toasts />
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
stroke: #9aa0ac;
|
stroke: #9aa0ac;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active .nav-icon {
|
.nav-tabs .nav-link.active .nav-icon,
|
||||||
|
.nav-tabs .nav-item.show .nav-icon {
|
||||||
stroke: #66b574;
|
stroke: #66b574;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active:hover .nav-icon {
|
.nav-tabs .nav-link.active:hover .nav-icon,
|
||||||
|
.nav-tabs .nav-item.show:hover .nav-icon {
|
||||||
stroke: #58a273;
|
stroke: #58a273;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +89,12 @@
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-item.show .nav-link {
|
||||||
|
color: #66b574;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom-color: #66b574;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 992px) {
|
@media screen and (min-width: 992px) {
|
||||||
.header {
|
.header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
@ -5,6 +5,9 @@ import enhanceWithClickOutside from 'react-click-outside';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
|
|
||||||
|
import { SETTINGS_URLS } from '../../helpers/constants';
|
||||||
|
import Dropdown from '../ui/Dropdown';
|
||||||
|
|
||||||
class Menu extends Component {
|
class Menu extends Component {
|
||||||
handleClickOutside = () => {
|
handleClickOutside = () => {
|
||||||
this.props.closeMenu();
|
this.props.closeMenu();
|
||||||
|
@ -14,49 +17,86 @@ class Menu extends Component {
|
||||||
this.props.toggleMenuOpen();
|
this.props.toggleMenuOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getActiveClassForSettings = () => {
|
||||||
|
const { pathname } = this.props.location;
|
||||||
|
const isSettingsPage = SETTINGS_URLS.some(item => item === pathname);
|
||||||
|
|
||||||
|
return isSettingsPage ? 'active' : '';
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const menuClass = classnames({
|
const menuClass = classnames({
|
||||||
'col-lg-6 mobile-menu': true,
|
'col-lg-6 mobile-menu': true,
|
||||||
'mobile-menu--active': this.props.isMenuOpen,
|
'mobile-menu--active': this.props.isMenuOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dropdownControlClass = `nav-link ${this.getActiveClassForSettings()}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className={menuClass}>
|
<div className={menuClass}>
|
||||||
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
|
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
|
||||||
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
|
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
|
||||||
<div className="nav-link nav-link--back">
|
<div className="nav-link nav-link--back">
|
||||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg>
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref="#back" />
|
||||||
|
</svg>
|
||||||
<Trans>back</Trans>
|
<Trans>back</Trans>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<NavLink to="/" exact={true} className="nav-link">
|
<NavLink to="/" exact={true} className="nav-link">
|
||||||
<svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg>
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref="#dashboard" />
|
||||||
|
</svg>
|
||||||
<Trans>dashboard</Trans>
|
<Trans>dashboard</Trans>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
<Dropdown
|
||||||
<NavLink to="/settings" className="nav-link">
|
label={this.props.t('settings')}
|
||||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg>
|
baseClassName="dropdown nav-item"
|
||||||
<Trans>settings</Trans>
|
controlClassName={dropdownControlClass}
|
||||||
|
icon="settings"
|
||||||
|
>
|
||||||
|
<Fragment>
|
||||||
|
<NavLink to="/settings" className="dropdown-item">
|
||||||
|
<Trans>general_settings</Trans>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
<NavLink to="/dns" className="dropdown-item">
|
||||||
|
<Trans>dns_settings</Trans>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/encryption" className="dropdown-item">
|
||||||
|
<Trans>encryption_settings</Trans>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/clients" className="dropdown-item">
|
||||||
|
<Trans>clients_settings</Trans>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/dhcp" className="dropdown-item">
|
||||||
|
<Trans>dhcp_settings</Trans>
|
||||||
|
</NavLink>
|
||||||
|
</Fragment>
|
||||||
|
</Dropdown>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<NavLink to="/filters" className="nav-link">
|
<NavLink to="/filters" className="nav-link">
|
||||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg>
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref="#filters" />
|
||||||
|
</svg>
|
||||||
<Trans>filters</Trans>
|
<Trans>filters</Trans>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<NavLink to="/logs" className="nav-link">
|
<NavLink to="/logs" className="nav-link">
|
||||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg>
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref="#log" />
|
||||||
|
</svg>
|
||||||
<Trans>query_log</Trans>
|
<Trans>query_log</Trans>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<NavLink to="/guide" href="/guide" className="nav-link">
|
<NavLink to="/guide" className="nav-link">
|
||||||
<svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref="#setup" />
|
||||||
|
</svg>
|
||||||
<Trans>setup_guide</Trans>
|
<Trans>setup_guide</Trans>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
@ -71,6 +111,8 @@ Menu.propTypes = {
|
||||||
isMenuOpen: PropTypes.bool,
|
isMenuOpen: PropTypes.bool,
|
||||||
closeMenu: PropTypes.func,
|
closeMenu: PropTypes.func,
|
||||||
toggleMenuOpen: PropTypes.func,
|
toggleMenuOpen: PropTypes.func,
|
||||||
|
location: PropTypes.object,
|
||||||
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNamespaces()(enhanceWithClickOutside(Menu));
|
export default withNamespaces()(enhanceWithClickOutside(Menu));
|
||||||
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
|
import ReactTable from 'react-table';
|
||||||
|
|
||||||
|
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
||||||
|
import Card from '../../ui/Card';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
class ClientsTable extends Component {
|
||||||
|
handleFormAdd = (values) => {
|
||||||
|
this.props.addClient(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleFormUpdate = (values, name) => {
|
||||||
|
this.props.updateClient(values, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (values) => {
|
||||||
|
if (this.props.modalType === MODAL_TYPE.EDIT) {
|
||||||
|
this.handleFormUpdate(values, this.props.modalClientName);
|
||||||
|
} else {
|
||||||
|
this.handleFormAdd(values);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cellWrap = ({ value }) => (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text" title={value}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
getClient = (name, clients) => {
|
||||||
|
const client = clients.find(item => name === item.name);
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
|
||||||
|
|
||||||
|
return {
|
||||||
|
identifier,
|
||||||
|
use_global_settings: true,
|
||||||
|
...client,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
identifier: CLIENT_ID.IP,
|
||||||
|
use_global_settings: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
getStats = (ip, stats) => {
|
||||||
|
if (stats && stats.top_clients) {
|
||||||
|
return stats.top_clients[ip];
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDelete = (data) => {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
|
||||||
|
this.props.deleteClient(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
Header: this.props.t('table_client'),
|
||||||
|
accessor: 'ip',
|
||||||
|
Cell: (row) => {
|
||||||
|
if (row.original && row.original.mac) {
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text" title={row.original.mac}>
|
||||||
|
{row.original.mac} <em>(MAC)</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (row.value) {
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text" title={row.value}>
|
||||||
|
{row.value} <em>(IP)</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('table_name'),
|
||||||
|
accessor: 'name',
|
||||||
|
Cell: this.cellWrap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('settings'),
|
||||||
|
accessor: 'use_global_settings',
|
||||||
|
Cell: ({ value }) => {
|
||||||
|
const title = value ? (
|
||||||
|
<Trans>settings_global</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>settings_custom</Trans>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<div className="logs__text" title={title}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('table_statistics'),
|
||||||
|
accessor: 'statistics',
|
||||||
|
Cell: (row) => {
|
||||||
|
const clientIP = row.original.ip;
|
||||||
|
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
|
||||||
|
|
||||||
|
if (clientStats) {
|
||||||
|
return (
|
||||||
|
<div className="logs__row">
|
||||||
|
<div className="logs__text" title={clientStats}>
|
||||||
|
{clientStats}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '–';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: this.props.t('actions_table_header'),
|
||||||
|
accessor: 'actions',
|
||||||
|
maxWidth: 150,
|
||||||
|
Cell: (row) => {
|
||||||
|
const clientName = row.original.name;
|
||||||
|
const {
|
||||||
|
toggleClientModal, processingDeleting, processingUpdating, t,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||||
|
onClick={() =>
|
||||||
|
toggleClientModal({
|
||||||
|
type: MODAL_TYPE.EDIT,
|
||||||
|
name: clientName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={processingUpdating}
|
||||||
|
title={t('edit_table_action')}
|
||||||
|
>
|
||||||
|
<svg className="icons">
|
||||||
|
<use xlinkHref="#edit" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||||
|
onClick={() => this.handleDelete({ name: clientName })}
|
||||||
|
disabled={processingDeleting}
|
||||||
|
title={t('delete_table_action')}
|
||||||
|
>
|
||||||
|
<svg className="icons">
|
||||||
|
<use xlinkHref="#delete" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
t,
|
||||||
|
clients,
|
||||||
|
isModalOpen,
|
||||||
|
modalType,
|
||||||
|
modalClientName,
|
||||||
|
toggleClientModal,
|
||||||
|
processingAdding,
|
||||||
|
processingUpdating,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const currentClientData = this.getClient(modalClientName, clients);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={t('clients_title')}
|
||||||
|
subtitle={t('clients_desc')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
|
<Fragment>
|
||||||
|
<ReactTable
|
||||||
|
data={clients || []}
|
||||||
|
columns={this.columns}
|
||||||
|
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('clients_not_found')}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success btn-standard mt-3"
|
||||||
|
onClick={() => toggleClientModal(MODAL_TYPE.ADD)}
|
||||||
|
disabled={processingAdding}
|
||||||
|
>
|
||||||
|
<Trans>client_add</Trans>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isModalOpen={isModalOpen}
|
||||||
|
modalType={modalType}
|
||||||
|
toggleClientModal={toggleClientModal}
|
||||||
|
currentClientData={currentClientData}
|
||||||
|
handleSubmit={this.handleSubmit}
|
||||||
|
processingAdding={processingAdding}
|
||||||
|
processingUpdating={processingUpdating}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientsTable.propTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
clients: PropTypes.array.isRequired,
|
||||||
|
topStats: PropTypes.object.isRequired,
|
||||||
|
toggleClientModal: PropTypes.func.isRequired,
|
||||||
|
deleteClient: PropTypes.func.isRequired,
|
||||||
|
addClient: PropTypes.func.isRequired,
|
||||||
|
updateClient: PropTypes.func.isRequired,
|
||||||
|
isModalOpen: PropTypes.bool.isRequired,
|
||||||
|
modalType: PropTypes.string.isRequired,
|
||||||
|
modalClientName: PropTypes.string.isRequired,
|
||||||
|
processingAdding: PropTypes.bool.isRequired,
|
||||||
|
processingDeleting: PropTypes.bool.isRequired,
|
||||||
|
processingUpdating: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNamespaces()(ClientsTable);
|
|
@ -1,251 +1,50 @@
|
||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { withNamespaces } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
|
||||||
import ReactTable from 'react-table';
|
|
||||||
|
|
||||||
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
import ClientsTable from './ClientsTable';
|
||||||
import Card from '../../ui/Card';
|
import AutoClients from './AutoClients';
|
||||||
import Modal from './Modal';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
|
||||||
class Clients extends Component {
|
class Clients extends Component {
|
||||||
handleFormAdd = (values) => {
|
|
||||||
this.props.addClient(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFormUpdate = (values, name) => {
|
|
||||||
this.props.updateClient(values, name);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = (values) => {
|
|
||||||
if (this.props.modalType === MODAL_TYPE.EDIT) {
|
|
||||||
this.handleFormUpdate(values, this.props.modalClientName);
|
|
||||||
} else {
|
|
||||||
this.handleFormAdd(values);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cellWrap = ({ value }) => (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={value}>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
getClient = (name, clients) => {
|
|
||||||
const client = clients.find(item => name === item.name);
|
|
||||||
|
|
||||||
if (client) {
|
|
||||||
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
|
|
||||||
|
|
||||||
return {
|
|
||||||
identifier,
|
|
||||||
use_global_settings: true,
|
|
||||||
...client,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
identifier: CLIENT_ID.IP,
|
|
||||||
use_global_settings: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
getStats = (ip, stats) => {
|
|
||||||
if (stats && stats.top_clients) {
|
|
||||||
return stats.top_clients[ip];
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDelete = (data) => {
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
|
|
||||||
this.props.deleteClient(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columns = [
|
|
||||||
{
|
|
||||||
Header: this.props.t('table_client'),
|
|
||||||
accessor: 'ip',
|
|
||||||
Cell: (row) => {
|
|
||||||
if (row.original && row.original.mac) {
|
|
||||||
return (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={row.original.mac}>
|
|
||||||
{row.original.mac} <em>(MAC)</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (row.value) {
|
|
||||||
return (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<span className="logs__text" title={row.value}>
|
|
||||||
{row.value} <em>(IP)</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: this.props.t('table_name'),
|
|
||||||
accessor: 'name',
|
|
||||||
Cell: this.cellWrap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: this.props.t('settings'),
|
|
||||||
accessor: 'use_global_settings',
|
|
||||||
Cell: ({ value }) => {
|
|
||||||
const title = value ? (
|
|
||||||
<Trans>settings_global</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>settings_custom</Trans>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__row logs__row--overflow">
|
|
||||||
<div className="logs__text" title={title}>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: this.props.t('table_statistics'),
|
|
||||||
accessor: 'statistics',
|
|
||||||
Cell: (row) => {
|
|
||||||
const clientIP = row.original.ip;
|
|
||||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
|
|
||||||
|
|
||||||
if (clientStats) {
|
|
||||||
return (
|
|
||||||
<div className="logs__row">
|
|
||||||
<div className="logs__text" title={clientStats}>
|
|
||||||
{clientStats}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '–';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: this.props.t('actions_table_header'),
|
|
||||||
accessor: 'actions',
|
|
||||||
maxWidth: 150,
|
|
||||||
Cell: (row) => {
|
|
||||||
const clientName = row.original.name;
|
|
||||||
const {
|
|
||||||
toggleClientModal,
|
|
||||||
processingDeleting,
|
|
||||||
processingUpdating,
|
|
||||||
t,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__row logs__row--center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
|
||||||
onClick={() =>
|
|
||||||
toggleClientModal({
|
|
||||||
type: MODAL_TYPE.EDIT,
|
|
||||||
name: clientName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={processingUpdating}
|
|
||||||
title={t('edit_table_action')}
|
|
||||||
>
|
|
||||||
<svg className="icons">
|
|
||||||
<use xlinkHref="#edit" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
|
||||||
onClick={() => this.handleDelete({ name: clientName })}
|
|
||||||
disabled={processingDeleting}
|
|
||||||
title={t('delete_table_action')}
|
|
||||||
>
|
|
||||||
<svg className="icons">
|
|
||||||
<use xlinkHref="#delete" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { dashboard, clients, t } = this.props;
|
||||||
t,
|
|
||||||
clients,
|
|
||||||
isModalOpen,
|
|
||||||
modalType,
|
|
||||||
modalClientName,
|
|
||||||
toggleClientModal,
|
|
||||||
processingAdding,
|
|
||||||
processingUpdating,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const currentClientData = this.getClient(modalClientName, clients);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
|
||||||
title={t('clients_title')}
|
|
||||||
subtitle={t('clients_desc')}
|
|
||||||
bodyType="card-body box-body--settings"
|
|
||||||
>
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ReactTable
|
<PageTitle title={t('clients_settings')} />
|
||||||
data={clients || []}
|
{!dashboard.processingTopStats || (!dashboard.processingClients && <Loading />)}
|
||||||
columns={this.columns}
|
{!dashboard.processingTopStats && !dashboard.processingClients && (
|
||||||
className="-striped -highlight card-table-overflow"
|
<Fragment>
|
||||||
showPagination={true}
|
<ClientsTable
|
||||||
defaultPageSize={10}
|
clients={dashboard.clients}
|
||||||
minRows={5}
|
topStats={dashboard.topStats}
|
||||||
previousText={t('previous_btn')}
|
isModalOpen={clients.isModalOpen}
|
||||||
nextText={t('next_btn')}
|
modalClientName={clients.modalClientName}
|
||||||
loadingText={t('loading_table_status')}
|
modalType={clients.modalType}
|
||||||
pageText={t('page_table_footer_text')}
|
addClient={this.props.addClient}
|
||||||
ofText={t('of_table_footer_text')}
|
updateClient={this.props.updateClient}
|
||||||
rowsText={t('rows_table_footer_text')}
|
deleteClient={this.props.deleteClient}
|
||||||
noDataText={t('clients_not_found')}
|
toggleClientModal={this.props.toggleClientModal}
|
||||||
|
processingAdding={clients.processingAdding}
|
||||||
|
processingDeleting={clients.processingDeleting}
|
||||||
|
processingUpdating={clients.processingUpdating}
|
||||||
/>
|
/>
|
||||||
<button
|
<AutoClients
|
||||||
type="button"
|
autoClients={dashboard.autoClients}
|
||||||
className="btn btn-success btn-standard mt-3"
|
topStats={dashboard.topStats}
|
||||||
onClick={() => toggleClientModal(MODAL_TYPE.ADD)}
|
|
||||||
disabled={processingAdding}
|
|
||||||
>
|
|
||||||
<Trans>client_add</Trans>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
isModalOpen={isModalOpen}
|
|
||||||
modalType={modalType}
|
|
||||||
toggleClientModal={toggleClientModal}
|
|
||||||
currentClientData={currentClientData}
|
|
||||||
handleSubmit={this.handleSubmit}
|
|
||||||
processingAdding={processingAdding}
|
|
||||||
processingUpdating={processingUpdating}
|
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Card>
|
)}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Clients.propTypes = {
|
Clients.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
dashboard: PropTypes.object.isRequired,
|
||||||
clients: PropTypes.array.isRequired,
|
clients: PropTypes.array.isRequired,
|
||||||
topStats: PropTypes.object.isRequired,
|
topStats: PropTypes.object.isRequired,
|
||||||
toggleClientModal: PropTypes.func.isRequired,
|
toggleClientModal: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -9,8 +9,15 @@ import Leases from './Leases';
|
||||||
import StaticLeases from './StaticLeases/index';
|
import StaticLeases from './StaticLeases/index';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import Accordion from '../../ui/Accordion';
|
import Accordion from '../../ui/Accordion';
|
||||||
|
import PageTitle from '../../ui/PageTitle';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
|
||||||
class Dhcp extends Component {
|
class Dhcp extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.getDhcpStatus();
|
||||||
|
this.props.getDhcpInterfaces();
|
||||||
|
}
|
||||||
|
|
||||||
handleFormSubmit = (values) => {
|
handleFormSubmit = (values) => {
|
||||||
if (values.interface_name) {
|
if (values.interface_name) {
|
||||||
this.props.setDhcpConfig(values);
|
this.props.setDhcpConfig(values);
|
||||||
|
@ -19,7 +26,7 @@ class Dhcp extends Component {
|
||||||
|
|
||||||
handleToggle = (config) => {
|
handleToggle = (config) => {
|
||||||
this.props.toggleDhcp(config);
|
this.props.toggleDhcp(config);
|
||||||
}
|
};
|
||||||
|
|
||||||
getToggleDhcpButton = () => {
|
getToggleDhcpButton = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -54,17 +61,13 @@ class Dhcp extends Component {
|
||||||
className="btn btn-standard mr-2 btn-success"
|
className="btn btn-standard mr-2 btn-success"
|
||||||
onClick={() => this.handleToggle(config)}
|
onClick={() => this.handleToggle(config)}
|
||||||
disabled={
|
disabled={
|
||||||
!filledConfig
|
!filledConfig || !check || otherDhcpFound || processingDhcp || processingConfig
|
||||||
|| !check
|
|
||||||
|| otherDhcpFound
|
|
||||||
|| processingDhcp
|
|
||||||
|| processingConfig
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trans>dhcp_enable</Trans>
|
<Trans>dhcp_enable</Trans>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
getActiveDhcpMessage = (t, check) => {
|
getActiveDhcpMessage = (t, check) => {
|
||||||
const { found } = check.otherServer;
|
const { found } = check.otherServer;
|
||||||
|
@ -95,7 +98,7 @@ class Dhcp extends Component {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
getDhcpWarning = (check) => {
|
getDhcpWarning = (check) => {
|
||||||
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
|
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
|
||||||
|
@ -107,7 +110,7 @@ class Dhcp extends Component {
|
||||||
<Trans>dhcp_warning</Trans>
|
<Trans>dhcp_warning</Trans>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
getStaticIpWarning = (t, check, interfaceName) => {
|
getStaticIpWarning = (t, check, interfaceName) => {
|
||||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
||||||
|
@ -125,17 +128,15 @@ class Dhcp extends Component {
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
check.staticIP.static === DHCP_STATUS_RESPONSE.NO &&
|
||||||
&& check.staticIP.ip
|
check.staticIP.ip &&
|
||||||
&& interfaceName
|
interfaceName
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="text-secondary mb-2">
|
<div className="text-secondary mb-2">
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[<strong key="0">example</strong>]}
|
||||||
<strong key="0">example</strong>,
|
|
||||||
]}
|
|
||||||
values={{
|
values={{
|
||||||
interfaceName,
|
interfaceName,
|
||||||
ipAddress: check.staticIP.ip,
|
ipAddress: check.staticIP.ip,
|
||||||
|
@ -150,7 +151,7 @@ class Dhcp extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, dhcp } = this.props;
|
const { t, dhcp } = this.props;
|
||||||
|
@ -158,17 +159,20 @@ class Dhcp extends Component {
|
||||||
'btn btn-primary btn-standard': true,
|
'btn btn-primary btn-standard': true,
|
||||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||||
});
|
});
|
||||||
const {
|
const { enabled, interface_name, ...values } = dhcp.config;
|
||||||
enabled,
|
|
||||||
interface_name,
|
|
||||||
...values
|
|
||||||
} = dhcp.config;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings">
|
<PageTitle title={t('dhcp_settings')} />
|
||||||
|
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
||||||
|
{!dhcp.processing && !dhcp.processingInterfaces && (
|
||||||
|
<Fragment>
|
||||||
|
<Card
|
||||||
|
title={t('dhcp_title')}
|
||||||
|
subtitle={t('dhcp_description')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
<div className="dhcp">
|
<div className="dhcp">
|
||||||
{!dhcp.processing &&
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Form
|
<Form
|
||||||
onSubmit={this.handleFormSubmit}
|
onSubmit={this.handleFormSubmit}
|
||||||
|
@ -191,35 +195,38 @@ class Dhcp extends Component {
|
||||||
this.props.findActiveDhcp(interface_name)
|
this.props.findActiveDhcp(interface_name)
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
enabled
|
enabled || !interface_name || dhcp.processingConfig
|
||||||
|| !interface_name
|
|
||||||
|| dhcp.processingConfig
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trans>check_dhcp_servers</Trans>
|
<Trans>check_dhcp_servers</Trans>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{!enabled && dhcp.check &&
|
{!enabled && dhcp.check && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||||
{this.getDhcpWarning(dhcp.check)}
|
{this.getDhcpWarning(dhcp.check)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{!dhcp.processing && dhcp.config.enabled &&
|
{dhcp.config.enabled && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Card title={ t('dhcp_leases') } bodyType="card-body box-body--settings">
|
<Card
|
||||||
|
title={t('dhcp_leases')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<Leases leases={dhcp.leases} />
|
<Leases leases={dhcp.leases} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title={ t('dhcp_static_leases') } bodyType="card-body box-body--settings">
|
<Card
|
||||||
|
title={t('dhcp_static_leases')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<StaticLeases
|
<StaticLeases
|
||||||
|
@ -244,7 +251,9 @@ class Dhcp extends Component {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -256,10 +265,10 @@ Dhcp.propTypes = {
|
||||||
getDhcpStatus: PropTypes.func,
|
getDhcpStatus: PropTypes.func,
|
||||||
setDhcpConfig: PropTypes.func,
|
setDhcpConfig: PropTypes.func,
|
||||||
findActiveDhcp: PropTypes.func,
|
findActiveDhcp: PropTypes.func,
|
||||||
handleSubmit: PropTypes.func,
|
|
||||||
addStaticLease: PropTypes.func,
|
addStaticLease: PropTypes.func,
|
||||||
removeStaticLease: PropTypes.func,
|
removeStaticLease: PropTypes.func,
|
||||||
toggleLeaseModal: PropTypes.func,
|
toggleLeaseModal: PropTypes.func,
|
||||||
|
getDhcpInterfaces: PropTypes.func,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,12 @@ const Examples = props => (
|
||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[
|
||||||
<a href="https://kb.adguard.com/general/dns-providers" target="_blank" rel="noopener noreferrer" key="0">
|
<a
|
||||||
|
href="https://kb.adguard.com/general/dns-providers"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
DNS providers
|
DNS providers
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
|
@ -25,7 +30,12 @@ const Examples = props => (
|
||||||
<span>
|
<span>
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[
|
||||||
<a href="https://en.wikipedia.org/wiki/DNS_over_TLS" target="_blank" rel="noopener noreferrer" key="0">
|
<a
|
||||||
|
href="https://en.wikipedia.org/wiki/DNS_over_TLS"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
DNS-over-TLS
|
DNS-over-TLS
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
|
@ -39,7 +49,12 @@ const Examples = props => (
|
||||||
<span>
|
<span>
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[
|
||||||
<a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS" target="_blank" rel="noopener noreferrer" key="0">
|
<a
|
||||||
|
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
DNS-over-HTTPS
|
DNS-over-HTTPS
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
|
@ -56,13 +71,28 @@ const Examples = props => (
|
||||||
<span>
|
<span>
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[
|
||||||
<a href="https://dnscrypt.info/stamps/" target="_blank" rel="noopener noreferrer" key="0">
|
<a
|
||||||
|
href="https://dnscrypt.info/stamps/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
DNS Stamps
|
DNS Stamps
|
||||||
</a>,
|
</a>,
|
||||||
<a href="https://dnscrypt.info/" target="_blank" rel="noopener noreferrer" key="1">
|
<a
|
||||||
|
href="https://dnscrypt.info/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
DNSCrypt
|
DNSCrypt
|
||||||
</a>,
|
</a>,
|
||||||
<a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS" target="_blank" rel="noopener noreferrer" key="2">
|
<a
|
||||||
|
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
DNS-over-HTTPS
|
DNS-over-HTTPS
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
|
@ -76,7 +106,12 @@ const Examples = props => (
|
||||||
<span>
|
<span>
|
||||||
<Trans
|
<Trans
|
||||||
components={[
|
components={[
|
||||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains" target="_blank" rel="noopener noreferrer" key="0">
|
<a
|
||||||
|
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
Link
|
Link
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
|
@ -6,7 +6,7 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import { renderSelectField } from '../../../helpers/form';
|
import { renderSelectField } from '../../../../helpers/form';
|
||||||
import Examples from './Examples';
|
import Examples from './Examples';
|
||||||
|
|
||||||
let Form = (props) => {
|
let Form = (props) => {
|
||||||
|
@ -84,11 +84,13 @@ let Form = (props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={testButtonClass}
|
className={testButtonClass}
|
||||||
onClick={() => testUpstream({
|
onClick={() =>
|
||||||
|
testUpstream({
|
||||||
upstream_dns: upstreamDns,
|
upstream_dns: upstreamDns,
|
||||||
bootstrap_dns: bootstrapDns,
|
bootstrap_dns: bootstrapDns,
|
||||||
all_servers: allServers,
|
all_servers: allServers,
|
||||||
})}
|
})
|
||||||
|
}
|
||||||
disabled={!upstreamDns || processingTestUpstream}
|
disabled={!upstreamDns || processingTestUpstream}
|
||||||
>
|
>
|
||||||
<Trans>test_upstream_btn</Trans>
|
<Trans>test_upstream_btn</Trans>
|
||||||
|
@ -97,10 +99,7 @@ let Form = (props) => {
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standard"
|
className="btn btn-success btn-standard"
|
||||||
disabled={
|
disabled={
|
||||||
submitting
|
submitting || invalid || processingSetUpstream || processingTestUpstream
|
||||||
|| invalid
|
|
||||||
|| processingSetUpstream
|
|
||||||
|| processingTestUpstream
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trans>apply_btn</Trans>
|
<Trans>apply_btn</Trans>
|
||||||
|
@ -140,5 +139,7 @@ Form = connect((state) => {
|
||||||
|
|
||||||
export default flow([
|
export default flow([
|
||||||
withNamespaces(),
|
withNamespaces(),
|
||||||
reduxForm({ form: 'upstreamForm' }),
|
reduxForm({
|
||||||
|
form: 'upstreamForm',
|
||||||
|
}),
|
||||||
])(Form);
|
])(Form);
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import { withNamespaces } from 'react-i18next';
|
import { withNamespaces } from 'react-i18next';
|
||||||
|
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../../ui/Card';
|
||||||
|
|
||||||
class Upstream extends Component {
|
class Upstream extends Component {
|
||||||
handleSubmit = (values) => {
|
handleSubmit = (values) => {
|
||||||
|
@ -12,7 +12,7 @@ class Upstream extends Component {
|
||||||
|
|
||||||
handleTest = (values) => {
|
handleTest = (values) => {
|
||||||
this.props.testUpstream(values);
|
this.props.testUpstream(values);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { withNamespaces } from 'react-i18next';
|
||||||
|
|
||||||
|
import Upstream from './Upstream';
|
||||||
|
import PageTitle from '../../ui/PageTitle';
|
||||||
|
|
||||||
|
const Dns = (props) => {
|
||||||
|
const { dashboard, settings, t } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<PageTitle title={t('dns_settings')} />
|
||||||
|
<Upstream
|
||||||
|
upstreamDns={dashboard.upstreamDns}
|
||||||
|
bootstrapDns={dashboard.bootstrapDns}
|
||||||
|
allServers={dashboard.allServers}
|
||||||
|
setUpstream={props.setUpstream}
|
||||||
|
testUpstream={props.testUpstream}
|
||||||
|
processingTestUpstream={settings.processingTestUpstream}
|
||||||
|
processingSetUpstream={settings.processingSetUpstream}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dns.propTypes = {
|
||||||
|
dashboard: PropTypes.object.isRequired,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
setUpstream: PropTypes.func.isRequired,
|
||||||
|
testUpstream: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNamespaces()(Dns);
|
|
@ -66,14 +66,15 @@ let Form = (props) => {
|
||||||
setTlsConfig,
|
setTlsConfig,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isSavingDisabled = invalid
|
const isSavingDisabled =
|
||||||
|| submitting
|
invalid ||
|
||||||
|| processingConfig
|
submitting ||
|
||||||
|| processingValidate
|
processingConfig ||
|
||||||
|| (isEnabled && (!privateKey || !certificateChain))
|
processingValidate ||
|
||||||
|| (privateKey && !valid_key)
|
(isEnabled && (!privateKey || !certificateChain)) ||
|
||||||
|| (certificateChain && !valid_cert)
|
(privateKey && !valid_key) ||
|
||||||
|| (privateKey && certificateChain && !valid_pair);
|
(certificateChain && !valid_cert) ||
|
||||||
|
(privateKey && certificateChain && !valid_pair);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
@ -180,13 +181,20 @@ let Form = (props) => {
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
<label className="form__label form__label--bold" htmlFor="certificate_chain">
|
<label
|
||||||
|
className="form__label form__label--bold"
|
||||||
|
htmlFor="certificate_chain"
|
||||||
|
>
|
||||||
<Trans>encryption_certificates</Trans>
|
<Trans>encryption_certificates</Trans>
|
||||||
</label>
|
</label>
|
||||||
<div className="form__desc form__desc--top">
|
<div className="form__desc form__desc--top">
|
||||||
<Trans
|
<Trans
|
||||||
values={{ link: 'letsencrypt.org' }}
|
values={{ link: 'letsencrypt.org' }}
|
||||||
components={[<a href="https://letsencrypt.org/" key="0">link</a>]}
|
components={[
|
||||||
|
<a href="https://letsencrypt.org/" key="0">
|
||||||
|
link
|
||||||
|
</a>,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
encryption_certificates_desc
|
encryption_certificates_desc
|
||||||
</Trans>
|
</Trans>
|
||||||
|
@ -202,49 +210,52 @@ let Form = (props) => {
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__status">
|
<div className="form__status">
|
||||||
{certificateChain &&
|
{certificateChain && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="form__label form__label--bold">
|
<div className="form__label form__label--bold">
|
||||||
<Trans>encryption_status</Trans>:
|
<Trans>encryption_status</Trans>:
|
||||||
</div>
|
</div>
|
||||||
<ul className="encryption__list">
|
<ul className="encryption__list">
|
||||||
<li className={valid_chain ? 'text-success' : 'text-danger'}>
|
<li
|
||||||
{valid_chain ?
|
className={valid_chain ? 'text-success' : 'text-danger'}
|
||||||
|
>
|
||||||
|
{valid_chain ? (
|
||||||
<Trans>encryption_chain_valid</Trans>
|
<Trans>encryption_chain_valid</Trans>
|
||||||
: <Trans>encryption_chain_invalid</Trans>
|
) : (
|
||||||
}
|
<Trans>encryption_chain_invalid</Trans>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
{valid_cert &&
|
{valid_cert && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{subject &&
|
{subject && (
|
||||||
<li>
|
<li>
|
||||||
<Trans>encryption_subject</Trans>:
|
<Trans>encryption_subject</Trans>:
|
||||||
{subject}
|
{subject}
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
{issuer &&
|
{issuer && (
|
||||||
<li>
|
<li>
|
||||||
<Trans>encryption_issuer</Trans>:
|
<Trans>encryption_issuer</Trans>:
|
||||||
{issuer}
|
{issuer}
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
{not_after && not_after !== EMPTY_DATE &&
|
{not_after && not_after !== EMPTY_DATE && (
|
||||||
<li>
|
<li>
|
||||||
<Trans>encryption_expire</Trans>:
|
<Trans>encryption_expire</Trans>:
|
||||||
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
|
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
{dns_names &&
|
{dns_names && (
|
||||||
<li>
|
<li>
|
||||||
<Trans>encryption_hostnames</Trans>:
|
<Trans>encryption_hostnames</Trans>:
|
||||||
{dns_names}
|
{dns_names}
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -266,35 +277,34 @@ let Form = (props) => {
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__status">
|
<div className="form__status">
|
||||||
{privateKey &&
|
{privateKey && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="form__label form__label--bold">
|
<div className="form__label form__label--bold">
|
||||||
<Trans>encryption_status</Trans>:
|
<Trans>encryption_status</Trans>:
|
||||||
</div>
|
</div>
|
||||||
<ul className="encryption__list">
|
<ul className="encryption__list">
|
||||||
<li className={valid_key ? 'text-success' : 'text-danger'}>
|
<li className={valid_key ? 'text-success' : 'text-danger'}>
|
||||||
{valid_key ?
|
{valid_key ? (
|
||||||
<Trans values={{ type: key_type }}>
|
<Trans values={{ type: key_type }}>
|
||||||
encryption_key_valid
|
encryption_key_valid
|
||||||
</Trans>
|
</Trans>
|
||||||
: <Trans values={{ type: key_type }}>
|
) : (
|
||||||
|
<Trans values={{ type: key_type }}>
|
||||||
encryption_key_invalid
|
encryption_key_invalid
|
||||||
</Trans>
|
</Trans>
|
||||||
}
|
)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{warning_validation &&
|
{warning_validation && (
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<p className="text-danger">
|
<p className="text-danger">{warning_validation}</p>
|
||||||
{warning_validation}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="btn-list mt-2">
|
<div className="btn-list mt-2">
|
||||||
|
|
|
@ -6,11 +6,17 @@ import debounce from 'lodash/debounce';
|
||||||
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
|
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
|
import PageTitle from '../../ui/PageTitle';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
|
||||||
class Encryption extends Component {
|
class Encryption extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.encryption.enabled) {
|
const { getTlsStatus, validateTlsConfig, encryption } = this.props;
|
||||||
this.props.validateTlsConfig(this.props.encryption);
|
|
||||||
|
getTlsStatus();
|
||||||
|
|
||||||
|
if (encryption.enabled) {
|
||||||
|
validateTlsConfig(encryption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +42,9 @@ class Encryption extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="encryption">
|
<div className="encryption">
|
||||||
{encryption &&
|
<PageTitle title={t('encryption_settings')} />
|
||||||
|
{encryption.processing && <Loading />}
|
||||||
|
{!encryption.processing && (
|
||||||
<Card
|
<Card
|
||||||
title={t('encryption_title')}
|
title={t('encryption_title')}
|
||||||
subtitle={t('encryption_desc')}
|
subtitle={t('encryption_desc')}
|
||||||
|
@ -58,7 +66,7 @@ class Encryption extends Component {
|
||||||
{...this.props.encryption}
|
{...this.props.encryption}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,6 @@ import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withNamespaces, Trans } from 'react-i18next';
|
import { withNamespaces, Trans } from 'react-i18next';
|
||||||
|
|
||||||
import Upstream from './Upstream';
|
|
||||||
import Dhcp from './Dhcp';
|
|
||||||
import Encryption from './Encryption';
|
|
||||||
import Clients from './Clients';
|
|
||||||
import AutoClients from './Clients/AutoClients';
|
|
||||||
import Access from './Access';
|
|
||||||
import Checkbox from '../ui/Checkbox';
|
import Checkbox from '../ui/Checkbox';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
|
@ -41,10 +35,6 @@ class Settings extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.initSettings(this.settings);
|
this.props.initSettings(this.settings);
|
||||||
this.props.getDhcpStatus();
|
|
||||||
this.props.getDhcpInterfaces();
|
|
||||||
this.props.getTlsStatus();
|
|
||||||
this.props.getAccessList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSettings = (settings) => {
|
renderSettings = (settings) => {
|
||||||
|
@ -69,72 +59,20 @@ class Settings extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { settings, t } = this.props;
|
||||||
settings, dashboard, clients, access, t,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<PageTitle title={t('settings')} />
|
<PageTitle title={t('general_settings')} />
|
||||||
{settings.processing && <Loading />}
|
{settings.processing && <Loading />}
|
||||||
{!settings.processing && (
|
{!settings.processing && (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<Card
|
<Card bodyType="card-body box-body--settings">
|
||||||
title={t('general_settings')}
|
|
||||||
bodyType="card-body box-body--settings"
|
|
||||||
>
|
|
||||||
<div className="form">
|
<div className="form">
|
||||||
{this.renderSettings(settings.settingsList)}
|
{this.renderSettings(settings.settingsList)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Upstream
|
|
||||||
upstreamDns={dashboard.upstreamDns}
|
|
||||||
bootstrapDns={dashboard.bootstrapDns}
|
|
||||||
allServers={dashboard.allServers}
|
|
||||||
setUpstream={this.props.setUpstream}
|
|
||||||
testUpstream={this.props.testUpstream}
|
|
||||||
processingTestUpstream={settings.processingTestUpstream}
|
|
||||||
processingSetUpstream={settings.processingSetUpstream}
|
|
||||||
/>
|
|
||||||
{!dashboard.processingTopStats && !dashboard.processingClients && (
|
|
||||||
<Fragment>
|
|
||||||
<Clients
|
|
||||||
clients={dashboard.clients}
|
|
||||||
topStats={dashboard.topStats}
|
|
||||||
isModalOpen={clients.isModalOpen}
|
|
||||||
modalClientName={clients.modalClientName}
|
|
||||||
modalType={clients.modalType}
|
|
||||||
addClient={this.props.addClient}
|
|
||||||
updateClient={this.props.updateClient}
|
|
||||||
deleteClient={this.props.deleteClient}
|
|
||||||
toggleClientModal={this.props.toggleClientModal}
|
|
||||||
processingAdding={clients.processingAdding}
|
|
||||||
processingDeleting={clients.processingDeleting}
|
|
||||||
processingUpdating={clients.processingUpdating}
|
|
||||||
/>
|
|
||||||
<AutoClients
|
|
||||||
autoClients={dashboard.autoClients}
|
|
||||||
topStats={dashboard.topStats}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
<Access access={access} setAccessList={this.props.setAccessList} />
|
|
||||||
<Encryption
|
|
||||||
encryption={this.props.encryption}
|
|
||||||
setTlsConfig={this.props.setTlsConfig}
|
|
||||||
validateTlsConfig={this.props.validateTlsConfig}
|
|
||||||
/>
|
|
||||||
<Dhcp
|
|
||||||
dhcp={this.props.dhcp}
|
|
||||||
toggleDhcp={this.props.toggleDhcp}
|
|
||||||
getDhcpStatus={this.props.getDhcpStatus}
|
|
||||||
findActiveDhcp={this.props.findActiveDhcp}
|
|
||||||
setDhcpConfig={this.props.setDhcpConfig}
|
|
||||||
addStaticLease={this.props.addStaticLease}
|
|
||||||
removeStaticLease={this.props.removeStaticLease}
|
|
||||||
toggleLeaseModal={this.props.toggleLeaseModal}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,8 +87,6 @@ Settings.propTypes = {
|
||||||
settings: PropTypes.object,
|
settings: PropTypes.object,
|
||||||
settingsList: PropTypes.object,
|
settingsList: PropTypes.object,
|
||||||
toggleSetting: PropTypes.func,
|
toggleSetting: PropTypes.func,
|
||||||
handleUpstreamChange: PropTypes.func,
|
|
||||||
setUpstream: PropTypes.func,
|
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.dropdown-item.active,
|
||||||
|
.dropdown-item:active {
|
||||||
|
background-color: #66b574;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
cursor: default;
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { withNamespaces } from 'react-i18next';
|
||||||
|
import enhanceWithClickOutside from 'react-click-outside';
|
||||||
|
|
||||||
|
import './Dropdown.css';
|
||||||
|
|
||||||
|
class Dropdown extends Component {
|
||||||
|
state = {
|
||||||
|
isOpen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleDropdown = () => {
|
||||||
|
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
|
||||||
|
};
|
||||||
|
|
||||||
|
hideDropdown = () => {
|
||||||
|
this.setState({ isOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClickOutside = () => {
|
||||||
|
if (this.state.isOpen) {
|
||||||
|
this.hideDropdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
controlClassName,
|
||||||
|
menuClassName,
|
||||||
|
baseClassName,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { isOpen } = this.state;
|
||||||
|
|
||||||
|
const dropdownClass = classnames({
|
||||||
|
[baseClassName]: true,
|
||||||
|
show: isOpen,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdownMenuClass = classnames({
|
||||||
|
[menuClassName]: true,
|
||||||
|
show: isOpen,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ariaSettings = isOpen ? 'true' : 'false';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={dropdownClass}>
|
||||||
|
<a
|
||||||
|
className={controlClassName}
|
||||||
|
aria-expanded={ariaSettings}
|
||||||
|
onClick={this.toggleDropdown}
|
||||||
|
>
|
||||||
|
{icon && (
|
||||||
|
<svg className="nav-icon">
|
||||||
|
<use xlinkHref={`#${icon}`} />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
<div className={dropdownMenuClass} onClick={this.hideDropdown}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dropdown.defaultProps = {
|
||||||
|
baseClassName: 'dropdown',
|
||||||
|
menuClassName: 'dropdown-menu dropdown-menu-arrow',
|
||||||
|
controlClassName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
Dropdown.propTypes = {
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
controlClassName: PropTypes.node.isRequired,
|
||||||
|
menuClassName: PropTypes.string.isRequired,
|
||||||
|
baseClassName: PropTypes.string.isRequired,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNamespaces()(enhanceWithClickOutside(Dropdown));
|
|
@ -31,6 +31,30 @@ const Icons = () => (
|
||||||
<symbol id="delete" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
<symbol id="delete" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
<path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/>
|
<path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="back" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="dashboard" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="filters" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="log" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="setup" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<symbol id="settings" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||||
|
<circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { addErrorToast } from '../actions';
|
||||||
|
import { addClient, updateClient, deleteClient, toggleClientModal } from '../actions/clients';
|
||||||
|
import Clients from '../components/Settings/Clients';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const { dashboard, clients } = state;
|
||||||
|
const props = {
|
||||||
|
dashboard,
|
||||||
|
clients,
|
||||||
|
};
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
addErrorToast,
|
||||||
|
addClient,
|
||||||
|
updateClient,
|
||||||
|
deleteClient,
|
||||||
|
toggleClientModal,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Clients);
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
addErrorToast,
|
||||||
|
toggleDhcp,
|
||||||
|
getDhcpStatus,
|
||||||
|
getDhcpInterfaces,
|
||||||
|
setDhcpConfig,
|
||||||
|
findActiveDhcp,
|
||||||
|
toggleLeaseModal,
|
||||||
|
addStaticLease,
|
||||||
|
removeStaticLease,
|
||||||
|
} from '../actions';
|
||||||
|
import Dhcp from '../components/Settings/Dhcp';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const { dhcp } = state;
|
||||||
|
const props = {
|
||||||
|
dhcp,
|
||||||
|
};
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
addErrorToast,
|
||||||
|
toggleDhcp,
|
||||||
|
getDhcpStatus,
|
||||||
|
getDhcpInterfaces,
|
||||||
|
setDhcpConfig,
|
||||||
|
findActiveDhcp,
|
||||||
|
toggleLeaseModal,
|
||||||
|
addStaticLease,
|
||||||
|
removeStaticLease,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Dhcp);
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { handleUpstreamChange, setUpstream, testUpstream, addErrorToast } from '../actions';
|
||||||
|
import Dns from '../components/Settings/Dns';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const { dashboard, settings } = state;
|
||||||
|
const props = {
|
||||||
|
dashboard,
|
||||||
|
settings,
|
||||||
|
};
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
handleUpstreamChange,
|
||||||
|
setUpstream,
|
||||||
|
testUpstream,
|
||||||
|
addErrorToast,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Dns);
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { addErrorToast } from '../actions';
|
||||||
|
import { getTlsStatus, setTlsConfig, validateTlsConfig } from '../actions/encryption';
|
||||||
|
import Encryption from '../components/Settings/Encryption';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const { encryption } = state;
|
||||||
|
const props = {
|
||||||
|
encryption,
|
||||||
|
};
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
addErrorToast,
|
||||||
|
getTlsStatus,
|
||||||
|
setTlsConfig,
|
||||||
|
validateTlsConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Encryption);
|
|
@ -177,3 +177,5 @@ export const CLIENT_ID = {
|
||||||
MAC: 'mac',
|
MAC: 'mac',
|
||||||
IP: 'ip',
|
IP: 'ip',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SETTINGS_URLS = ['/encryption', '/dhcp', '/dns', '/settings', '/clients'];
|
||||||
|
|
Loading…
Reference in New Issue