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/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..5213320 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,66 @@ 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 79d7450..1fc6788 100644
--- a/frontend/src/components/NetworkRules/NetworkRules.jsx
+++ b/frontend/src/components/NetworkRules/NetworkRules.jsx
@@ -1,34 +1,33 @@
-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],
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,12 +36,16 @@ function NetworkRules({ network }) {
const req = await API.post("/network/" + network["config"]["id"], {
config: { ...flowData },
rulesSource: editor.getValue(),
+ ...tagCapByNameData,
});
console.log("Action", req);
setSnackbarOpen(true);
const timer = setTimeout(() => {
setSnackbarOpen(false);
}, 1500);
+
+ callback();
+
return () => clearTimeout(timer);
}
};
@@ -51,14 +54,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 {
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;
}