Skip to content

Commit 4061679

Browse files
committed
feat: add group support
1 parent 6c2654e commit 4061679

File tree

8 files changed

+518
-71
lines changed

8 files changed

+518
-71
lines changed

Diff for: .yarn/patches/react-data-table-component-npm-7.7.0-ecb7088530.patch

+191
Large diffs are not rendered by default.

Diff for: backend/services/member.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
138138
if (data.hasOwnProperty("description")) {
139139
additionalData.description = data.description;
140140
}
141+
if (data.hasOwnProperty("group")) {
142+
additionalData.group = data.group;
143+
}
141144

142145
if (additionalData) {
143146
const member = db
@@ -152,14 +155,17 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
152155
.map((additionalConfig) => _.assign(additionalConfig, additionalData))
153156
.write();
154157
} else {
155-
additionalData = { name: "", description: "" };
158+
additionalData = { name: "", description: "", group: "" };
156159

157160
if (data.hasOwnProperty("name")) {
158161
additionalData.name = data.name;
159162
}
160163
if (data.hasOwnProperty("description")) {
161164
additionalData.description = data.description;
162165
}
166+
if (data.hasOwnProperty("group")) {
167+
additionalData.group = data.group;
168+
}
163169
db.get("networks")
164170
.filter({ id: nwid })
165171
.map("members")

Diff for: frontend/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"ipaddr.js": "^2.0.1",
1818
"lodash": "^4.17.21",
1919
"react": "^17.0.2",
20-
"react-data-table-component": "^6.11.8",
20+
"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",
21+
"react-dnd": "^16.0.1",
22+
"react-dnd-html5-backend": "^16.0.1",
2123
"react-dom": "^17.0.2",
2224
"react-i18next": "^13.3.0",
2325
"react-is": "^17.0.2",

Diff for: frontend/public/locales/en/common.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"member_one": "Member",
2424
"member_other": "Members",
2525
"addMemberManually": "Manually Add Member",
26+
"addGroup": "Add group",
2627
"name": "Name",
2728
"description": "Description",
2829
"allowBridging": "Allow Ethernet Bridging",

Diff for: frontend/src/components/NetworkMembers/NetworkMembers.jsx

+149-31
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,86 @@ import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
1111
import RefreshIcon from "@material-ui/icons/Refresh";
1212
import { useCallback, useEffect, useState } from "react";
1313
import DataTable from "react-data-table-component";
14+
import { DndProvider, useDrag, useDrop } from "react-dnd";
15+
import { HTML5Backend } from "react-dnd-html5-backend";
1416
import { useParams } from "react-router-dom";
1517
import API from "utils/API";
1618
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
1719
import { formatDistance } from "date-fns";
1820
import AddMember from "./components/AddMember";
21+
import AddGroup from "./components/AddGroup";
1922
import DeleteMember from "./components/DeleteMember";
2023
import ManagedIP from "./components/ManagedIP";
2124
import MemberName from "./components/MemberName";
2225
import MemberSettings from "./components/MemberSettings";
2326

2427
import { useTranslation } from "react-i18next";
2528

29+
const MemberItemType = "MEMBER_ITEM";
30+
31+
const DraggableRow = ({ zoneId, row, content, ...other }) => {
32+
const [, drag] = useDrag({
33+
type: MemberItemType,
34+
item: { zoneId, row },
35+
});
36+
37+
return (
38+
<div style={{ userSelect: "text" }} ref={(node) => drag(node)} {...other}>
39+
{content}
40+
</div>
41+
);
42+
};
43+
44+
const DropZone = ({ zoneId, moveRow, children }) => {
45+
const [, drop] = useDrop({
46+
accept: MemberItemType,
47+
canDrop: (item, monitor) => {
48+
return item.zoneId !== zoneId;
49+
},
50+
drop: (item, monitor) => {
51+
if (monitor.canDrop()) {
52+
moveRow(item.zoneId, item.row, zoneId);
53+
}
54+
},
55+
collect: (monitor) => ({
56+
isOver: monitor.isOver(),
57+
canDrop: monitor.canDrop(),
58+
}),
59+
});
60+
61+
return (
62+
<div
63+
style={{ all: "unset", display: "contents" }}
64+
ref={(node) => drop(node)}
65+
>
66+
{children}
67+
</div>
68+
);
69+
};
70+
2671
function NetworkMembers({ network }) {
2772
const { nwid } = useParams();
2873
const [members, setMembers] = useState([]);
74+
const [groups, setGroups] = useState([]);
75+
const [extraGroups, setExtraGroups] = useState([]);
76+
77+
const addGroup = useCallback(
78+
async (name) => {
79+
if (!groups.includes(name) && !groups.includes(name)) {
80+
let mutableExtraGroups = [...extraGroups];
81+
mutableExtraGroups.push(name);
82+
setExtraGroups(mutableExtraGroups);
83+
}
84+
},
85+
[groups, extraGroups]
86+
);
2987

3088
const fetchData = useCallback(async () => {
3189
try {
3290
const members = await API.get("network/" + nwid + "/member");
91+
let groupSet = new Set();
92+
members.data.forEach((x) => groupSet.add(x.group));
93+
setGroups([...groupSet]);
3394
setMembers(members.data);
3495
console.log("Members:", members.data);
3596
} catch (err) {
@@ -62,7 +123,13 @@ function NetworkMembers({ network }) {
62123
});
63124
let mutableMembers = [...members];
64125
mutableMembers[index] = updatedMember;
126+
const groups = new Set();
127+
mutableMembers.forEach((x) => groups.add(x.group));
128+
let mutableExtraGroups = extraGroups.filter((x) => !groups.has(x));
129+
65130
setMembers(mutableMembers);
131+
setGroups([...groups]);
132+
setExtraGroups(mutableExtraGroups);
66133

67134
const data = setValue({}, key1, key2, value);
68135
sendReq(member["config"]["id"], data);
@@ -163,44 +230,95 @@ function NetworkMembers({ network }) {
163230
},
164231
];
165232

233+
const changeMemberGroup = (oldGroup, member, newGroup) => {
234+
member.group = newGroup;
235+
236+
let mutableMembers = [...members];
237+
const groups = new Set();
238+
mutableMembers.forEach((x) => groups.add(x.group));
239+
240+
// Remove extra group
241+
let mutableExtraGroups = extraGroups.filter((x) => !groups.has(x));
242+
243+
setGroups([...groups]);
244+
setExtraGroups(mutableExtraGroups);
245+
setMembers(mutableMembers);
246+
247+
const data = setValue({}, "group", null, newGroup);
248+
sendReq(member["config"]["id"], data);
249+
};
250+
166251
return (
167252
<Accordion defaultExpanded={true}>
168253
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
169254
<Typography>{t("member", { count: members.length })}</Typography>
170255
</AccordionSummary>
171256
<AccordionDetails>
172-
<Grid container direction="column" spacing={3}>
173-
<IconButton color="primary" onClick={fetchData}>
174-
<RefreshIcon />
175-
</IconButton>
176-
<Grid container>
177-
{members.length ? (
178-
<DataTable
179-
noHeader={true}
180-
columns={columns}
181-
data={[...members]}
182-
/>
183-
) : (
184-
<Grid
185-
container
186-
spacing={0}
187-
direction="column"
188-
alignItems="center"
189-
justifyContent="center"
190-
style={{
191-
minHeight: "50vh",
192-
}}
193-
>
194-
<Typography variant="h6" style={{ padding: "10%" }}>
195-
{t("noDevices")} <b>{nwid}</b>.
196-
</Typography>
197-
</Grid>
198-
)}
199-
</Grid>
200-
<Grid item>
201-
<AddMember nwid={nwid} callback={fetchData} />
257+
<DndProvider backend={HTML5Backend}>
258+
<Grid container direction="column" spacing={3}>
259+
{groups
260+
.concat(extraGroups)
261+
.sort()
262+
.map((group) => (
263+
<Accordion defaultExpanded={group == ""} key={group}>
264+
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
265+
<Typography>{group || "Ungrouped"}</Typography>
266+
</AccordionSummary>
267+
<AccordionDetails>
268+
<Grid container direction="column" spacing={3}>
269+
<IconButton color="primary" onClick={fetchData}>
270+
<RefreshIcon />
271+
</IconButton>
272+
<Grid container>
273+
<DropZone zoneId={group} moveRow={changeMemberGroup}>
274+
{members.length ? (
275+
<DataTable
276+
noHeader={true}
277+
columns={columns}
278+
data={[
279+
...members.filter((x) => x.group == group),
280+
]}
281+
renderRow={(row, content) => (
282+
<DraggableRow
283+
zoneId={group}
284+
row={row}
285+
content={content}
286+
key={row.config.address}
287+
/>
288+
)}
289+
/>
290+
) : (
291+
<Grid
292+
container
293+
spacing={0}
294+
direction="column"
295+
alignItems="center"
296+
justifyContent="center"
297+
style={{
298+
minHeight: "50vh",
299+
}}
300+
>
301+
<Typography
302+
variant="h6"
303+
style={{ padding: "10%" }}
304+
>
305+
{t("noDevices")} <b>{nwid}</b>.
306+
</Typography>
307+
</Grid>
308+
)}
309+
</DropZone>
310+
</Grid>
311+
<Grid item></Grid>
312+
</Grid>
313+
</AccordionDetails>
314+
</Accordion>
315+
))}
316+
<Grid item>
317+
<AddMember nwid={nwid} callback={fetchData} />
318+
<AddGroup callback={addGroup} />
319+
</Grid>
202320
</Grid>
203-
</Grid>
321+
</DndProvider>
204322
</AccordionDetails>
205323
</Accordion>
206324
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useState } from "react";
2+
3+
import { List, Typography, IconButton, TextField } from "@material-ui/core";
4+
import AddIcon from "@material-ui/icons/Add";
5+
6+
import { useTranslation } from "react-i18next";
7+
8+
function AddGroup({ callback }) {
9+
const [name, setName] = useState("");
10+
11+
const handleInput = (event) => {
12+
setName(event.target.value);
13+
};
14+
15+
const addMemberReq = async () => {
16+
callback(name);
17+
};
18+
19+
const { t } = useTranslation();
20+
21+
return (
22+
<>
23+
<Typography>{t("addGroup")}</Typography>
24+
<List
25+
disablePadding={true}
26+
style={{
27+
display: "flex",
28+
flexDirection: "row",
29+
}}
30+
>
31+
<TextField
32+
value={name}
33+
onChange={handleInput}
34+
placeholder={"##########"}
35+
/>
36+
37+
<IconButton size="small" color="primary" onClick={addMemberReq}>
38+
<AddIcon
39+
style={{
40+
fontSize: 16,
41+
}}
42+
/>
43+
</IconButton>
44+
</List>
45+
</>
46+
);
47+
}
48+
49+
export default AddGroup;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./AddGroup";

0 commit comments

Comments
 (0)