feat: add group support

This commit is contained in:
Diorcet Yann 2025-04-12 11:42:06 +02:00
parent 6c2654ea8c
commit 5455e1fa6b
8 changed files with 518 additions and 71 deletions

File diff suppressed because one or more lines are too long

View file

@ -138,6 +138,9 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
if (data.hasOwnProperty("description")) {
additionalData.description = data.description;
}
if (data.hasOwnProperty("group")) {
additionalData.group = data.group;
}
if (additionalData) {
const member = db
@ -152,7 +155,7 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
.map((additionalConfig) => _.assign(additionalConfig, additionalData))
.write();
} else {
additionalData = { name: "", description: "" };
additionalData = { name: "", description: "", group: "" };
if (data.hasOwnProperty("name")) {
additionalData.name = data.name;
@ -160,6 +163,9 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
if (data.hasOwnProperty("description")) {
additionalData.description = data.description;
}
if (data.hasOwnProperty("group")) {
additionalData.group = data.group;
}
db.get("networks")
.filter({ id: nwid })
.map("members")

View file

@ -17,7 +17,9 @@
"ipaddr.js": "^2.0.1",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-data-table-component": "^6.11.8",
"react-data-table-component": "patch:react-data-table-component@npm%3A7.7.0#~/.yarn/patches/react-data-table-component-npm-7.7.0-ecb7088530.patch",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^17.0.2",
"react-i18next": "^13.3.0",
"react-is": "^17.0.2",

View file

@ -23,6 +23,7 @@
"member_one": "Member",
"member_other": "Members",
"addMemberManually": "Manually Add Member",
"addGroup": "Add group",
"name": "Name",
"description": "Description",
"allowBridging": "Allow Ethernet Bridging",

View file

@ -11,11 +11,14 @@ 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 { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useParams } from "react-router-dom";
import API from "utils/API";
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
import { formatDistance } from "date-fns";
import AddMember from "./components/AddMember";
import AddGroup from "./components/AddGroup";
import DeleteMember from "./components/DeleteMember";
import ManagedIP from "./components/ManagedIP";
import MemberName from "./components/MemberName";
@ -23,13 +26,71 @@ import MemberSettings from "./components/MemberSettings";
import { useTranslation } from "react-i18next";
const MemberItemType = "MEMBER_ITEM";
const DraggableRow = ({ zoneId, row, content, ...other }) => {
const [, drag] = useDrag({
type: MemberItemType,
item: { zoneId, row },
});
return (
<div style={{ userSelect: "text" }} ref={(node) => drag(node)} {...other}>
{content}
</div>
);
};
const DropZone = ({ zoneId, moveRow, children }) => {
const [, drop] = useDrop({
accept: MemberItemType,
canDrop: (item, monitor) => {
return item.zoneId !== zoneId;
},
drop: (item, monitor) => {
if (monitor.canDrop()) {
moveRow(item.zoneId, item.row, zoneId);
}
},
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
});
return (
<div
style={{ all: "unset", display: "contents" }}
ref={(node) => drop(node)}
>
{children}
</div>
);
};
function NetworkMembers({ network }) {
const { nwid } = useParams();
const [members, setMembers] = useState([]);
const [groups, setGroups] = useState([]);
const [extraGroups, setExtraGroups] = useState([]);
const addGroup = useCallback(
async (name) => {
if (!groups.includes(name) && !groups.includes(name)) {
let mutableExtraGroups = [...extraGroups];
mutableExtraGroups.push(name);
setExtraGroups(mutableExtraGroups);
}
},
[groups, extraGroups]
);
const fetchData = useCallback(async () => {
try {
const members = await API.get("network/" + nwid + "/member");
let groupSet = new Set();
members.data.forEach((x) => groupSet.add(x.group));
setGroups([...groupSet]);
setMembers(members.data);
console.log("Members:", members.data);
} catch (err) {
@ -62,7 +123,13 @@ function NetworkMembers({ network }) {
});
let mutableMembers = [...members];
mutableMembers[index] = updatedMember;
const groups = new Set();
mutableMembers.forEach((x) => groups.add(x.group));
let mutableExtraGroups = extraGroups.filter((x) => !groups.has(x));
setMembers(mutableMembers);
setGroups([...groups]);
setExtraGroups(mutableExtraGroups);
const data = setValue({}, key1, key2, value);
sendReq(member["config"]["id"], data);
@ -163,44 +230,95 @@ function NetworkMembers({ network }) {
},
];
const changeMemberGroup = (oldGroup, member, newGroup) => {
member.group = newGroup;
let mutableMembers = [...members];
const groups = new Set();
mutableMembers.forEach((x) => groups.add(x.group));
// Remove extra group
let mutableExtraGroups = extraGroups.filter((x) => !groups.has(x));
setGroups([...groups]);
setExtraGroups(mutableExtraGroups);
setMembers(mutableMembers);
const data = setValue({}, "group", null, newGroup);
sendReq(member["config"]["id"], data);
};
return (
<Accordion defaultExpanded={true}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>{t("member", { count: members.length })}</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container direction="column" spacing={3}>
<IconButton color="primary" onClick={fetchData}>
<RefreshIcon />
</IconButton>
<Grid container>
{members.length ? (
<DataTable
noHeader={true}
columns={columns}
data={[...members]}
/>
) : (
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
style={{
minHeight: "50vh",
}}
>
<Typography variant="h6" style={{ padding: "10%" }}>
{t("noDevices")} <b>{nwid}</b>.
</Typography>
</Grid>
)}
<DndProvider backend={HTML5Backend}>
<Grid container direction="column" spacing={3}>
{groups
.concat(extraGroups)
.sort()
.map((group) => (
<DropZone zoneId={group} moveRow={changeMemberGroup}>
<Accordion defaultExpanded={group == ""} key={group}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>{group || "Ungrouped"}</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container direction="column" spacing={3}>
<IconButton color="primary" onClick={fetchData}>
<RefreshIcon />
</IconButton>
<Grid container>
{members.length ? (
<DataTable
noHeader={true}
columns={columns}
data={[
...members.filter((x) => x.group == group),
]}
renderRow={(row, content) => (
<DraggableRow
zoneId={group}
row={row}
content={content}
key={row.config.address}
/>
)}
/>
) : (
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
style={{
minHeight: "50vh",
}}
>
<Typography
variant="h6"
style={{ padding: "10%" }}
>
{t("noDevices")} <b>{nwid}</b>.
</Typography>
</Grid>
)}
</Grid>
<Grid item></Grid>
</Grid>
</AccordionDetails>
</Accordion>
</DropZone>
))}
<Grid item>
<AddMember nwid={nwid} callback={fetchData} />
<AddGroup callback={addGroup} />
</Grid>
</Grid>
<Grid item>
<AddMember nwid={nwid} callback={fetchData} />
</Grid>
</Grid>
</DndProvider>
</AccordionDetails>
</Accordion>
);

View file

@ -0,0 +1,49 @@
import { useState } from "react";
import { List, Typography, IconButton, TextField } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import { useTranslation } from "react-i18next";
function AddGroup({ callback }) {
const [name, setName] = useState("");
const handleInput = (event) => {
setName(event.target.value);
};
const addMemberReq = async () => {
callback(name);
};
const { t } = useTranslation();
return (
<>
<Typography>{t("addGroup")}</Typography>
<List
disablePadding={true}
style={{
display: "flex",
flexDirection: "row",
}}
>
<TextField
value={name}
onChange={handleInput}
placeholder={"##########"}
/>
<IconButton size="small" color="primary" onClick={addMemberReq}>
<AddIcon
style={{
fontSize: 16,
}}
/>
</IconButton>
</List>
</>
);
}
export default AddGroup;

View file

@ -0,0 +1 @@
export { default } from "./AddGroup";

155
yarn.lock
View file

@ -250,7 +250,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7":
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7":
version: 7.24.5
resolution: "@babel/runtime@npm:7.24.5"
dependencies:
@ -259,6 +259,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.9.2":
version: 7.27.0
resolution: "@babel/runtime@npm:7.27.0"
dependencies:
regenerator-runtime: "npm:^0.14.0"
checksum: 10c0/35091ea9de48bd7fd26fb177693d64f4d195eb58ab2b142b893b7f3fa0f1d7c677604d36499ae0621a3703f35ba0c6a8f6c572cc8f7dc0317213841e493cf663
languageName: node
linkType: hard
"@babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0":
version: 7.24.0
resolution: "@babel/template@npm:7.24.0"
@ -1089,6 +1098,27 @@ __metadata:
languageName: node
linkType: hard
"@react-dnd/asap@npm:^5.0.1":
version: 5.0.2
resolution: "@react-dnd/asap@npm:5.0.2"
checksum: 10c0/0063db616db9801c9be18f11a912c3e214f87e714b1e4bf9462952af7ead65cba0b43e1f7c34bc8748811b6926e74d929e5e126f85ebb91b870faf809ceb5177
languageName: node
linkType: hard
"@react-dnd/invariant@npm:^4.0.1":
version: 4.0.2
resolution: "@react-dnd/invariant@npm:4.0.2"
checksum: 10c0/b303cc53fc5074cefb2a76b45b9c73ebb5d35630b18f5dc282ed9a9ac9b0287b9da1f6ac63acfdea2341b8f8187f615afc12d5eb14ec6015964f5c1b167332e2
languageName: node
linkType: hard
"@react-dnd/shallowequal@npm:^4.0.1":
version: 4.0.2
resolution: "@react-dnd/shallowequal@npm:4.0.2"
checksum: 10c0/9a352fd176752e5d9c2797d598aca034b7829111ae0c992d80f40d5f068fcd6a039b1841c741dfa1ab67a36a00664310aec4f0ce216e4112f80875c9fe6fd8dc
languageName: node
linkType: hard
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.11
resolution: "@tsconfig/node10@npm:1.0.11"
@ -2504,9 +2534,9 @@ __metadata:
linkType: hard
"codemirror@npm:^5.62.3, codemirror@npm:^5.65.8":
version: 5.65.16
resolution: "codemirror@npm:5.65.16"
checksum: 10c0/72ab3aae5ee0511b33348761da43585a0368f2845016f1fe177e1aa9bf3d7beee7f98550ffd82908726bf731df2376dc371e383bf4c0c91a66e3f18d0b7c4f3b
version: 5.65.19
resolution: "codemirror@npm:5.65.19"
checksum: 10c0/067024d74a9a98721063bcd78373899e827914c92881c595a2d6789649a3c4e061edd892b29bca10995a5450488c9c93fc3998a3c4ed6fd19cba2e97cd23d4e1
languageName: node
linkType: hard
@ -3298,7 +3328,7 @@ __metadata:
languageName: node
linkType: hard
"deepmerge@npm:^4.2.2":
"deepmerge@npm:^4.3.1":
version: 4.3.1
resolution: "deepmerge@npm:4.3.1"
checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044
@ -3394,6 +3424,17 @@ __metadata:
languageName: node
linkType: hard
"dnd-core@npm:^16.0.1":
version: 16.0.1
resolution: "dnd-core@npm:16.0.1"
dependencies:
"@react-dnd/asap": "npm:^5.0.1"
"@react-dnd/invariant": "npm:^4.0.1"
redux: "npm:^4.2.0"
checksum: 10c0/6b852c576c88b0a42e618efb37e046334f5e9914b8d38ad139933dd9595b6caf2a484953a6301094d23119c17479549553d71e92fd77fa37318122ea1e579f65
languageName: node
linkType: hard
"doctrine@npm:^2.1.0":
version: 2.1.0
resolution: "doctrine@npm:2.1.0"
@ -4524,7 +4565,9 @@ __metadata:
ipaddr.js: "npm:^2.0.1"
lodash: "npm:^4.17.21"
react: "npm:^17.0.2"
react-data-table-component: "npm:^6.11.8"
react-data-table-component: "patch:react-data-table-component@npm%3A7.7.0#~/.yarn/patches/react-data-table-component-npm-7.7.0-ecb7088530.patch"
react-dnd: "npm:^16.0.1"
react-dnd-html5-backend: "npm:^16.0.1"
react-dom: "npm:^17.0.2"
react-i18next: "npm:^13.3.0"
react-is: "npm:^17.0.2"
@ -6220,13 +6263,6 @@ __metadata:
languageName: node
linkType: hard
"lodash.orderby@npm:^4.6.0":
version: 4.6.0
resolution: "lodash.orderby@npm:4.6.0"
checksum: 10c0/cb58111e49c33ad4e6ed4f52a55a8e40f7d090fc4dd604615be2acc59b3a090a77fd144b72315f4f2d0ecbdabc1f0e0923d19b9d56360ae59b79932a69d261ec
languageName: node
linkType: hard
"lodash.snakecase@npm:^4.1.1":
version: 4.1.1
resolution: "lodash.snakecase@npm:4.1.1"
@ -6738,13 +6774,6 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^2.1.0":
version: 2.1.11
resolution: "nanoid@npm:2.1.11"
checksum: 10c0/8640d17698633ff78b2549ec8d5dffd8f56909bad1cf0da08bf3a4012f98553b1b9f2327a2d7fb3613084f33189a8ab4b889eb4c7939f3f9e242d9fd8ff059d5
languageName: node
linkType: hard
"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
@ -7527,17 +7556,67 @@ __metadata:
languageName: node
linkType: hard
"react-data-table-component@npm:^6.11.8":
version: 6.11.8
resolution: "react-data-table-component@npm:6.11.8"
"react-data-table-component@npm:7.7.0":
version: 7.7.0
resolution: "react-data-table-component@npm:7.7.0"
dependencies:
deepmerge: "npm:^4.2.2"
lodash.orderby: "npm:^4.6.0"
shortid: "npm:^2.2.16"
deepmerge: "npm:^4.3.1"
peerDependencies:
react: ^16.8.0 || ^17.0.0
styled-components: ^4.0.0 || ^5.0.0
checksum: 10c0/b41950d965d852aaf8dc7484490701318441a5466c2bcb8d1593d0ae1132f8a3be52321fafa62c954496259ee4b9ea6aa1feaa3277118bc7b8e91033a7b79cef
react: ">= 17.0.0"
styled-components: ">= 5.0.0"
peerDependenciesMeta:
styled-components:
optional: false
checksum: 10c0/47e9c68feba0d6728b38d507cff70e8c7f80de47d1dc3bd37769351ad888b51a599cfecb3f40e391b325105f861b049a87dacfdc8b0cbe56a91dfc079bded661
languageName: node
linkType: hard
"react-data-table-component@patch:react-data-table-component@npm%3A7.7.0#~/.yarn/patches/react-data-table-component-npm-7.7.0-ecb7088530.patch":
version: 7.7.0
resolution: "react-data-table-component@patch:react-data-table-component@npm%3A7.7.0#~/.yarn/patches/react-data-table-component-npm-7.7.0-ecb7088530.patch::version=7.7.0&hash=66cdef"
dependencies:
deepmerge: "npm:^4.3.1"
peerDependencies:
react: ">= 17.0.0"
styled-components: ">= 5.0.0"
peerDependenciesMeta:
styled-components:
optional: false
checksum: 10c0/eb0ffb0c37e527e91e3dc342a31e02162db40f77abc7b576e5a32cf7a788753c6f4adc76f6c0e03472ec88968490c4a2e7538f709d4eb9482a421d30ccb3bcc1
languageName: node
linkType: hard
"react-dnd-html5-backend@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd-html5-backend@npm:16.0.1"
dependencies:
dnd-core: "npm:^16.0.1"
checksum: 10c0/6e4b632a11e20211d71f5f3bedadf13ecec2fa73372fde388619838294b1375f15b717d1ce128e12c872ff7b15c32d26761d2026b33c14fc55e4fd5477c15289
languageName: node
linkType: hard
"react-dnd@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd@npm:16.0.1"
dependencies:
"@react-dnd/invariant": "npm:^4.0.1"
"@react-dnd/shallowequal": "npm:^4.0.1"
dnd-core: "npm:^16.0.1"
fast-deep-equal: "npm:^3.1.3"
hoist-non-react-statics: "npm:^3.3.2"
peerDependencies:
"@types/hoist-non-react-statics": ">= 3.3.1"
"@types/node": ">= 12"
"@types/react": ">= 16"
react: ">= 16.14"
peerDependenciesMeta:
"@types/hoist-non-react-statics":
optional: true
"@types/node":
optional: true
"@types/react":
optional: true
checksum: 10c0/d069435750f0d6653cfa2b951cac8abb3583fb144ff134a20176608877d9c5964c63384ebbacaa0fdeef819b592a103de0d8e06f3b742311d64a029ffed0baa3
languageName: node
linkType: hard
@ -7778,6 +7857,15 @@ __metadata:
languageName: node
linkType: hard
"redux@npm:^4.2.0":
version: 4.2.1
resolution: "redux@npm:4.2.1"
dependencies:
"@babel/runtime": "npm:^7.9.2"
checksum: 10c0/136d98b3d5dbed1cd6279c8c18a6a74c416db98b8a432a46836bdd668475de6279a2d4fd9d1363f63904e00f0678a8a3e7fa532c897163340baf1e71bb42c742
languageName: node
linkType: hard
"reflect.getprototypeof@npm:^1.0.4":
version: 1.0.6
resolution: "reflect.getprototypeof@npm:1.0.6"
@ -8304,15 +8392,6 @@ __metadata:
languageName: node
linkType: hard
"shortid@npm:^2.2.16":
version: 2.2.16
resolution: "shortid@npm:2.2.16"
dependencies:
nanoid: "npm:^2.1.0"
checksum: 10c0/7f389eb96cc11b569ac02655b861290a194f3a5402b3e3c86d21b9d016ac964683bdd6aac03b61fb6ddc5a727641442f56b70266742cf0911eee3bdc61be99eb
languageName: node
linkType: hard
"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6":
version: 1.0.6
resolution: "side-channel@npm:1.0.6"