Loading capif_frontend/src/components/ConfigCapif.js +1 −1 Original line number Diff line number Diff line Loading @@ -277,7 +277,7 @@ const ConfigCapif = () => { <Box display="flex" justifyContent="space-between" alignItems="center"> <Typography variant="h6">General Configuration</Typography> <Button variant="contained" color="secondary" startIcon={<DownloadIcon />} onClick={handleDownloadJson}> Download JSON config Download config </Button> </Box> } Loading capif_frontend/src/components/ConfigRegister.js +307 −70 Original line number Diff line number Diff line import React, { useEffect, useState } from 'react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { getRegisterConfiguration, updateRegisterConfigParam, replaceRegisterConfiguration } from 'src/configs/apiService'; import { getConfiguration, updateConfigParam, replaceConfiguration, addCategoryConfiguration, addParamConfiguration } from 'src/configs/apiService'; import { getRegisterConfiguration, updateRegisterConfigParam, replaceRegisterConfiguration, addRegisterCategoryConfiguration, addRegisterParamConfiguration } from 'src/configs/apiService'; import { Grid, Card, Loading @@ -9,43 +11,41 @@ import { CardContent, TextField, Button, Box Accordion, AccordionSummary, AccordionDetails, Box, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; import DownloadIcon from '@mui/icons-material/Download'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import Ajv from 'ajv'; const ajv = new Ajv(); // Esquema de validación para la configuración del Register const registerConfigSchema = { type: "object", properties: { settings: { type: "object", properties: { certificates_expiry: { // Nuevo objeto que envuelve el TTL type: "object", properties: { ttl_superadmin_cert: { type: "string" } }, required: ["ttl_superadmin_cert"] } }, required: ["certificates_expiry"] } } }; const ConfigRegister = () => { // Obtener configuración desde el backend const { data: configData, isLoading, isError, refetch } = useQuery({ queryKey: ['registerConfiguration'], queryKey: ['configuration'], queryFn: getRegisterConfiguration, }); const [editableConfig, setEditableConfig] = useState(null); const [jsonConfig, setJsonConfig] = useState(''); const [isJsonValid, setIsJsonValid] = useState(true); const [newParams, setNewParams] = useState({}); // Estado para nuevos parámetros const [openDialog, setOpenDialog] = useState(false); const [currentSection, setCurrentSection] = useState(''); const [newParamName, setNewParamName] = useState(''); const [newParamValue, setNewParamValue] = useState(''); const [openSectionDialog, setOpenSectionDialog] = useState(false); const [newSectionName, setNewSectionName] = useState(''); const [newSectionParams, setNewSectionParams] = useState([{ key: '', value: '' }]); useEffect(() => { if (configData) { Loading Loading @@ -88,51 +88,198 @@ const ConfigRegister = () => { } }; const [jsonError, setJsonError] = useState(""); // Nuevo estado para manejar el error del JSON const handleJsonChange = (e) => { const value = e.target.value; setJsonConfig(value); try { const parsedJson = JSON.parse(value); const validate = ajv.compile(registerConfigSchema); setIsJsonValid(validate(parsedJson)); const error = validateJsonConfig(parsedJson); if (error) { setJsonError(error); } else { setJsonError(""); } } catch (error) { setIsJsonValid(false); setJsonError("Invalid JSON format"); } }; const handleJsonSubmit = () => { if (jsonError) { alert(`Invalid JSON: ${jsonError}`); return; } try { const parsedJson = JSON.parse(jsonConfig); const validate = ajv.compile(registerConfigSchema); const valid = validate(parsedJson); if (!valid) { setIsJsonValid(false); console.error("Errores de validación:", validate.errors); alert("JSON inválido: No cumple con la estructura requerida."); return; replaceConfigMutation.mutate(parsedJson, { onSuccess: () => { setJsonConfig(""); } }); setIsJsonValid(true); replaceConfigMutation.mutate(parsedJson); } catch (error) { setIsJsonValid(false); alert("Formato de JSON inválido"); alert("Invalid JSON format"); } }; const validateJsonConfig = (json) => { const requiredFields = ["config_name", "version", "description", "settings"]; const missingFields = requiredFields.filter(field => !(field in json)); if (missingFields.length > 0) { return `The following required fields are missing: ${missingFields.join(", ")}`; } if (typeof json.settings !== "object" || Object.keys(json.settings).length === 0) { return "The 'settings' field must contain at least one section with settings."; } return null; // No hay errores }; if (isLoading) return <Typography>Loading...</Typography>; if (isError) return <Typography>Error loading configuration</Typography>; const formatTitle = (text) => { return text .split(" ") .map(word => word.toLowerCase() === "acl" ? "ACL" : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ) .join(" "); }; const addParamMutation = useMutation({ mutationFn: addRegisterParamConfiguration, onSuccess: () => { refetch(); setOpenDialog(false); setNewParamName(''); setNewParamValue(''); }, }); const handleOpenDialog = (sectionKey) => { setCurrentSection(sectionKey); setOpenDialog(true); }; const handleCloseDialog = () => { setOpenDialog(false); setNewParamName(''); setNewParamValue(''); }; const handleAddConfig = () => { if (!newParamName || newParamValue === undefined) { alert("Debe ingresar un nombre y un valor para la configuración."); return; } const paramPath = `${currentSection}.${newParamName}`; addParamMutation.mutate({ param_path: paramPath, new_value: newParamValue }); }; const handleOpenSectionDialog = () => setOpenSectionDialog(true); const handleCloseSectionDialog = () => { setOpenSectionDialog(false); setNewSectionName(''); setNewSectionParams([{ key: '', value: '' }]); }; const handleAddSection = () => { if (!newSectionName.trim() || newSectionParams.some(p => !p.key.trim())) { alert("Debes ingresar un nombre para la sección y al menos un parámetro."); return; } const categoryValues = newSectionParams.reduce((acc, param) => { acc[param.key] = param.value; return acc; }, {}); addCategoryMutation.mutate({ category_name: newSectionName, category_values: categoryValues }); }; const handleParamChange = (index, field, value) => { setNewSectionParams(prevParams => { const updatedParams = [...prevParams]; updatedParams[index][field] = value; return updatedParams; }); }; const handleAddParamField = () => { setNewSectionParams(prevParams => [...prevParams, { key: '', value: '' }]); }; const addCategoryMutation = useMutation({ mutationFn: addRegisterCategoryConfiguration, onSuccess: () => { refetch(); setOpenSectionDialog(false); setNewSectionName(''); setNewSectionParams([{ key: '', value: '' }]); }, }); const capitalizeFirstWord = (text) => { const words = text.split(" "); if (words.length === 0) return text; words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1).toLowerCase(); return words.join(" "); }; const handleDownloadJson = () => { if (!editableConfig) { alert("There is no configuration available for download."); return; } const jsonString = JSON.stringify(editableConfig, null, 2); const blob = new Blob([jsonString], { type: "application/json" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "configuration.json"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return ( <Grid container spacing={3} marginTop={2}> {/* Cuadro principal con datos generales y secciones desplegables */} <Grid item xs={12}> <Card> <CardHeader title="Register Configuration" /> <CardHeader title={ <Box display="flex" justifyContent="space-between" alignItems="center"> <Typography variant="h6">General Configuration</Typography> <Button variant="contained" color="secondary" startIcon={<DownloadIcon />} onClick={handleDownloadJson}> Download config </Button> </Box> } /> <CardContent> {/* General Configuration */} <Box display="flex" alignItems="center" mb={2}> <Box display="flex" alignItems="center" mb={2} mt={4}> <Typography variant="subtitle1" sx={{ flex: 1 }}>Configuration Name</Typography> <TextField sx={{ width: '700px' }} Loading Loading @@ -165,25 +312,115 @@ const ConfigRegister = () => { /> </Box> {/* Certificate Expiration Period */} <Box display="flex" alignItems="center" mb={2}> <Typography variant="subtitle1" sx={{ flex: 1 }}>TTL Superadmin Cert</Typography> {/* Accordions Dinámicos */} {editableConfig?.settings && Object.entries(editableConfig.settings).map(([sectionKey, sectionValue]) => ( <Accordion disableGutters> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="h6">{formatTitle(sectionKey.replace(/_/g, ' '))}</Typography> </AccordionSummary> <AccordionDetails> {sectionValue && Object.entries(sectionValue).map(([key, value]) => ( <Box key={key} display="flex" alignItems="center" mb={1}> <Typography variant="subtitle1" sx={{ flex: 1 }}> {capitalizeFirstWord(key.replace(/_/g, ' '))} </Typography> <TextField sx={{ width: '700px' }} value={editableConfig?.settings?.certificates_expiry?.ttl_superadmin_cert || ''} onChange={(e) => handleValueChange('settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} onBlur={(e) => handleBlur('settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} onKeyDown={(e) => handleKeyDown(e, 'settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} value={String(value)} onChange={(e) => handleValueChange(`settings.${sectionKey}.${key}`, e.target.value)} onBlur={(e) => handleBlur(`settings.${sectionKey}.${key}`, e.target.value)} onKeyDown={(e) => handleKeyDown(e, `settings.${sectionKey}.${key}`, e.target.value)} /> </Box> ))} <Box> <Box display="flex" justifyContent="flex-end" mt={3}> <Button variant="contained" onClick={() => handleOpenDialog(sectionKey)}> Add new param </Button> </Box> </Box> </AccordionDetails> </Accordion> ))} {/* Botón para añadir nueva sección */} <Box display="flex" justifyContent="flex-end" mt={5}> <Button variant="contained" color="primary" onClick={handleOpenSectionDialog}> Add New Configuration Section </Button> </Box> </CardContent> </Card> {/* Dialog para agregar nuevo parámetro */} <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="sm"> <DialogTitle>Add new param</DialogTitle> <DialogContent> <TextField label="Param name" fullWidth sx={{ mt: 2 }} value={newParamName} onChange={(e) => setNewParamName(e.target.value)} /> <TextField label="Valor" fullWidth sx={{ mt: 2 }} value={newParamValue} onChange={(e) => setNewParamValue(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleCloseDialog} color="secondary">Cancel</Button> <Button onClick={handleAddConfig} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> {/* Dialog para añadir nueva sección */} <Dialog open={openSectionDialog} onClose={handleCloseSectionDialog} fullWidth maxWidth="sm"> <DialogTitle>Add new section</DialogTitle> <DialogContent> <TextField label="Section name" fullWidth sx={{ mt: 2 }} value={newSectionName} onChange={(e) => setNewSectionName(e.target.value)} /> {newSectionParams.map((param, index) => ( <Box key={index} display="flex" gap={2} mt={2}> <TextField label="Param name" fullWidth value={param.key} onChange={(e) => handleParamChange(index, 'key', e.target.value)} /> <TextField label="Value" fullWidth value={param.value} onChange={(e) => handleParamChange(index, 'value', e.target.value)} /> </Box> ))} <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Add Param </Button> </DialogContent> <DialogActions> <Button onClick={handleCloseSectionDialog} color="secondary">Cancel</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> </Grid> {/* Upload JSON Configuration */} <Grid item xs={12}> <Card> <CardHeader title="Upload New Register Configuration" /> <CardHeader title="Upload New Configuration" /> <CardContent> <TextField fullWidth Loading @@ -193,11 +430,11 @@ const ConfigRegister = () => { variant="outlined" value={jsonConfig} onChange={handleJsonChange} error={!isJsonValid} helperText={!isJsonValid ? "Invalid JSON format" : ""} error={!!jsonError} helperText={jsonError} /> <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit} disabled={!isJsonValid}> Replace Register Configuration <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit}> Replace Configuration </Button> </CardContent> </Card> Loading capif_frontend/src/configs/apiService.js +27 −1 Original line number Diff line number Diff line Loading @@ -509,3 +509,29 @@ export const replaceRegisterConfiguration = async (newConfig) => { throw new Error(`Error replacing configuration: ${error.message}`); } } export const addRegisterCategoryConfiguration = async ({ category_name, category_values }) => { try { const response = await axiosInstance.post(`/configuration/register/add-category-config`, { category_name, category_values }); return response.data; } catch (error) { throw new Error(`Error adding category configuration: ${error.message}`); } }; export const addRegisterParamConfiguration = async ({ param_path, new_value }) => { try { const response = await axiosInstance.patch(`/configuration/register/add-config-param-setting`, { param_path, new_value }); return response.data; } catch (error) { throw new Error(`Error adding parameter configuration: ${error.message}`); } }; Loading
capif_frontend/src/components/ConfigCapif.js +1 −1 Original line number Diff line number Diff line Loading @@ -277,7 +277,7 @@ const ConfigCapif = () => { <Box display="flex" justifyContent="space-between" alignItems="center"> <Typography variant="h6">General Configuration</Typography> <Button variant="contained" color="secondary" startIcon={<DownloadIcon />} onClick={handleDownloadJson}> Download JSON config Download config </Button> </Box> } Loading
capif_frontend/src/components/ConfigRegister.js +307 −70 Original line number Diff line number Diff line import React, { useEffect, useState } from 'react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { getRegisterConfiguration, updateRegisterConfigParam, replaceRegisterConfiguration } from 'src/configs/apiService'; import { getConfiguration, updateConfigParam, replaceConfiguration, addCategoryConfiguration, addParamConfiguration } from 'src/configs/apiService'; import { getRegisterConfiguration, updateRegisterConfigParam, replaceRegisterConfiguration, addRegisterCategoryConfiguration, addRegisterParamConfiguration } from 'src/configs/apiService'; import { Grid, Card, Loading @@ -9,43 +11,41 @@ import { CardContent, TextField, Button, Box Accordion, AccordionSummary, AccordionDetails, Box, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; import DownloadIcon from '@mui/icons-material/Download'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import Ajv from 'ajv'; const ajv = new Ajv(); // Esquema de validación para la configuración del Register const registerConfigSchema = { type: "object", properties: { settings: { type: "object", properties: { certificates_expiry: { // Nuevo objeto que envuelve el TTL type: "object", properties: { ttl_superadmin_cert: { type: "string" } }, required: ["ttl_superadmin_cert"] } }, required: ["certificates_expiry"] } } }; const ConfigRegister = () => { // Obtener configuración desde el backend const { data: configData, isLoading, isError, refetch } = useQuery({ queryKey: ['registerConfiguration'], queryKey: ['configuration'], queryFn: getRegisterConfiguration, }); const [editableConfig, setEditableConfig] = useState(null); const [jsonConfig, setJsonConfig] = useState(''); const [isJsonValid, setIsJsonValid] = useState(true); const [newParams, setNewParams] = useState({}); // Estado para nuevos parámetros const [openDialog, setOpenDialog] = useState(false); const [currentSection, setCurrentSection] = useState(''); const [newParamName, setNewParamName] = useState(''); const [newParamValue, setNewParamValue] = useState(''); const [openSectionDialog, setOpenSectionDialog] = useState(false); const [newSectionName, setNewSectionName] = useState(''); const [newSectionParams, setNewSectionParams] = useState([{ key: '', value: '' }]); useEffect(() => { if (configData) { Loading Loading @@ -88,51 +88,198 @@ const ConfigRegister = () => { } }; const [jsonError, setJsonError] = useState(""); // Nuevo estado para manejar el error del JSON const handleJsonChange = (e) => { const value = e.target.value; setJsonConfig(value); try { const parsedJson = JSON.parse(value); const validate = ajv.compile(registerConfigSchema); setIsJsonValid(validate(parsedJson)); const error = validateJsonConfig(parsedJson); if (error) { setJsonError(error); } else { setJsonError(""); } } catch (error) { setIsJsonValid(false); setJsonError("Invalid JSON format"); } }; const handleJsonSubmit = () => { if (jsonError) { alert(`Invalid JSON: ${jsonError}`); return; } try { const parsedJson = JSON.parse(jsonConfig); const validate = ajv.compile(registerConfigSchema); const valid = validate(parsedJson); if (!valid) { setIsJsonValid(false); console.error("Errores de validación:", validate.errors); alert("JSON inválido: No cumple con la estructura requerida."); return; replaceConfigMutation.mutate(parsedJson, { onSuccess: () => { setJsonConfig(""); } }); setIsJsonValid(true); replaceConfigMutation.mutate(parsedJson); } catch (error) { setIsJsonValid(false); alert("Formato de JSON inválido"); alert("Invalid JSON format"); } }; const validateJsonConfig = (json) => { const requiredFields = ["config_name", "version", "description", "settings"]; const missingFields = requiredFields.filter(field => !(field in json)); if (missingFields.length > 0) { return `The following required fields are missing: ${missingFields.join(", ")}`; } if (typeof json.settings !== "object" || Object.keys(json.settings).length === 0) { return "The 'settings' field must contain at least one section with settings."; } return null; // No hay errores }; if (isLoading) return <Typography>Loading...</Typography>; if (isError) return <Typography>Error loading configuration</Typography>; const formatTitle = (text) => { return text .split(" ") .map(word => word.toLowerCase() === "acl" ? "ACL" : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ) .join(" "); }; const addParamMutation = useMutation({ mutationFn: addRegisterParamConfiguration, onSuccess: () => { refetch(); setOpenDialog(false); setNewParamName(''); setNewParamValue(''); }, }); const handleOpenDialog = (sectionKey) => { setCurrentSection(sectionKey); setOpenDialog(true); }; const handleCloseDialog = () => { setOpenDialog(false); setNewParamName(''); setNewParamValue(''); }; const handleAddConfig = () => { if (!newParamName || newParamValue === undefined) { alert("Debe ingresar un nombre y un valor para la configuración."); return; } const paramPath = `${currentSection}.${newParamName}`; addParamMutation.mutate({ param_path: paramPath, new_value: newParamValue }); }; const handleOpenSectionDialog = () => setOpenSectionDialog(true); const handleCloseSectionDialog = () => { setOpenSectionDialog(false); setNewSectionName(''); setNewSectionParams([{ key: '', value: '' }]); }; const handleAddSection = () => { if (!newSectionName.trim() || newSectionParams.some(p => !p.key.trim())) { alert("Debes ingresar un nombre para la sección y al menos un parámetro."); return; } const categoryValues = newSectionParams.reduce((acc, param) => { acc[param.key] = param.value; return acc; }, {}); addCategoryMutation.mutate({ category_name: newSectionName, category_values: categoryValues }); }; const handleParamChange = (index, field, value) => { setNewSectionParams(prevParams => { const updatedParams = [...prevParams]; updatedParams[index][field] = value; return updatedParams; }); }; const handleAddParamField = () => { setNewSectionParams(prevParams => [...prevParams, { key: '', value: '' }]); }; const addCategoryMutation = useMutation({ mutationFn: addRegisterCategoryConfiguration, onSuccess: () => { refetch(); setOpenSectionDialog(false); setNewSectionName(''); setNewSectionParams([{ key: '', value: '' }]); }, }); const capitalizeFirstWord = (text) => { const words = text.split(" "); if (words.length === 0) return text; words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1).toLowerCase(); return words.join(" "); }; const handleDownloadJson = () => { if (!editableConfig) { alert("There is no configuration available for download."); return; } const jsonString = JSON.stringify(editableConfig, null, 2); const blob = new Blob([jsonString], { type: "application/json" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "configuration.json"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return ( <Grid container spacing={3} marginTop={2}> {/* Cuadro principal con datos generales y secciones desplegables */} <Grid item xs={12}> <Card> <CardHeader title="Register Configuration" /> <CardHeader title={ <Box display="flex" justifyContent="space-between" alignItems="center"> <Typography variant="h6">General Configuration</Typography> <Button variant="contained" color="secondary" startIcon={<DownloadIcon />} onClick={handleDownloadJson}> Download config </Button> </Box> } /> <CardContent> {/* General Configuration */} <Box display="flex" alignItems="center" mb={2}> <Box display="flex" alignItems="center" mb={2} mt={4}> <Typography variant="subtitle1" sx={{ flex: 1 }}>Configuration Name</Typography> <TextField sx={{ width: '700px' }} Loading Loading @@ -165,25 +312,115 @@ const ConfigRegister = () => { /> </Box> {/* Certificate Expiration Period */} <Box display="flex" alignItems="center" mb={2}> <Typography variant="subtitle1" sx={{ flex: 1 }}>TTL Superadmin Cert</Typography> {/* Accordions Dinámicos */} {editableConfig?.settings && Object.entries(editableConfig.settings).map(([sectionKey, sectionValue]) => ( <Accordion disableGutters> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="h6">{formatTitle(sectionKey.replace(/_/g, ' '))}</Typography> </AccordionSummary> <AccordionDetails> {sectionValue && Object.entries(sectionValue).map(([key, value]) => ( <Box key={key} display="flex" alignItems="center" mb={1}> <Typography variant="subtitle1" sx={{ flex: 1 }}> {capitalizeFirstWord(key.replace(/_/g, ' '))} </Typography> <TextField sx={{ width: '700px' }} value={editableConfig?.settings?.certificates_expiry?.ttl_superadmin_cert || ''} onChange={(e) => handleValueChange('settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} onBlur={(e) => handleBlur('settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} onKeyDown={(e) => handleKeyDown(e, 'settings.certificates_expiry.ttl_superadmin_cert', e.target.value)} value={String(value)} onChange={(e) => handleValueChange(`settings.${sectionKey}.${key}`, e.target.value)} onBlur={(e) => handleBlur(`settings.${sectionKey}.${key}`, e.target.value)} onKeyDown={(e) => handleKeyDown(e, `settings.${sectionKey}.${key}`, e.target.value)} /> </Box> ))} <Box> <Box display="flex" justifyContent="flex-end" mt={3}> <Button variant="contained" onClick={() => handleOpenDialog(sectionKey)}> Add new param </Button> </Box> </Box> </AccordionDetails> </Accordion> ))} {/* Botón para añadir nueva sección */} <Box display="flex" justifyContent="flex-end" mt={5}> <Button variant="contained" color="primary" onClick={handleOpenSectionDialog}> Add New Configuration Section </Button> </Box> </CardContent> </Card> {/* Dialog para agregar nuevo parámetro */} <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="sm"> <DialogTitle>Add new param</DialogTitle> <DialogContent> <TextField label="Param name" fullWidth sx={{ mt: 2 }} value={newParamName} onChange={(e) => setNewParamName(e.target.value)} /> <TextField label="Valor" fullWidth sx={{ mt: 2 }} value={newParamValue} onChange={(e) => setNewParamValue(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleCloseDialog} color="secondary">Cancel</Button> <Button onClick={handleAddConfig} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> {/* Dialog para añadir nueva sección */} <Dialog open={openSectionDialog} onClose={handleCloseSectionDialog} fullWidth maxWidth="sm"> <DialogTitle>Add new section</DialogTitle> <DialogContent> <TextField label="Section name" fullWidth sx={{ mt: 2 }} value={newSectionName} onChange={(e) => setNewSectionName(e.target.value)} /> {newSectionParams.map((param, index) => ( <Box key={index} display="flex" gap={2} mt={2}> <TextField label="Param name" fullWidth value={param.key} onChange={(e) => handleParamChange(index, 'key', e.target.value)} /> <TextField label="Value" fullWidth value={param.value} onChange={(e) => handleParamChange(index, 'value', e.target.value)} /> </Box> ))} <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Add Param </Button> </DialogContent> <DialogActions> <Button onClick={handleCloseSectionDialog} color="secondary">Cancel</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> </Grid> {/* Upload JSON Configuration */} <Grid item xs={12}> <Card> <CardHeader title="Upload New Register Configuration" /> <CardHeader title="Upload New Configuration" /> <CardContent> <TextField fullWidth Loading @@ -193,11 +430,11 @@ const ConfigRegister = () => { variant="outlined" value={jsonConfig} onChange={handleJsonChange} error={!isJsonValid} helperText={!isJsonValid ? "Invalid JSON format" : ""} error={!!jsonError} helperText={jsonError} /> <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit} disabled={!isJsonValid}> Replace Register Configuration <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit}> Replace Configuration </Button> </CardContent> </Card> Loading
capif_frontend/src/configs/apiService.js +27 −1 Original line number Diff line number Diff line Loading @@ -509,3 +509,29 @@ export const replaceRegisterConfiguration = async (newConfig) => { throw new Error(`Error replacing configuration: ${error.message}`); } } export const addRegisterCategoryConfiguration = async ({ category_name, category_values }) => { try { const response = await axiosInstance.post(`/configuration/register/add-category-config`, { category_name, category_values }); return response.data; } catch (error) { throw new Error(`Error adding category configuration: ${error.message}`); } }; export const addRegisterParamConfiguration = async ({ param_path, new_value }) => { try { const response = await axiosInstance.patch(`/configuration/register/add-config-param-setting`, { param_path, new_value }); return response.data; } catch (error) { throw new Error(`Error adding parameter configuration: ${error.message}`); } };