all: sync with master, upd chlog

This commit is contained in:
Eugene Burkov
2025-03-11 13:36:04 +03:00
parent 805de59805
commit 474cba52f0
166 changed files with 8809 additions and 10440 deletions

View File

@@ -7,7 +7,7 @@ import { MOBILE_CONFIG_LINKS } from '../../../helpers/constants';
import Tabs from '../Tabs';
import MobileConfigForm from './MobileConfigForm';
import { MobileConfigForm } from './MobileConfigForm';
import { RootState } from '../../../initialState';
interface renderLiProps {
@@ -346,7 +346,7 @@ interface GuideProps {
dnsAddresses?: unknown[];
}
const Guide = ({ dnsAddresses }: GuideProps) => {
export const Guide = ({ dnsAddresses }: GuideProps) => {
const { t } = useTranslation();
const serverName = useSelector((state: RootState) => state.encryption?.server_name);
@@ -381,5 +381,3 @@ const Guide = ({ dnsAddresses }: GuideProps) => {
Guide.defaultProps = {
dnsAddresses: [],
};
export default Guide;

View File

@@ -1,32 +1,31 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import { Controller, useForm } from 'react-hook-form';
import i18next from 'i18next';
import cn from 'classnames';
import { getPathWithQueryString } from '../../../helpers/helpers';
import { CLIENT_ID_LINK, FORM_NAME, MOBILE_CONFIG_LINKS, STANDARD_HTTPS_PORT } from '../../../helpers/constants';
import { renderInputField, renderSelectField, toNumber } from '../../../helpers/form';
import { CLIENT_ID_LINK, MOBILE_CONFIG_LINKS, STANDARD_HTTPS_PORT } from '../../../helpers/constants';
import { toNumber } from '../../../helpers/form';
import {
validateConfigClientId,
validateServerName,
validatePort,
validateIsSafePort,
} from '../../../helpers/validators';
import { RootState } from '../../../initialState';
import { Input } from '../Controls/Input';
import { Select } from '../Controls/Select';
const getDownloadLink = (host: any, clientId: any, protocol: any, invalid: any) => {
const getDownloadLink = (host: string, clientId: string, protocol: string, invalid: boolean) => {
if (!host || invalid) {
return (
<button type="button" className="btn btn-success btn-standard btn-large disabled">
<Trans>download_mobileconfig</Trans>
{i18next.t('download_mobileconfig')}
</button>
);
}
const linkParams: { host: string, client_id?: string } = { host };
const linkParams: { host: string; client_id?: string } = { host };
if (clientId) {
linkParams.client_id = clientId;
@@ -37,29 +36,48 @@ const getDownloadLink = (host: any, clientId: any, protocol: any, invalid: any)
href={getPathWithQueryString(protocol, linkParams)}
className={cn('btn btn-success btn-standard btn-large')}
download>
<Trans>download_mobileconfig</Trans>
{i18next.t('download_mobileconfig')}
</a>
);
};
interface MobileConfigFormProps {
invalid: boolean;
}
type FormValues = {
host: string;
clientId: string;
protocol: string;
port?: number;
};
const MobileConfigForm = ({ invalid }: MobileConfigFormProps) => {
const formValues = useSelector((state: RootState) => state.form[FORM_NAME.MOBILE_CONFIG]?.values);
type Props = {
initialValues?: FormValues;
};
if (!formValues) {
return null;
}
const defaultFormValues = {
host: '',
clientId: '',
protocol: MOBILE_CONFIG_LINKS.DOT,
port: undefined,
};
const { host, clientId, protocol, port } = formValues;
export const MobileConfigForm = ({ initialValues }: Props) => {
const { t } = useTranslation();
const githubLink = (
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer">
text
</a>
);
const {
watch,
control,
formState: { isValid },
} = useForm<FormValues>({
mode: 'onBlur',
defaultValues: {
...defaultFormValues,
...initialValues,
},
});
const protocol = watch('protocol');
const host = watch('host');
const clientId = watch('clientId');
const port = watch('port');
const getHostName = () => {
if (port && port !== STANDARD_HTTPS_PORT && protocol === MOBILE_CONFIG_LINKS.DOH) {
@@ -75,33 +93,47 @@ const MobileConfigForm = ({ invalid }: MobileConfigFormProps) => {
<div className="form__group form__group--settings">
<div className="row">
<div className="col">
<label htmlFor="host" className="form__label">
{i18next.t('dhcp_table_hostname')}
</label>
<Field
<Controller
name="host"
type="text"
component={renderInputField}
className="form-control"
placeholder={i18next.t('form_enter_hostname')}
validate={validateServerName}
control={control}
rules={{ validate: validateServerName }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="mobile_config_host"
label={t('dhcp_table_hostname')}
placeholder={t('form_enter_hostname')}
error={fieldState.error?.message}
/>
)}
/>
</div>
{protocol === MOBILE_CONFIG_LINKS.DOH && (
<div className="col">
<label htmlFor="port" className="form__label">
{i18next.t('encryption_https')}
</label>
<Field
<Controller
name="port"
type="number"
component={renderInputField}
className="form-control"
placeholder={i18next.t('encryption_https')}
validate={[validatePort, validateIsSafePort]}
normalize={toNumber}
control={control}
rules={{
validate: {
range: (value) => validatePort(value) || true,
safety: (value) => validateIsSafePort(value) || true,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="mobile_config_port"
label={t('encryption_https')}
placeholder={t('encryption_https')}
error={fieldState.error?.message}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/>
</div>
)}
@@ -110,39 +142,49 @@ const MobileConfigForm = ({ invalid }: MobileConfigFormProps) => {
<div className="form__group form__group--settings">
<label htmlFor="clientId" className="form__label form__label--with-desc">
{i18next.t('client_id')}
{t('client_id')}
</label>
<div className="form__desc form__desc--top">
<Trans components={{ a: githubLink }}>client_id_desc</Trans>
<Trans
components={{ a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" /> }}>
client_id_desc
</Trans>
</div>
<Field
<Controller
name="clientId"
type="text"
component={renderInputField}
className="form-control"
placeholder={i18next.t('client_id_placeholder')}
validate={validateConfigClientId}
control={control}
rules={{
validate: validateConfigClientId,
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="mobile_config_client_id"
placeholder={t('client_id_placeholder')}
error={fieldState.error?.message}
/>
)}
/>
</div>
<div className="form__group form__group--settings">
<label htmlFor="protocol" className="form__label">
{i18next.t('protocol')}
</label>
<Field name="protocol" type="text" component={renderSelectField} className="form-control">
<option value={MOBILE_CONFIG_LINKS.DOT}>{i18next.t('dns_over_tls')}</option>
<option value={MOBILE_CONFIG_LINKS.DOH}>{i18next.t('dns_over_https')}</option>
</Field>
<Controller
name="protocol"
control={control}
render={({ field }) => (
<Select {...field} label={t('protocol')} data-testid="mobile_config_protocol">
<option value={MOBILE_CONFIG_LINKS.DOT}>{t('dns_over_tls')}</option>
<option value={MOBILE_CONFIG_LINKS.DOH}>{t('dns_over_https')}</option>
</Select>
)}
/>
</div>
</div>
{getDownloadLink(getHostName(), clientId, protocol, invalid)}
{getDownloadLink(getHostName(), clientId, protocol, !isValid)}
</form>
);
};
export default reduxForm({ form: FORM_NAME.MOBILE_CONFIG })(MobileConfigForm);

View File

@@ -1 +1 @@
export { default } from './Guide';
export * from './Guide';