Commit 6537dc43 authored by guillecxb's avatar guillecxb
Browse files

working with dynamic render

parent abbecafb
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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>
            } 
+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,
@@ -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) {
@@ -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' }} 
@@ -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
@@ -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>
+27 −1
Original line number Diff line number Diff line
@@ -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}`);
  }
};