From 369d96e50ab523c85123e6d783c44d012e7756ed Mon Sep 17 00:00:00 2001 From: snachx Date: Tue, 7 Dec 2021 00:05:24 +0800 Subject: [PATCH 1/3] fix: parse tags and capabilities in flow rules correctly --- backend/services/network.js | 8 ++++++ .../components/NetworkRules/NetworkRules.jsx | 28 ++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backend/services/network.js b/backend/services/network.js index c2a0958..91234ca 100644 --- a/backend/services/network.js +++ b/backend/services/network.js @@ -62,6 +62,8 @@ async function createNetworkAdditionalData(nwid) { additionalConfig: { description: "", rulesSource: constants.defaultRulesSource, + tagsByName: {}, + capabilitiesByName: {}, }, members: [], }; @@ -79,6 +81,12 @@ async function updateNetworkAdditionalData(nwid, data) { if (data.hasOwnProperty("rulesSource")) { additionalData.rulesSource = data.rulesSource; } + if (data.hasOwnProperty("tagsByName")) { + additionalData.tagsByName = data.tagsByName; + } + if (data.hasOwnProperty("capabilitiesByName")) { + additionalData.capabilitiesByName = data.capabilitiesByName; + } if (additionalData) { db.get("networks") diff --git a/frontend/src/components/NetworkRules/NetworkRules.jsx b/frontend/src/components/NetworkRules/NetworkRules.jsx index 79d7450..dd4fbad 100644 --- a/frontend/src/components/NetworkRules/NetworkRules.jsx +++ b/frontend/src/components/NetworkRules/NetworkRules.jsx @@ -29,6 +29,10 @@ function NetworkRules({ network }) { capabilities: [...network.config.capabilities], tags: [...network.config.tags], }); + const [tagCapByNameData, setTagCapByNameData] = useState({ + tagsByName: network.tagsByName || {}, + capabilitiesByName: network.capabilitiesByName || {}, + }); const [errors, setErrors] = useState([]); const [snackbarOpen, setSnackbarOpen] = useState(false); @@ -37,6 +41,7 @@ function NetworkRules({ network }) { const req = await API.post("/network/" + network["config"]["id"], { config: { ...flowData }, rulesSource: editor.getValue(), + ...tagCapByNameData, }); console.log("Action", req); setSnackbarOpen(true); @@ -51,14 +56,29 @@ function NetworkRules({ network }) { const src = event.getValue(); setEditor(event); let rules = [], - caps = [], - tags = []; + caps = {}, + tags = {}; const res = compile(src, rules, caps, tags); if (!res) { + let capabilitiesByName = {}; + for (var key in caps) { + capabilitiesByName[key] = caps[key].id; + } + + let tagsByName = { ...tags }; + for (let key in tags) { + tags[key] = { id: tags[key].id, default: tags[key].default }; + } + setFlowData({ rules: [...rules], - capabilities: [...caps], - tags: [...tags], + capabilities: [...Object.values(caps)], + tags: [...Object.values(tags)], + }); + + setTagCapByNameData({ + tagsByName: tagsByName, + capabilitiesByName: capabilitiesByName, }); setErrors([]); } else { From 8f891747d6d98c0957405954f50c5bab5d2f9551 Mon Sep 17 00:00:00 2001 From: snachx Date: Sat, 11 Dec 2021 21:14:26 +0800 Subject: [PATCH 2/3] feat: add capabilities and tags support #35 --- .../NetworkMembers/NetworkMembers.jsx | 62 +++---- .../MemberSettings/MemberSettings.jsx | 70 +++++++- .../MemberSettings/components/Tag/Tag.jsx | 165 ++++++++++++++++++ .../MemberSettings/components/Tag/index.jsx | 1 + .../components/NetworkRules/NetworkRules.jsx | 18 +- frontend/src/routes/Network/Network.jsx | 48 ++--- frontend/src/utils/ChangeHelper.js | 21 +++ 7 files changed, 314 insertions(+), 71 deletions(-) create mode 100644 frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/Tag.jsx create mode 100644 frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/index.jsx diff --git a/frontend/src/components/NetworkMembers/NetworkMembers.jsx b/frontend/src/components/NetworkMembers/NetworkMembers.jsx index 303fb25..3f1d368 100644 --- a/frontend/src/components/NetworkMembers/NetworkMembers.jsx +++ b/frontend/src/components/NetworkMembers/NetworkMembers.jsx @@ -1,30 +1,26 @@ -import { useState, useEffect, useCallback } from "react"; -import { useParams } from "react-router-dom"; - import { Accordion, - AccordionSummary, AccordionDetails, + AccordionSummary, Checkbox, Grid, - Typography, IconButton, + Typography, } from "@material-ui/core"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import RefreshIcon from "@material-ui/icons/Refresh"; - +import { useCallback, useEffect, useState } from "react"; import DataTable from "react-data-table-component"; - -import MemberName from "./components/MemberName"; -import ManagedIP from "./components/ManagedIP"; -import DeleteMember from "./components/DeleteMember"; -import MemberSettings from "./components/MemberSettings"; -import AddMember from "./components/AddMember"; - +import { useParams } from "react-router-dom"; import API from "utils/API"; import { parseValue, replaceValue, setValue } from "utils/ChangeHelper"; +import AddMember from "./components/AddMember"; +import DeleteMember from "./components/DeleteMember"; +import ManagedIP from "./components/ManagedIP"; +import MemberName from "./components/MemberName"; +import MemberSettings from "./components/MemberSettings"; -function NetworkMembers() { +function NetworkMembers({ network }) { const { nwid } = useParams(); const [members, setMembers] = useState([]); @@ -49,27 +45,23 @@ function NetworkMembers() { console.log("Action:", req); }; - const handleChange = ( - member, - key1, - key2 = null, - mode = "text", - id = null - ) => (event) => { - const value = parseValue(event, mode, member, key1, key2, id); + const handleChange = + (member, key1, key2 = null, mode = "text", id = null) => + (event) => { + const value = parseValue(event, mode, member, key1, key2, id); - const updatedMember = replaceValue({ ...member }, key1, key2, value); + const updatedMember = replaceValue({ ...member }, key1, key2, value); - const index = members.findIndex((item) => { - return item["config"]["id"] === member["config"]["id"]; - }); - let mutableMembers = [...members]; - mutableMembers[index] = updatedMember; - setMembers(mutableMembers); + const index = members.findIndex((item) => { + return item["config"]["id"] === member["config"]["id"]; + }); + let mutableMembers = [...members]; + mutableMembers[index] = updatedMember; + setMembers(mutableMembers); - const data = setValue({}, key1, key2, value); - sendReq(member["config"]["id"], data); - }; + const data = setValue({}, key1, key2, value); + sendReq(member["config"]["id"], data); + }; const columns = [ { @@ -130,7 +122,11 @@ function NetworkMembers() { right: true, cell: (row) => ( <> - + ), diff --git a/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx b/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx index f442e41..df6bb64 100644 --- a/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx +++ b/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx @@ -1,16 +1,19 @@ -import { useState } from "react"; - import { Checkbox, Dialog, - DialogTitle, DialogContent, + DialogTitle, + FormControlLabel, Grid, IconButton, + Paper, + Typography, } from "@material-ui/core"; import BuildIcon from "@material-ui/icons/Build"; +import { useState } from "react"; +import Tag from "./components/Tag"; -function MemberSettings({ member, handleChange }) { +function MemberSettings({ member, network, handleChange }) { const [open, setOpen] = useState(false); const handleClickOpen = () => { @@ -55,6 +58,65 @@ function MemberSettings({ member, handleChange }) { /> Do Not Auto-Assign IPs + + + Capabilities + + + + {Object.entries(network["capabilitiesByName"]).length === 0 + ? "No capabilities defined" + : ""} + {Object.entries(network["capabilitiesByName"]).map( + ([capName, capId]) => ( + + } + key={"cap-" + capId} + label={capName} + /> + ) + )} + + + + + + Tags + + {Object.entries(network["tagsByName"]).length === 0 ? ( + + No tags defined + + ) : ( + "" + )} + {Object.entries(network["tagsByName"]).map( + ([tagName, tagDetail]) => ( + + + + ) + )} + diff --git a/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/Tag.jsx b/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/Tag.jsx new file mode 100644 index 0000000..3335f33 --- /dev/null +++ b/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/Tag.jsx @@ -0,0 +1,165 @@ +import { + Checkbox, + FormControlLabel, + Grid, + IconButton, + Input, + Paper, + Select, + Typography, +} from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; +import { useEffect, useState } from "react"; +import { useDebounce } from "react-use"; + +function Tag({ member, tagName, tagDetail, handleChange }) { + const [tagValue, setTagValue] = useState(""); + const [tagChangedByUser, setTagChangedByUser] = useState(false); + + useEffect(() => { + let tagIndex = member["config"]["tags"].findIndex((item) => { + return item[0] === tagDetail["id"]; + }); + let value = ""; + if (tagIndex !== -1) { + value = member["config"]["tags"][tagIndex][1]; + } + value = value !== false ? value : ""; + setTagValue(value); + }, [member, tagDetail]); + + useDebounce( + async () => { + if (tagChangedByUser) { + let value = tagValue === "" ? "" : parseInt(tagValue); + let event = { target: { value: value } }; + handleChange( + member, + "config", + "tags", + "tagChange", + tagDetail["id"] + )(event); + } + setTagChangedByUser(false); + }, + 500, + [tagValue] + ); + + const handleSelectChange = (event) => { + let newValue = event.target.value; + setTagChangedByUser(true); + setTagValue(newValue); + }; + + const handleFlagChange = (value) => (event) => { + let newValue; + let oldValue; + + if (tagValue === "") { + oldValue = 0; + } else { + oldValue = tagValue; + } + + if (event.target.checked) { + newValue = oldValue + value; + } else { + newValue = oldValue - value; + } + setTagChangedByUser(true); + setTagValue(newValue); + }; + + const handleInputChange = (event) => { + let value = event.target.value; + if (/^(|0|[1-9]\d*)$/.test(value)) { + value = value === "" ? value : parseInt(value); + } else { + value = 0; + } + setTagChangedByUser(true); + setTagValue(value); + }; + + const clearTag = (event) => { + setTagChangedByUser(true); + setTagValue(""); + }; + + return ( + + + + + {tagName} + {tagValue === "" ? ( + "" + ) : ( + + + + )} + + + + + + + + + + + + {Object.entries(tagDetail["flags"]).map(([flagKey, flagValue]) => ( + + } + key={"flag-" + flagKey} + label={flagKey} + /> + ))} + + + + ); +} + +export default Tag; diff --git a/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/index.jsx b/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/index.jsx new file mode 100644 index 0000000..21bb058 --- /dev/null +++ b/frontend/src/components/NetworkMembers/components/MemberSettings/components/Tag/index.jsx @@ -0,0 +1 @@ +export { default } from "./Tag"; diff --git a/frontend/src/components/NetworkRules/NetworkRules.jsx b/frontend/src/components/NetworkRules/NetworkRules.jsx index dd4fbad..1fc6788 100644 --- a/frontend/src/components/NetworkRules/NetworkRules.jsx +++ b/frontend/src/components/NetworkRules/NetworkRules.jsx @@ -1,28 +1,23 @@ -import { useState } from "react"; - import { Accordion, - AccordionSummary, AccordionDetails, + AccordionSummary, Button, Divider, - Snackbar, - Hidden, Grid, + Hidden, + Snackbar, Typography, } from "@material-ui/core"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; - import CodeMirror from "@uiw/react-codemirror"; import "codemirror/theme/3024-day.css"; - import { compile } from "external/RuleCompiler"; - import debounce from "lodash/debounce"; - +import { useState } from "react"; import API from "utils/API"; -function NetworkRules({ network }) { +function NetworkRules({ network, callback }) { const [editor, setEditor] = useState(null); const [flowData, setFlowData] = useState({ rules: [...network.config.rules], @@ -48,6 +43,9 @@ function NetworkRules({ network }) { const timer = setTimeout(() => { setSnackbarOpen(false); }, 1500); + + callback(); + return () => clearTimeout(timer); } }; diff --git a/frontend/src/routes/Network/Network.jsx b/frontend/src/routes/Network/Network.jsx index 7a41669..f1475fb 100644 --- a/frontend/src/routes/Network/Network.jsx +++ b/frontend/src/routes/Network/Network.jsx @@ -1,18 +1,15 @@ -import { useState, useEffect } from "react"; -import { Link as RouterLink, useParams, useHistory } from "react-router-dom"; -import { useLocalStorage } from "react-use"; - -import { Link, Grid, Typography } from "@material-ui/core"; +import { Grid, Link, Typography } from "@material-ui/core"; import ArrowBackIcon from "@material-ui/icons/ArrowBack"; -import useStyles from "./Network.styles"; - import NetworkHeader from "components/NetworkHeader"; -import NetworkSettings from "components/NetworkSettings"; +import NetworkManagement from "components/NetworkManagement"; import NetworkMembers from "components/NetworkMembers"; import NetworkRules from "components/NetworkRules"; -import NetworkManagement from "components/NetworkManagement"; - +import NetworkSettings from "components/NetworkSettings"; +import { useCallback, useEffect, useState } from "react"; +import { Link as RouterLink, useHistory, useParams } from "react-router-dom"; +import { useLocalStorage } from "react-use"; import API from "utils/API"; +import useStyles from "./Network.styles"; function Network() { const { nwid } = useParams(); @@ -22,22 +19,23 @@ function Network() { const classes = useStyles(); const history = useHistory(); - useEffect(() => { - async function fetchData() { - try { - const network = await API.get("network/" + nwid); - setNetwork(network.data); - console.log("Current network:", network.data); - } catch (err) { - if (err.response.status === 404) { - history.push("/404"); - } - console.error(err); + const fetchData = useCallback(async () => { + try { + const network = await API.get("network/" + nwid); + setNetwork(network.data); + console.log("Current network:", network.data); + } catch (err) { + if (err.response.status === 404) { + history.push("/404"); } + console.error(err); } - fetchData(); }, [nwid, history]); + useEffect(() => { + fetchData(); + }, [nwid, fetchData]); + if (loggedIn) { return ( <> @@ -52,8 +50,10 @@ function Network() { )} - - {network["config"] && } + + {network["config"] && ( + + )} diff --git a/frontend/src/utils/ChangeHelper.js b/frontend/src/utils/ChangeHelper.js index f425320..2183ffc 100644 --- a/frontend/src/utils/ChangeHelper.js +++ b/frontend/src/utils/ChangeHelper.js @@ -1,3 +1,5 @@ +import { pull } from "lodash"; + export function parseValue( event, mode = "text", @@ -23,6 +25,25 @@ export function parseValue( } } else if (mode === "custom") { value = data; + } else if (mode === "capChange") { + value = data[key1][key2]; + if (event.target.checked) { + value.push(id); + } else { + pull(value, id); + } + } else if (mode === "tagChange") { + value = data[key1][key2]; + let tagValue = event.target.value; + let tagIndex = value.findIndex((item) => { + return item[0] === id; + }); + if (tagIndex !== -1) { + value.splice(tagIndex, 1); + } + if (tagValue !== "") { + value.push([id, tagValue]); + } } else { value = event.target.value; } From a813d05b3c2c2c286ae1df860eca209215347ce0 Mon Sep 17 00:00:00 2001 From: snachx Date: Mon, 13 Dec 2021 22:22:27 +0800 Subject: [PATCH 3/3] fix: fix compatibility for existing networks after supporting tags and capabilities --- .../components/MemberSettings/MemberSettings.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx b/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx index df6bb64..5213320 100644 --- a/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx +++ b/frontend/src/components/NetworkMembers/components/MemberSettings/MemberSettings.jsx @@ -64,10 +64,11 @@ function MemberSettings({ member, network, handleChange }) { - {Object.entries(network["capabilitiesByName"]).length === 0 + {Object.entries(network["capabilitiesByName"] || []).length === + 0 ? "No capabilities defined" : ""} - {Object.entries(network["capabilitiesByName"]).map( + {Object.entries(network["capabilitiesByName"] || []).map( ([capName, capId]) => ( Tags - {Object.entries(network["tagsByName"]).length === 0 ? ( + {Object.entries(network["tagsByName"] || []).length === 0 ? ( No tags defined ) : ( "" )} - {Object.entries(network["tagsByName"]).map( + {Object.entries(network["tagsByName"] || []).map( ([tagName, tagDetail]) => (