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: 4c89cf720 1085d59a6
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Mon Jun 10 17:22:20 2024 +0200

    merge

commit 4c89cf7209
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jun 6 13:27:18 2024 +0300

    remove install from initial state

commit b943f2011f
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 23:10:55 2024 +0200

    frontend production build fix

commit cd1be2d66d
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 20:23:14 2024 +0200

    production build quickfix

commit 7b8ac01fc2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 5 19:57:31 2024 +0300

    all: upd node docker

commit 02afed66d5
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 18:23:12 2024 +0200

    changelog fixes

commit 9c0f736f0c
Merge: 62c4fbf1e e04775c4f
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 18:18:29 2024 +0200

    merge

commit 62c4fbf1e3
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 16:22:22 2024 +0200

    empty line in changelog

commit 76b1e44a93
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 16:20:37 2024 +0200

    changelog

commit f783e90040
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 16:19:13 2024 +0200

    filters.js -> filters.ts

commit 3d4ce6554c
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 16:18:03 2024 +0200

    generated file removed

commit e35ba58f2a
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 15:45:21 2024 +0200

    rollback unwanted changes

commit 1f30d4216d
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 15:27:36 2024 +0200

    review fix

commit 6cd4e44f07
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Jun 5 11:55:39 2024 +0200

    missing generated file restoresd

commit 2ab738b303
Author: 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:
Igor Lobanov
2024-06-10 18:42:23 +03:00
parent 1085d59a65
commit 1afe226ce8
296 changed files with 32122 additions and 32651 deletions

View File

@@ -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;

View 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;

View File

@@ -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({

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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>
);

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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);
}

View File

@@ -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) => {

View File

@@ -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);