Pull request #730: + client: Add Hot Module Replacement
Merge in DNS/adguard-home from feature/hmr to master
Squashed commit of the following:
commit 952ed1955c2a7a32446d99489f137f02eb47c99e
Merge: 83484931 de92c852
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Thu Aug 13 11:02:10 2020 +0300
Merge branch 'master' into feature/hmr
commit 8348493105d7d63d8b0836a5c272df2b17a6b142
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Wed Aug 5 15:07:31 2020 +0300
Remove empty prop types, remove Services empty container
commit b2fe4a30b79d91e482318ee5deea8e49c7038f7e
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Wed Aug 5 13:56:35 2020 +0300
Move constants
commit f8be4c18c35193ad77bf5e25f311ad834c1d6870
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Wed Aug 5 13:19:02 2020 +0300
Fix Setup bug, update webpack.dev
commit 1d9cc4ddf8af2c979eb707a7f0fc06744eec186c
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Wed Aug 5 12:10:38 2020 +0300
Review changes
commit a1edb21358def21ed1808b081ffc2f0b6755e3da
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Wed Aug 5 11:46:58 2020 +0300
Remove lazy loading, fix updated components
commit 0aa2cf55f8d4206ac9e2f99fc1b990ed8a9c7825
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Aug 4 20:32:19 2020 +0300
Refactor App component, add lazy loading
commit 3c2ba4772a91ff7b06641dba6c6bf3fdcd2fdf7f
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Aug 4 17:12:41 2020 +0300
Simplify App hot loading boilerplate, setup lazy loading, update Header
commit 8df3221f315372b066f2ac0c9a1687f1677b8415
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Aug 4 15:16:06 2020 +0300
+ client: Add Hot Module Replacement
@@ -1,30 +1,16 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { HashRouter, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import LoadingBar from 'react-redux-loading-bar';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
|
||||
import 'react-table/react-table.css';
|
||||
import '../ui/Tabler.css';
|
||||
import '../ui/ReactTable.css';
|
||||
import './index.css';
|
||||
|
||||
import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import CustomRules from '../../containers/CustomRules';
|
||||
import DnsBlocklist from '../../containers/DnsBlocklist';
|
||||
import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||
import DnsRewrites from '../../containers/DnsRewrites';
|
||||
|
||||
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 SetupGuide from '../../containers/SetupGuide';
|
||||
import propTypes from 'prop-types';
|
||||
import Toasts from '../Toasts';
|
||||
import Footer from '../ui/Footer';
|
||||
import Status from '../ui/Status';
|
||||
@@ -35,31 +21,107 @@ import Icons from '../ui/Icons';
|
||||
import i18n from '../../i18n';
|
||||
import Loading from '../ui/Loading';
|
||||
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
|
||||
import Services from '../Filters/Services';
|
||||
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
|
||||
import Header from '../Header';
|
||||
import { changeLanguage, getDnsStatus } from '../../actions';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsStatus();
|
||||
}
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Logs from '../../containers/Logs';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
import Dhcp from '../../containers/Dhcp';
|
||||
import Clients from '../../containers/Clients';
|
||||
import DnsBlocklist from '../../containers/DnsBlocklist';
|
||||
import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||
import DnsRewrites from '../../containers/DnsRewrites';
|
||||
import CustomRules from '../../containers/CustomRules';
|
||||
import Services from '../Filters/Services';
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.dashboard.language !== prevProps.dashboard.language) {
|
||||
this.setLanguage();
|
||||
}
|
||||
}
|
||||
const ROUTES = [
|
||||
{
|
||||
path: MENU_URLS.root,
|
||||
component: Dashboard,
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: [`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs],
|
||||
component: Logs,
|
||||
},
|
||||
{
|
||||
path: MENU_URLS.guide,
|
||||
component: SetupGuide,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.settings,
|
||||
component: Settings,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.dns,
|
||||
component: Dns,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.encryption,
|
||||
component: Encryption,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.dhcp,
|
||||
component: Dhcp,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.clients,
|
||||
component: Clients,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_blocklists,
|
||||
component: DnsBlocklist,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_allowlists,
|
||||
component: DnsAllowlist,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_rewrites,
|
||||
component: DnsRewrites,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.custom_rules,
|
||||
component: CustomRules,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.blocked_services,
|
||||
component: Services,
|
||||
},
|
||||
];
|
||||
|
||||
reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
const renderRoute = ({ path, component, exact }, idx) => <Route
|
||||
key={idx}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>;
|
||||
|
||||
handleUpdate = () => {
|
||||
this.props.getUpdate();
|
||||
};
|
||||
const App = () => {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
language,
|
||||
isCoreRunning,
|
||||
isUpdateAvailable,
|
||||
processing,
|
||||
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||
|
||||
setLanguage = () => {
|
||||
const { processing, language } = this.props.dashboard;
|
||||
const { processing: processingEncryption } = useSelector((
|
||||
state,
|
||||
) => state.encryption, shallowEqual);
|
||||
|
||||
const updateAvailable = isCoreRunning && isUpdateAvailable;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDnsStatus());
|
||||
}, []);
|
||||
|
||||
const setLanguage = () => {
|
||||
if (!processing) {
|
||||
if (language) {
|
||||
i18n.changeLanguage(language);
|
||||
@@ -68,93 +130,52 @@ class App extends Component {
|
||||
}
|
||||
|
||||
i18n.on('languageChanged', (lang) => {
|
||||
this.props.changeLanguage(lang);
|
||||
dispatch(changeLanguage(lang));
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard, encryption, getVersion } = this.props;
|
||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
useEffect(() => {
|
||||
setLanguage();
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<HashRouter hashType="noslash">
|
||||
<Fragment>
|
||||
{updateAvailable && (
|
||||
<Fragment>
|
||||
<UpdateTopline
|
||||
url={dashboard.announcementUrl}
|
||||
version={dashboard.newVersion}
|
||||
canAutoUpdate={dashboard.canAutoUpdate}
|
||||
getUpdate={this.handleUpdate}
|
||||
processingUpdate={dashboard.processingUpdate}
|
||||
/>
|
||||
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
||||
</Fragment>
|
||||
)}
|
||||
{!encryption.processing && (
|
||||
<EncryptionTopline notAfter={encryption.not_after} />
|
||||
)}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap pb-5">
|
||||
{dashboard.processing && <Loading />}
|
||||
{!dashboard.isCoreRunning && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status reloadPage={this.reloadPage}
|
||||
message="dns_start"
|
||||
/>
|
||||
<Loading />
|
||||
</div>
|
||||
const reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<HashRouter hashType="noslash">
|
||||
<>
|
||||
{updateAvailable && <>
|
||||
<UpdateTopline />
|
||||
<UpdateOverlay />
|
||||
</>}
|
||||
{!processingEncryption && <EncryptionTopline />}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Header />
|
||||
<div className="container container--wrap pb-5">
|
||||
{processing && <Loading />}
|
||||
{!isCoreRunning && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status reloadPage={reloadPage} message="dns_start" />
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||
<>
|
||||
<Route path={MENU_URLS.root} exact component={Dashboard} />
|
||||
<Route
|
||||
path={[`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs]}
|
||||
component={Logs} />
|
||||
<Route path={MENU_URLS.guide} component={SetupGuide} />
|
||||
<Route path={SETTINGS_URLS.settings} component={Settings} />
|
||||
<Route path={SETTINGS_URLS.dns} component={Dns} />
|
||||
<Route path={SETTINGS_URLS.encryption} component={Encryption} />
|
||||
<Route path={SETTINGS_URLS.dhcp} component={Dhcp} />
|
||||
<Route path={SETTINGS_URLS.clients} component={Clients} />
|
||||
<Route path={FILTERS_URLS.dns_blocklists}
|
||||
component={DnsBlocklist} />
|
||||
<Route path={FILTERS_URLS.dns_allowlists}
|
||||
component={DnsAllowlist} />
|
||||
<Route path={FILTERS_URLS.dns_rewrites} component={DnsRewrites} />
|
||||
<Route path={FILTERS_URLS.custom_rules} component={CustomRules} />
|
||||
<Route path={FILTERS_URLS.blocked_services} component={Services} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Footer
|
||||
dnsVersion={dashboard.dnsVersion}
|
||||
dnsPort={dashboard.dnsPort}
|
||||
processingVersion={dashboard.processingVersion}
|
||||
getVersion={getVersion}
|
||||
checkUpdateFlag={dashboard.checkUpdateFlag}
|
||||
/>
|
||||
<Toasts />
|
||||
<Icons />
|
||||
</Fragment>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
getDnsStatus: PropTypes.func,
|
||||
getUpdate: PropTypes.func,
|
||||
enableDns: PropTypes.func,
|
||||
dashboard: PropTypes.object,
|
||||
isCoreRunning: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
changeLanguage: PropTypes.func,
|
||||
encryption: PropTypes.object,
|
||||
getVersion: PropTypes.func,
|
||||
</div>
|
||||
)}
|
||||
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
|
||||
</div>
|
||||
<Footer />
|
||||
<Toasts />
|
||||
<Icons />
|
||||
</>
|
||||
</HashRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTranslation()(App);
|
||||
renderRoute.propTypes = {
|
||||
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
|
||||
component: propTypes.element.isRequired,
|
||||
exact: propTypes.bool,
|
||||
};
|
||||
|
||||
export default hot(App);
|
||||
|
||||
@@ -59,6 +59,4 @@ const Services = () => {
|
||||
);
|
||||
};
|
||||
|
||||
Services.propTypes = {};
|
||||
|
||||
export default Services;
|
||||
|
||||
@@ -90,9 +90,8 @@ class Menu extends Component {
|
||||
};
|
||||
|
||||
getActiveClassForDropdown = (URLS) => {
|
||||
const { pathname } = this.props.location;
|
||||
const isActivePage = Object.values(URLS)
|
||||
.some((item) => item === pathname);
|
||||
.some((item) => item === this.props.pathname);
|
||||
|
||||
return isActivePage ? 'active' : '';
|
||||
};
|
||||
@@ -180,9 +179,9 @@ class Menu extends Component {
|
||||
}
|
||||
|
||||
Menu.propTypes = {
|
||||
isMenuOpen: PropTypes.bool,
|
||||
closeMenu: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
isMenuOpen: PropTypes.bool.isRequired,
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
pathname: PropTypes.string.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,83 +1,77 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { Trans } from 'react-i18next';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import Menu from './Menu';
|
||||
import logo from '../ui/svg/logo.svg';
|
||||
import './Header.css';
|
||||
|
||||
class Header extends Component {
|
||||
state = {
|
||||
isMenuOpen: false,
|
||||
const Header = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const {
|
||||
protectionEnabled,
|
||||
processing,
|
||||
isCoreRunning,
|
||||
processingProfile,
|
||||
name,
|
||||
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const toggleMenuOpen = () => {
|
||||
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
|
||||
};
|
||||
|
||||
toggleMenuOpen = () => {
|
||||
this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen }));
|
||||
const closeMenu = () => {
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
closeMenu = () => {
|
||||
this.setState({ isMenuOpen: false });
|
||||
};
|
||||
const badgeClass = classnames('badge dns-status', {
|
||||
'badge-success': protectionEnabled,
|
||||
'badge-danger': !protectionEnabled,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { dashboard, location } = this.props;
|
||||
const { isMenuOpen } = this.state;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
'badge-success': dashboard.protectionEnabled,
|
||||
'badge-danger': !dashboard.protectionEnabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="header__container">
|
||||
<div className="header__row">
|
||||
<div
|
||||
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
||||
onClick={this.toggleMenuOpen}
|
||||
>
|
||||
<span className="header-toggler-icon" />
|
||||
</div>
|
||||
<div className="header__column">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||
<span className={badgeClass}>
|
||||
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="header__container">
|
||||
<div className="header__row">
|
||||
<div
|
||||
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
||||
onClick={toggleMenuOpen}
|
||||
>
|
||||
<span className="header-toggler-icon" />
|
||||
</div>
|
||||
<div className="header__column">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!processing && isCoreRunning && (
|
||||
<span className={badgeClass}>
|
||||
<Trans>{protectionEnabled ? 'on' : 'off'}</Trans>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Menu
|
||||
location={location}
|
||||
isMenuOpen={isMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
<div className="header__column">
|
||||
<div className="header__right">
|
||||
{!dashboard.processingProfile && dashboard.name
|
||||
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
||||
<Trans>sign_out</Trans>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
pathname={pathname}
|
||||
isMenuOpen={isMenuOpen}
|
||||
closeMenu={closeMenu}
|
||||
/>
|
||||
<div className="header__column">
|
||||
<div className="header__right">
|
||||
{!processingProfile && name
|
||||
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
||||
<Trans>sign_out</Trans>
|
||||
</a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTranslation()(Header);
|
||||
export default Header;
|
||||
|
||||
@@ -437,7 +437,7 @@
|
||||
}
|
||||
|
||||
.custom-select__arrow--left {
|
||||
background: #fff url('./chevron-down.svg') no-repeat;
|
||||
background: var(--white) url('../ui/svg/chevron-down.svg') no-repeat;
|
||||
background-position: 5px 9px;
|
||||
background-size: 22px;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
|
Before Width: | Height: | Size: 264 B |
@@ -1,4 +1,4 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
@@ -34,13 +34,14 @@ class Dhcp extends Component {
|
||||
} = this.props.dhcp;
|
||||
const otherDhcpFound = check?.otherServer
|
||||
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
|
||||
const filledConfig = Object.keys(config).every((key) => {
|
||||
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
||||
return true;
|
||||
}
|
||||
const filledConfig = Object.keys(config)
|
||||
.every((key) => {
|
||||
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return config[key];
|
||||
});
|
||||
return config[key];
|
||||
});
|
||||
|
||||
if (config.enabled) {
|
||||
return (
|
||||
@@ -114,40 +115,35 @@ class Dhcp extends Component {
|
||||
|
||||
getStaticIpWarning = (t, check, interfaceName) => {
|
||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_static_ip_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.staticIP.error}</span>
|
||||
</Accordion>
|
||||
</div>
|
||||
return <>
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_static_ip_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.staticIP.error}</span>
|
||||
</Accordion>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
} if (
|
||||
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</>;
|
||||
}
|
||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
||||
&& check.staticIP.ip
|
||||
&& interfaceName
|
||||
) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-secondary mb-2">
|
||||
<Trans
|
||||
components={[<strong key="0">example</strong>]}
|
||||
values={{
|
||||
interfaceName,
|
||||
ipAddress: check.staticIP.ip,
|
||||
}}
|
||||
>
|
||||
dhcp_dynamic_ip_found
|
||||
</Trans>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
&& interfaceName) {
|
||||
return <>
|
||||
<div className="text-secondary mb-2">
|
||||
<Trans
|
||||
components={[<strong key="0">example</strong>]}
|
||||
values={{
|
||||
interfaceName,
|
||||
ipAddress: check.staticIP.ip,
|
||||
}}
|
||||
>
|
||||
dhcp_dynamic_ip_found
|
||||
</Trans>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</>;
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -163,104 +159,101 @@ class Dhcp extends Component {
|
||||
removeStaticLease,
|
||||
toggleLeaseModal,
|
||||
} = this.props;
|
||||
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
const { enabled, interface_name, ...values } = dhcp.config;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<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">
|
||||
<Fragment>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={{
|
||||
interface_name,
|
||||
...values,
|
||||
}}
|
||||
interfaces={dhcp.interfaces}
|
||||
processingConfig={dhcp.processingConfig}
|
||||
processingInterfaces={dhcp.processingInterfaces}
|
||||
enabled={enabled}
|
||||
resetDhcp={resetDhcp}
|
||||
/>
|
||||
<hr />
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() => findActiveDhcp(interface_name)}
|
||||
disabled={
|
||||
enabled || !interface_name || dhcp.processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{!enabled && dhcp.check && (
|
||||
<Fragment>
|
||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||
{this.getDhcpWarning(dhcp.check)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
return <>
|
||||
<PageTitle title={t('dhcp_settings')} />
|
||||
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
||||
{!dhcp.processing && !dhcp.processingInterfaces && <>
|
||||
<Card
|
||||
title={t('dhcp_title')}
|
||||
subtitle={t('dhcp_description')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="dhcp">
|
||||
<>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={{
|
||||
interface_name,
|
||||
...values,
|
||||
}}
|
||||
interfaces={dhcp.interfaces}
|
||||
processingConfig={dhcp.processingConfig}
|
||||
processingInterfaces={dhcp.processingInterfaces}
|
||||
enabled={enabled}
|
||||
resetDhcp={resetDhcp}
|
||||
/>
|
||||
<hr />
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() => findActiveDhcp(interface_name)}
|
||||
disabled={
|
||||
enabled || !interface_name || dhcp.processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
{dhcp.config.enabled && (
|
||||
<Card
|
||||
title={t('dhcp_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
<Card
|
||||
title={t('dhcp_static_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<StaticLeases
|
||||
staticLeases={dhcp.staticLeases}
|
||||
isModalOpen={dhcp.isModalOpen}
|
||||
addStaticLease={addStaticLease}
|
||||
removeStaticLease={removeStaticLease}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={dhcp.processingAdding}
|
||||
processingDeleting={dhcp.processingDeleting}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleLeaseModal()}
|
||||
>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{!enabled && dhcp.check && (
|
||||
<>
|
||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||
{this.getDhcpWarning(dhcp.check)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</Card>
|
||||
{dhcp.config.enabled && (
|
||||
<Card
|
||||
title={t('dhcp_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
</div>
|
||||
</Card>
|
||||
</Fragment>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
<Card
|
||||
title={t('dhcp_static_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<StaticLeases
|
||||
staticLeases={dhcp.staticLeases}
|
||||
isModalOpen={dhcp.isModalOpen}
|
||||
addStaticLease={addStaticLease}
|
||||
removeStaticLease={removeStaticLease}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={dhcp.processingAdding}
|
||||
processingDeleting={dhcp.processingDeleting}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleLeaseModal()}
|
||||
>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
import { setAccessList } from '../../../../actions/access';
|
||||
|
||||
class Access extends Component {
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setAccessList(values);
|
||||
const Access = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
processing,
|
||||
processingSet,
|
||||
...values
|
||||
} = useSelector((state) => state.access, shallowEqual);
|
||||
|
||||
const handleFormSubmit = (values) => {
|
||||
dispatch(setAccessList(values));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, access } = this.props;
|
||||
|
||||
const { processing, processingSet, ...values } = access;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('access_title')}
|
||||
subtitle={t('access_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={values}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
processingSet={processingSet}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Access.propTypes = {
|
||||
access: PropTypes.object.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
return (
|
||||
<Card
|
||||
title={t('access_title')}
|
||||
subtitle={t('access_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={values}
|
||||
onSubmit={handleFormSubmit}
|
||||
processingSet={processingSet}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTranslation()(Access);
|
||||
export default Access;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import {
|
||||
renderInputField,
|
||||
renderRadioField,
|
||||
@@ -18,32 +17,36 @@ import {
|
||||
} from '../../../../helpers/validators';
|
||||
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
|
||||
|
||||
const checkboxes = [{
|
||||
name: 'edns_cs_enabled',
|
||||
placeholder: 'edns_enable',
|
||||
subtitle: 'edns_cs_desc',
|
||||
},
|
||||
{
|
||||
name: 'dnssec_enabled',
|
||||
placeholder: 'dnssec_enable',
|
||||
subtitle: 'dnssec_enable_desc',
|
||||
},
|
||||
{
|
||||
name: 'disable_ipv6',
|
||||
placeholder: 'disable_ipv6',
|
||||
subtitle: 'disable_ipv6_desc',
|
||||
}];
|
||||
const checkboxes = [
|
||||
{
|
||||
name: 'edns_cs_enabled',
|
||||
placeholder: 'edns_enable',
|
||||
subtitle: 'edns_cs_desc',
|
||||
},
|
||||
{
|
||||
name: 'dnssec_enabled',
|
||||
placeholder: 'dnssec_enable',
|
||||
subtitle: 'dnssec_enable_desc',
|
||||
},
|
||||
{
|
||||
name: 'disable_ipv6',
|
||||
placeholder: 'disable_ipv6',
|
||||
subtitle: 'disable_ipv6_desc',
|
||||
},
|
||||
];
|
||||
|
||||
const customIps = [{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
validateIp: validateIpv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
validateIp: validateIpv6,
|
||||
}];
|
||||
const customIps = [
|
||||
{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
validateIp: validateIpv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
validateIp: validateIpv6,
|
||||
},
|
||||
];
|
||||
|
||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
@@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||
/>
|
||||
));
|
||||
|
||||
let Form = ({
|
||||
handleSubmit, submitting, invalid, processing, blockingMode, t,
|
||||
}) => <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(placeholder)}
|
||||
disabled={processing}
|
||||
subtitle={t(subtitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
<li key={mode}>
|
||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blockingMode === BLOCKING_MODES.custom_ip && (
|
||||
<Fragment>
|
||||
{customIps.map(({
|
||||
description,
|
||||
name,
|
||||
validateIp,
|
||||
}) => <div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--with-desc"
|
||||
htmlFor={name}><Trans>{name}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>{description}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name={name}
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[validateIp, validateRequiredValue]}
|
||||
/>
|
||||
const Form = ({
|
||||
handleSubmit, submitting, invalid, processing,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
blocking_mode,
|
||||
} = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_desc</Trans>
|
||||
</div>
|
||||
</div>)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || invalid || processing}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>;
|
||||
<Field
|
||||
name="ratelimit"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(placeholder)}
|
||||
disabled={processing}
|
||||
subtitle={t(subtitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
<li key={mode}>
|
||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blocking_mode === BLOCKING_MODES.custom_ip && (
|
||||
<>
|
||||
{customIps.map(({
|
||||
description,
|
||||
name,
|
||||
validateIp,
|
||||
}) => <div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--with-desc"
|
||||
htmlFor={name}><Trans>{name}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>{description}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name={name}
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[validateIp, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || invalid || processing}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>;
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
blockingMode: PropTypes.string.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
processing: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE);
|
||||
|
||||
Form = connect((state) => {
|
||||
const blockingMode = selector(state, 'blocking_mode');
|
||||
return {
|
||||
blockingMode,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withTranslation(),
|
||||
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
|
||||
])(Form);
|
||||
export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Card from '../../../ui/Card';
|
||||
import Form from './Form';
|
||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||
|
||||
const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
const handleFormSubmit = (values) => {
|
||||
setDnsConfig(values);
|
||||
};
|
||||
|
||||
const Config = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
blocking_mode,
|
||||
ratelimit,
|
||||
@@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
dnssec_enabled,
|
||||
disable_ipv6,
|
||||
processingSetConfig,
|
||||
} = dnsConfig;
|
||||
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||
|
||||
const handleFormSubmit = (values) => {
|
||||
dispatch(setDnsConfig(values));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
);
|
||||
};
|
||||
|
||||
Config.propTypes = {
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Config);
|
||||
export default Config;
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { shallowEqual, useDispatch } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||
|
||||
const Upstream = (props) => {
|
||||
const [t] = useTranslation();
|
||||
const Upstream = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
upstream_mode,
|
||||
processingSetConfig,
|
||||
} = ((state) => state.dnsConfig, shallowEqual);
|
||||
|
||||
const { processingTestUpstream } = ((state) => state.settings, shallowEqual);
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
dispatch(setDnsConfig(values));
|
||||
};
|
||||
|
||||
const {
|
||||
processingTestUpstream,
|
||||
dnsConfig: {
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
processingSetConfig,
|
||||
upstream_mode,
|
||||
},
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('upstream_dns')}
|
||||
@@ -48,9 +45,4 @@ const Upstream = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Upstream.propTypes = {
|
||||
processingTestUpstream: PropTypes.bool.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Upstream;
|
||||
|
||||
@@ -1,67 +1,40 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Upstream from './Upstream';
|
||||
import Access from './Access';
|
||||
import Config from './Config';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
import CacheConfig from './Cache';
|
||||
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||
import { getAccessList } from '../../../actions/access';
|
||||
|
||||
const Dns = (props) => {
|
||||
const Dns = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const processing = useSelector((state) => state.access.processing);
|
||||
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
|
||||
|
||||
const isDataLoading = processing || processingGetConfig;
|
||||
|
||||
useEffect(() => {
|
||||
props.getAccessList();
|
||||
props.getDnsConfig();
|
||||
dispatch(getAccessList());
|
||||
dispatch(getDnsConfig());
|
||||
}, []);
|
||||
|
||||
const {
|
||||
settings,
|
||||
access,
|
||||
setAccessList,
|
||||
dnsConfig,
|
||||
setDnsConfig,
|
||||
} = props;
|
||||
|
||||
const isDataLoading = access.processing || dnsConfig.processingGetConfig;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{isDataLoading
|
||||
? <Loading />
|
||||
: <>
|
||||
<Upstream
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
dnsConfig={dnsConfig}
|
||||
/>
|
||||
<Config
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<CacheConfig
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<Access
|
||||
access={access}
|
||||
setAccessList={setAccessList}
|
||||
/>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Dns.propTypes = {
|
||||
settings: PropTypes.object.isRequired,
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
access: PropTypes.object.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
getDnsConfig: PropTypes.func.isRequired,
|
||||
return <>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{isDataLoading
|
||||
? <Loading />
|
||||
: <>
|
||||
<Upstream />
|
||||
<Config />
|
||||
<CacheConfig />
|
||||
<Access />
|
||||
</>}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default Dns;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { Trans } from 'react-i18next';
|
||||
import isAfter from 'date-fns/is_after';
|
||||
import addDays from 'date-fns/add_days';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import Topline from './Topline';
|
||||
import { EMPTY_DATE } from '../../helpers/constants';
|
||||
|
||||
const EncryptionTopline = (props) => {
|
||||
if (props.notAfter === EMPTY_DATE) {
|
||||
return false;
|
||||
const EncryptionTopline = () => {
|
||||
const not_after = useSelector((state) => state.encryption.not_after);
|
||||
|
||||
if (not_after === EMPTY_DATE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
|
||||
const isExpired = isAfter(Date.now(), props.notAfter);
|
||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
|
||||
const isExpired = isAfter(Date.now(), not_after);
|
||||
|
||||
if (isExpired) {
|
||||
return (
|
||||
@@ -23,7 +24,9 @@ const EncryptionTopline = (props) => {
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
} if (isAboutExpire) {
|
||||
}
|
||||
|
||||
if (isAboutExpire) {
|
||||
return (
|
||||
<Topline type="warning">
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
@@ -36,8 +39,4 @@ const EncryptionTopline = (props) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
EncryptionTopline.propTypes = {
|
||||
notAfter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(EncryptionTopline);
|
||||
export default EncryptionTopline;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -28,7 +27,7 @@ const linksData = [
|
||||
},
|
||||
];
|
||||
|
||||
const Footer = (props) => {
|
||||
const Footer = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getYear = () => {
|
||||
@@ -59,11 +58,6 @@ const Footer = (props) => {
|
||||
{t(name)}
|
||||
</a>);
|
||||
|
||||
|
||||
const {
|
||||
dnsVersion, processingVersion, getVersion, checkUpdateFlag,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<footer className="footer">
|
||||
@@ -94,12 +88,7 @@ const Footer = (props) => {
|
||||
<div className="footer__row">
|
||||
{renderCopyright()}
|
||||
<div className="footer__column footer__column--language">
|
||||
<Version
|
||||
dnsVersion={dnsVersion}
|
||||
processingVersion={processingVersion}
|
||||
getVersion={getVersion}
|
||||
checkUpdateFlag={checkUpdateFlag}
|
||||
/>
|
||||
<Version />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,11 +97,4 @@ const Footer = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Footer.propTypes = {
|
||||
dnsVersion: PropTypes.string,
|
||||
processingVersion: PropTypes.bool,
|
||||
getVersion: PropTypes.func,
|
||||
checkUpdateFlag: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import './Loading.css';
|
||||
|
||||
const Loading = () => (
|
||||
<div className="loading" />
|
||||
const Loading = ({ className }) => (
|
||||
<div className={classNames('loading', className)} />
|
||||
);
|
||||
|
||||
Loading.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { Trans } from 'react-i18next';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import './Overlay.css';
|
||||
|
||||
const UpdateOverlay = (props) => {
|
||||
const overlayClass = classnames({
|
||||
overlay: true,
|
||||
'overlay--visible': props.processingUpdate,
|
||||
const UpdateOverlay = () => {
|
||||
const processingUpdate = useSelector((state) => state.dashboard.processingUpdate);
|
||||
const overlayClass = classnames('overlay', {
|
||||
'overlay--visible': processingUpdate,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -19,8 +18,4 @@ const UpdateOverlay = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
UpdateOverlay.propTypes = {
|
||||
processingUpdate: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withTranslation()(UpdateOverlay);
|
||||
export default UpdateOverlay;
|
||||
|
||||
@@ -1,42 +1,46 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import React from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Topline from './Topline';
|
||||
import { getUpdate } from '../../actions';
|
||||
|
||||
const UpdateTopline = (props) => (
|
||||
<Topline type="info">
|
||||
<Fragment>
|
||||
const UpdateTopline = () => {
|
||||
const {
|
||||
announcementUrl,
|
||||
newVersion,
|
||||
canAutoUpdate,
|
||||
processingUpdate,
|
||||
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleUpdate = () => {
|
||||
dispatch(getUpdate());
|
||||
};
|
||||
|
||||
return <Topline type="info">
|
||||
<>
|
||||
<Trans
|
||||
values={{ version: props.version }}
|
||||
values={{ version: newVersion }}
|
||||
components={[
|
||||
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
|
||||
<a href={announcementUrl} target="_blank" rel="noopener noreferrer" key="0">
|
||||
Click here
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
update_announcement
|
||||
</Trans>
|
||||
{props.canAutoUpdate
|
||||
&& <button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary ml-3"
|
||||
onClick={props.getUpdate}
|
||||
disabled={props.processingUpdate}
|
||||
>
|
||||
<Trans>update_now</Trans>
|
||||
</button>
|
||||
{canAutoUpdate
|
||||
&& <button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary ml-3"
|
||||
onClick={handleUpdate}
|
||||
disabled={processingUpdate}
|
||||
>
|
||||
<Trans>update_now</Trans>
|
||||
</button>
|
||||
}
|
||||
</Fragment>
|
||||
</Topline>
|
||||
);
|
||||
|
||||
UpdateTopline.propTypes = {
|
||||
version: PropTypes.string,
|
||||
url: PropTypes.string.isRequired,
|
||||
canAutoUpdate: PropTypes.bool,
|
||||
getUpdate: PropTypes.func,
|
||||
processingUpdate: PropTypes.bool,
|
||||
</>
|
||||
</Topline>;
|
||||
};
|
||||
|
||||
export default withTranslation()(UpdateTopline);
|
||||
export default UpdateTopline;
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { getVersion } from '../../actions';
|
||||
import './Version.css';
|
||||
|
||||
const Version = (props) => {
|
||||
const Version = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
dnsVersion, processingVersion, t, checkUpdateFlag,
|
||||
} = props;
|
||||
dnsVersion,
|
||||
processingVersion,
|
||||
checkUpdateFlag,
|
||||
} = useSelector((state) => state?.dashboard ?? {}, shallowEqual);
|
||||
|
||||
const onClick = () => {
|
||||
dispatch(getVersion(true));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="version">
|
||||
@@ -20,7 +28,7 @@ const Version = (props) => {
|
||||
{checkUpdateFlag && <button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
|
||||
onClick={() => props.getVersion(true)}
|
||||
onClick={onClick}
|
||||
disabled={processingVersion}
|
||||
title={t('check_updates_now')}
|
||||
>
|
||||
@@ -33,12 +41,4 @@ const Version = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Version.propTypes = {
|
||||
dnsVersion: PropTypes.string,
|
||||
getVersion: PropTypes.func,
|
||||
processingVersion: PropTypes.bool,
|
||||
checkUpdateFlag: PropTypes.bool,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Version);
|
||||
export default Version;
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 276 B |
@@ -1 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 371 B |
@@ -1 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><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 xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle">
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 379 B |
@@ -1 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41"><g fill-rule="evenodd"><path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/><path fill-rule="nonzero" d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/><path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/>
|
||||
<path fill-rule="nonzero"
|
||||
d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/>
|
||||
<path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1 +1,7 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><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"/></svg>
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 367 B |
@@ -1 +1,5 @@
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m18 6-12 12"/>
|
||||
<path d="m6 6 12 12"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 244 B |