Added select for listen interfaces

This commit is contained in:
Ildar Kamalov 2019-02-01 19:52:42 +03:00 committed by Eugene Bujak
parent 5abe5af707
commit f379d34813
10 changed files with 211 additions and 107 deletions

View File

@ -164,10 +164,11 @@
"install_settings_title": "Admin Web Interface",
"install_settings_listen": "Listen interface",
"install_settings_port": "Port",
"install_settings_interface_link": "Your AdGuard Home admin web interface is available on <0>{{link}}</0>",
"install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses: <0>{{link}}</0>",
"form_error_port": "Enter valid port value",
"install_settings_dns": "DNS server",
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server at <0>{{ip}}</0>",
"install_settings_all_interfaces": "All interfaces",
"install_auth_title": "Authentication",
"install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to have it protected from unrestricted access.",
"install_auth_username": "Username",
@ -182,6 +183,7 @@
"install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
"install_devices_address": "AdGuard Home DNS server is listening to the following addresses",
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.",
"install_devices_router_list_2": "Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",

View File

@ -48,8 +48,10 @@ export const setAllSettings = values => async (dispatch) => {
await apiClient.setAllSettings(config);
dispatch(setAllSettingsSuccess());
dispatch(addSuccessToast('install_saved'));
dispatch(nextStep());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setAllSettingsFailure());
dispatch(prevStep());
}
};

View File

@ -338,16 +338,16 @@ export default class Api {
}
// Installation
GET_DEFAULT_ADDRESSES = { path: 'install/get_default_addresses', method: 'GET' };
SET_ALL_SETTINGS = { path: 'install/set_all_settings', method: 'POST' };
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
getDefaultAddresses() {
const { path, method } = this.GET_DEFAULT_ADDRESSES;
const { path, method } = this.INSTALL_GET_ADDRESSES;
return this.makeRequest(path, method);
}
setAllSettings(config) {
const { path, method } = this.SET_ALL_SETTINGS;
const { path, method } = this.INSTALL_CONFIGURE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },

View File

@ -19,7 +19,26 @@ class Controls extends Component {
}
}
renderButtons(step) {
renderPrevButton(step) {
switch (step) {
case 2:
case 3:
case 4:
return (
<button
type="button"
className="btn btn-secondary btn-standard btn-lg"
onClick={this.props.prevStep}
>
<Trans>back</Trans>
</button>
);
default:
return false;
}
}
renderNextButton(step) {
switch (step) {
case 1:
return (
@ -34,14 +53,6 @@ class Controls extends Component {
case 2:
case 3:
return (
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard btn-lg"
onClick={this.props.prevStep}
>
<Trans>back</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard btn-lg"
@ -49,18 +60,9 @@ class Controls extends Component {
>
<Trans>next</Trans>
</button>
</div>
);
case 4:
return (
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard btn-lg"
onClick={this.props.prevStep}
>
<Trans>back</Trans>
</button>
<button
type="button"
className="btn btn-success btn-standard btn-lg"
@ -68,14 +70,13 @@ class Controls extends Component {
>
<Trans>next</Trans>
</button>
</div>
);
case 5:
return (
<button
type="submit"
type="button"
className="btn btn-success btn-standard btn-lg"
disabled={this.props.submitting || this.props.pristine}
onClick={this.props.openDashboard}
>
<Trans>open_dashboard</Trans>
</button>
@ -88,7 +89,10 @@ class Controls extends Component {
render() {
return (
<div className="setup__nav">
{this.renderButtons(this.props.step)}
<div className="btn-list">
{this.renderPrevButton(this.props.step)}
{this.renderNextButton(this.props.step)}
</div>
</div>
);
}
@ -98,9 +102,10 @@ Controls.propTypes = {
step: PropTypes.number.isRequired,
nextStep: PropTypes.func,
prevStep: PropTypes.func,
pristine: PropTypes.bool,
openDashboard: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
pristine: PropTypes.bool,
};
const mapStateToProps = (state) => {

View File

@ -1,19 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
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 Controls from './Controls';
const Devices = () => (
let Devices = props => (
<div className="setup__step">
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_devices_title</Trans>
</div>
<p className="setup__desc">
<div className="setup__desc">
<Trans>install_devices_desc</Trans>
</p>
<div className="mt-1">
<Trans>install_devices_address</Trans>:
</div>
<div>
<strong>{`${props.dnsIp}:${props.dnsPort}`}</strong>
</div>
</div>
<Icons />
<Tabs>
<div label="Router">
@ -90,4 +100,28 @@ const Devices = () => (
</div>
);
export default withNamespaces()(Devices);
Devices.propTypes = {
dnsIp: PropTypes.string.isRequired,
dnsPort: PropTypes.number.isRequired,
};
const selector = formValueSelector('install');
Devices = connect((state) => {
const dnsIp = selector(state, 'dns.ip');
const dnsPort = selector(state, 'dns.port');
return {
dnsIp,
dnsPort,
};
})(Devices);
export default flow([
withNamespaces(),
reduxForm({
form: 'install',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
}),
])(Devices);

View File

@ -7,7 +7,6 @@ import flow from 'lodash/flow';
import Controls from './Controls';
import renderField from './renderField';
import { R_IPV4 } from '../../helpers/constants';
const required = (value) => {
if (value || value === 0) {
@ -16,13 +15,6 @@ const required = (value) => {
return <Trans>form_error_required</Trans>;
};
const ipv4 = (value) => {
if (value && !new RegExp(R_IPV4).test(value)) {
return <Trans>form_error_ip_format</Trans>;
}
return false;
};
const port = (value) => {
if (value < 1 || value > 65535) {
return <Trans>form_error_port</Trans>;
@ -32,6 +24,29 @@ const port = (value) => {
const toNumber = value => value && parseInt(value, 10);
const renderInterfaces = (interfaces => (
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return (
<option value={interfaceIP} key={name}>
{name} - {interfaceIP}
</option>
);
})
));
let Settings = (props) => {
const {
handleSubmit,
@ -39,7 +54,10 @@ let Settings = (props) => {
interfacePort,
dnsIp,
dnsPort,
interfaces,
invalid,
webWarning,
dnsWarning,
} = props;
const dnsAddress = dnsPort && dnsPort !== 53 ? `${dnsIp}:${dnsPort}` : dnsIp;
const interfaceAddress = interfacePort ? `http://${interfaceIp}:${interfacePort}` : `http://${interfaceIp}`;
@ -58,12 +76,14 @@ let Settings = (props) => {
</label>
<Field
name="web.ip"
component={renderField}
type="text"
className="form-control"
placeholder="0.0.0.0"
validate={[ipv4, required]}
/>
component="select"
className="form-control custom-select"
>
<option value="0.0.0.0">
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
<div className="col-4">
@ -90,6 +110,11 @@ let Settings = (props) => {
>
install_settings_interface_link
</Trans>
{webWarning &&
<div className="text-danger mt-2">
{webWarning}
</div>
}
</div>
</div>
<div className="setup__group">
@ -104,12 +129,14 @@ let Settings = (props) => {
</label>
<Field
name="dns.ip"
component={renderField}
type="text"
className="form-control"
placeholder="0.0.0.0"
validate={[ipv4, required]}
/>
component="select"
className="form-control custom-select"
>
<option value="0.0.0.0" defaultValue>
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
<div className="col-4">
@ -129,14 +156,19 @@ let Settings = (props) => {
</div>
</div>
</div>
<p className="setup__desc">
<div className="setup__desc">
<Trans
components={[<strong key="0">ip</strong>]}
values={{ ip: dnsAddress }}
>
install_settings_dns_desc
</Trans>
</p>
{dnsWarning &&
<div className="text-danger mt-2">
{dnsWarning}
</div>
}
</div>
</div>
<Controls invalid={invalid} />
</form>
@ -155,17 +187,13 @@ Settings.propTypes = {
PropTypes.string,
PropTypes.number,
]),
webWarning: PropTypes.string.isRequired,
dnsWarning: PropTypes.string.isRequired,
interfaces: PropTypes.object.isRequired,
invalid: PropTypes.bool.isRequired,
initialValues: PropTypes.object,
};
Settings.defaultProps = {
interfaceIp: '192.168.0.1',
interfacePort: 3000,
dnsIp: '192.168.0.1',
dnsPort: 53,
};
const selector = formValueSelector('install');
Settings = connect((state) => {

View File

@ -54,6 +54,7 @@
}
.setup__desc {
margin-bottom: 20px;
font-size: 15px;
}

View File

@ -1,20 +1,13 @@
import React, { Component } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { reduxForm } from 'redux-form';
import { reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import Controls from './Controls';
class Submit extends Component {
render() {
const {
handleSubmit,
pristine,
submitting,
} = this.props;
return (
let Submit = props => (
<div className="setup__step">
<div className="setup__group">
<h1 className="setup__title">
@ -24,20 +17,37 @@ class Submit extends Component {
<Trans>install_submit_desc</Trans>
</p>
</div>
<form onSubmit={handleSubmit}>
<Controls submitting={submitting} pristine={pristine} />
<form onSubmit={props.handleSubmit}>
<Controls
submitting={props.submitting}
pristine={props.pristine}
address={`http://${props.interfaceIp}`}
/>
</form>
</div>
);
}
}
);
Submit.propTypes = {
interfaceIp: PropTypes.string.isRequired,
interfacePort: PropTypes.number.isRequired,
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
};
const selector = formValueSelector('install');
Submit = connect((state) => {
const interfaceIp = selector(state, 'web.ip');
const interfacePort = selector(state, 'web.port');
return {
interfaceIp,
interfacePort,
};
})(Submit);
export default flow([
withNamespaces(),
reduxForm({

View File

@ -29,6 +29,10 @@ class Setup extends Component {
this.props.setAllSettings(values);
};
openDashboard = () => {
console.log('Open dashboard');
}
nextStep = () => {
if (this.props.install.step < INSTALL_TOTAL_STEPS) {
this.props.nextStep();
@ -41,7 +45,7 @@ class Setup extends Component {
}
}
renderPage(step, config) {
renderPage(step, config, interfaces) {
switch (step) {
case 1:
return <Greeting />;
@ -49,17 +53,20 @@ class Setup extends Component {
return (
<Settings
initialValues={config}
interfaces={interfaces}
webWarning={config.web.warning}
dnsWarning={config.dns.warning}
onSubmit={this.nextStep}
/>
);
case 3:
return (
<Auth onSubmit={this.nextStep} />
<Auth onSubmit={this.handleFormSubmit} />
);
case 4:
return <Devices />;
case 5:
return <Submit onSubmit={this.handleFormSubmit} />;
return <Submit onSubmit={this.openDashboard} />;
default:
return false;
}
@ -71,6 +78,7 @@ class Setup extends Component {
step,
web,
dns,
interfaces,
} = this.props.install;
return (
@ -81,7 +89,7 @@ class Setup extends Component {
<div className="setup">
<div className="setup__container">
<img src={logo} className="setup__logo" alt="logo" />
{this.renderPage(step, { web, dns })}
{this.renderPage(step, { web, dns }, interfaces)}
<Progress step={step} />
</div>
</div>

View File

@ -10,7 +10,10 @@ const install = handleActions({
[actions.getDefaultAddressesRequest]: state => ({ ...state, processingDefault: true }),
[actions.getDefaultAddressesFailure]: state => ({ ...state, processingDefault: false }),
[actions.getDefaultAddressesSuccess]: (state, { payload }) => {
const newState = { ...state, ...payload, processingDefault: false };
const values = payload;
values.web.ip = state.web.ip;
values.dns.ip = state.dns.ip;
const newState = { ...state, ...values, processingDefault: false };
return newState;
},
@ -23,6 +26,17 @@ const install = handleActions({
}, {
step: INSTALL_FIRST_STEP,
processingDefault: true,
web: {
ip: '0.0.0.0',
port: 80,
warning: '',
},
dns: {
ip: '0.0.0.0',
port: 53,
warning: '',
},
interfaces: {},
});
const toasts = handleActions({