Загрузка данных
import { DisableMonitoringButton } from '@/entities/Monitoring';
import { ModelSelect } from '@/features/Settings/selects';
import { EquipmentModel } from '@/features/Settings/selects/ModelSelect';
import { DeviceModel } from '@/shared/api/controller/client';
import { TestIdsConsts } from '@/shared/consts/TestIdsConsts';
import { isMultilineValue } from '@/utils';
import { InterfaceSelect } from '@/widgets/Devices/ui/InterfaceSelect/InterfaceSelect';
import { EquipmentNetInterfaceListForm } from '@/widgets/Monitoring/components/EquipmentNetInterfaceListForm/EquipmentNetInterfaceListForm';
import {
IpInput,
SegmentButtonProps,
Switch,
TextField,
} from '@shared/components';
import { MonitoringConsts } from '@shared/consts/MonitoringConsts';
import { useFieldValidation } from '@shared/hooks';
import { InputMask } from '@shared/lib/InputMask';
import { ProtocolIpSelect } from '@widgets/Devices/ui/ProtocolIpSelect/ProtocolIpSelect';
import { Select8021X } from '@widgets/Devices/ui/Select802-1X/Select802-1X';
import { useEffect } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
AVLAN_VALUES,
BASIC_VALUES,
ETHERNET_VALUES,
OTHER_VALUES,
} from './model/formNames';
import { CreateDeviceFormData } from './model/types';
import { createDeviceFormSchema } from './validation/validator';
type Props = {
setModel?: (model: DeviceModel | null) => void;
};
/**
* @requires refactoring
*/
export const CreateDeviceForm = ({ setModel }: Props) => {
const { t } = useTranslation();
const { control, watch, setValue, trigger } =
useFormContext<CreateDeviceFormData>();
const { isRequiredField } = useFieldValidation(createDeviceFormSchema);
const interfaceType = watch(OTHER_VALUES.type);
const hasSipUri = watch(OTHER_VALUES.hasSipUri);
const isEthernet = interfaceType === 'ethernet';
const isAvLan = interfaceType === 'av-lan';
useEffect(() => {
if (interfaceType !== 'ethernet') {
setValue(ETHERNET_VALUES.ip, '');
setValue(ETHERNET_VALUES.port, undefined);
setValue(ETHERNET_VALUES.ieee8021x, undefined);
setValue(ETHERNET_VALUES.protocolIp, 'dhcp');
}
if (interfaceType !== 'av-lan') {
setValue(AVLAN_VALUES.ipAvLan, '');
setValue(OTHER_VALUES.netInterface, false);
setValue(OTHER_VALUES.netInterfaceInfo, []);
}
if (interfaceType !== 'ethernet' && interfaceType !== 'av-lan') {
setValue(ETHERNET_VALUES.mac, '');
setValue(OTHER_VALUES.netInterface, false);
setValue(OTHER_VALUES.netInterfaceInfo, []);
}
trigger(OTHER_VALUES.type);
}, [interfaceType]);
useEffect(() => {
if (!hasSipUri) {
setValue(OTHER_VALUES.sipUri, '');
}
}, [hasSipUri]);
const getTestId = TestIdsConsts.getEquipmentTestId('create');
return (
<>
{Object.values(BASIC_VALUES).map((inputName) => (
<Controller
key={inputName}
name={inputName}
control={control}
render={({
field: { value, name, ...rest },
fieldState: { error },
}) => (
<TextField
data-fui-tid={getTestId(inputName)}
fullWidth
label={t(`controller:${name}`)}
error={!!error?.message}
helperText={t(error?.message as string)}
multiline={isMultilineValue(name)}
inputProps={{
'data-testid': name,
}}
required={isRequiredField(name)}
value={value}
limitedSymbols={
MonitoringConsts.EQUIPMENT_FIELDS_LIMITS[name] ?? undefined
}
{...rest}
/>
)}
/>
))}
<Controller
name={OTHER_VALUES.modelId}
control={control}
render={({ field: { name, ...rest }, fieldState: { error } }) => (
<ModelSelect
data-fui-tid={getTestId(name)}
type={EquipmentModel.device}
data-testid={name}
error={!!error?.message}
setModel={setModel}
handleClearButtonClick={() => setModel?.(null)}
helperText={t(error?.message as string)}
required={isRequiredField(name)}
{...rest}
/>
)}
/>
<Controller
name={OTHER_VALUES.typeName}
control={control}
render={({ field: { value, name } }) => (
<TextField
data-fui-tid={getTestId(name)}
label={t(`controller:${name}`)}
value={value}
readOnly
/>
)}
/>
{hasSipUri && (
<Controller
name={OTHER_VALUES.sipUri}
control={control}
render={({ field: { name, ...rest }, fieldState: { error } }) => (
<TextField
data-fui-tid={getTestId(name)}
placeholder={t('equipment:sipUriPlaceholder')}
fullWidth
label='SIP URI'
error={!!error?.message}
helperText={t(error?.message as string)}
inputProps={{
'data-testid': name,
}}
required={isRequiredField(name)}
limitedSymbols={
MonitoringConsts.EQUIPMENT_FIELDS_LIMITS[name] ?? undefined
}
{...rest}
/>
)}
/>
)}
<Controller
name={OTHER_VALUES.manufacturerName}
control={control}
render={({ field: { value, name } }) => (
<TextField
data-fui-tid={getTestId(name)}
label={t(`controller:${name}`)}
value={value}
readOnly
/>
)}
/>
<Controller
name={OTHER_VALUES.bidirectional}
control={control}
render={({ field: { value, name, ...rest } }) => (
<Switch
data-fui-tid={getTestId(name)}
inputProps={{
'data-testid': name,
}}
label={t(`device:${name}`)}
checked={!!value}
required={isRequiredField(name)}
{...rest}
/>
)}
/>
<Controller
name={OTHER_VALUES.type}
control={control}
render={({ field: { name, ...rest }, fieldState: { error } }) => (
<InterfaceSelect
data-fui-tid={getTestId(name)}
data-testid={name}
required={isRequiredField(name)}
error={!!error?.message}
helperText={t(error?.message as string)}
{...rest}
/>
)}
/>
{isEthernet && (
<>
{Object.values(ETHERNET_VALUES).map((inputName) => {
const isIp = inputName === 'ip';
const isMac = inputName === 'mac';
const protocolIp = inputName === 'protocolIp';
const ieee8021x = inputName === 'ieee8021x';
const mask = (() => {
if (isMac) {
return InputMask.mac;
}
return undefined;
})();
if (protocolIp) {
return (
<Controller
name={ETHERNET_VALUES.protocolIp}
control={control}
render={({
field: { name, ...rest },
fieldState: { error },
}) => (
<ProtocolIpSelect
data-fui-tid={getTestId(inputName)}
error={!!error?.message}
helperText={t(error?.message as string)}
required
{...rest}
/>
)}
/>
);
}
if (ieee8021x) {
return (
<Controller
name={ETHERNET_VALUES.ieee8021x}
control={control}
render={({
field: { name, ...rest },
fieldState: { error },
}) => (
<Select8021X
data-fui-tid={getTestId(inputName)}
error={!!error?.message}
helperText={t(error?.message as string)}
required
{...rest}
/>
)}
/>
);
}
return (
<Controller
key={inputName}
name={inputName}
control={control}
render={({
field: { value, name, ...rest },
fieldState: { error },
}) => {
if (isIp) {
return (
<IpInput
data-fui-tid={getTestId(inputName)}
value={(value as string) ?? ''}
error={!!error?.message}
helperText={t(error?.message as string)}
label={name}
// HACK: use updateDeviceSchema to check required ip
required={(() => {
switch (inputName) {
case 'ip':
return true;
default:
isRequiredField(name);
}
})()}
{...rest}
/>
);
}
return (
<TextField
data-fui-tid={getTestId(inputName)}
fullWidth
label={t(`controller:${name}`)}
error={!!error?.message}
helperText={t(error?.message as string)}
multiline={isMultilineValue(name)}
inputProps={{
'data-testid': name,
}}
// HACK: use updateDeviceSchema to check required mac
required={(() => {
switch (inputName) {
case 'mac':
return true;
default:
return isRequiredField(name);
}
})()}
value={value}
mask={mask}
limitedSymbols={
MonitoringConsts.EQUIPMENT_FIELDS_LIMITS[name] ??
undefined
}
{...rest}
/>
);
}}
/>
);
})}
</>
)}
{isAvLan && (
<>
{Object.values(AVLAN_VALUES).map((inputName) => {
const isMac = inputName === 'mac';
const isIpAvLan = inputName === 'ipAvLan';
return (
<Controller
key={inputName}
name={inputName}
control={control}
render={({
field: { value, name, ...rest },
fieldState: { error },
}) => {
if (isIpAvLan) {
return (
<IpInput
data-fui-tid={getTestId(inputName)}
value={(value as string) ?? ''}
error={!!error?.message}
helperText={t(error?.message as string)}
label={name}
required
{...rest}
/>
);
}
return (
<TextField
data-fui-tid={getTestId(inputName)}
fullWidth
label={t(`controller:${name}`)}
error={!!error?.message}
helperText={t(error?.message as string)}
multiline={isMultilineValue(name)}
inputProps={{
'data-testid': name,
}}
// HACK: use updateDeviceSchema to check required mac
required={(() => {
switch (inputName) {
case 'mac':
return false;
default:
return isRequiredField(name);
}
})()}
value={value}
mask={isMac ? InputMask.mac : undefined}
{...rest}
/>
);
}}
/>
);
})}
</>
)}
{isEthernet && (
<>
<Controller
name={OTHER_VALUES.netInterface}
control={control}
render={({ field: { onChange, value, name } }) => (
<Switch
data-fui-tid={getTestId(name)}
margin='normal'
label={t(`controller:${name}`)}
checked={!!value}
onChange={onChange}
required={isRequiredField(name)}
/>
)}
/>
<EquipmentNetInterfaceListForm
name={OTHER_VALUES.netInterfaceInfo}
netInterfaceName={OTHER_VALUES.netInterface}
isRequiredField={isRequiredField}
/>
</>
)}
<Controller
name={OTHER_VALUES.disableMonitoring}
control={control}
render={({ field: { onChange, value } }) => {
const handleChange: SegmentButtonProps['onChange'] = (
_,
newValue,
) => {
onChange(newValue);
};
return (
<DisableMonitoringButton value={!!value} onChange={handleChange} />
);
}}
/>
</>
);
};
import { Ieee8021X } from '@/widgets/Monitoring/model/networkEquipment.model';
import { Select, SelectProps } from '@shared/components';
export const Select8021X = ({ value, ...rest }: SelectProps) => {
const options = [
{ label: 'MAB', value: Ieee8021X.MAB },
{ label: 'EAP-TLS', value: Ieee8021X.EAP_TLS },
{ label: 'NOT_SUPPORT', value: Ieee8021X.NOT_SUPPORT },
];
const normalizedValue = value ?? '';
console.log('Select8021X value:', value);
console.log('Normalized value passed to Select:', normalizedValue);
return (
<Select
options={options}
label='802.1X'
value={normalizedValue}
{...rest}
/>
);
};
import { InterfaceType } from '@/shared/api/controller/client';
import { Ieee8021X } from '@/widgets/Monitoring/model/networkEquipment.model';
import { Schema } from '@shared/lib/Schema';
import { SchemaMessage } from '@shared/lib/SchemaMessage';
import { Validator } from '@shared/lib/Validator';
import * as yup from 'yup';
import { array, boolean, object, string } from 'yup';
import { OTHER_VALUES } from '../model/formNames';
const validator = new Validator();
const schemaMessage = new SchemaMessage();
export const createDeviceFormSchema = object().shape(
{
name: string().required(schemaMessage.requiredMessage),
description: string().notRequired(),
bidirectional: boolean().notRequired(),
modelId: string().required(schemaMessage.requiredMessage),
sipUri: Schema.sipUriSchema,
ip: string().when('type', ([type]: InterfaceType[], schema) => {
if (type === 'ethernet') {
return schema
.required(schemaMessage.requiredMessage)
.matches(validator.ipV4RegExp, schemaMessage.wrongFormatMessage);
}
return schema.notRequired().nullable();
}),
mac: string().when('type', ([type]: InterfaceType[], schema) => {
if (type === 'ethernet') {
return schema
.required(schemaMessage.requiredMessage)
.matches(validator.macRegExp, schemaMessage.wrongFormatMessage);
}
if (type === 'av-lan') {
return schema.notRequired().nullable(); // или можно просто .optional()
}
return schema.notRequired().nullable();
}),
controlPage: string().when('controlPage', (controlPage, schema) => {
if (!controlPage) {
return schema.notRequired().nullable();
}
return schema
.matches(validator.controlPageRegExp, schemaMessage.wrongFormatMessage)
.notRequired();
}),
hardwareId: Schema.hardwareId,
type: Schema.interfaceType,
ieee8021x: yup
.mixed<Ieee8021X>()
.oneOf(
[Ieee8021X.MAB, Ieee8021X.EAP_TLS, Ieee8021X.NOT_SUPPORT],
schemaMessage.ieee8021XMessage,
)
.when('type', ([type]: InterfaceType[], schema) => {
if (type === 'ethernet') {
return schema.required(schemaMessage.requiredMessage);
}
return schema.notRequired().nullable();
}),
protocolIp: Schema.protocolIp.when('type', {
is: 'ethernet',
then: (schema) => schema.required(schemaMessage.requiredMessage),
otherwise: (schema) => schema.notRequired(),
}),
firmware: string().nullable(),
serialNumber: string().nullable(),
inventoryNumber: string().nullable(),
ipAvLan: string().when('type', ([type]: InterfaceType[], schema) => {
if (type === 'av-lan') {
return schema
.matches(validator.ipV4RegExp, schemaMessage.wrongFormatMessage)
.required();
}
return schema.notRequired().nullable();
}),
localAddress: Schema.localAddressCreateSchema,
port: Schema.portNotRequired,
netInterface: boolean().optional(),
disableMonitoring: boolean().optional(),
netInterfaceInfo: array().when(
[OTHER_VALUES.netInterface, OTHER_VALUES.type],
([netInterface, type], schema) => {
if (netInterface && type === 'ethernet') {
return schema.of(
object({
nameId: string().required(schemaMessage.requiredMessage),
ip: Schema.ipV4NotRequired,
mac: Schema.macNotRequired,
subnetMask: Schema.ipV4NotRequired,
gateway: Schema.ipV4NotRequired,
}),
);
} else {
return schema.optional().default([]);
}
},
),
},
[
['mac', 'mac'],
['port', 'port'],
['controlPage', 'controlPage'],
['localAddress', 'localAddress'],
],
);
import { AppConsts } from '@/shared/consts';
import {
Ieee8021X,
ProtocolIp,
Segment,
} from '@/widgets/Monitoring/model/networkEquipment.model';
import { InterfaceType } from '@shared/api/controller/client';
import { SchemaMessage } from '@shared/lib/SchemaMessage';
import { Validator } from '@shared/lib/Validator';
import * as yup from 'yup';
import { StringSchema, number, string } from 'yup';
export class Schema {
private static validator: Validator = new Validator();
private static schemaMessage: SchemaMessage = new SchemaMessage();
public static ipV4Required = string()
.required(Schema.schemaMessage.requiredMessage)
.test('ip', this.schemaMessage.wrongFormatMessage, this.validator.ipV4);
public static ipV4 = string().test(
'ip',
this.schemaMessage.wrongFormatMessage,
this.validator.ipV4,
);
public static ipV4NotRequired = string()
.nullable()
.notRequired()
.test('ip-validation', this.schemaMessage.wrongFormatMessage, (value) => {
if (!value || value.trim() === '') return true;
return this.validator.ipV4RegExp.test(value);
});
public static deviceIpV4 = string()
.required(Schema.schemaMessage.requiredMessage)
.matches(
Schema.validator.ipV4RegExp,
Schema.schemaMessage.wrongFormatMessage,
);
public static deviceMac = string()
.required(Schema.schemaMessage.requiredMessage)
.matches(
Schema.validator.macRegExp,
Schema.schemaMessage.wrongFormatMessage,
);
public static macNotRequired = string()
.nullable()
.notRequired()
.test('mac-validation', this.schemaMessage.wrongFormatMessage, (value) => {
if (!value || value.trim() === '') return true;
return this.validator.macRegExp.test(value);
});
public static portNotRequired = number().when('port', ([port], schema) => {
if (!port) {
return schema
.transform((value) => (Number.isNaN(value) ? null : value))
.notRequired()
.nullable();
}
return schema
.transform((value) => (Number.isNaN(value) ? null : value))
.test(
'port',
this.schemaMessage.interfacePortMessage,
this.validator.port,
);
});
public static portRequired = number()
.required(this.schemaMessage.requiredMessage)
.transform((value) => (isNaN(value) ? null : value))
.test('port', this.schemaMessage.interfacePortMessage, this.validator.port);
public static telnetPort = string().max(
5,
this.schemaMessage.getMaxMessage(5),
);
public static mac = string().when('port', (port: InterfaceType[], schema) => {
if (port.includes('ethernet')) {
return schema
.required(this.schemaMessage.requiredMessage)
.matches(
this.validator.macRegExp,
this.schemaMessage.wrongFormatMessage,
);
}
return schema;
});
public static hardwareId: StringSchema = yup
.string()
.required(this.schemaMessage.requiredMessage)
.max(50, this.schemaMessage.getMaxMessage(50));
public static interfaceType = yup
.mixed<InterfaceType>()
.oneOf(
['serial', 'infrared', 'ethernet', 'av-lan'],
this.schemaMessage.interfaceTypeMessage,
)
.required(this.schemaMessage.requiredMessage);
public static protocolIp = yup
.mixed<ProtocolIp>()
.oneOf(
[ProtocolIp.STATIC, ProtocolIp.DHCP],
this.schemaMessage.protocolIpMessage,
);
public static ieee8021x = yup
.mixed<Ieee8021X>()
.oneOf(
[Ieee8021X.MAB, Ieee8021X.EAP_TLS, Ieee8021X.NOT_SUPPORT],
this.schemaMessage.ieee8021XMessage,
);
public static segment = yup
.mixed<Segment>()
.oneOf([Segment.SIGMA, Segment.OMEGA], this.schemaMessage.segmentMessage);
public static controlPage = string().test(
'controlPage',
this.schemaMessage.wrongFormatMessage,
this.validator.controlPage,
);
public static timeZoneSchema = string().test(
'timeZone',
this.schemaMessage.wrongFormatMessage,
this.validator.timeZone,
);
public static ipV4FieldSchema = string().test(
'ipV4Field',
this.schemaMessage.wrongFormatMessage,
this.validator.ipV4Field,
);
public static localAddressCreateSchema = string().when(
'localAddress',
([localAddress], schema) => {
if (!localAddress) {
return schema
.transform((value) => value || undefined)
.notRequired()
.nullable();
}
return schema.matches(
this.validator.ipV4RegExp,
this.schemaMessage.wrongFormatMessage,
);
},
);
public static sipUriSchema = string()
.optional()
.test('sipUri-format', this.schemaMessage.sipUriFormatMessage, (value) => {
if (!value) {
return true;
}
const match = value.match(AppConsts.SIP_URI_REGEX);
return !!match;
})
.test('sipUri-domain', this.schemaMessage.sipUriDomainMessage, (value) => {
if (!value) {
return true;
}
const match = value.match(AppConsts.SIP_URI_REGEX);
if (!match) {
return true;
}
const { domain } = match.groups ?? {};
const allowedDomains = [
'sber.ru',
'sberbank.ru',
'voip.sigma.sbrf.ru',
'sberbank-pb.ru',
'sberbank-cib.ru',
'ensys-edge.sber.ru',
];
return allowedDomains.includes(domain);
});
}
export enum ProtocolIp {
DHCP = 'dhcp',
STATIC = 'static',
}
export enum Ieee8021X {
MAB = 'MAB',
EAP_TLS = 'EAP-TLS',
NOT_SUPPORT = 'NOT_SUPPORT',
}
export type EquipmentInterfaceDto = {
protocolIp: ProtocolIp;
port: number;
ieee8021x: Ieee8021X;
};
export type EquipmentTypeDto = {
id: string;
name: string;
};
export type EquipmentManufacturerDto = {
id: string;
name: string;
};
export type EquipmentModelDto = {
id: string;
name: string;
type: EquipmentTypeDto;
manufacturer: EquipmentManufacturerDto;
};
export type SecondaryInterfaceDto = {
id: string;
nameId: string;
ownerType: 'Controller';
ownerId: string;
IP: string;
SubnetMask: string;
Gateway: string;
MAC: string;
};
export enum InterfaceType {
'ETHERNET' = 'ethernet',
}
export enum Segment {
SIGMA = 'sigma',
OMEGA = 'omega',
}
export type NetworkEquipmentDto = {
id: string;
name: string;
locationId: string;
status: string;
ip: string;
mac: string;
subnetMask: string;
gateway: string;
interface: EquipmentInterfaceDto;
model: EquipmentModelDto;
timeZone: string;
segment: Segment;
description?: string;
controlPage?: string;
telnetPort?: number;
firmware?: string;
serialNumber?: string;
inventoryNumber?: string;
interfaceType?: InterfaceType;
sendNotifications?: boolean;
netInterface?: boolean;
netInterfaceInfo?: SecondaryInterfaceDto[];
inventarisationOnly?: boolean;
};
после изменения интерфейса у меня Select8021X value: MAB
VM17457 Select802-1X.tsx:32 Normalized value passed to Select: MAB
CreateDeviceDrawer.tsx:139 {ip: {…}, mac: {…}, ieee8021x: {…}}ieee8021x: {message: 'common:messages.requiredMessage', type: 'optionality', ref: {…}}ip: {message: 'common:messages.requiredMessage', type: 'required', ref: {…}}mac: {message: 'common:messages.requiredMessage', type: 'required', ref: {…}}[[Prototype]]: Object
CreateDeviceDrawer.tsx:140 {name: '', description: '', ip: '', mac: '', controlPage: '', …}bidirectional: falsecontrolPage: ""description: ""disableMonitoring: falsefirmware: nullhardwareId: ""hasSipUri: nullieee8021x: undefinedinventoryNumber: nullip: ""ipAvLan: ""localAddress: undefinedmac: ""manufacturerName: ""modelId: ""name: ""netInterface: falsenetInterfaceInfo: []port: undefinedprotocolIp: "dhcp"serialNumber: nullsipUri: ""type: "ethernet"typeName: ""[[Prototype]]: Object
index.js:485 [webpack-dev-server] App updated. Recompiling...
index.js:485 [webpack-dev-server] App hot update...
log.js:39 [HMR] Checking for updates on the server...
log.js:39 [HMR] Updated modules:
log.js:39 [HMR] - ./src/widgets/Devices/ui/Select802-1X/Select802-1X.tsx
log.js:39 [HMR] App is up to date.
Select802-1X.tsx:13 Select8021X value: MAB
Select802-1X.tsx:14 Normalized value passed to Select: MAB
и из ui не отчищается выбор поля селекта