From 30f6d9053f51acf36c6bbbe990e0b55fb581df86 Mon Sep 17 00:00:00 2001 From: Wojciech Zyla Date: Mon, 26 Jun 2023 11:07:30 +0200 Subject: [PATCH 1/3] fix: enable compound indices in profiles and configuration of hosts in the inventory using string address fix: enable compound indices and configuration of string based hosts fix: refactor condition in profiles form fix: in ProfileConversion._backend2ui_map for empty patterns filed return empty list instead of None fix: refactor varBinds form --- .../SC4SNMP_UI_backend/common/conversions.py | 8 +- backend/SC4SNMP_UI_backend/common/helpers.py | 4 +- .../src/components/menu_header/Header.jsx | 6 +- .../components/profiles/AddProfileModal.jsx | 53 ++--- .../src/components/profiles/Condition.jsx | 59 ++++++ .../src/components/profiles/Conditions.jsx | 75 ------- .../src/components/profiles/FieldPatterns.jsx | 126 +++++++++++ .../components/profiles/PatternsCreator.jsx | 168 --------------- .../src/components/profiles/ProfilesList.jsx | 4 +- .../src/components/profiles/VarBinds.jsx | 159 ++++++++++++++ .../components/profiles/VarbindsCreator.jsx | 200 ------------------ .../validation/ValidateInventoryAndGroup.jsx | 10 +- .../validation/ValidateProfiles.jsx | 18 +- .../manager/src/store/profile-contxt.jsx | 14 +- 14 files changed, 397 insertions(+), 507 deletions(-) create mode 100644 frontend/packages/manager/src/components/profiles/Condition.jsx delete mode 100644 frontend/packages/manager/src/components/profiles/Conditions.jsx create mode 100644 frontend/packages/manager/src/components/profiles/FieldPatterns.jsx delete mode 100644 frontend/packages/manager/src/components/profiles/PatternsCreator.jsx create mode 100644 frontend/packages/manager/src/components/profiles/VarBinds.jsx delete mode 100644 frontend/packages/manager/src/components/profiles/VarbindsCreator.jsx diff --git a/backend/SC4SNMP_UI_backend/common/conversions.py b/backend/SC4SNMP_UI_backend/common/conversions.py index 4cdf7b0..0f2a3ab 100644 --- a/backend/SC4SNMP_UI_backend/common/conversions.py +++ b/backend/SC4SNMP_UI_backend/common/conversions.py @@ -61,7 +61,7 @@ def _backend2ui_map(self, document: dict, **kwargs): new_vb = { "family": vb[0], "category": vb[1] if len(vb) >= 2 else "", - "index": str(vb[2]) if len(vb) == 3 else "", + "index": '.'.join(vb[2:]) if len(vb) >= 3 else "", } var_binds.append(new_vb) @@ -70,7 +70,7 @@ def _backend2ui_map(self, document: dict, **kwargs): condition_type = backend_condition["type"] field = backend_condition["field"] if condition_type == "field" else "" patterns = [{"pattern": p} for p in backend_condition["patterns"]] \ - if condition_type == "field" else None + if condition_type == "field" else [] conditions = { "condition": condition_type, "field": field, @@ -80,7 +80,7 @@ def _backend2ui_map(self, document: dict, **kwargs): conditions = { "condition": "None", "field": "", - "patterns": None + "patterns": [] } result = { "_id": str(document["_id"]), @@ -110,7 +110,7 @@ def _ui2backend_map(self, document: dict, **kwargs): if len(var_b['category']) > 0: single_var_bind.append(var_b['category']) if len(var_b['index']) > 0: - single_var_bind.append(int(var_b['index'])) + single_var_bind += var_b['index'].split(".") var_binds.append(single_var_bind) item = { diff --git a/backend/SC4SNMP_UI_backend/common/helpers.py b/backend/SC4SNMP_UI_backend/common/helpers.py index 42db3ab..c8fa4f0 100644 --- a/backend/SC4SNMP_UI_backend/common/helpers.py +++ b/backend/SC4SNMP_UI_backend/common/helpers.py @@ -192,8 +192,8 @@ def add_group_to_inventory(self, group_name: str, group_port: str, group_object= deleted_inventory_record = list(self._mongo_inventory.find({'address': group_name, "delete": True})) group = list(self._mongo_groups.find({group_name: {"$exists": 1}})) if len(group) == 0: - group_added = False - message = f"There is no group {group_name} configured. Record was not added." + group_added = True + message = f"Group {group_name} doesn't exist in the configuration. Treating {group_name} as a hostname." elif len(existing_inventory_record) > 0: group_added = False message = f"Group {group_name} has already been added to the inventory. Record was not added." diff --git a/frontend/packages/manager/src/components/menu_header/Header.jsx b/frontend/packages/manager/src/components/menu_header/Header.jsx index 479d8b4..6c7e37d 100644 --- a/frontend/packages/manager/src/components/menu_header/Header.jsx +++ b/frontend/packages/manager/src/components/menu_header/Header.jsx @@ -21,8 +21,10 @@ function Header(){ const handleRequestOpenProfile = () => { ProfCtx.setProfileName(""); ProfCtx.setFrequency(1); - ProfCtx.setVarBinds(null); - ProfCtx.setConditions(null); + ProfCtx.setVarBinds([]); + ProfCtx.setCondition("None"); + ProfCtx.setConditionField(""); + ProfCtx.setConditionPatterns([]); ProfCtx.setAddOpen(true); ProfCtx.setIsEdit(false); }; diff --git a/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx b/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx index 2ef5801..1f04be6 100644 --- a/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx +++ b/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx @@ -5,8 +5,8 @@ import Number from '@splunk/react-ui/Number'; import Text from '@splunk/react-ui/Text'; import { createDOMID } from '@splunk/ui-utils/id'; import P from '@splunk/react-ui/Paragraph'; -import VarbindsCreator from "./VarbindsCreator"; -import Conditions from "./Conditions"; +import VarBinds from "./VarBinds"; +import Condition from "./Condition"; import axios from "axios"; import ProfileContext from "../../store/profile-contxt"; import validateProfiles from "../validation/ValidateProfiles"; @@ -21,16 +21,7 @@ function AddProfileModal(props) { const ProfCtx = useContext(ProfileContext); const ValCtx = useContext(ProfilesValidationContxt); const ErrCtx = useContext(ErrorsModalContext); - const [newSubmitPatterns, setNewSubmitPatterns] = useState(false); - const [newSubmitVarBinds, setNewSubmitVarBinds] = useState(false); - - const newSubmitPatternsHandler = () =>{ - setNewSubmitPatterns(!newSubmitPatterns); - }; - - const newSubmitVarBindsHandler = () =>{ - setNewSubmitVarBinds(!newSubmitVarBinds); - }; + const [newSubmit, setNewSubmit] = useState(false); const handleProfileName = useCallback((e, { value: val }) => { @@ -41,14 +32,6 @@ function AddProfileModal(props) { ProfCtx.setFrequency(val); }, []); - const handleVarBinds = (value) => { - ProfCtx.setVarBinds(value); - } - - const handleConditions = (value) => { - ProfCtx.setConditions(value); - } - const postProfile = (profileObj) => { axios.post(`http://${backendHost}/profiles/add`, profileObj) .then((response) => { @@ -91,11 +74,15 @@ function AddProfileModal(props) { const handleApply = useCallback( (e) => { - let profileObj = { + const profileObj = { profileName: ProfCtx.profileName, frequency: ProfCtx.frequency, varBinds: ProfCtx.varBinds, - conditions: ProfCtx.conditions + conditions: { + condition: ProfCtx.condition, + field: ProfCtx.conditionField, + patterns: ProfCtx.conditionPatterns + } }; const validation = validateProfiles(profileObj); @@ -112,16 +99,16 @@ function AddProfileModal(props) { ProfCtx.addModalToggle?.current?.focus(); }else{ // form is invalid - setNewSubmitPatterns(prevNewSubmitPatterns => {return !prevNewSubmitPatterns;}); - setNewSubmitVarBinds(prevNewSubmitVarBinds => {return !prevNewSubmitVarBinds;}); + setNewSubmit(prevNewSubmitPatterns => {return !prevNewSubmitPatterns;}); const errors = validation[1]; - for (const property in errors) { - if (errors[property].length > 0 || Object.keys(errors[property]).length > 0){ - ValCtx.setErrors(property, errors[property]); + const errorKeys = Object.keys(errors); + errorKeys.forEach((errorKey) => { + if (errors[errorKey].length > 0 || Object.keys(errors[errorKey]).length > 0){ + ValCtx.setErrors(errorKey, errors[errorKey]); }else { - ValCtx.resetErrors(property); + ValCtx.resetErrors(errorKey); }; - }; + }) }; }, @@ -150,14 +137,10 @@ function AddProfileModal(props) { - + - + diff --git a/frontend/packages/manager/src/components/profiles/Condition.jsx b/frontend/packages/manager/src/components/profiles/Condition.jsx new file mode 100644 index 0000000..26cdb34 --- /dev/null +++ b/frontend/packages/manager/src/components/profiles/Condition.jsx @@ -0,0 +1,59 @@ +import React, {useState, useRef, useCallback, useContext, useEffect} from 'react'; +import { createDOMID } from '@splunk/ui-utils/id'; +import ProfileContext from "../../store/profile-contxt"; +import {StyledControlGroup} from "../../styles/inventory/InventoryStyle"; +import Select from "@splunk/react-ui/Select"; +import Text from "@splunk/react-ui/Text"; +import P from "@splunk/react-ui/Paragraph"; +import FieldPatterns from "./FieldPatterns"; +import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; +import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; + +function Condition(props){ + const ProfCtx = useContext(ProfileContext); + const ValCtx = useContext(ProfilesValidationContxt); + + const handleFieldChange = useCallback((e, { value: val }) => { + ProfCtx.setConditionField(val); + }, []); + + const handleChange = useCallback((e, { value: val }) => { + ProfCtx.setCondition(val); + }, []); + + return( +
+ + + + { + ProfCtx.condition === 'field' ? ( +
+ +
+ + {((ValCtx.conditionFieldErrors) ? ValCtx.conditionFieldErrors.map((el) => +

{el}

) :

)} +

+
+ + + +
) : null + } + + {ProfCtx.condition === 'conditional' ? ( +
+ ) : null} +
+ ) +} + +export default Condition; diff --git a/frontend/packages/manager/src/components/profiles/Conditions.jsx b/frontend/packages/manager/src/components/profiles/Conditions.jsx deleted file mode 100644 index 0af64b7..0000000 --- a/frontend/packages/manager/src/components/profiles/Conditions.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, {Component} from 'react'; -import Select from '@splunk/react-ui/Select'; -import Text from "@splunk/react-ui/Text"; -import PatternsCreator from "./PatternsCreator"; -import P from '@splunk/react-ui/Paragraph'; -import { createDOMID } from '@splunk/ui-utils/id'; -import { StyledControlGroup } from "../../styles/inventory/InventoryStyle"; - -class Conditions extends Component { - constructor(props) { - super(props); - let stateValue; - if(this.props.value){ - stateValue = this.props.value; - }else{ - stateValue = { - condition: 'None', - field: '', - patterns: null - }; - } - this.state = stateValue; - this.props.onConditionsCreator(this.state); - } - - handleChange = (e, {value}) => { - if (value != "field"){ - this.setState({condition: value, field: '', patterns :null}, - () => {this.props.onConditionsCreator(this.state);}); - }else{ - this.setState({condition: value}, () => {this.props.onConditionsCreator(this.state);}); - }; - }; - - handleFieldChange = (e, {value}) => { - this.setState({field: value}, () => {this.props.onConditionsCreator(this.state);}); - }; - - handlePatterns = (value) => { - this.setState({patterns: value}, () => {this.props.onConditionsCreator(this.state);}); - } - - render() { - return ( -
- - - - {this.state.condition === 'field' ? ( -
- -
- - {((this.props.errorField) ? this.props.errorField.map((el) => -

{el}

) :

)} -

-
- - - -
- ) :
} -
) - } -} - -export default Conditions; diff --git a/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx b/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx new file mode 100644 index 0000000..086d7e8 --- /dev/null +++ b/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx @@ -0,0 +1,126 @@ +import React, {useContext, useEffect, useState} from 'react'; +import {createDOMID} from '@splunk/ui-utils/id'; +import Text from "@splunk/react-ui/Text"; +import P from "@splunk/react-ui/Paragraph"; +import FormRows from "@splunk/react-ui/FormRows"; +import ProfileContext from "../../store/profile-contxt"; +import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; +import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; + +function FieldPatterns(props){ + const ProfCtx = useContext(ProfileContext); + const ValCtx = useContext(ProfilesValidationContxt); + const [indices, setIndices] = useState({}); + const [reload, setReload] = useState(true); + const [rowItems, setRowItems] = useState([]); + + const handleRequestRemove = (e, { index }) => { + const indicesCopy = {...indices}; + let keyToDelete; + const patternsCopy = ProfCtx.conditionPatterns; + patternsCopy.splice(index, 1); + + const indicesKeys = Object.keys(indices) + indicesKeys.forEach((keyID) => { + if (indices[`${keyID}`] > index){ + indicesCopy[`${keyID}`] -= 1; + } + if (indices[`${keyID}`] === index){ + keyToDelete = keyID; + } + }) + delete indicesCopy[`${keyToDelete}`]; + + // Update errors indexes after deleting an element + const error = ValCtx.conditionPatternsErrors; + if (error){ + const errorKeys = Object.keys(error); + errorKeys.forEach((errorID) => { + if (Number(errorID) === index){delete error[Number(errorID)];} + if (Number(errorID) > index) { + error[Number(errorID)-1] = error[Number(errorID)]; + delete error[Number(errorID)] + } + }) + ValCtx.setConditionPatternsErrors(error); + } + setRowItems((prev) => FormRows.removeRow(index, prev)); + setIndices(indicesCopy); + ProfCtx.setConditionPatterns(patternsCopy); + setReload((prev)=>{return !prev}); + }; + + const handleItemValue = (index, e) => { + const patternsCopy = ProfCtx.conditionPatterns; + patternsCopy[index].pattern = e.target.value + ProfCtx.setConditionPatterns(patternsCopy); + } + + const handleRequestAdd = () => { + const indicesCopy = indices; + const newIndex = rowItems.length; + const keyID = createDOMID(); + const patternsCopy = ProfCtx.conditionPatterns; + patternsCopy.push({pattern: ""}); + indicesCopy[`${keyID}`] = newIndex; + setRowItems((prev) => + FormRows.addRow( + +
+ handleItemValue(indices[`${keyID}`], e)} + error={((ValCtx.conditionPatternsErrors && indices[`${keyID}`] in ValCtx.conditionPatternsErrors))}/> + {((ValCtx.conditionPatternsErrors && indices[`${keyID}`] in ValCtx.conditionPatternsErrors) ? + ValCtx.conditionPatternsErrors[indices[`${keyID}`]].map((el) => +

{el}

) :

)} +

+
, + prev + ) + ); + setIndices(indicesCopy); + ProfCtx.setConditionPatterns(patternsCopy); + setReload((prev)=>{return !prev}); + }; + + const loadFormRows = () => { + let index = -1; + const newIndices = {} + const items = ProfCtx.conditionPatterns.map(value => { + index += 1; + const indexCopy = index; + const keyID = createDOMID(); + newIndices[`${keyID}`] = indexCopy; + return ( + +
+ handleItemValue(newIndices[`${keyID}`], e)} + error={((ValCtx.conditionPatternsErrors && newIndices[`${keyID}`] in ValCtx.conditionPatternsErrors))}/> + {((ValCtx.conditionPatternsErrors && newIndices[`${keyID}`] in ValCtx.conditionPatternsErrors) ? + ValCtx.conditionPatternsErrors[newIndices[`${keyID}`]].map((el) => +

{el}

) :

)} +

+
+ ); + }); + setIndices(newIndices); + return items; + } + + useEffect(() => { + let isMounted = true; + setRowItems(loadFormRows()); + return () => { isMounted = false } + }, [props.newSubmit, reload]); + + return ( + + {rowItems} + + ) +} + +export default FieldPatterns; diff --git a/frontend/packages/manager/src/components/profiles/PatternsCreator.jsx b/frontend/packages/manager/src/components/profiles/PatternsCreator.jsx deleted file mode 100644 index 141b0f0..0000000 --- a/frontend/packages/manager/src/components/profiles/PatternsCreator.jsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { Component } from 'react'; -import { createDOMID } from '@splunk/ui-utils/id'; -import FormRows from '@splunk/react-ui/FormRows'; -import Text from '@splunk/react-ui/Text'; -import P from '@splunk/react-ui/Paragraph'; - -class PatternsCreator extends Component { - - constructor(props) { - super(props); - - if(this.props.value){ - this.patterns = this.props.value - }else{ - this.patterns = [ - {pattern: ".*SNMP.*"} - ] - } - - // 'indexes', 'items', 'reload', 'newSubmit' have the same use as in VarbindsCreator.jsx - let indexes = {}; - let items = []; - this.reload = true; - this.newSubmit = this.props.newSubmit; - - this.state = { - items, - indexes - }; - - props.onPatternsCreator(this.patterns); - } - - reloadItems = () => { - const indexes = this.state.indexes; - let item_id = -1; - const items = this.patterns.map(value => { - item_id += 1; - let internal_id = item_id; - let keyID = createDOMID(); - indexes[`${keyID}`] = internal_id; - return( - -
- this.handleItemValue(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error))}/> - {((this.props.error && indexes[`${keyID}`] in this.props.error) ? - this.props.error[indexes[`${keyID}`]].map((el) => -

{el}

) :

)} -

-
- ); - }); - this.setState({ - items: items - }); - }; - - handlePatternChange = () => { - this.props.onPatternsCreator(this.patterns); - } - - handleItemValue = (index, e) => { - this.patterns[index].pattern = e.target.value - } - - handleRequestAdd = () => { - let indexes = this.state.indexes; - let internal_id = this.patterns.length; - let keyID = createDOMID(); - indexes[`${keyID}`] = internal_id; - this.patterns.push({pattern: ""}); - this.setState((state) => ({ - items: FormRows.addRow( - -
- this.handleItemValue(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> - {((this.props.error && indexes[`${keyID}`] in this.props.error) ? - this.props.error[indexes[`${keyID}`]].map((el) => -

{el}

) :

)} -

-
, - state.items - ), - indexes: indexes - })); - this.props.onPatternsCreator(this.patterns); - }; - - handleRequestMove = ({ fromIndex, toIndex }) => { - this.reload = true; - this.setState((state) => ({ - items: FormRows.moveRow(fromIndex, toIndex, state.items), - })); - }; - - handleRequestRemove = (e, { index }) => { - this.reload = true; - - // Update indexes after deleting an element - let indexes = this.state.indexes; - let keyToDelete; - for (const keyID in indexes){ - if (indexes[`${keyID}`] > index){ - indexes[`${keyID}`] -= 1; - } - if (indexes[`${keyID}`] == indexes){ - keyToDelete = keyID; - } - } - delete indexes[`${keyToDelete}`]; - - // Update errors indexes after deleting an element - let error = this.props.error; - if (error){ - const errrorKeys = Object.keys(error); - for (const errorID of errrorKeys){ - if (Number(errorID) === index){delete error[Number(errorID)];} - if (Number(errorID) > index) { - error[Number(errorID)-1] = error[Number(errorID)]; - delete error[Number(errorID)] - } - } - this.props.setError(error); - } - - this.setState((state) => ({ - items: FormRows.removeRow(index, state.items), - indexes: indexes - })); - this.patterns.splice(index, 1); - this.props.onPatternsCreator(this.patterns); - }; - - componentDidUpdate(){ - if(this.reload){ - this.reload = false; - this.reloadItems(); - } - } - - componentDidMount(){ - if(this.reload){ - this.reload = false; - this.reloadItems(); - } - } - - render() { - if (this.props.newSubmit != this.newSubmit){ - this.newSubmit = this.props.newSubmit; - this.reload = true; - } - return ( - - {this.state.items} - - ); - } -} - -export default PatternsCreator; diff --git a/frontend/packages/manager/src/components/profiles/ProfilesList.jsx b/frontend/packages/manager/src/components/profiles/ProfilesList.jsx index 5abf745..7ae3836 100644 --- a/frontend/packages/manager/src/components/profiles/ProfilesList.jsx +++ b/frontend/packages/manager/src/components/profiles/ProfilesList.jsx @@ -109,7 +109,9 @@ function ProfilesList() { ProfCtx.setProfileName(row.profileName); ProfCtx.setFrequency(row.frequency); ProfCtx.setVarBinds(row.varBinds); - ProfCtx.setConditions(row.conditions); + ProfCtx.setCondition(row.conditions.condition); + ProfCtx.setConditionField(row.conditions.field); + ProfCtx.setConditionPatterns(row.conditions.patterns); ProfCtx.setIsEdit(true); ProfCtx.setAddOpen(true); }; diff --git a/frontend/packages/manager/src/components/profiles/VarBinds.jsx b/frontend/packages/manager/src/components/profiles/VarBinds.jsx new file mode 100644 index 0000000..d3d8a06 --- /dev/null +++ b/frontend/packages/manager/src/components/profiles/VarBinds.jsx @@ -0,0 +1,159 @@ +import React, {useContext, useEffect, useState} from 'react'; +import {createDOMID} from '@splunk/ui-utils/id'; +import Text from "@splunk/react-ui/Text"; +import P from "@splunk/react-ui/Paragraph"; +import FormRows from "@splunk/react-ui/FormRows"; +import ProfileContext from "../../store/profile-contxt"; +import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; +import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; + +function VarBinds(props){ + const ProfCtx = useContext(ProfileContext); + const ValCtx = useContext(ProfilesValidationContxt); + const [indices, setIndices] = useState({}); + const [rowItems, setRowItems] = useState([]); + const [reload, setReload] = useState(true); + + const handleRequestRemove = (e, { index }) => { + const indicesCopy = {...indices}; + let keyToDelete; + const varBindsCopy = ProfCtx.varBinds; + varBindsCopy.splice(index, 1); + + const indicesKeys = Object.keys(indices) + indicesKeys.forEach((keyID) => { + if (indices[`${keyID}`] > index){ + indicesCopy[`${keyID}`] -= 1; + } + if (indices[`${keyID}`] === index){ + keyToDelete = keyID; + } + }) + delete indicesCopy[`${keyToDelete}`]; + + // Update errors indexes after deleting an element + const error = ValCtx.varBindsErrors; + if (error){ + const errorKeys = Object.keys(error); + errorKeys.forEach((errorID) => { + if (Number(errorID) === index){delete error[Number(errorID)];} + if (Number(errorID) > index) { + error[Number(errorID)-1] = error[Number(errorID)]; + delete error[Number(errorID)] + } + }) + ValCtx.setVarBindsErrors(error); + } + setRowItems((prev) => FormRows.removeRow(index, prev)); + setIndices(indicesCopy); + ProfCtx.setVarBinds(varBindsCopy); + setReload((prev)=>{return !prev}); + }; + + const handleItemValueFamily = (index, e) => { + const varBindsCopy = ProfCtx.varBinds; + varBindsCopy[index].family = e.target.value + ProfCtx.setVarBinds(varBindsCopy); + } + + const handleItemValueCategory = (index, e) => { + const varBindsCopy = ProfCtx.varBinds; + varBindsCopy[index].category = e.target.value + ProfCtx.setVarBinds(varBindsCopy); + } + + const handleItemValueIndex = (index, e) => { + const varBindsCopy = ProfCtx.varBinds; + varBindsCopy[index].index = e.target.value + ProfCtx.setVarBinds(varBindsCopy); + } + + const handleRequestAdd = () => { + const indicesCopy = indices; + const newIndex = rowItems.length; + const keyID = createDOMID(); + const varBindsCopy = ProfCtx.varBinds; + varBindsCopy.push({family: "", category: "", index: ""}); + indicesCopy[`${keyID}`] = newIndex; + setRowItems((prev) => + FormRows.addRow( + +
+
+ handleItemValueFamily(indices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> + handleItemValueCategory(indices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> + handleItemValueIndex(indices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> +
+ {((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors) ? + ValCtx.varBindsErrors[indices[`${keyID}`]].map((el) =>

{el}

) : +

)} +

+
, + prev + ) + ); + setIndices(indicesCopy); + ProfCtx.setVarBinds(varBindsCopy); + setReload((prev)=>{return !prev}); + }; + + const loadFormRows = () => { + let index = -1; + const newIndices = {} + const items = ProfCtx.varBinds.map(value => { + index += 1; + const indexCopy = index; + const keyID = createDOMID(); + newIndices[`${keyID}`] = indexCopy; + return ( + +
+
+ handleItemValueFamily(newIndices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/> + handleItemValueCategory(newIndices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/> + handleItemValueIndex(newIndices[`${keyID}`], e)} + error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/> +
+ {((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors) ? + ValCtx.varBindsErrors[newIndices[`${keyID}`]].map((el) =>

{el}

) : +

)} +

+
+ ); + }); + setIndices(newIndices); + return items; + } + + useEffect(() => { + let isMounted = true; + setRowItems(loadFormRows()); + return () => { isMounted = false } + }, [props.newSubmit, reload]); + + return ( + + {rowItems} + + ) + +} + +export default VarBinds; diff --git a/frontend/packages/manager/src/components/profiles/VarbindsCreator.jsx b/frontend/packages/manager/src/components/profiles/VarbindsCreator.jsx deleted file mode 100644 index 216d83c..0000000 --- a/frontend/packages/manager/src/components/profiles/VarbindsCreator.jsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { Component, useEffect } from 'react'; -import { createDOMID } from '@splunk/ui-utils/id'; -import FormRows from '@splunk/react-ui/FormRows'; -import Text from '@splunk/react-ui/Text'; -import P from '@splunk/react-ui/Paragraph'; -import axios from "axios"; - -class VarbindsCreator extends Component { - constructor(props) { - super(props); - - if (this.props.value) { - this.varBinds = this.props.value; - } else { - this.varBinds = [{family: "IF-MIB", category: "ifDescr", index: "1"}]; - } - - // 'indexes' is an object storing key:value pairs of key:index of each row from form. - // When the row with specific 'key' is deleted, other objects with 'index' greater than - // deleted must have their indexes updated. - let indexes = {}; - - // 'items' list stores HTML rows - let items = []; - - // If this.reload=true all rows in 'items' list are reloaded. Must be set to true at begining to load items. - // It is later used to reload items in order to update error messages - this.reload = true; - - // Each time the submit button is pressed and there were some errors, 'newSubmit' value changes to the - // opposite one (boolean value). If this.props.newSubmit is different from this.newSubmit it means, - // that new submit has been sent, new errors arrived and 'items' must be reloaded. - this.newSubmit = this.props.newSubmit; - - this.state = { - items, - indexes - }; - - this.props.onVarbindsCreator(this.varBinds); - } - - reloadItems = () => { - const indexes = this.state.indexes; - let item_id = -1; - const items = this.varBinds.map(value => { - item_id += 1; - let internal_id = item_id; - let keyID = createDOMID(); - indexes[`${keyID}`] = internal_id; - return ( - -
-
- this.handleItemValueFamily(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> - this.handleItemValueCategory(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> - this.handleItemValueIndex(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> -
- {((this.props.error && indexes[`${keyID}`] in this.props.error) ? - this.props.error[indexes[`${keyID}`]].map((el) =>

{el}

) : -

)} -

-
- ); - }); - this.setState((state) => ({ - items: items - })); - }; - - handleRequestAdd = () => { - this.reload = true; - let indexes = this.state.indexes; - let internal_id = this.varBinds.length; - let keyID = createDOMID(); - indexes[`${keyID}`] = internal_id; - - this.varBinds.push({family: "", category: "", index: ""}); - this.setState((state) => ({ - items: FormRows.addRow( - -
-
- this.handleItemValueFamily(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> - this.handleItemValueCategory(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> - this.handleItemValueIndex(indexes[`${keyID}`], e)} - error={((this.props.error && indexes[`${keyID}`] in this.props.error) ? true : false)}/> -
- {((this.props.error && indexes[`${keyID}`] in this.props.error) ? - this.props.error[indexes[`${keyID}`]].map((el) =>

{el}

) : -

)} -

-
, - state.items - ), - indexes: indexes - }), () => {this.handleVarbindsChange();}); - }; - - handleItemValueFamily = (index, e) => { - this.varBinds[index].family = e.target.value - } - - handleItemValueCategory = (index, e) => { - this.varBinds[index].category = e.target.value - } - - handleItemValueIndex = (index, e) => { - this.varBinds[index].index = e.target.value - } - - handleRequestMove = ({fromIndex, toIndex}) => { - this.reload = true; - this.setState((state) => ({ - items: FormRows.moveRow(fromIndex, toIndex, state.items), - })); - }; - - handleVarbindsChange = () => { - this.reload = true; - var varBinds = this.varBinds; - this.props.onVarbindsCreator(varBinds); - } - - handleRequestRemove = (e, {index}) => { - this.reload = true; - - // Update indexes after deleting an element - let indexes = this.state.indexes; - let keyToDelete; - for (const keyID in indexes){ - if (indexes[`${keyID}`] > index){indexes[`${keyID}`] -= 1;} - if (indexes[`${keyID}`] == indexes){keyToDelete = keyID;} - } - delete indexes[`${keyToDelete}`]; - - // Update errors indexes after deleting an element - let error = this.props.error; - if (error){ - const errrorKeys = Object.keys(error); - for (const errorID of errrorKeys){ - if (Number(errorID) === index){delete error[Number(errorID)];} - if (Number(errorID) > index) { - error[Number(errorID)-1] = error[Number(errorID)]; - delete error[Number(errorID)] - } - } - this.props.setError(error); - } - - this.setState((state) => ({ - items: FormRows.removeRow(index, state.items), - indexes: indexes, - })); - this.varBinds.splice(index, 1); - this.props.onVarbindsCreator(this.varBinds); - }; - - componentDidUpdate(){ - if(this.reload){ - this.reload = false; - this.reloadItems(); - } - } - - componentDidMount(){ - if(this.reload){ - this.reload = false; - this.reloadItems(); - } - } - render() { - if (this.props.newSubmit != this.newSubmit){ - this.newSubmit = this.props.newSubmit; - this.reload = true; - } - return ( - - {this.state.items} - - ); - } -} - -export default VarbindsCreator; diff --git a/frontend/packages/manager/src/components/validation/ValidateInventoryAndGroup.jsx b/frontend/packages/manager/src/components/validation/ValidateInventoryAndGroup.jsx index 34070b6..b3fec4b 100644 --- a/frontend/packages/manager/src/components/validation/ValidateInventoryAndGroup.jsx +++ b/frontend/packages/manager/src/components/validation/ValidateInventoryAndGroup.jsx @@ -7,7 +7,7 @@ const validateInventoryAndGroup = (validationObj) => { Each property is a list of errors for the respective field. */ - let errors = { + const errors = { groupName: [], address: [], port: [], @@ -53,13 +53,11 @@ const validateInventoryAndGroup = (validationObj) => { isValid = false; errors.address.push("Provided address isn't a valid IPv4 address") } - }else{ - if (!validationObj.address.match(/^[a-zA-Z0-9_-]+$/)){ + }else if (!validationObj.address.match(/^[.a-zA-Z0-9_\-]+$/)){ isValid = false; - errors.address.push("Group name can consist only of upper and lower english letters, " + - "numbers and two special characters: '-' and '_'. No spaces are allowed."); + errors.address.push("Group or host name can consist only of upper and lower english letters, " + + "numbers and three special characters: '-', '.' and '_'. No spaces are allowed."); } - } } // Validate port diff --git a/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx b/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx index d21450f..4bda4d4 100644 --- a/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx +++ b/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx @@ -10,7 +10,7 @@ const validateProfiles = (validationObj) => { varBinds: {0: ["message1", "message2"], 3: ["message3"]} -> key indicates index of a varBind. Each varBind has its own list of errors */ - let errors = { + const errors = { profileName: [], frequency: [], conditionField: [], @@ -77,7 +77,6 @@ const validateProfiles = (validationObj) => { }; // Validate VarBinds - let varBindsCategoryValid; if (validationObj.hasOwnProperty("varBinds")){ for (let i = 0; i < validationObj.varBinds.length; i++){ if (validationObj.varBinds[i].family.length === 0){ @@ -90,7 +89,7 @@ const validateProfiles = (validationObj) => { isValid = false; }else if (!validationObj.varBinds[i].family.match(/^[a-zA-Z0-9_-]+$/)){ - let message = "MIB-Component can consist only of upper and lower english letters, " + + message = "MIB-Component can consist only of upper and lower english letters, " + "numbers and two special characters: '-' and '_'. No spaces are allowed." if (i in errors.varBinds){ errors.varBinds[i].push(message); @@ -100,7 +99,6 @@ const validateProfiles = (validationObj) => { isValid = false; }; - varBindsCategoryValid = true; if (validationObj.varBinds[i].category.length > 0){ if (!validationObj.varBinds[i].category.match(/^[a-zA-Z0-9_-]+$/)){ message = "MIB object can consist only of upper and lower english letters, " + @@ -111,28 +109,28 @@ const validateProfiles = (validationObj) => { errors.varBinds[i] = [message]; }; isValid = false; - varBindsCategoryValid = false; }; }; if (validationObj.varBinds[i].index.length > 0){ - if (!(Number.isInteger(Number(validationObj.varBinds[i].index)) && Number(validationObj.varBinds[i].index) >= 0)){ - message = "MIB index number must be a integer greater or equal 0"; + if (validationObj.varBinds[i].category.length === 0){ + message = "MIB object is required when MIB index is specified"; if (i in errors.varBinds){ errors.varBinds[i].push(message); }else{ errors.varBinds[i] = [message]; }; isValid = false; - }else if (validationObj.varBinds[i].category.length == 0){ - message = "MIB object is required when MIB index is specified"; + }; + if (!validationObj.varBinds[i].index.match(/^[^\s]+$/)){ + message = "Index can't include white spaces"; if (i in errors.varBinds){ errors.varBinds[i].push(message); }else{ errors.varBinds[i] = [message]; }; isValid = false; - }; + } }; }; }; diff --git a/frontend/packages/manager/src/store/profile-contxt.jsx b/frontend/packages/manager/src/store/profile-contxt.jsx index 3055d29..bda89c2 100644 --- a/frontend/packages/manager/src/store/profile-contxt.jsx +++ b/frontend/packages/manager/src/store/profile-contxt.jsx @@ -20,8 +20,10 @@ export function ProfileContxtProvider(props) { // data for editing in AddProfileModal const [profileName, setProfileName] = useState(''); const [frequency, setFrequency] = useState(1); - const [varBinds, setVarBinds] = useState(null); - const [conditions, setConditions] = useState(null); + const [varBinds, setVarBinds] = useState([]); + const [condition, setCondition] = useState("None"); + const [conditionField, setConditionField] = useState(""); + const [conditionPatterns, setConditionPatterns] = useState([]); const [isEdit, setIsEdit] = useState(false); function profilesChangeHandler() { @@ -46,8 +48,12 @@ export function ProfileContxtProvider(props) { setFrequency, varBinds, setVarBinds, - conditions, - setConditions, + condition, + setCondition, + conditionField, + setConditionField, + conditionPatterns, + setConditionPatterns, isEdit, setIsEdit }; From 1e146c95332d6a9a5a211e8f92aacc0a494f86d8 Mon Sep 17 00:00:00 2001 From: Wojciech Zyla Date: Mon, 3 Jul 2023 15:28:45 +0200 Subject: [PATCH 2/3] fix: add conditional profiles fix: add conversion of conditional profiles fix: validate if any varBind, pattern or conditions were added fix: remove filed and patterns columns from profiles screen, rename fileds in profile form fix: rename UI fileds in profiles change conversion to int and float --- .../SC4SNMP_UI_backend/common/conversions.py | 87 +++++++-- .../src/components/menu_header/Header.jsx | 1 + .../components/profiles/AddProfileModal.jsx | 10 +- .../src/components/profiles/Condition.jsx | 35 +++- .../src/components/profiles/Conditional.jsx | 177 ++++++++++++++++++ .../src/components/profiles/Conditionaln.jsx | 116 ++++++++++++ .../src/components/profiles/FieldPatterns.jsx | 16 +- .../src/components/profiles/ProfilesList.jsx | 12 +- .../src/components/profiles/VarBinds.jsx | 30 +-- .../validation/ValidateProfiles.jsx | 95 +++++++++- .../manager/src/store/profile-contxt.jsx | 3 + .../src/store/profiles-validation-contxt.jsx | 97 ++++++++-- 12 files changed, 580 insertions(+), 99 deletions(-) create mode 100644 frontend/packages/manager/src/components/profiles/Conditional.jsx create mode 100644 frontend/packages/manager/src/components/profiles/Conditionaln.jsx diff --git a/backend/SC4SNMP_UI_backend/common/conversions.py b/backend/SC4SNMP_UI_backend/common/conversions.py index 0f2a3ab..bab5693 100644 --- a/backend/SC4SNMP_UI_backend/common/conversions.py +++ b/backend/SC4SNMP_UI_backend/common/conversions.py @@ -46,6 +46,29 @@ def ui2backend(self, document: dict, **kwargs): class ProfileConversion(Conversion): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.__backend2ui_conditional_operations = { + "lt": "less than", + "gt": "greater than", + "equal": "equal", + "in": "in" + } + self.__ui2backend_conditional_operations = {} + for key, value in self.__backend2ui_conditional_operations.items(): + self.__ui2backend_conditional_operations[value] = key + + self.__backend2ui_profile_types = { + "field": "smart" + } + self.__ui2backend_profile_types = {} + for key, value in self.__backend2ui_profile_types.items(): + self.__ui2backend_profile_types[value] = key + + def __string_value_to_numeric(self, value: str): + if value.isnumeric(): + value = int(value) + elif value.replace(".", "").isnumeric(): + value = float(value) + return value def _backend2ui_map(self, document: dict, **kwargs): profile_name = None @@ -67,20 +90,42 @@ def _backend2ui_map(self, document: dict, **kwargs): if "condition" in document[profile_name]: backend_condition = document[profile_name]["condition"] - condition_type = backend_condition["type"] - field = backend_condition["field"] if condition_type == "field" else "" + condition_type = self.__backend2ui_profile_types[backend_condition["type"]] + field = backend_condition["field"] if backend_condition["type"] == "field" else "" patterns = [{"pattern": p} for p in backend_condition["patterns"]] \ - if condition_type == "field" else [] + if backend_condition["type"] == "field" else [] conditions = { "condition": condition_type, "field": field, - "patterns": patterns + "patterns": patterns, + "conditions": [] + } + elif "conditions" in document[profile_name]: + conditional = [] + for back_condition in document[profile_name]["conditions"]: + field = back_condition["field"] + operation = self.__backend2ui_conditional_operations[back_condition["operation"]] + value = [] + if operation == "in": + for v in back_condition["value"]: + value.append(str(v)) + else: + value.append(str(back_condition["value"])) + conditional.append( + {"field": field, "operation": operation, "value": value} + ) + conditions = { + "condition": "conditional", + "field": "", + "patterns": [], + "conditions": conditional } else: conditions = { - "condition": "None", + "condition": "standard", "field": "", - "patterns": [] + "patterns": [], + "conditions": [] } result = { "_id": str(document["_id"]), @@ -92,16 +137,30 @@ def _backend2ui_map(self, document: dict, **kwargs): return result def _ui2backend_map(self, document: dict, **kwargs): - if document['conditions']['condition'] == "field": - conditions = { + conditions = None + condition = None + if document['conditions']['condition'] == "smart": + condition = { 'type': 'field', 'field': document['conditions']['field'], 'patterns': [el['pattern'] for el in document['conditions']['patterns']] } - elif document['conditions']['condition'] == "None": - conditions = None - else: - conditions = { + elif document['conditions']['condition'] == "conditional": + conditions = [] + for ui_condition in document['conditions']['conditions']: + field = ui_condition["field"] + operation = self.__ui2backend_conditional_operations[ui_condition["operation"]] + if operation == "in": + value = [] + for v in ui_condition["value"]: + value.append(self.__string_value_to_numeric(v)) + else: + value = self.__string_value_to_numeric(ui_condition["value"][0]) + conditions.append( + {"field": field, "operation": operation, "value": value} + ) + elif document['conditions']['condition'] != "standard": + condition = { 'type': document['conditions']['condition'] } var_binds = [] @@ -119,8 +178,10 @@ def _ui2backend_map(self, document: dict, **kwargs): 'varBinds': var_binds } } + if condition is not None: + item[document['profileName']].update({'condition': condition}) if conditions is not None: - item[document['profileName']].update({'condition': conditions}) + item[document['profileName']].update({'conditions': conditions}) return item diff --git a/frontend/packages/manager/src/components/menu_header/Header.jsx b/frontend/packages/manager/src/components/menu_header/Header.jsx index 6c7e37d..9bf86a0 100644 --- a/frontend/packages/manager/src/components/menu_header/Header.jsx +++ b/frontend/packages/manager/src/components/menu_header/Header.jsx @@ -25,6 +25,7 @@ function Header(){ ProfCtx.setCondition("None"); ProfCtx.setConditionField(""); ProfCtx.setConditionPatterns([]); + ProfCtx.setConditional([]); ProfCtx.setAddOpen(true); ProfCtx.setIsEdit(false); }; diff --git a/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx b/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx index 1f04be6..dfdb42b 100644 --- a/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx +++ b/frontend/packages/manager/src/components/profiles/AddProfileModal.jsx @@ -81,7 +81,8 @@ function AddProfileModal(props) { conditions: { condition: ProfCtx.condition, field: ProfCtx.conditionField, - patterns: ProfCtx.conditionPatterns + patterns: ProfCtx.conditionPatterns, + conditions: ProfCtx.conditional } }; @@ -140,7 +141,12 @@ function AddProfileModal(props) { - +
+ + {((ValCtx.varBindsExistErrors) ? +

{ValCtx.varBindsExistErrors}

+ : null)} +
diff --git a/frontend/packages/manager/src/components/profiles/Condition.jsx b/frontend/packages/manager/src/components/profiles/Condition.jsx index 26cdb34..916efd6 100644 --- a/frontend/packages/manager/src/components/profiles/Condition.jsx +++ b/frontend/packages/manager/src/components/profiles/Condition.jsx @@ -8,6 +8,7 @@ import P from "@splunk/react-ui/Paragraph"; import FieldPatterns from "./FieldPatterns"; import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; +import Conditional from "./Conditional"; function Condition(props){ const ProfCtx = useContext(ProfileContext); @@ -23,35 +24,49 @@ function Condition(props){ return(
- { - ProfCtx.condition === 'field' ? ( + ProfCtx.condition === 'smart' ? (
- +
{((ValCtx.conditionFieldErrors) ? ValCtx.conditionFieldErrors.map((el) =>

{el}

) :

)}

- - + +
+ + {((ValCtx.patternsExistErrors) ? +

{ValCtx.patternsExistErrors}

+ : null)} +
) : null } - {ProfCtx.condition === 'conditional' ? ( -
- ) : null} + { + ProfCtx.condition === 'conditional' ? ( + +
+ + {((ValCtx.conditionalExistErrors) ? +

{ValCtx.conditionalExistErrors}

+ : null)} +
+
+ ) : null + }
) } diff --git a/frontend/packages/manager/src/components/profiles/Conditional.jsx b/frontend/packages/manager/src/components/profiles/Conditional.jsx new file mode 100644 index 0000000..378a037 --- /dev/null +++ b/frontend/packages/manager/src/components/profiles/Conditional.jsx @@ -0,0 +1,177 @@ +import React, {useContext, useEffect, useState} from 'react'; +import {createDOMID} from '@splunk/ui-utils/id'; +import Text from "@splunk/react-ui/Text"; +import P from "@splunk/react-ui/Paragraph"; +import FormRows from "@splunk/react-ui/FormRows"; +import ProfileContext from "../../store/profile-contxt"; +import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; +import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; +import Select from "@splunk/react-ui/Select"; +import Card from '@splunk/react-ui/Card'; +import ConditionalIn from "./Conditionaln"; + +function Conditional(props){ + const ProfCtx = useContext(ProfileContext); + const ValCtx = useContext(ProfilesValidationContxt); + const [indices, setIndices] = useState({}); + const [reload, setReload] = useState(true); + const [rowItems, setRowItems] = useState([]); + + const handleRequestRemove = (e, { index }) => { + const indicesCopy = {...indices}; + let keyToDelete; + const conditionsCopy = ProfCtx.conditional; + conditionsCopy.splice(index, 1); + + const indicesKeys = Object.keys(indices) + indicesKeys.forEach((keyID) => { + if (indices[`${keyID}`] > index){ + indicesCopy[`${keyID}`] -= 1; + } + if (indices[`${keyID}`] === index){ + keyToDelete = keyID; + } + }) + delete indicesCopy[`${keyToDelete}`]; + + // Update field errors indexes after deleting an element + const errorField = ValCtx.conditionalFieldErrors; + if (errorField){ + const errorKeys = Object.keys(errorField); + errorKeys.forEach((errorID) => { + if (Number(errorID) === index){delete errorField[Number(errorID)];} + if (Number(errorID) > index) { + errorField[Number(errorID)-1] = errorField[Number(errorID)]; + delete errorField[Number(errorID)] + } + }) + ValCtx.setConditionalFieldErrors(errorField); + } + + // Update values errors indexes after deleting an element + const errorValue = ValCtx.conditionalValuesErrors; + if (errorValue){ + const errorKeys = Object.keys(errorValue); + errorKeys.forEach((errorID) => { + if (Number(errorID) === index){delete errorValue[Number(errorID)];} + if (Number(errorID) > index) { + errorValue[Number(errorID)-1] = errorValue[Number(errorID)]; + delete errorValue[Number(errorID)] + } + }) + ValCtx.setConditionalValuesErrors(errorValue); + } + + setRowItems((prev) => FormRows.removeRow(index, prev)); + setIndices(indicesCopy); + ProfCtx.setConditionPatterns(conditionsCopy); + setReload((prev)=>{return !prev}); + }; + + const handleField = (index, e) => { + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[index].field = e.target.value + ProfCtx.setConditional(conditionalCopy); + } + + const handleOperation = (index, operationValue) => { + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[index].operation = operationValue + ProfCtx.setConditional(conditionalCopy); + setReload((prev)=>{return !prev}); + } + + const handleValue = (index, e) => { + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[index].value[0] = e.target.value + ProfCtx.setConditional(conditionalCopy); + } + + const handleRequestAdd = () => { + const indicesCopy = indices; + const newIndex = rowItems.length; + const keyID = createDOMID(); + const conditionalCopy = ProfCtx.conditional; + conditionalCopy.push({field: "", operation: "equal", value: [""]}); + indicesCopy[`${keyID}`] = newIndex; + setIndices(indicesCopy); + ProfCtx.setConditional(conditionalCopy); + setReload((prev)=>{return !prev}); + }; + + const loadFormRows = () => { + let index = -1; + const newIndices = {}; + const items = ProfCtx.conditional.map(condition => { + index += 1; + const indexCopy = index; + const keyID = createDOMID(); + newIndices[`${keyID}`] = indexCopy; + return ( + + + +
+ handleField(newIndices[`${keyID}`], e)} + error={((ValCtx.conditionalFieldErrors && newIndices[`${keyID}`] in ValCtx.conditionalFieldErrors))}/> + {((ValCtx.conditionalFieldErrors && newIndices[`${keyID}`] in ValCtx.conditionalFieldErrors) ? + ValCtx.conditionalFieldErrors[newIndices[`${keyID}`]].map((el) => +

{el}

) :

)} +

+
+ +

+

+ { + ProfCtx.conditional[newIndices[`${keyID}`]].operation === 'in' ? ( +
+ + {((ValCtx.conditionalValuesExistErrors && newIndices[`${keyID}`] in ValCtx.conditionalValuesExistErrors) ? +

{ValCtx.conditionalValuesExistErrors[newIndices[`${keyID}`]]}

+ : null)} +
+ ) : ( +
+ handleValue(newIndices[`${keyID}`], e)} + error={((ValCtx.conditionalValuesErrors && newIndices[`${keyID}`] in ValCtx.conditionalValuesErrors + && 0 in ValCtx.conditionalValuesErrors[newIndices[`${keyID}`]]))}/> + {((ValCtx.conditionalValuesErrors && newIndices[`${keyID}`] in ValCtx.conditionalValuesErrors + && 0 in ValCtx.conditionalValuesErrors[newIndices[`${keyID}`]]) ? + ValCtx.conditionalValuesErrors[newIndices[`${keyID}`]][0].map((el) => +

{el}

) :

)} +

+ ) + } +
+
+
+ ); + }); + setIndices(newIndices); + return items; + } + + useEffect(() => { + let isMounted = true; + setRowItems(loadFormRows()); + return () => { isMounted = false } + }, [props.newSubmit, reload]); + + return ( + + {rowItems} + + ) +} + +export default Conditional; diff --git a/frontend/packages/manager/src/components/profiles/Conditionaln.jsx b/frontend/packages/manager/src/components/profiles/Conditionaln.jsx new file mode 100644 index 0000000..05b0f6a --- /dev/null +++ b/frontend/packages/manager/src/components/profiles/Conditionaln.jsx @@ -0,0 +1,116 @@ +import React, {useContext, useEffect, useState} from 'react'; +import {createDOMID} from '@splunk/ui-utils/id'; +import Text from "@splunk/react-ui/Text"; +import P from "@splunk/react-ui/Paragraph"; +import FormRows from "@splunk/react-ui/FormRows"; +import ProfileContext from "../../store/profile-contxt"; +import {validationGroup, validationMessage} from "../../styles/ValidationStyles"; +import ProfilesValidationContxt from "../../store/profiles-validation-contxt"; + +function ConditionalIn(props){ + const ProfCtx = useContext(ProfileContext); + const ValCtx = useContext(ProfilesValidationContxt); + const [indices, setIndices] = useState({}); + const [reload, setReload] = useState(true); + const [rowItems, setRowItems] = useState([]); + + const handleRequestRemove = (e, { index }) => { + const indicesCopy = {...indices}; + let keyToDelete; + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[props.conditionIndex].value.splice(index, 1); + + const indicesKeys = Object.keys(indices) + indicesKeys.forEach((keyID) => { + if (indices[`${keyID}`] > index){ + indicesCopy[`${keyID}`] -= 1; + } + if (indices[`${keyID}`] === index){ + keyToDelete = keyID; + } + }) + delete indicesCopy[`${keyToDelete}`]; + + // Update errors indexes after deleting an element + const errors = ValCtx.conditionalValuesErrors; + if (errors && errors.hasOwnProperty(props.conditionIndex)){ + const error = errors[props.conditionIndex] + const errorKeys = Object.keys(error); + errorKeys.forEach((errorID) => { + if (Number(errorID) === index){delete error[Number(errorID)];} + if (Number(errorID) > index) { + error[Number(errorID)-1] = error[Number(errorID)]; + delete error[Number(errorID)] + } + }) + errors[props.conditionIndex] = error + ValCtx.setConditionalValuesErrors(errors); + } + setRowItems((prev) => FormRows.removeRow(index, prev)); + setIndices(indicesCopy); + ProfCtx.setConditional(conditionalCopy); + setReload((prev)=>{return !prev}); + }; + + const handleItemValue = (index, e) => { + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[props.conditionIndex].value[index] = e.target.value + ProfCtx.setConditional(conditionalCopy); + } + + const handleRequestAdd = () => { + const indicesCopy = indices; + const newIndex = rowItems.length; + const keyID = createDOMID(); + const conditionalCopy = ProfCtx.conditional; + conditionalCopy[props.conditionIndex].value.push(""); + indicesCopy[`${keyID}`] = newIndex; + setIndices(indicesCopy); + ProfCtx.setConditionPatterns(conditionalCopy); + setReload((prev)=>{return !prev}); + }; + + const loadFormRows = () => { + let index = -1; + const newIndices = {} + const items = ProfCtx.conditional[props.conditionIndex].value.map(value => { + index += 1; + const indexCopy = index; + const keyID = createDOMID(); + newIndices[`${keyID}`] = indexCopy; + return ( + +
+ handleItemValue(newIndices[`${keyID}`], e)} + error={((ValCtx.conditionalValuesErrors && props.conditionIndex in ValCtx.conditionalValuesErrors)) && + newIndices[`${keyID}`] in ValCtx.conditionalValuesErrors[props.conditionIndex]}/> + {((ValCtx.conditionalValuesErrors && props.conditionIndex in ValCtx.conditionalValuesErrors + && newIndices[`${keyID}`] in ValCtx.conditionalValuesErrors[props.conditionIndex]) ? + ValCtx.conditionalValuesErrors[props.conditionIndex][newIndices[`${keyID}`]].map((el) => +

{el}

) :

)} +

+
+ ); + }); + setIndices(newIndices); + return items; + } + + useEffect(() => { + let isMounted = true; + setRowItems(loadFormRows()); + return () => { isMounted = false } + }, [props.newSubmit, reload]); + + return ( + + {rowItems} + + ) +} + +export default ConditionalIn; diff --git a/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx b/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx index 086d7e8..731e79d 100644 --- a/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx +++ b/frontend/packages/manager/src/components/profiles/FieldPatterns.jsx @@ -63,20 +63,6 @@ function FieldPatterns(props){ const patternsCopy = ProfCtx.conditionPatterns; patternsCopy.push({pattern: ""}); indicesCopy[`${keyID}`] = newIndex; - setRowItems((prev) => - FormRows.addRow( - -
- handleItemValue(indices[`${keyID}`], e)} - error={((ValCtx.conditionPatternsErrors && indices[`${keyID}`] in ValCtx.conditionPatternsErrors))}/> - {((ValCtx.conditionPatternsErrors && indices[`${keyID}`] in ValCtx.conditionPatternsErrors) ? - ValCtx.conditionPatternsErrors[indices[`${keyID}`]].map((el) => -

{el}

) :

)} -

-
, - prev - ) - ); setIndices(indicesCopy); ProfCtx.setConditionPatterns(patternsCopy); setReload((prev)=>{return !prev}); @@ -91,7 +77,7 @@ function FieldPatterns(props){ const keyID = createDOMID(); newIndices[`${keyID}`] = indexCopy; return ( - +
handleItemValue(newIndices[`${keyID}`], e)} error={((ValCtx.conditionPatternsErrors && newIndices[`${keyID}`] in ValCtx.conditionPatternsErrors))}/> diff --git a/frontend/packages/manager/src/components/profiles/ProfilesList.jsx b/frontend/packages/manager/src/components/profiles/ProfilesList.jsx index 7ae3836..bc432f3 100644 --- a/frontend/packages/manager/src/components/profiles/ProfilesList.jsx +++ b/frontend/packages/manager/src/components/profiles/ProfilesList.jsx @@ -21,9 +21,6 @@ function getExpansionRow(row) { {/* Empty cell */} {/* Empty cell */} {/* Empty cell */} - {/* Empty cell */} - {row.conditions.patterns && row.conditions.patterns.map(value => -

{value.pattern}

)}
{row.varBinds.map((value) => (

{value.family}

@@ -52,12 +49,10 @@ function ProfilesList() { const columns = [ {sortKey: 'profileName', label: 'Profile name'}, {sortKey: 'frequency', label: 'Frequency'}, - {sortKey: 'condition', label: 'Condition'}, - {sortKey: 'field', label: 'Field'}, - {sortKey: 'patterns', label: 'Patterns'}, + {sortKey: 'profileType', label: 'Profile type'}, {sortKey: `mibFamily`, label: 'MIB family'}, {sortKey: `mibCategory`, label: 'MIB category'}, - {sortKey: `index`, label: 'Index'}, + {sortKey: `index`, label: 'MIB Index'}, {sortKey: `actions`, label: 'Actions'}, ]; @@ -112,6 +107,7 @@ function ProfilesList() { ProfCtx.setCondition(row.conditions.condition); ProfCtx.setConditionField(row.conditions.field); ProfCtx.setConditionPatterns(row.conditions.patterns); + ProfCtx.setConditional(row.conditions.conditions); ProfCtx.setIsEdit(true); ProfCtx.setAddOpen(true); }; @@ -186,8 +182,6 @@ function ProfilesList() { {row.profileName} {row.frequency} {row.conditions.condition} - {row.conditions.field} - {/* Condition patterns is empty in this view */} {(row.varBinds.length === 1) ? `1 MIB family` : `${row.varBinds.length} MIB families`} {/* MIB category is empty in this view */} diff --git a/frontend/packages/manager/src/components/profiles/VarBinds.jsx b/frontend/packages/manager/src/components/profiles/VarBinds.jsx index d3d8a06..288c23c 100644 --- a/frontend/packages/manager/src/components/profiles/VarBinds.jsx +++ b/frontend/packages/manager/src/components/profiles/VarBinds.jsx @@ -75,30 +75,6 @@ function VarBinds(props){ const varBindsCopy = ProfCtx.varBinds; varBindsCopy.push({family: "", category: "", index: ""}); indicesCopy[`${keyID}`] = newIndex; - setRowItems((prev) => - FormRows.addRow( - -
-
- handleItemValueFamily(indices[`${keyID}`], e)} - error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> - handleItemValueCategory(indices[`${keyID}`], e)} - error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> - handleItemValueIndex(indices[`${keyID}`], e)} - error={((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors))}/> -
- {((ValCtx.varBindsErrors && indices[`${keyID}`] in ValCtx.varBindsErrors) ? - ValCtx.varBindsErrors[indices[`${keyID}`]].map((el) =>

{el}

) : -

)} -

-
, - prev - ) - ); setIndices(indicesCopy); ProfCtx.setVarBinds(varBindsCopy); setReload((prev)=>{return !prev}); @@ -116,13 +92,13 @@ function VarBinds(props){
- handleItemValueFamily(newIndices[`${keyID}`], e)} error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/> - handleItemValueCategory(newIndices[`${keyID}`], e)} error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/> - handleItemValueIndex(newIndices[`${keyID}`], e)} error={((ValCtx.varBindsErrors && newIndices[`${keyID}`] in ValCtx.varBindsErrors))}/>
diff --git a/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx b/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx index 4bda4d4..b798df0 100644 --- a/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx +++ b/frontend/packages/manager/src/components/validation/ValidateProfiles.jsx @@ -3,10 +3,20 @@ import React from 'react'; const validateProfiles = (validationObj) => { /* 'errors' is an object storing error messages for each field. Example data structure for 'errors': + profileName: ["message1", "message2"] -> list of messages for profile name + frequency: ["message1", "message2"] -> list of messages for frequency + conditionField: ["message1", "message2"] -> list of messages for 'field' input + conditionPatterns: {2: ["message1", "message2"], 5: ["message3"]} -> key indicates index of a pattern. Each pattern has its own list of errors + + conditionalField: {2: ["message1", "message2"], 5: ["message3"]} -> key indicates index of a conditional key. Each pattern has its own list of errors + + conditionalValues: {2:{1:["message1", "message2"], 3:["message1"]}, 6:{0:["message1"]}} -> key indicates index of a condition. Each condition + is represented by an object where keys correspond to indices of values of this condition. Each value has its own list of errors. + varBinds: {0: ["message1", "message2"], 3: ["message3"]} -> key indicates index of a varBind. Each varBind has its own list of errors */ @@ -15,7 +25,13 @@ const validateProfiles = (validationObj) => { frequency: [], conditionField: [], conditionPatterns: {}, - varBinds: {} + patternsExist: "", + conditionalField: {}, + conditionalValues: {}, + conditionalValuesExist: {}, + conditionalExist: "", + varBinds: {}, + varBindsExist: "" }; let isValid = true; @@ -28,21 +44,21 @@ const validateProfiles = (validationObj) => { errors.profileName.push("Profile Name can consist only of upper and lower english letters, " + "numbers and two special characters: '-' and '_'. No spaces are allowed."); isValid = false; - }; - }; + } + } // Validate Frequency if (validationObj.hasOwnProperty("frequency")){ if (!(Number.isInteger(validationObj.frequency) && validationObj.frequency > 0)){ errors.frequency.push("Frequency must be a positive integer"); isValid = false; - }; - }; + } + } let message; // Validate Condition if (validationObj.hasOwnProperty("conditions")){ - if (validationObj.conditions.condition === "field"){ + if (validationObj.conditions.condition === "smart"){ // Validate 'field' input if (validationObj.conditions.field.length === 0){ errors.conditionField.push("Field is required"); @@ -51,7 +67,12 @@ const validateProfiles = (validationObj) => { errors.conditionField.push("Field can consist only of upper and lower english letters, " + "numbers and three special characters: '.' '-' and '_'. No spaces are allowed."); isValid = false; - }; + } + // Check if patterns exist + if ( validationObj.conditions.patterns.length === 0){ + errors.patternsExist = "At least one patter must be specified."; + isValid = false; + } // Validate each pattern for (let i = 0; i < validationObj.conditions.patterns.length; i++){ if (validationObj.conditions.patterns[i].pattern.length === 0){ @@ -72,12 +93,66 @@ const validateProfiles = (validationObj) => { } isValid = false; }; */ - }; - }; - }; + } + }else if (validationObj.conditions.condition === "conditional"){ + // Validate 'field' input + let field + let values; + if (validationObj.conditions.conditions.length === 0){ + errors.conditionalExist = "At least one condition must be specified."; + isValid = false; + } + for (let i = 0; i < validationObj.conditions.conditions.length; i++){ + field = validationObj.conditions.conditions[i].field; + if (field.length === 0){ + if (errors.conditionalField.hasOwnProperty(i)){ + errors.conditionalField[i].push("Field is required"); + }else{ + errors.conditionalField[i] = ["Field is required"]; + } + isValid = false; + }else if (!field.match(/^[.a-zA-Z0-9_-]+$/)){ + if (errors.conditionalField.hasOwnProperty(i)){ + errors.conditionalField[i].push("Field can consist only of upper and lower english letters, " + + "numbers and three special characters: '.' '-' and '_'. No spaces are allowed."); + }else{ + errors.conditionalField[i] = ["Field can consist only of upper and lower english letters, " + + "numbers and three special characters: '.' '-' and '_'. No spaces are allowed."]; + } + isValid = false; + } + + values = validationObj.conditions.conditions[i].value; + if (values.length === 0){ + errors.conditionalValuesExist[i] = "At least one value must be specified."; + isValid = false; + } + let conditionsErrors = {} + for (let j = 0; j < values.length; j++){ + if (values[j].length === 0){ + if (errors.conditionalValues.hasOwnProperty(i) && errors.conditionalValues[i].hasOwnProperty(j)){ + errors.conditionalValues[i][j].push("Value is required"); + }else if (errors.conditionalValues.hasOwnProperty(i)){ + errors.conditionalValues[i][j] = ["Value is required"]; + } + else{ + conditionsErrors = {} + conditionsErrors[j] = ["Value is required"] + errors.conditionalValues[i] = conditionsErrors; + } + isValid = false; + } + } + } + } + } // Validate VarBinds if (validationObj.hasOwnProperty("varBinds")){ + if (validationObj.varBinds.length === 0){ + errors.varBindsExist = "At least one varBind must be specified."; + isValid = false; + } for (let i = 0; i < validationObj.varBinds.length; i++){ if (validationObj.varBinds[i].family.length === 0){ message = "MIB-Component is required"; diff --git a/frontend/packages/manager/src/store/profile-contxt.jsx b/frontend/packages/manager/src/store/profile-contxt.jsx index bda89c2..c39a9a4 100644 --- a/frontend/packages/manager/src/store/profile-contxt.jsx +++ b/frontend/packages/manager/src/store/profile-contxt.jsx @@ -24,6 +24,7 @@ export function ProfileContxtProvider(props) { const [condition, setCondition] = useState("None"); const [conditionField, setConditionField] = useState(""); const [conditionPatterns, setConditionPatterns] = useState([]); + const [conditional, setConditional] = useState([]); const [isEdit, setIsEdit] = useState(false); function profilesChangeHandler() { @@ -54,6 +55,8 @@ export function ProfileContxtProvider(props) { setConditionField, conditionPatterns, setConditionPatterns, + conditional, + setConditional, isEdit, setIsEdit }; diff --git a/frontend/packages/manager/src/store/profiles-validation-contxt.jsx b/frontend/packages/manager/src/store/profiles-validation-contxt.jsx index c533689..0ab7b24 100644 --- a/frontend/packages/manager/src/store/profiles-validation-contxt.jsx +++ b/frontend/packages/manager/src/store/profiles-validation-contxt.jsx @@ -6,8 +6,14 @@ export function ProfilesValidationContxtProvider(props){ const [profileNameErrors, setProfileNameErrors] = useState(null); const [frequencyErrors, setFrequencyErrors] = useState(null); const [varBindsErrors, setVarBindsErrors] = useState(null); + const [varBindsExistErrors, setVarBindsExistErrors] = useState(null); const [conditionFieldErrors, setConditionFieldErrors] = useState(null); const [conditionPatternsErrors, setConditionPatternsErrors] = useState(null); + const [patternsExistErrors, setPatternsExistErrors] = useState(null); + const [conditionalFieldErrors, setConditionalFieldErrors] = useState(null); + const [conditionalValuesErrors, setConditionalValuesErrors] = useState(null); + const [conditionalValuesExistErrors, setConditionalValuesExistErrors] = useState(null); + const [conditionalExistErrors, setConditionalExistErrors] = useState(null); const resetAllErrors = () =>{ setProfileNameErrors(null); @@ -15,6 +21,12 @@ export function ProfilesValidationContxtProvider(props){ setConditionFieldErrors(null); setConditionPatternsErrors(null); setVarBindsErrors(null); + setConditionalFieldErrors(null); + setConditionalValuesErrors(null); + setVarBindsExistErrors(null); + setPatternsExistErrors(null); + setConditionalValuesExistErrors(null); + setConditionalExistErrors(null); }; const resetErrors = (category) =>{ @@ -34,6 +46,24 @@ export function ProfilesValidationContxtProvider(props){ case "varBinds": setVarBindsErrors(null); break; + case "conditionalField": + setConditionalFieldErrors(null); + break; + case "conditionalValues": + setConditionalValuesErrors(null); + break; + case "patternsExist": + setPatternsExistErrors(null); + break; + case "conditionalValuesExist": + setConditionalValuesExistErrors(null); + break; + case "conditionalExist": + setConditionalExistErrors(null); + break; + case "varBindsExist": + setVarBindsExistErrors(null); + break; default: break; } @@ -56,25 +86,66 @@ export function ProfilesValidationContxtProvider(props){ case "varBinds": setVarBindsErrors(errors); break; + case "conditionalField": + setConditionalFieldErrors(errors); + break; + case "conditionalValues": + setConditionalValuesErrors(errors); + break; + case "patternsExist": + setPatternsExistErrors(errors); + break; + case "conditionalValuesExist": + setConditionalValuesExistErrors(errors); + break; + case "conditionalExist": + setConditionalExistErrors(errors); + break; + case "varBindsExist": + setVarBindsExistErrors(errors); + break; default: break; } }; const context = { - profileNameErrors: profileNameErrors, - setProfileNameErrors: setProfileNameErrors, - frequencyErrors: frequencyErrors, - setFrequencyErrors: setFrequencyErrors, - varBindsErrors: varBindsErrors, - setVarBindsErrors: setVarBindsErrors, - conditionFieldErrors: conditionFieldErrors, - setConditionFieldErrors: setConditionFieldErrors, - conditionPatternsErrors: conditionPatternsErrors, - setConditionPatternsErrors: setConditionPatternsErrors, - resetAllErrors: resetAllErrors, - resetErrors: resetErrors, - setErrors: setErrors, + profileNameErrors, + setProfileNameErrors, + + frequencyErrors, + setFrequencyErrors, + + varBindsErrors, + setVarBindsErrors, + + varBindsExistErrors, + setVarBindsExistErrors, + + conditionFieldErrors, + setConditionFieldErrors, + + conditionPatternsErrors, + setConditionPatternsErrors, + + patternsExistErrors, + setPatternsExistErrors, + + conditionalFieldErrors, + setConditionalFieldErrors, + + conditionalValuesErrors, + setConditionalValuesErrors, + + conditionalValuesExistErrors, + setConditionalValuesExistErrors, + + conditionalExistErrors, + setConditionalExistErrors, + + resetAllErrors, + resetErrors, + setErrors, }; return ( From 3f7bbdfd4afb2f1880712b13b17caa33eeea289b Mon Sep 17 00:00:00 2001 From: Wojciech Zyla Date: Fri, 7 Jul 2023 13:05:22 +0200 Subject: [PATCH 3/3] fix: update __string_value_to_numeric method --- backend/SC4SNMP_UI_backend/common/conversions.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/SC4SNMP_UI_backend/common/conversions.py b/backend/SC4SNMP_UI_backend/common/conversions.py index bab5693..c484487 100644 --- a/backend/SC4SNMP_UI_backend/common/conversions.py +++ b/backend/SC4SNMP_UI_backend/common/conversions.py @@ -64,11 +64,13 @@ def __init__(self, *args, **kwargs): self.__ui2backend_profile_types[value] = key def __string_value_to_numeric(self, value: str): - if value.isnumeric(): - value = int(value) - elif value.replace(".", "").isnumeric(): - value = float(value) - return value + try: + if value.isnumeric(): + return int(value) + elif value.replace(".", "").isnumeric(): + return float(value) + except ValueError: + return value def _backend2ui_map(self, document: dict, **kwargs): profile_name = None