Merge: Add "Setup guide" menu item #605

* commit 'c091d10a416b0ea9c72fb1addd95e7194281d9ce':
  * client: update translations
  + client: added setup guide page and DNS addresses popover
  + control: use the list of IP addresses instead of single string in "dns_address"
This commit is contained in:
Simon Zolin 2019-03-20 12:56:47 +03:00
commit d5e57248a0
16 changed files with 280 additions and 98 deletions

View File

@ -1,4 +1,7 @@
{
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
"url_added_successfully": "URL added successfully",
"check_dhcp_servers": "Check for DHCP servers",
"save_config": "Save config",
@ -246,8 +249,7 @@
"form_error_equal": "Shouldn't be equal",
"form_error_password": "Password mismatched",
"reset_settings": "Reset settings",
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info.",
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DOH/DOT resolvers you specify as upstreams."
}
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info.",
"setup_guide": "Setup guide",
"dns_addresses": "DNS addresses"
}

View File

@ -1,4 +1,7 @@
{
"upstream_parallel": "\u901a\u8fc7\u540c\u65f6\u67e5\u8be2\u6240\u6709\u4e0a\u6d41\u670d\u52a1\u5668\u4ee5\u4f7f\u7528\u5e76\u884c\u67e5\u8be2\u52a0\u901f\u89e3\u6790",
"bootstrap_dns": "Bootstrap DNS \u670d\u52a1\u5668",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
"url_added_successfully": "\u7f51\u5740\u6dfb\u52a0\u6210\u529f",
"check_dhcp_servers": "\u68c0\u67e5 DHCP \u670d\u52a1\u5668",
"save_config": "\u4fdd\u5b58\u914d\u7f6e",

View File

@ -1,4 +1,7 @@
{
"upstream_parallel": "\u900f\u904e\u540c\u6642\u5730\u67e5\u8a62\u6240\u6709\u4e0a\u6e38\u7684\u4f3a\u670d\u5668\uff0c\u4f7f\u7528\u4e26\u884c\u7684\u67e5\u8a62\u4ee5\u52a0\u901f\u89e3\u6790",
"bootstrap_dns": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668",
"bootstrap_dns_desc": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS\u4f3a\u670d\u5668\u88ab\u7528\u65bc\u89e3\u6790\u60a8\u660e\u78ba\u6307\u5b9a\u4f5c\u70ba\u4e0a\u6e38\u7684DoH\/DoT\u89e3\u6790\u5668\u4e4bIP\u4f4d\u5740\u3002",
"url_added_successfully": "\u7db2\u5740\u88ab\u6210\u529f\u5730\u52a0\u5165",
"check_dhcp_servers": "\u6aa2\u67e5\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
"save_config": "\u5132\u5b58\u914d\u7f6e",
@ -79,7 +82,7 @@
"no_settings": "\u7121\u8a2d\u5b9a",
"general_settings": "\u4e00\u822c\u7684\u8a2d\u5b9a",
"upstream_dns": "\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",
"upstream_dns_hint": "\u5982\u679c\u60a8\u4fdd\u7559\u8a72\u6b04\u4f4d\u7a7a\u767d\u7684\uff0cAdGuard Home\u5c07\u4f7f\u7528<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u4f5c\u70ba\u4e0a\u6e38\u3002\u5c0d\u65bcDNS over TLS\u4f3a\u670d\u5668\u4f7f\u7528 tls:\/\/ \u524d\u7db4\u3002",
"upstream_dns_hint": "\u5982\u679c\u60a8\u5c07\u8a72\u6b04\u4f4d\u7559\u7a7a\uff0cAdGuard Home\u5c07\u4f7f\u7528<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u4f5c\u70ba\u4e0a\u6e38\u3002",
"test_upstream_btn": "\u6e2c\u8a66\u4e0a\u884c\u8cc7\u6599\u6d41",
"apply_btn": "\u5957\u7528",
"disabled_filtering_toast": "\u5df2\u7981\u7528\u904e\u6ffe",

View File

@ -14,8 +14,9 @@ import Dashboard from '../../containers/Dashboard';
import Settings from '../../containers/Settings';
import Filters from '../../containers/Filters';
import Logs from '../../containers/Logs';
import Footer from '../ui/Footer';
import SetupGuide from '../../containers/SetupGuide';
import Toasts from '../Toasts';
import Footer from '../ui/Footer';
import Status from '../ui/Status';
import UpdateTopline from '../ui/UpdateTopline';
import EncryptionTopline from '../ui/EncryptionTopline';
@ -86,6 +87,7 @@ class App extends Component {
<Route path="/settings" component={Settings} />
<Route path="/filters" component={Filters} />
<Route path="/logs" component={Logs} />
<Route path="/guide" component={SetupGuide} />
</Fragment>
}
</div>

View File

@ -76,6 +76,13 @@
font-weight: 600;
}
.nav-version__link {
position: relative;
display: inline-block;
border-bottom: 1px dashed #495057;
cursor: pointer;
}
.header-brand-img {
height: 32px;
}

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import enhanceWithClickOutside from 'react-click-outside';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY } from '../../helpers/constants';
class Menu extends Component {
handleClickOutside = () => {
@ -56,10 +55,10 @@ class Menu extends Component {
</NavLink>
</li>
<li className="nav-item">
<a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
<NavLink to="/guide" href="/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>
<Trans>faq</Trans>
</a>
<Trans>setup_guide</Trans>
</NavLink>
</li>
</ul>
</div>

View File

@ -2,24 +2,35 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import { getDnsAddress } from '../../helpers/helpers';
function Version(props) {
const { dnsVersion, dnsAddress, dnsPort } = props;
const { dnsVersion, dnsAddresses, dnsPort } = props;
return (
<div className="nav-version">
<div className="nav-version__text">
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
</div>
<div className="nav-version__text">
<Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
<div className="nav-version__link">
<div className="popover__trigger popover__trigger--address">
<Trans>dns_addresses</Trans>
</div>
<div className="popover__body popover__body--address">
<div className="popover__list">
{dnsAddresses
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
}
</div>
</div>
</div>
</div>
);
}
Version.propTypes = {
dnsVersion: PropTypes.string,
dnsAddress: PropTypes.string,
dnsPort: PropTypes.number,
dnsVersion: PropTypes.string.isRequired,
dnsAddresses: PropTypes.array.isRequired,
dnsPort: PropTypes.number.isRequired,
};
export default withNamespaces()(Version);

View File

@ -56,11 +56,13 @@ class Header extends Component {
toggleMenuOpen={this.toggleMenuOpen}
closeMenu={this.closeMenu}
/>
<div className="col col-sm-6 col-lg-3">
<Version
{ ...this.props.dashboard }
/>
</div>
{!dashboard.processing &&
<div className="col col-sm-6 col-lg-3">
<Version
{ ...this.props.dashboard }
/>
</div>
}
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
.guide {
max-width: 768px;
margin: 0 auto;
}
.guide__title {
margin-bottom: 10px;
font-size: 17px;
font-weight: 700;
}
.guide__desc {
margin-bottom: 20px;
font-size: 15px;
}

View File

@ -0,0 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import { getDnsAddress } from '../../helpers/helpers';
import Guide from '../ui/Guide';
import Card from '../ui/Card';
import PageTitle from '../ui/PageTitle';
import './Guide.css';
const SetupGuide = ({
t,
dashboard: {
dnsAddresses,
dnsPort,
},
}) => (
<div className="guide">
<PageTitle title={t('setup_guide')} />
<Card>
<div className="guide__title">
<Trans>install_devices_title</Trans>
</div>
<div className="guide__desc">
<Trans>install_devices_desc</Trans>
<div className="mt-1">
<Trans>install_devices_address</Trans>:
</div>
<div className="mt-2 font-weight-bold">
{dnsAddresses
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
}
</div>
</div>
<Guide />
</Card>
</div>
);
SetupGuide.propTypes = {
dashboard: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(SetupGuide);

View File

@ -0,0 +1,83 @@
import React from 'react';
import { Trans, withNamespaces } from 'react-i18next';
import Tabs from '../ui/Tabs';
import Icons from '../ui/Icons';
const Guide = () => (
<div>
<Icons />
<Tabs>
<div label="Router">
<div className="tab__title">
<Trans>install_devices_router</Trans>
</div>
<div className="tab__text">
<p><Trans>install_devices_router_desc</Trans></p>
<ol>
<li><Trans>install_devices_router_list_1</Trans></li>
<li><Trans>install_devices_router_list_2</Trans></li>
<li><Trans>install_devices_router_list_3</Trans></li>
</ol>
</div>
</div>
<div label="Windows">
<div className="tab__title">
Windows
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_windows_list_1</Trans></li>
<li><Trans>install_devices_windows_list_2</Trans></li>
<li><Trans>install_devices_windows_list_3</Trans></li>
<li><Trans>install_devices_windows_list_4</Trans></li>
<li><Trans>install_devices_windows_list_5</Trans></li>
<li><Trans>install_devices_windows_list_6</Trans></li>
</ol>
</div>
</div>
<div label="macOS">
<div className="tab__title">
macOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_macos_list_1</Trans></li>
<li><Trans>install_devices_macos_list_2</Trans></li>
<li><Trans>install_devices_macos_list_3</Trans></li>
<li><Trans>install_devices_macos_list_4</Trans></li>
</ol>
</div>
</div>
<div label="Android">
<div className="tab__title">
Android
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_android_list_1</Trans></li>
<li><Trans>install_devices_android_list_2</Trans></li>
<li><Trans>install_devices_android_list_3</Trans></li>
<li><Trans>install_devices_android_list_4</Trans></li>
<li><Trans>install_devices_android_list_5</Trans></li>
</ol>
</div>
</div>
<div label="iOS">
<div className="tab__title">
iOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_ios_list_1</Trans></li>
<li><Trans>install_devices_ios_list_2</Trans></li>
<li><Trans>install_devices_ios_list_3</Trans></li>
<li><Trans>install_devices_ios_list_4</Trans></li>
</ol>
</div>
</div>
</Tabs>
</div>
);
export default withNamespaces()(Guide);

View File

@ -22,6 +22,16 @@
height: 24px;
}
.popover__trigger--address {
top: 0;
margin: 0;
line-height: 1.2;
}
.popover__trigger--address:after {
display: none;
}
.popover__body {
content: "";
display: flex;
@ -57,6 +67,38 @@
border-top: 6px solid #585965;
}
.popover__body--address {
top: calc(100% + 10px);
right: 0;
left: initial;
bottom: initial;
z-index: 1;
min-width: 100px;
padding: 12px 18px;
font-weight: 700;
text-align: left;
white-space: nowrap;
transform: none;
cursor: default;
}
.popover__body--address:after {
top: -11px;
left: initial;
right: 40px;
border-top: 6px solid transparent;
border-bottom: 6px solid #585965;
}
.popover__body--address:before {
content: "";
position: absolute;
top: -7px;
left: 0;
width: 100%;
height: 10px;
}
.popover__trigger:hover + .popover__body,
.popover__body:hover {
visibility: visible;
@ -73,6 +115,10 @@
stroke: #66b574;
}
.popover__list--bold {
font-weight: 700;
}
.popover__list-title {
margin-bottom: 3px;
}

View File

@ -0,0 +1,14 @@
import { connect } from 'react-redux';
import * as actionCreators from '../actions';
import SetupGuide from '../components/SetupGuide';
const mapStateToProps = (state) => {
const { dashboard } = state;
const props = { dashboard };
return props;
};
export default connect(
mapStateToProps,
actionCreators,
)(SetupGuide);

View File

@ -5,8 +5,7 @@ import { reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import Tabs from '../../components/ui/Tabs';
import Icons from '../../components/ui/Icons';
import Guide from '../../components/ui/Guide';
import Controls from './Controls';
import AddressList from './AddressList';
@ -30,77 +29,7 @@ let Devices = props => (
/>
</div>
</div>
<Icons />
<Tabs>
<div label="Router">
<div className="tab__title">
<Trans>install_devices_router</Trans>
</div>
<div className="tab__text">
<p><Trans>install_devices_router_desc</Trans></p>
<ol>
<li><Trans>install_devices_router_list_1</Trans></li>
<li><Trans>install_devices_router_list_2</Trans></li>
<li><Trans>install_devices_router_list_3</Trans></li>
</ol>
</div>
</div>
<div label="Windows">
<div className="tab__title">
Windows
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_windows_list_1</Trans></li>
<li><Trans>install_devices_windows_list_2</Trans></li>
<li><Trans>install_devices_windows_list_3</Trans></li>
<li><Trans>install_devices_windows_list_4</Trans></li>
<li><Trans>install_devices_windows_list_5</Trans></li>
<li><Trans>install_devices_windows_list_6</Trans></li>
</ol>
</div>
</div>
<div label="macOS">
<div className="tab__title">
macOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_macos_list_1</Trans></li>
<li><Trans>install_devices_macos_list_2</Trans></li>
<li><Trans>install_devices_macos_list_3</Trans></li>
<li><Trans>install_devices_macos_list_4</Trans></li>
</ol>
</div>
</div>
<div label="Android">
<div className="tab__title">
Android
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_android_list_1</Trans></li>
<li><Trans>install_devices_android_list_2</Trans></li>
<li><Trans>install_devices_android_list_3</Trans></li>
<li><Trans>install_devices_android_list_4</Trans></li>
<li><Trans>install_devices_android_list_5</Trans></li>
</ol>
</div>
</div>
<div label="iOS">
<div className="tab__title">
iOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_ios_list_1</Trans></li>
<li><Trans>install_devices_ios_list_2</Trans></li>
<li><Trans>install_devices_ios_list_3</Trans></li>
<li><Trans>install_devices_ios_list_4</Trans></li>
</ol>
</div>
</div>
</Tabs>
<Guide />
</div>
<Controls />
</div>

View File

@ -48,7 +48,7 @@ const dashboard = handleActions({
version,
running,
dns_port: dnsPort,
dns_address: dnsAddress,
dns_addresses: dnsAddresses,
querylog_enabled: queryLogEnabled,
upstream_dns: upstreamDns,
bootstrap_dns: bootstrapDns,
@ -63,7 +63,7 @@ const dashboard = handleActions({
processing: false,
dnsVersion: version,
dnsPort,
dnsAddress,
dnsAddresses,
queryLogEnabled,
upstreamDns: upstreamDns.join('\n'),
bootstrapDns: bootstrapDns.join('\n'),
@ -181,6 +181,9 @@ const dashboard = handleActions({
protectionEnabled: false,
processingProtection: false,
httpPort: 80,
dnsPort: 53,
dnsAddresses: [],
dnsVersion: '',
});
const queryLogs = handleActions({

View File

@ -79,8 +79,25 @@ func httpUpdateConfigReloadDNSReturnOK(w http.ResponseWriter, r *http.Request) {
func handleStatus(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
dnsAddresses := []string{}
if config.DNS.BindHost == "0.0.0.0" {
ifaces, e := getValidNetInterfacesForWeb()
if e != nil {
log.Error("Couldn't get network interfaces: %v", e)
}
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
dnsAddresses = append(dnsAddresses, addr)
}
}
}
if len(dnsAddresses) == 0 {
dnsAddresses = append(dnsAddresses, config.DNS.BindHost)
}
data := map[string]interface{}{
"dns_address": config.DNS.BindHost,
"dns_addresses": dnsAddresses,
"http_port": config.BindPort,
"dns_port": config.DNS.Port,
"protection_enabled": config.DNS.ProtectionEnabled,