Files
AdGuardHome/client/src/install/Setup/Settings.tsx
Ildar Kamalov 17c4c26ea8 fix query log
2025-02-14 17:18:20 +03:00

464 lines
16 KiB
TypeScript

import React, { useEffect, useCallback } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import i18n from 'i18next';
import Controls from './Controls';
import AddressList from './AddressList';
import { getInterfaceIp } from '../../helpers/helpers';
import {
ALL_INTERFACES_IP,
ADDRESS_IN_USE_TEXT,
PORT_53_FAQ_LINK,
STATUS_RESPONSE,
STANDARD_DNS_PORT,
STANDARD_WEB_PORT,
MAX_PORT,
MIN_PORT,
} from '../../helpers/constants';
import { validateRequiredValue } from '../../helpers/validators';
import { InstallInterface } from '../../initialState';
import { Input } from '../../components/ui/Controls/Input';
import { Select } from '../../components/ui/Controls/Select';
import { toNumber } from '../../helpers/form';
const validateInstallPort = (value: number) => {
if (value < MIN_PORT || value > MAX_PORT) {
return i18n.t('form_error_port');
}
return undefined;
};
export type WebConfig = {
ip: string;
port: number;
};
export type DnsConfig = {
ip: string;
port: number;
};
export type SettingsFormValues = {
web: WebConfig;
dns: DnsConfig;
};
type StaticIpType = {
ip: string;
static: string;
};
export type ConfigType = {
web: {
ip: string;
port?: number;
status: string;
can_autofix: boolean;
};
dns: {
ip: string;
port?: number;
status: string;
can_autofix: boolean;
};
staticIp: StaticIpType;
};
type Props = {
handleSubmit: (data: SettingsFormValues) => void;
handleChange?: (data: SettingsFormValues) => unknown;
handleFix: (web: WebConfig, dns: DnsConfig, set_static_ip: boolean) => void;
validateForm: (data: SettingsFormValues) => void;
config: ConfigType;
interfaces: InstallInterface[];
initialValues?: object;
};
const renderInterfaces = (interfaces: InstallInterface[]) =>
Object.values(interfaces).map((option: InstallInterface) => {
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 null;
});
export const Settings = ({ handleSubmit, handleFix, validateForm, config, interfaces }: Props) => {
const { t } = useTranslation();
const defaultValues = {
web: {
ip: config.web.ip || ALL_INTERFACES_IP,
port: config.web.port || STANDARD_WEB_PORT,
},
dns: {
ip: config.dns.ip || ALL_INTERFACES_IP,
port: config.dns.port || STANDARD_DNS_PORT,
},
};
const {
control,
watch,
handleSubmit: reactHookFormSubmit,
formState: { isValid },
} = useForm<SettingsFormValues>({
defaultValues,
mode: 'onBlur',
});
const watchFields = watch();
const { status: webStatus, can_autofix: isWebFixAvailable } = config.web;
const { status: dnsStatus, can_autofix: isDnsFixAvailable } = config.dns;
const { staticIp } = config;
const webIpVal = watch('web.ip');
const webPortVal = watch('web.port');
const dnsIpVal = watch('dns.ip');
const dnsPortVal = watch('dns.port');
useEffect(() => {
const webPortError = validateInstallPort(webPortVal);
const dnsPortError = validateInstallPort(dnsPortVal);
if (!isValid || webPortError || dnsPortError) {
return;
}
validateForm({
web: {
ip: webIpVal,
port: webPortVal,
},
dns: {
ip: dnsIpVal,
port: dnsPortVal,
},
});
}, [webIpVal, webPortVal, dnsIpVal, dnsPortVal]);
const handleAutofix = (type: string) => {
const web = {
ip: watchFields.web?.ip,
port: watchFields.web?.port,
autofix: false,
};
const dns = {
ip: watchFields.dns?.ip,
port: watchFields.dns?.port,
autofix: false,
};
const set_static_ip = false;
if (type === 'web') {
web.autofix = true;
} else {
dns.autofix = true;
}
handleFix(web, dns, set_static_ip);
};
const handleStaticIp = (ip: string) => {
const web = {
ip: watchFields.web?.ip,
port: watchFields.web?.port,
autofix: false,
};
const dns = {
ip: watchFields.dns?.ip,
port: watchFields.dns?.port,
autofix: false,
};
const set_static_ip = true;
if (window.confirm(t('confirm_static_ip', { ip }))) {
handleFix(web, dns, set_static_ip);
}
};
const getStaticIpMessage = useCallback(
(staticIp: StaticIpType) => {
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={() => handleStaticIp(ip)}>
<Trans>set_static_ip</Trans>
</button>
</>
);
case STATUS_RESPONSE.ERROR:
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>
);
default:
return null;
}
},
[handleStaticIp],
);
const onSubmit = (data: any) => {
validateForm(data);
handleSubmit(data);
};
return (
<form className="setup__step" onSubmit={reactHookFormSubmit(onSubmit)}>
<div className="setup__group">
<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>
<Controller
name="web.ip"
control={control}
render={({ field }) => (
<Select {...field} data-testid="install_web_ip">
<option value={ALL_INTERFACES_IP}>
{t('install_settings_all_interfaces')}
</option>
{renderInterfaces(interfaces)}
</Select>
)}
/>
</div>
</div>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Controller
name="web.port"
control={control}
rules={{
validate: {
required: validateRequiredValue,
installPort: validateInstallPort,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="install_web_port"
placeholder={STANDARD_WEB_PORT.toString()}
error={fieldState.error?.message}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/>
</div>
</div>
<div className="col-12">
{webStatus && (
<div className="setup__error text-danger">
{webStatus}
{isWebFixAvailable && (
<button
type="button"
data-testid="install_web_fix"
className="btn btn-secondary btn-sm ml-2"
onClick={() => 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={watchFields.web?.ip}
port={watchFields.web?.port}
/>
</div>
</div>
</div>
<div className="setup__group">
<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>
<Controller
name="dns.ip"
control={control}
render={({ field }) => (
<Select {...field} data-testid="install_dns_ip">
<option value={ALL_INTERFACES_IP}>
{t('install_settings_all_interfaces')}
</option>
{renderInterfaces(interfaces)}
</Select>
)}
/>
</div>
</div>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Controller
name="dns.port"
control={control}
rules={{
required: t('form_error_required'),
validate: {
required: validateRequiredValue,
installPort: validateInstallPort,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="install_dns_port"
error={fieldState.error?.message}
placeholder={STANDARD_WEB_PORT.toString()}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/>
</div>
</div>
<div className="col-12">
{dnsStatus && (
<>
<div className="setup__error text-danger">
{dnsStatus}
{isDnsFixAvailable && (
<button
type="button"
data-testid="install_dns_fix"
className="btn btn-secondary btn-sm ml-2"
onClick={() => 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>
)}
</>
)}
{watchFields.dns?.port === 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={watchFields.dns?.ip}
port={watchFields.dns?.port}
isDns={true}
/>
</div>
</div>
</div>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>static_ip</Trans>
</div>
<div className="mb-2">
<Trans>static_ip_desc</Trans>
</div>
{getStaticIpMessage(staticIp)}
</div>
<Controls invalid={!isValid} />
</form>
);
};