import { Box } from '@mui/material'; import { FC, FormEventHandler, ReactElement, ReactNode, useMemo, useRef, useState, } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import AddFenceInputGroup from './AddFenceInputGroup'; import api from '../../lib/api'; import { ID_SEPARATOR } from './CommonFenceInputGroup'; import ConfirmDialog from '../ConfirmDialog'; import EditFenceInputGroup from './EditFenceInputGroup'; import FlexBox from '../FlexBox'; import handleAPIError from '../../lib/handleAPIError'; import List from '../List'; import { Panel, PanelHeader } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import Spinner from '../Spinner'; import { BodyText, HeaderText, InlineMonoText, MonoText, SensitiveText, SmallText, } from '../Text'; import useIsFirstRender from '../../hooks/useIsFirstRender'; import useProtectedState from '../../hooks/useProtectedState'; type FormFenceParameterData = { fenceAgent: string; fenceName: string; parameterInputs: { [parameterInputId: string]: { isParameterSensitive: boolean; parameterId: string; parameterType: string; parameterValue: string; }; }; }; const fenceParameterBooleanToString = (value: boolean) => (value ? '1' : '0'); const getFormFenceParameters = ( fenceTemplate: APIFenceTemplate, ...[{ target }]: Parameters> ) => { const { elements } = target as HTMLFormElement; return Object.values(elements).reduce( (previous, formElement) => { const { id: inputId } = formElement; const reExtract = new RegExp(`^(fence[^-]+)${ID_SEPARATOR}([^\\s]+)$`); const matched = inputId.match(reExtract); if (matched) { const [, fenceId, parameterId] = matched; previous.fenceAgent = fenceId; const inputElement = formElement as HTMLInputElement; const { checked, dataset: { sensitive: rawSensitive }, value, } = inputElement; if (parameterId === 'name') { previous.fenceName = value; } const { [fenceId]: { parameters: { [parameterId]: { content_type: parameterType = 'string' } = {}, }, }, } = fenceTemplate; previous.parameterInputs[inputId] = { isParameterSensitive: rawSensitive === 'true', parameterId, parameterType, parameterValue: parameterType === 'boolean' ? fenceParameterBooleanToString(checked) : value, }; } return previous; }, { fenceAgent: '', fenceName: '', parameterInputs: {} }, ); }; const buildConfirmFenceParameters = ( parameterInputs: FormFenceParameterData['parameterInputs'], ) => ( { let textElement: ReactElement; if (parameterValue) { textElement = isParameterSensitive ? ( {parameterValue} ) : ( {parameterValue} ); } else { textElement = none; } return ( {parameterId} {textElement} ); }} /> ); const ManageFencePanel: FC = () => { const isFirstRender = useIsFirstRender(); const confirmDialogRef = useRef({}); const formDialogRef = useRef({}); const [confirmDialogProps, setConfirmDialogProps] = useState({ actionProceedText: '', content: '', titleText: '', }); const [formDialogProps, setFormDialogProps] = useState({ actionProceedText: '', content: '', titleText: '', }); const [fenceTemplate, setFenceTemplate] = useProtectedState< APIFenceTemplate | undefined >(undefined); const [isEditFences, setIsEditFences] = useState(false); const [isLoadingFenceTemplate, setIsLoadingFenceTemplate] = useProtectedState(true); const { data: fenceOverviews, isLoading: isFenceOverviewsLoading } = periodicFetch(`${API_BASE_URL}/fence`, { refreshInterval: 60000, }); const listElement = useMemo( () => ( { setFormDialogProps({ actionProceedText: 'Add', content: , onSubmitAppend: (event) => { if (!fenceTemplate) { return; } const addData = getFormFenceParameters(fenceTemplate, event); setConfirmDialogProps({ actionProceedText: 'Add', content: buildConfirmFenceParameters(addData.parameterInputs), titleText: ( Add a{' '} {addData.fenceAgent} {' '} fence device with the following parameters? ), }); confirmDialogRef.current.setOpen?.call(null, true); }, titleText: 'Add a fence device', }); formDialogRef.current.setOpen?.call(null, true); }} onEdit={() => { setIsEditFences((previous) => !previous); }} onItemClick={({ fenceAgent: fenceId, fenceName, fenceParameters }) => { setFormDialogProps({ actionProceedText: 'Update', content: ( ), onSubmitAppend: (event) => { if (!fenceTemplate) { return; } const editData = getFormFenceParameters(fenceTemplate, event); setConfirmDialogProps({ actionProceedText: 'Update', content: buildConfirmFenceParameters(editData.parameterInputs), titleText: ( Update{' '} {editData.fenceName} {' '} fence device with the following parameters? ), }); confirmDialogRef.current.setOpen?.call(null, true); }, titleText: ( Update fence device{' '} {fenceName}{' '} parameters ), }); formDialogRef.current.setOpen?.call(null, true); }} renderListItem={( fenceUUID, { fenceAgent, fenceName, fenceParameters }, ) => ( {fenceName} {Object.entries(fenceParameters).reduce( (previous, [parameterId, parameterValue]) => { let current: ReactNode = <>{parameterId}="; current = /passw/i.test(parameterId) ? ( <> {current} {parameterValue} ) : ( <> {current} {parameterValue} ); return ( <> {previous} {current}" ); }, fenceAgent, )} )} /> ), [fenceOverviews, fenceTemplate, isEditFences], ); const panelContent = useMemo( () => isLoadingFenceTemplate || isFenceOverviewsLoading ? ( ) : ( listElement ), [isFenceOverviewsLoading, isLoadingFenceTemplate, listElement], ); if (isFirstRender) { api .get(`/fence/template`) .then(({ data }) => { setFenceTemplate(data); }) .catch((error) => { handleAPIError(error); }) .finally(() => { setIsLoadingFenceTemplate(false); }); } return ( <> Manage fence devices {panelContent} ); }; export default ManageFencePanel;