Pull request #2231: ADG-8368 Frontend rewritten in TypeScript, added Node 18 support
Merge in DNS/adguard-home from ADG-8368-typescript-node-18 to master Squashed commit of the following: commit daa288ae0d76178af24595cc807055902e6f09ab Merge:4c89cf7201085d59a6Author: Igor Lobanov <bniwredyc@gmail.com> Date: Mon Jun 10 17:22:20 2024 +0200 merge commit4c89cf7209Author: Ildar Kamalov <ik@adguard.com> Date: Thu Jun 6 13:27:18 2024 +0300 remove install from initial state commitb943f2011fAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 23:10:55 2024 +0200 frontend production build fix commitcd1be2d66dAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 20:23:14 2024 +0200 production build quickfix commit7b8ac01fc2Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Jun 5 19:57:31 2024 +0300 all: upd node docker commit02afed66d5Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 18:23:12 2024 +0200 changelog fixes commit9c0f736f0cMerge:62c4fbf1ee04775c4fAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 18:18:29 2024 +0200 merge commit62c4fbf1e3Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:22:22 2024 +0200 empty line in changelog commit76b1e44a93Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:20:37 2024 +0200 changelog commitf783e90040Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:19:13 2024 +0200 filters.js -> filters.ts commit3d4ce6554cAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 16:18:03 2024 +0200 generated file removed commite35ba58f2aAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 15:45:21 2024 +0200 rollback unwanted changes commit1f30d4216dAuthor: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 15:27:36 2024 +0200 review fix commit6cd4e44f07Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 11:55:39 2024 +0200 missing generated file restoresd commit2ab738b303Author: Igor Lobanov <bniwredyc@gmail.com> Date: Wed Jun 5 11:40:32 2024 +0200 Frontend rewritten in TypeScript, added Node 18 support
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
|
||||
import { ALL_INTERFACES_IP } from '../../helpers/constants';
|
||||
|
||||
const renderItem = ({
|
||||
ip, port, isDns,
|
||||
}) => {
|
||||
const webAddress = getWebAddress(ip, port);
|
||||
const dnsAddress = getDnsAddress(ip, port);
|
||||
|
||||
return <li key={ip}>{isDns
|
||||
? <strong>{dnsAddress}</strong>
|
||||
: <a href={webAddress} target="_blank" rel="noopener noreferrer">{webAddress}</a>
|
||||
}
|
||||
</li>;
|
||||
};
|
||||
|
||||
const AddressList = ({
|
||||
address,
|
||||
interfaces,
|
||||
port,
|
||||
isDns,
|
||||
}) => <ul className="list-group pl-4">{
|
||||
address === ALL_INTERFACES_IP
|
||||
? getIpList(interfaces)
|
||||
.map((ip) => renderItem({
|
||||
ip,
|
||||
port,
|
||||
isDns,
|
||||
}))
|
||||
: renderItem({
|
||||
ip: address,
|
||||
port,
|
||||
isDns,
|
||||
})
|
||||
}
|
||||
</ul>;
|
||||
|
||||
AddressList.propTypes = {
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
port: PropTypes.number.isRequired,
|
||||
isDns: PropTypes.bool,
|
||||
};
|
||||
|
||||
renderItem.propTypes = {
|
||||
ip: PropTypes.string.isRequired,
|
||||
port: PropTypes.number.isRequired,
|
||||
isDns: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default AddressList;
|
||||
55
client/src/install/Setup/AddressList.tsx
Normal file
55
client/src/install/Setup/AddressList.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
|
||||
import { ALL_INTERFACES_IP } from '../../helpers/constants';
|
||||
import { DhcpInterface } from '../../initialState';
|
||||
|
||||
interface renderItemProps {
|
||||
ip: string;
|
||||
port: number;
|
||||
isDns: boolean;
|
||||
}
|
||||
|
||||
const renderItem = ({ ip, port, isDns }: renderItemProps) => {
|
||||
const webAddress = getWebAddress(ip, port);
|
||||
const dnsAddress = getDnsAddress(ip, port);
|
||||
|
||||
return (
|
||||
<li key={ip}>
|
||||
{isDns ? (
|
||||
<strong>{dnsAddress}</strong>
|
||||
) : (
|
||||
<a href={webAddress} target="_blank" rel="noopener noreferrer">
|
||||
{webAddress}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
interface AddressListProps {
|
||||
interfaces: DhcpInterface[];
|
||||
address: string;
|
||||
port: number;
|
||||
isDns?: boolean;
|
||||
}
|
||||
|
||||
const AddressList = ({ address, interfaces, port, isDns }: AddressListProps) => (
|
||||
<ul className="list-group pl-4">
|
||||
{address === ALL_INTERFACES_IP
|
||||
? getIpList(interfaces).map((ip: any) =>
|
||||
renderItem({
|
||||
ip,
|
||||
port,
|
||||
isDns,
|
||||
}),
|
||||
)
|
||||
: renderItem({
|
||||
ip: address,
|
||||
port,
|
||||
isDns,
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
|
||||
export default AddressList;
|
||||
@@ -1,24 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withTranslation, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import i18n from '../../i18n';
|
||||
|
||||
import Controls from './Controls';
|
||||
|
||||
import { renderInputField } from '../../helpers/form';
|
||||
import { FORM_NAME } from '../../helpers/constants';
|
||||
import { validatePasswordLength } from '../../helpers/validators';
|
||||
|
||||
const required = (value) => {
|
||||
const required = (value: any) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
const validate = (values: any) => {
|
||||
const errors: { confirm_password?: string } = {};
|
||||
|
||||
if (values.confirm_password !== values.password) {
|
||||
errors.confirm_password = i18n.t('form_error_password');
|
||||
@@ -27,13 +30,15 @@ const validate = (values) => {
|
||||
return errors;
|
||||
};
|
||||
|
||||
const Auth = (props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
pristine,
|
||||
invalid,
|
||||
t,
|
||||
} = props;
|
||||
interface AuthProps {
|
||||
handleSubmit: (...args: unknown[]) => string;
|
||||
pristine: boolean;
|
||||
invalid: boolean;
|
||||
t: (...args: unknown[]) => string;
|
||||
}
|
||||
|
||||
const Auth = (props: AuthProps) => {
|
||||
const { handleSubmit, pristine, invalid, t } = props;
|
||||
|
||||
return (
|
||||
<form className="setup__step" onSubmit={handleSubmit}>
|
||||
@@ -41,64 +46,65 @@ const Auth = (props) => {
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_auth_title</Trans>
|
||||
</div>
|
||||
|
||||
<p className="setup__desc">
|
||||
<Trans>install_auth_desc</Trans>
|
||||
</p>
|
||||
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_username</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="username"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_username_enter') }
|
||||
placeholder={t('install_auth_username_enter')}
|
||||
validate={[required]}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_password</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="password"
|
||||
component={renderInputField}
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_password_enter') }
|
||||
placeholder={t('install_auth_password_enter')}
|
||||
validate={[required, validatePasswordLength]}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_confirm</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="confirm_password"
|
||||
component={renderInputField}
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_confirm') }
|
||||
placeholder={t('install_auth_confirm')}
|
||||
validate={[required]}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Controls pristine={pristine} invalid={invalid} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Auth.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withTranslation(),
|
||||
reduxForm({
|
||||
@@ -1,47 +1,67 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import * as actionCreators from '../../actions/install';
|
||||
|
||||
class Controls extends Component {
|
||||
renderPrevButton(step) {
|
||||
interface ControlsProps {
|
||||
install: {
|
||||
step: number;
|
||||
processingSubmit: boolean;
|
||||
dns: {
|
||||
status: string;
|
||||
};
|
||||
web: {
|
||||
status: string;
|
||||
};
|
||||
};
|
||||
nextStep?: (...args: unknown[]) => unknown;
|
||||
prevStep?: (...args: unknown[]) => unknown;
|
||||
openDashboard?: (...args: unknown[]) => unknown;
|
||||
submitting?: boolean;
|
||||
invalid?: boolean;
|
||||
pristine?: boolean;
|
||||
ip?: string;
|
||||
port?: number;
|
||||
}
|
||||
|
||||
class Controls extends Component<ControlsProps> {
|
||||
renderPrevButton(step: any) {
|
||||
switch (step) {
|
||||
case 2:
|
||||
case 3:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-lg setup__button"
|
||||
onClick={this.props.prevStep}
|
||||
>
|
||||
<Trans>back</Trans>
|
||||
</button>
|
||||
type="button"
|
||||
className="btn btn-secondary btn-lg setup__button"
|
||||
onClick={this.props.prevStep}>
|
||||
<Trans>back</Trans>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
renderNextButton(step) {
|
||||
renderNextButton(step: any) {
|
||||
const {
|
||||
nextStep,
|
||||
|
||||
invalid,
|
||||
|
||||
pristine,
|
||||
|
||||
install,
|
||||
|
||||
ip,
|
||||
|
||||
port,
|
||||
} = this.props;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={nextStep}
|
||||
>
|
||||
<button type="button" className="btn btn-success btn-lg setup__button" onClick={nextStep}>
|
||||
<Trans>get_started</Trans>
|
||||
</button>
|
||||
);
|
||||
@@ -51,24 +71,13 @@ class Controls extends Component {
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
disabled={
|
||||
invalid
|
||||
|| pristine
|
||||
|| install.processingSubmit
|
||||
|| install.dns.status
|
||||
|| install.web.status
|
||||
}
|
||||
>
|
||||
disabled={invalid || pristine || install.processingSubmit}>
|
||||
<Trans>next</Trans>
|
||||
</button>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={nextStep}
|
||||
>
|
||||
<button type="button" className="btn btn-success btn-lg setup__button" onClick={nextStep}>
|
||||
<Trans>next</Trans>
|
||||
</button>
|
||||
);
|
||||
@@ -77,8 +86,7 @@ class Controls extends Component {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={() => this.props.openDashboard(ip, port)}
|
||||
>
|
||||
onClick={() => this.props.openDashboard(ip, port)}>
|
||||
<Trans>open_dashboard</Trans>
|
||||
</button>
|
||||
);
|
||||
@@ -101,25 +109,9 @@ class Controls extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Controls.propTypes = {
|
||||
install: PropTypes.object.isRequired,
|
||||
nextStep: PropTypes.func,
|
||||
prevStep: PropTypes.func,
|
||||
openDashboard: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
pristine: PropTypes.bool,
|
||||
ip: PropTypes.string,
|
||||
port: PropTypes.number,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
const { install } = state;
|
||||
const props = { install };
|
||||
return props;
|
||||
return { install };
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Controls);
|
||||
export default connect(mapStateToProps, actionCreators)(Controls);
|
||||
@@ -1,47 +1,50 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import Guide from '../../components/ui/Guide';
|
||||
|
||||
import Controls from './Controls';
|
||||
|
||||
import AddressList from './AddressList';
|
||||
import { FORM_NAME } from '../../helpers/constants';
|
||||
import { DhcpInterface } from '../../initialState';
|
||||
|
||||
let Devices = (props) => (
|
||||
interface DevicesProps {
|
||||
interfaces: DhcpInterface[];
|
||||
dnsIp: string;
|
||||
dnsPort: number;
|
||||
}
|
||||
|
||||
let Devices = (props: DevicesProps) => (
|
||||
<div className="setup__step">
|
||||
<div className="setup__group">
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_devices_title</Trans>
|
||||
</div>
|
||||
|
||||
<div className="setup__desc">
|
||||
<Trans>install_devices_desc</Trans>
|
||||
|
||||
<div className="mt-1">
|
||||
<Trans>install_devices_address</Trans>:
|
||||
</div>
|
||||
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={props.interfaces}
|
||||
address={props.dnsIp}
|
||||
port={props.dnsPort}
|
||||
isDns
|
||||
/>
|
||||
<AddressList interfaces={props.interfaces} address={props.dnsIp} port={props.dnsPort} isDns />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Guide />
|
||||
</div>
|
||||
|
||||
<Controls />
|
||||
</div>
|
||||
);
|
||||
|
||||
Devices.propTypes = {
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
dnsIp: PropTypes.string.isRequired,
|
||||
dnsPort: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('install');
|
||||
|
||||
Devices = connect((state) => {
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import Controls from './Controls';
|
||||
|
||||
const Greeting = () => (
|
||||
@@ -8,10 +9,12 @@ const Greeting = () => (
|
||||
<h1 className="setup__title">
|
||||
<Trans>install_welcome_title</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="setup__desc text-center">
|
||||
<Trans>install_welcome_desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Controls />
|
||||
</div>
|
||||
);
|
||||
@@ -1,25 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import { INSTALL_TOTAL_STEPS } from '../../helpers/constants';
|
||||
|
||||
const getProgressPercent = (step) => (step / INSTALL_TOTAL_STEPS) * 100;
|
||||
const getProgressPercent = (step: any) => (step / INSTALL_TOTAL_STEPS) * 100;
|
||||
|
||||
const Progress = (props) => (
|
||||
type Props = {
|
||||
step: number;
|
||||
};
|
||||
|
||||
const Progress = (props: Props) => (
|
||||
<div className="setup__progress">
|
||||
<Trans>install_step</Trans> {props.step}/{INSTALL_TOTAL_STEPS}
|
||||
<div className="setup__progress-wrap">
|
||||
<div
|
||||
className="setup__progress-inner"
|
||||
style={{ width: `${getProgressPercent(props.step)}%` }}
|
||||
/>
|
||||
<div className="setup__progress-inner" style={{ width: `${getProgressPercent(props.step)}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Progress.propTypes = {
|
||||
step: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Progress);
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import i18n from 'i18next';
|
||||
import i18n, { TFunction } from 'i18next';
|
||||
|
||||
import Controls from './Controls';
|
||||
|
||||
import AddressList from './AddressList';
|
||||
|
||||
import { getInterfaceIp } from '../../helpers/helpers';
|
||||
@@ -19,34 +20,61 @@ import {
|
||||
STANDARD_DNS_PORT,
|
||||
STANDARD_WEB_PORT,
|
||||
} from '../../helpers/constants';
|
||||
|
||||
import { renderInputField, toNumber } from '../../helpers/form';
|
||||
import { validateRequiredValue, validateInstallPort } from '../../helpers/validators';
|
||||
import { DhcpInterface } from '../../initialState';
|
||||
|
||||
const renderInterfaces = (interfaces) => Object.values(interfaces)
|
||||
.map((option) => {
|
||||
const {
|
||||
name,
|
||||
ip_addresses,
|
||||
flags,
|
||||
} = option;
|
||||
const renderInterfaces = (interfaces: DhcpInterface[]) =>
|
||||
Object.values(interfaces).map((option: DhcpInterface) => {
|
||||
const { name, ip_addresses, flags } = option;
|
||||
|
||||
if (option && ip_addresses?.length > 0) {
|
||||
const ip = getInterfaceIp(option);
|
||||
const isUp = flags?.includes('up');
|
||||
|
||||
return <option value={ip} key={name} disabled={!isUp}>
|
||||
{name} - {ip} {!isUp && `(${i18n.t('down')})`}
|
||||
</option>;
|
||||
return (
|
||||
<option value={ip} key={name} disabled={!isUp}>
|
||||
{name} - {ip} {!isUp && `(${i18n.t('down')})`}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
class Settings extends Component {
|
||||
type Props = {
|
||||
handleSubmit: (...args: unknown[]) => string;
|
||||
handleChange?: (...args: unknown[]) => unknown;
|
||||
handleFix: (...args: unknown[]) => unknown;
|
||||
validateForm?: (...args: unknown[]) => unknown;
|
||||
webIp: string;
|
||||
dnsIp: string;
|
||||
config: {
|
||||
web: {
|
||||
status: string;
|
||||
can_autofix: boolean;
|
||||
};
|
||||
dns: {
|
||||
status: string;
|
||||
can_autofix: boolean;
|
||||
};
|
||||
staticIp: {
|
||||
ip: string;
|
||||
static: string;
|
||||
};
|
||||
};
|
||||
webPort?: number;
|
||||
dnsPort?: number;
|
||||
interfaces: DhcpInterface[];
|
||||
invalid: boolean;
|
||||
initialValues?: object;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
class Settings extends Component<Props> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
webIp, webPort, dnsIp, dnsPort,
|
||||
} = this.props;
|
||||
const { webIp, webPort, dnsIp, dnsPort } = this.props;
|
||||
|
||||
this.props.validateForm({
|
||||
web: {
|
||||
@@ -60,47 +88,57 @@ class Settings extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
getStaticIpMessage = (staticIp) => {
|
||||
getStaticIpMessage = (staticIp: { ip: string; static: string }) => {
|
||||
const { static: status, ip } = staticIp;
|
||||
|
||||
switch (status) {
|
||||
case STATUS_RESPONSE.NO: {
|
||||
return <>
|
||||
<div className="mb-2">
|
||||
<Trans values={{ ip }} components={[<strong key="0">text</strong>]}>
|
||||
install_static_configure
|
||||
</Trans>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => this.handleStaticIp(ip)}
|
||||
>
|
||||
<Trans>set_static_ip</Trans>
|
||||
</button>
|
||||
</>;
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Trans values={{ ip }} components={[<strong key="0">text</strong>]}>
|
||||
install_static_configure
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => this.handleStaticIp(ip)}>
|
||||
<Trans>set_static_ip</Trans>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case STATUS_RESPONSE.ERROR: {
|
||||
return <div className="text-danger">
|
||||
<Trans>install_static_error</Trans>
|
||||
</div>;
|
||||
return (
|
||||
<div className="text-danger">
|
||||
<Trans>install_static_error</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case STATUS_RESPONSE.YES: {
|
||||
return <div className="text-success">
|
||||
<Trans>install_static_ok</Trans>
|
||||
</div>;
|
||||
return (
|
||||
<div className="text-success">
|
||||
<Trans>install_static_ok</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
handleAutofix = (type) => {
|
||||
handleAutofix = (type: any) => {
|
||||
const {
|
||||
webIp,
|
||||
|
||||
webPort,
|
||||
|
||||
dnsIp,
|
||||
|
||||
dnsPort,
|
||||
|
||||
handleFix,
|
||||
} = this.props;
|
||||
|
||||
@@ -125,12 +163,16 @@ class Settings extends Component {
|
||||
handleFix(web, dns, set_static_ip);
|
||||
};
|
||||
|
||||
handleStaticIp = (ip) => {
|
||||
handleStaticIp = (ip: any) => {
|
||||
const {
|
||||
webIp,
|
||||
|
||||
webPort,
|
||||
|
||||
dnsIp,
|
||||
|
||||
dnsPort,
|
||||
|
||||
handleFix,
|
||||
} = this.props;
|
||||
|
||||
@@ -154,24 +196,27 @@ class Settings extends Component {
|
||||
render() {
|
||||
const {
|
||||
handleSubmit,
|
||||
|
||||
handleChange,
|
||||
|
||||
webIp,
|
||||
|
||||
webPort,
|
||||
|
||||
dnsIp,
|
||||
|
||||
dnsPort,
|
||||
|
||||
interfaces,
|
||||
|
||||
invalid,
|
||||
|
||||
config,
|
||||
|
||||
t,
|
||||
} = this.props;
|
||||
const {
|
||||
status: webStatus,
|
||||
can_autofix: isWebFixAvailable,
|
||||
} = config.web;
|
||||
const {
|
||||
status: dnsStatus,
|
||||
can_autofix: isDnsFixAvailable,
|
||||
} = config.dns;
|
||||
const { status: webStatus, can_autofix: isWebFixAvailable } = config.web;
|
||||
const { status: dnsStatus, can_autofix: isDnsFixAvailable } = config.dns;
|
||||
const { staticIp } = config;
|
||||
|
||||
return (
|
||||
@@ -180,30 +225,33 @@ class Settings extends Component {
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_settings_title</Trans>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-8">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_listen</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="web.ip"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
onChange={handleChange}
|
||||
>
|
||||
onChange={handleChange}>
|
||||
<option value={ALL_INTERFACES_IP}>
|
||||
{t('install_settings_all_interfaces')}
|
||||
{this.props.t('install_settings_all_interfaces')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_port</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="web.port"
|
||||
component={renderInputField}
|
||||
@@ -216,30 +264,31 @@ class Settings extends Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
{webStatus
|
||||
&& <div className="setup__error text-danger">
|
||||
{webStatus}
|
||||
{isWebFixAvailable
|
||||
&& <button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('web')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>}
|
||||
</div>}
|
||||
{webStatus && (
|
||||
<div className="setup__error text-danger">
|
||||
{webStatus}
|
||||
{isWebFixAvailable && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('web')}>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr className="divider--small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="setup__desc">
|
||||
<Trans>install_settings_interface_link</Trans>
|
||||
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={interfaces}
|
||||
address={webIp}
|
||||
port={webPort}
|
||||
/>
|
||||
<AddressList interfaces={interfaces} address={webIp} port={webPort} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,30 +297,31 @@ class Settings extends Component {
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_settings_dns</Trans>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-8">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_listen</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="dns.ip"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value={ALL_INTERFACES_IP}>
|
||||
{t('install_settings_all_interfaces')}
|
||||
</option>
|
||||
onChange={handleChange}>
|
||||
<option value={ALL_INTERFACES_IP}>{t('install_settings_all_interfaces')}</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-4">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_port</Trans>
|
||||
</label>
|
||||
|
||||
<Field
|
||||
name="dns.port"
|
||||
component={renderInputField}
|
||||
@@ -284,53 +334,62 @@ class Settings extends Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
{dnsStatus
|
||||
&& <>
|
||||
<div className="setup__error text-danger">
|
||||
{dnsStatus}
|
||||
{isDnsFixAvailable
|
||||
&& <button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('dns')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
{isDnsFixAvailable
|
||||
&& <div className="text-muted mb-2">
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_text</Trans>
|
||||
</p>
|
||||
<Trans components={[<li key="0">text</li>]}>
|
||||
autofix_warning_list
|
||||
{dnsStatus && (
|
||||
<>
|
||||
<div className="setup__error text-danger">
|
||||
{dnsStatus}
|
||||
{isDnsFixAvailable && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('dns')}>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isDnsFixAvailable && (
|
||||
<div className="text-muted mb-2">
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_text</Trans>
|
||||
</p>
|
||||
|
||||
<Trans components={[<li key="0">text</li>]}>autofix_warning_list</Trans>
|
||||
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_result</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{dnsPort === STANDARD_DNS_PORT &&
|
||||
!isDnsFixAvailable &&
|
||||
dnsStatus.includes(ADDRESS_IN_USE_TEXT) && (
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href={PORT_53_FAQ_LINK}
|
||||
key="0"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
link
|
||||
</a>,
|
||||
]}>
|
||||
port_53_faq_link
|
||||
</Trans>
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_result</Trans>
|
||||
</p>
|
||||
</div>}
|
||||
</>}
|
||||
{dnsPort === STANDARD_DNS_PORT && !isDnsFixAvailable
|
||||
&& dnsStatus.includes(ADDRESS_IN_USE_TEXT)
|
||||
&& <Trans
|
||||
components={[<a href={PORT_53_FAQ_LINK} key="0" target="_blank"
|
||||
rel="noopener noreferrer">link</a>]}>
|
||||
port_53_faq_link
|
||||
</Trans>}
|
||||
)}
|
||||
|
||||
<hr className="divider--small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="setup__desc">
|
||||
<Trans>install_settings_dns_desc</Trans>
|
||||
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={interfaces}
|
||||
address={dnsIp}
|
||||
port={dnsPort}
|
||||
isDns={true}
|
||||
/>
|
||||
<AddressList interfaces={interfaces} address={dnsIp} port={dnsPort} isDns={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -353,28 +412,6 @@ class Settings extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
handleChange: PropTypes.func,
|
||||
handleFix: PropTypes.func.isRequired,
|
||||
validateForm: PropTypes.func,
|
||||
webIp: PropTypes.string.isRequired,
|
||||
dnsIp: PropTypes.string.isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
webPort: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
dnsPort: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector(FORM_NAME.INSTALL);
|
||||
|
||||
const SettingsForm = connect((state) => {
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||
@media screen and (max-width: 767px) {
|
||||
input, select, textarea {
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +29,7 @@
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .setup__container {
|
||||
[data-theme='dark'] .setup__container {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--card-border-color);
|
||||
}
|
||||
@@ -45,7 +47,7 @@
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .setup__logo {
|
||||
[data-theme='dark'] .setup__logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
@@ -8,33 +8,31 @@ import flow from 'lodash/flow';
|
||||
import Controls from './Controls';
|
||||
import { FORM_NAME } from '../../helpers/constants';
|
||||
|
||||
let Submit = (props) => (
|
||||
interface SubmitProps {
|
||||
webIp: string;
|
||||
webPort: number;
|
||||
handleSubmit: (...args: unknown[]) => string;
|
||||
pristine: boolean;
|
||||
submitting: boolean;
|
||||
openDashboard: (...args: unknown[]) => unknown;
|
||||
}
|
||||
|
||||
let Submit = (props: SubmitProps) => (
|
||||
<div className="setup__step">
|
||||
<div className="setup__group">
|
||||
<h1 className="setup__title">
|
||||
<Trans>install_submit_title</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="setup__desc">
|
||||
<Trans>install_submit_desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<Controls
|
||||
openDashboard={props.openDashboard}
|
||||
ip={props.webIp}
|
||||
port={props.webPort}
|
||||
/>
|
||||
|
||||
<Controls openDashboard={props.openDashboard} ip={props.webIp} port={props.webPort} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Submit.propTypes = {
|
||||
webIp: PropTypes.string.isRequired,
|
||||
webPort: PropTypes.number.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
openDashboard: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('install');
|
||||
|
||||
Submit = connect((state) => {
|
||||
@@ -1,40 +1,64 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import * as actionCreators from '../../actions/install';
|
||||
|
||||
import { getWebAddress } from '../../helpers/helpers';
|
||||
import {
|
||||
INSTALL_FIRST_STEP,
|
||||
INSTALL_TOTAL_STEPS,
|
||||
ALL_INTERFACES_IP,
|
||||
DEBOUNCE_TIMEOUT,
|
||||
} from '../../helpers/constants';
|
||||
import { INSTALL_FIRST_STEP, INSTALL_TOTAL_STEPS, ALL_INTERFACES_IP, DEBOUNCE_TIMEOUT } from '../../helpers/constants';
|
||||
|
||||
import Loading from '../../components/ui/Loading';
|
||||
|
||||
import Greeting from './Greeting';
|
||||
|
||||
import Settings from './Settings';
|
||||
|
||||
import Auth from './Auth';
|
||||
|
||||
import Devices from './Devices';
|
||||
|
||||
import Submit from './Submit';
|
||||
|
||||
import Progress from './Progress';
|
||||
|
||||
import Toasts from '../../components/Toasts';
|
||||
|
||||
import Footer from '../../components/ui/Footer';
|
||||
|
||||
import Icons from '../../components/ui/Icons';
|
||||
import logo from '../../components/ui/svg/logo.svg';
|
||||
|
||||
import { Logo } from '../../components/ui/svg/logo';
|
||||
|
||||
import './Setup.css';
|
||||
import '../../components/ui/Tabler.css';
|
||||
|
||||
class Setup extends Component {
|
||||
interface SetupProps {
|
||||
getDefaultAddresses: (...args: unknown[]) => unknown;
|
||||
setAllSettings: (...args: unknown[]) => unknown;
|
||||
checkConfig: (...args: unknown[]) => unknown;
|
||||
nextStep: (...args: unknown[]) => unknown;
|
||||
prevStep: (...args: unknown[]) => unknown;
|
||||
install: {
|
||||
step: number;
|
||||
processingDefault: boolean;
|
||||
web;
|
||||
dns;
|
||||
staticIp;
|
||||
interfaces;
|
||||
};
|
||||
step?: number;
|
||||
web?: object;
|
||||
dns?: object;
|
||||
}
|
||||
|
||||
class Setup extends Component<SetupProps> {
|
||||
componentDidMount() {
|
||||
this.props.getDefaultAddresses();
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
handleFormSubmit = (values: any) => {
|
||||
const { staticIp, ...config } = values;
|
||||
|
||||
this.props.setAllSettings(config);
|
||||
};
|
||||
|
||||
@@ -45,11 +69,11 @@ class Setup extends Component {
|
||||
}
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
handleFix = (web, dns, set_static_ip) => {
|
||||
handleFix = (web: any, dns: any, set_static_ip: any) => {
|
||||
this.props.checkConfig({ web, dns, set_static_ip });
|
||||
};
|
||||
|
||||
openDashboard = (ip, port) => {
|
||||
openDashboard = (ip: any, port: any) => {
|
||||
let address = getWebAddress(ip, port);
|
||||
|
||||
if (ip === ALL_INTERFACES_IP) {
|
||||
@@ -57,21 +81,21 @@ class Setup extends Component {
|
||||
}
|
||||
|
||||
window.location.replace(address);
|
||||
}
|
||||
};
|
||||
|
||||
nextStep = () => {
|
||||
if (this.props.install.step < INSTALL_TOTAL_STEPS) {
|
||||
this.props.nextStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
prevStep = () => {
|
||||
if (this.props.install.step > INSTALL_FIRST_STEP) {
|
||||
this.props.prevStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderPage(step, config, interfaces) {
|
||||
renderPage(step: any, config: any, interfaces: any) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return <Greeting />;
|
||||
@@ -88,9 +112,7 @@ class Setup extends Component {
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Auth onSubmit={this.handleFormSubmit} />
|
||||
);
|
||||
return <Auth onSubmit={this.handleFormSubmit} />;
|
||||
case 4:
|
||||
return <Devices interfaces={interfaces} />;
|
||||
case 5:
|
||||
@@ -101,56 +123,37 @@ class Setup extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
processingDefault,
|
||||
step,
|
||||
web,
|
||||
dns,
|
||||
staticIp,
|
||||
interfaces,
|
||||
} = this.props.install;
|
||||
const { processingDefault, step, web, dns, staticIp, interfaces } = this.props.install;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{processingDefault && <Loading />}
|
||||
{!processingDefault
|
||||
&& <Fragment>
|
||||
{!processingDefault && (
|
||||
<Fragment>
|
||||
<div className="setup">
|
||||
<div className="setup__container">
|
||||
<img src={logo} className="setup__logo" alt="logo" />
|
||||
<Logo className="setup__logo" />
|
||||
{this.renderPage(step, { web, dns, staticIp }, interfaces)}
|
||||
<Progress step={step} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<Toasts />
|
||||
|
||||
<Icons />
|
||||
</Fragment>
|
||||
}
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Setup.propTypes = {
|
||||
getDefaultAddresses: PropTypes.func.isRequired,
|
||||
setAllSettings: PropTypes.func.isRequired,
|
||||
checkConfig: PropTypes.func.isRequired,
|
||||
nextStep: PropTypes.func.isRequired,
|
||||
prevStep: PropTypes.func.isRequired,
|
||||
install: PropTypes.object.isRequired,
|
||||
step: PropTypes.number,
|
||||
web: PropTypes.object,
|
||||
dns: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
const { install, toasts } = state;
|
||||
const props = { install, toasts };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Setup);
|
||||
export default connect(mapStateToProps, actionCreators)(Setup);
|
||||
Reference in New Issue
Block a user