Loading capif_frontend/src/components/ConfigCapif.js +79 −88 Original line number Diff line number Diff line Loading @@ -23,50 +23,6 @@ import Ajv from 'ajv'; const ajv = new Ajv(); // Esquema de validación para la configuración const configSchema = { type: "object", properties: { config_name: { type: "string" }, version: { type: "string" }, description: { type: "string" }, settings: { type: "object", properties: { certificates_expiry: { type: "object", properties: { ttl_invoker_cert: { type: "string" }, ttl_provider_cert: { type: "string" }, ttl_superadmin_cert: { type: "string" } }, required: ["ttl_invoker_cert", "ttl_provider_cert", "ttl_superadmin_cert"] }, security_method_priority: { type: "object", properties: { oauth: { type: "number" }, pki: { type: "number" }, psk: { type: "number" } }, required: ["oauth", "pki", "psk"] }, acl_policy_settings: { type: "object", properties: { allowedTotalInvocations: { type: "number" }, allowedInvocationsPerSecond: { type: "number" }, allowedInvocationTimeRangeDays: { type: "number" } }, required: ["allowedTotalInvocations", "allowedInvocationsPerSecond", "allowedInvocationTimeRangeDays"] } }, required: ["certificates_expiry", "security_method_priority", "acl_policy_settings"] } }, required: ["config_name", "version", "description", "settings"] }; const ConfigCapif = () => { // Obtener configuración desde el backend const { data: configData, isLoading, isError, refetch } = useQuery({ Loading Loading @@ -137,40 +93,63 @@ const ConfigCapif = () => { } }; 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(configSchema); 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(configSchema); 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>; Loading Loading @@ -260,6 +239,15 @@ const ConfigCapif = () => { }, }); 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(" "); }; return ( <Grid container spacing={3} marginTop={2}> Loading Loading @@ -300,7 +288,7 @@ const ConfigCapif = () => { /> </Box> {/* 🔥 Accordions Dinámicos 🔥 */} {/* Accordions Dinámicos */} {editableConfig?.settings && Object.entries(editableConfig.settings).map(([sectionKey, sectionValue]) => ( <Accordion disableGutters> Loading @@ -311,7 +299,9 @@ const ConfigCapif = () => { {sectionValue && Object.entries(sectionValue).map(([key, value]) => ( <Box key={key} display="flex" alignItems="center" mb={1}> <Typography variant="subtitle1" sx={{ flex: 1 }}>{key.replace(/_/g, ' ')}</Typography> <Typography variant="subtitle1" sx={{ flex: 1 }}> {capitalizeFirstWord(key.replace(/_/g, ' '))} </Typography> <TextField sx={{ width: '700px' }} value={String(value)} Loading Loading @@ -342,10 +332,10 @@ const ConfigCapif = () => { {/* 🔹 Dialog para agregar nuevo parámetro 🔹 */} <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="sm"> <DialogTitle>Añadir nuevo parámetro</DialogTitle> <DialogTitle>Add new param</DialogTitle> <DialogContent> <TextField label="Nombre del parámetro" label="Param name" fullWidth sx={{ mt: 2 }} value={newParamName} Loading @@ -360,17 +350,17 @@ const ConfigCapif = () => { /> </DialogContent> <DialogActions> <Button onClick={handleCloseDialog} color="secondary">Cancelar</Button> <Button onClick={handleAddConfig} variant="contained" color="primary">Guardar</Button> <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>Añadir nueva sección</DialogTitle> <DialogTitle>Add new section</DialogTitle> <DialogContent> <TextField label="Nombre de la sección" label="Section name" fullWidth sx={{ mt: 2 }} value={newSectionName} Loading @@ -380,13 +370,13 @@ const ConfigCapif = () => { {newSectionParams.map((param, index) => ( <Box key={index} display="flex" gap={2} mt={2}> <TextField label="Nombre del parámetro" label="Param name" fullWidth value={param.key} onChange={(e) => handleParamChange(index, 'key', e.target.value)} /> <TextField label="Valor" label="Value" fullWidth value={param.value} onChange={(e) => handleParamChange(index, 'value', e.target.value)} Loading @@ -394,11 +384,11 @@ const ConfigCapif = () => { </Box> ))} <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Añadir parámetro</Button> <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Add Param </Button> </DialogContent> <DialogActions> <Button onClick={handleCloseSectionDialog} color="secondary">Cancelar</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Guardar</Button> <Button onClick={handleCloseSectionDialog} color="secondary">Cancel</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> </Grid> Loading @@ -416,15 +406,16 @@ const ConfigCapif = () => { 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}> <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit}> Replace Configuration </Button> </CardContent> </Card> </Grid> </Grid> ); }; Loading Loading
capif_frontend/src/components/ConfigCapif.js +79 −88 Original line number Diff line number Diff line Loading @@ -23,50 +23,6 @@ import Ajv from 'ajv'; const ajv = new Ajv(); // Esquema de validación para la configuración const configSchema = { type: "object", properties: { config_name: { type: "string" }, version: { type: "string" }, description: { type: "string" }, settings: { type: "object", properties: { certificates_expiry: { type: "object", properties: { ttl_invoker_cert: { type: "string" }, ttl_provider_cert: { type: "string" }, ttl_superadmin_cert: { type: "string" } }, required: ["ttl_invoker_cert", "ttl_provider_cert", "ttl_superadmin_cert"] }, security_method_priority: { type: "object", properties: { oauth: { type: "number" }, pki: { type: "number" }, psk: { type: "number" } }, required: ["oauth", "pki", "psk"] }, acl_policy_settings: { type: "object", properties: { allowedTotalInvocations: { type: "number" }, allowedInvocationsPerSecond: { type: "number" }, allowedInvocationTimeRangeDays: { type: "number" } }, required: ["allowedTotalInvocations", "allowedInvocationsPerSecond", "allowedInvocationTimeRangeDays"] } }, required: ["certificates_expiry", "security_method_priority", "acl_policy_settings"] } }, required: ["config_name", "version", "description", "settings"] }; const ConfigCapif = () => { // Obtener configuración desde el backend const { data: configData, isLoading, isError, refetch } = useQuery({ Loading Loading @@ -137,40 +93,63 @@ const ConfigCapif = () => { } }; 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(configSchema); 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(configSchema); 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>; Loading Loading @@ -260,6 +239,15 @@ const ConfigCapif = () => { }, }); 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(" "); }; return ( <Grid container spacing={3} marginTop={2}> Loading Loading @@ -300,7 +288,7 @@ const ConfigCapif = () => { /> </Box> {/* 🔥 Accordions Dinámicos 🔥 */} {/* Accordions Dinámicos */} {editableConfig?.settings && Object.entries(editableConfig.settings).map(([sectionKey, sectionValue]) => ( <Accordion disableGutters> Loading @@ -311,7 +299,9 @@ const ConfigCapif = () => { {sectionValue && Object.entries(sectionValue).map(([key, value]) => ( <Box key={key} display="flex" alignItems="center" mb={1}> <Typography variant="subtitle1" sx={{ flex: 1 }}>{key.replace(/_/g, ' ')}</Typography> <Typography variant="subtitle1" sx={{ flex: 1 }}> {capitalizeFirstWord(key.replace(/_/g, ' '))} </Typography> <TextField sx={{ width: '700px' }} value={String(value)} Loading Loading @@ -342,10 +332,10 @@ const ConfigCapif = () => { {/* 🔹 Dialog para agregar nuevo parámetro 🔹 */} <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="sm"> <DialogTitle>Añadir nuevo parámetro</DialogTitle> <DialogTitle>Add new param</DialogTitle> <DialogContent> <TextField label="Nombre del parámetro" label="Param name" fullWidth sx={{ mt: 2 }} value={newParamName} Loading @@ -360,17 +350,17 @@ const ConfigCapif = () => { /> </DialogContent> <DialogActions> <Button onClick={handleCloseDialog} color="secondary">Cancelar</Button> <Button onClick={handleAddConfig} variant="contained" color="primary">Guardar</Button> <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>Añadir nueva sección</DialogTitle> <DialogTitle>Add new section</DialogTitle> <DialogContent> <TextField label="Nombre de la sección" label="Section name" fullWidth sx={{ mt: 2 }} value={newSectionName} Loading @@ -380,13 +370,13 @@ const ConfigCapif = () => { {newSectionParams.map((param, index) => ( <Box key={index} display="flex" gap={2} mt={2}> <TextField label="Nombre del parámetro" label="Param name" fullWidth value={param.key} onChange={(e) => handleParamChange(index, 'key', e.target.value)} /> <TextField label="Valor" label="Value" fullWidth value={param.value} onChange={(e) => handleParamChange(index, 'value', e.target.value)} Loading @@ -394,11 +384,11 @@ const ConfigCapif = () => { </Box> ))} <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Añadir parámetro</Button> <Button onClick={handleAddParamField} sx={{ mt: 2 }}>Add Param </Button> </DialogContent> <DialogActions> <Button onClick={handleCloseSectionDialog} color="secondary">Cancelar</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Guardar</Button> <Button onClick={handleCloseSectionDialog} color="secondary">Cancel</Button> <Button onClick={handleAddSection} variant="contained" color="primary">Save</Button> </DialogActions> </Dialog> </Grid> Loading @@ -416,15 +406,16 @@ const ConfigCapif = () => { 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}> <Button variant="contained" fullWidth sx={{ mt: 2 }} onClick={handleJsonSubmit}> Replace Configuration </Button> </CardContent> </Card> </Grid> </Grid> ); }; Loading