refactor: squash commits

This commit is contained in:
dec0dOS 2021-03-21 22:25:13 +03:00
parent 63ebcb5915
commit 1e6e237aa3
107 changed files with 20077 additions and 0 deletions

View file

@ -0,0 +1,144 @@
import logo from "./assets/logo.png";
import { useState } from "react";
import { Link as RouterLink, useHistory } from "react-router-dom";
import { useLocalStorage } from "react-use";
import {
AppBar,
Toolbar,
Typography,
Box,
Button,
Divider,
Menu,
MenuItem,
Link,
} from "@material-ui/core";
import MenuIcon from "@material-ui/icons/Menu";
import LogIn from "components/LogIn";
function Bar() {
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
const [anchorEl, setAnchorEl] = useState(null);
const history = useHistory();
const openMenu = (event) => {
setAnchorEl(event.currentTarget);
};
const closeMenu = () => {
setAnchorEl(null);
};
const onLogOutClick = () => {
setLoggedIn(false);
localStorage.clear();
history.push("/");
history.go(0);
};
const menuItems = [
// TODO: add settings page
// {
// name: "Settings",
// to: "/settings",
// },
{
name: "Log out",
divide: true,
onClick: onLogOutClick,
},
];
return (
<AppBar
color="secondary"
style={{ background: "#000000" }}
position="static"
>
<Toolbar>
<Box display="flex" flexGrow={1}>
<Typography color="inherit" variant="h6">
<Link
color="inherit"
component={RouterLink}
to="/"
underline="none"
>
<img src={logo} width="100" height="100" alt="logo" />
</Link>
</Typography>
</Box>
{loggedIn && (
<>
<Button color="inherit" onClick={openMenu}>
<MenuIcon></MenuIcon>
</Button>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeMenu}
>
{menuItems.map((menuItem, index) => {
if (
menuItem.hasOwnProperty("condition") &&
!menuItem.condition
) {
return null;
}
let component = null;
if (menuItem.to) {
component = (
<MenuItem
key={index}
component={RouterLink}
to={menuItem.to}
onClick={closeMenu}
>
{menuItem.name}
</MenuItem>
);
} else {
component = (
<MenuItem
key={index}
onClick={() => {
closeMenu();
menuItem.onClick();
}}
>
{menuItem.name}
</MenuItem>
);
}
if (menuItem.divide) {
return (
<span key={index}>
<Divider />
{component}
</span>
);
}
return component;
})}
</Menu>
</>
)}
{!loggedIn && LogIn()}
</Toolbar>
</AppBar>
);
}
export default Bar;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

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

View file

@ -0,0 +1,71 @@
import { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { Divider, Button, Grid, Typography, Box } from "@material-ui/core";
import useStyles from "./HomeLoggedIn.styles";
import NetworkButton from "./components/NetworkButton";
import API from "utils/API";
import { generateNetworkConfig } from "utils/NetworkConfig";
function HomeLoggedIn() {
const [networks, setNetworks] = useState([]);
const classes = useStyles();
const history = useHistory();
const createNetwork = async () => {
const network = await API.post("network", generateNetworkConfig());
console.log(network);
history.push("/network/" + network.data["config"]["id"]);
};
useEffect(() => {
async function fetchData() {
const networks = await API.get("network");
setNetworks(networks.data);
console.log("Networks:", networks.data);
}
fetchData();
}, []);
return (
<div className={classes.root}>
<Button
variant="contained"
color="primary"
className={classes.createBtn}
onClick={createNetwork}
>
Create A Network
</Button>
<Divider />
<Grid container spacing={3} className={classes.container}>
<Grid item xs={6}>
<Typography variant="h5">Controller networks</Typography>
{networks[0] && "Network controller address"}
<Box fontWeight="fontWeightBold">
{networks[0] && networks[0]["id"].slice(0, 10)}
</Box>
</Grid>
<Grid item xs="auto">
<Typography>Networks</Typography>
<Grid item>
{networks[0] ? (
networks.map((network) => (
<Grid key={network["id"]} item>
<NetworkButton network={network} />
</Grid>
))
) : (
<div>Please create at least one network</div>
)}
</Grid>
</Grid>
</Grid>
</div>
);
}
export default HomeLoggedIn;

View file

@ -0,0 +1,16 @@
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
margin: "5%",
flexGrow: 1,
},
createBtn: {
marginBottom: "1%",
},
container: {
marginTop: "1%",
},
}));
export default useStyles;

View file

@ -0,0 +1,20 @@
.netBtn {
font-size: 1em;
padding: 0 10px;
min-height: 50px;
max-height: 50px;
border-radius: 2px;
border: 1px solid #b5b5b5;
margin: 2px;
}
.netBtn:hover {
transform: translateY(0) scale(1.02);
background: rgba(0,0,0,0);
box-shadow: inset 0 0 0 3px #ffc107;
}
.netBtn:focus {
border: 1px solid white;
outline: 0;
}

View file

@ -0,0 +1,35 @@
import "./NetworkButton.css";
import { Link } from "react-router-dom";
import { List, ListItem, Hidden } from "@material-ui/core";
import useStyles from "./NetworkButton.styles";
import { getCIDRAddress } from "utils/IP";
function NetworkButton({ network }) {
const classes = useStyles();
return (
<div className="netBtn" role="button">
<Link to={"/network/" + network["id"]} className={classes.link}>
<List className={classes.flexContainer}>
<ListItem className={classes.nwid}>{network["id"]}</ListItem>
<ListItem className={classes.name}>
{network["config"]["name"]}
</ListItem>
<Hidden mdDown>
<ListItem className={classes.cidr}>
{network["config"]["ipAssignmentPools"] &&
getCIDRAddress(
network["config"]["ipAssignmentPools"][0]["ipRangeStart"],
network["config"]["ipAssignmentPools"][0]["ipRangeEnd"]
)}
</ListItem>
</Hidden>
</List>
</Link>
</div>
);
}
export default NetworkButton;

View file

@ -0,0 +1,27 @@
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
link: {
textDecoration: "none",
color: "black",
},
flexContainer: {
display: "flex",
flexDirection: "row",
paddingTop: "8px",
},
name: {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
},
nwid: {
color: "#007fff",
fontWeight: "bolder",
},
cidr: {
color: "#b5b5b5",
},
}));
export default useStyles;

View file

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

View file

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

View file

@ -0,0 +1,31 @@
import { Grid, Typography } from "@material-ui/core";
function HomeLoggedOut() {
return (
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justify="center"
style={{
minHeight: "50vh",
}}
>
<Grid item xs={10}>
<Typography variant="h5">
<span>
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
self-hosted ZeroTier network controller.
</span>
</Typography>
<Typography>
<span>Please Log In to continue</span>
</Typography>
</Grid>
</Grid>
);
}
export default HomeLoggedOut;

View file

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

View file

@ -0,0 +1,20 @@
import { Divider } from "@material-ui/core";
import LogInUser from "./components/LogInUser";
import LogInToken from "./components/LogInToken";
function LogIn() {
return (
<>
{process.env.NODE_ENV === "development" && (
<>
<LogInToken />
<Divider orientation="vertical" />
</>
)}
<LogInUser />
</>
);
}
export default LogIn;

View file

@ -0,0 +1,90 @@
import { useState } from "react";
import { useHistory } from "react-router-dom";
import { useLocalStorage } from "react-use";
import {
TextField,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@material-ui/core";
function LogInToken() {
const [open, setOpen] = useState(false);
const [errorText, setErrorText] = useState("");
const [, setLoggedIn] = useLocalStorage("loggedIn", false);
const [token, setToken] = useLocalStorage("token", null);
const history = useHistory();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleKeyPress = (event) => {
const key = event.key;
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
if (key === "Enter") {
LogIn();
}
};
const LogIn = () => {
if (token.length !== 32) {
setErrorText("Token length error");
return;
}
setLoggedIn(true);
setToken(token);
handleClose();
history.go(0);
};
return (
<div>
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
Token Log In
</Button>
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
<DialogTitle>Log In</DialogTitle>
<DialogContent>
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
<TextField
value={token}
onChange={(e) => {
setToken(e.target.value);
}}
error={!!errorText}
helperText={errorText}
margin="dense"
label="token"
type="text"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={LogIn} color="primary">
Log In
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default LogInToken;

View file

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

View file

@ -0,0 +1,123 @@
import { useState } from "react";
import { useHistory } from "react-router-dom";
import { useLocalStorage } from "react-use";
import {
TextField,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Snackbar,
} from "@material-ui/core";
import axios from "axios";
function LogInUser() {
const [open, setOpen] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [, setLoggedIn] = useLocalStorage("loggedIn", false);
const [, setToken] = useLocalStorage("token", null);
const history = useHistory();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
setSnackbarOpen(false);
};
const handleKeyPress = (event) => {
const key = event.key;
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
if (key === "Enter") {
LogIn();
}
};
const LogIn = () => {
if (!username || !password) {
return;
}
axios
.post("/auth/login", {
username: username,
password: password,
})
.then(function (response) {
setLoggedIn(true);
setToken(response.data.token);
handleClose();
history.go(0);
})
.catch(function (error) {
setPassword("");
setSnackbarOpen(true);
console.log(error);
});
};
return (
<>
<Button onClick={handleClickOpen} color="primary" variant="contained">
Log In
</Button>
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
<DialogTitle>Log In</DialogTitle>
<DialogContent>
<TextField
autoFocus
value={username}
onChange={(e) => {
setUsername(e.target.value);
}}
margin="dense"
label="username"
type="username"
fullWidth
/>
<TextField
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
margin="dense"
label="password"
type="password"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={LogIn} color="primary">
Log In
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={snackbarOpen}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
message="Invalid username or password"
/>
</>
);
}
export default LogInUser;

View file

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

View file

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

View file

@ -0,0 +1,17 @@
import { Grid, Typography } from "@material-ui/core";
function NetworkHeader({ network }) {
return (
<Grid item>
<Typography variant="h5">
<span>{network["config"]["id"]}</span>
</Typography>
<Typography variant="h6" style={{ fontStyle: "italic" }}>
<span>{network["config"] && network["config"]["name"]}</span>
</Typography>
<span>{network["config"] && network["description"]}</span>
</Grid>
);
}
export default NetworkHeader;

View file

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

View file

@ -0,0 +1,80 @@
import { useState } from "react";
import { useParams, useHistory } from "react-router-dom";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Button,
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
DialogActions,
Typography,
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import DeleteIcon from "@material-ui/icons/Delete";
import API from "utils/API";
function NetworkManagment() {
const { nwid } = useParams();
const history = useHistory();
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const sendDelReq = async () => {
const req = await API.delete("/network/" + nwid);
console.log("Action:", req);
};
const deleteNetwork = async () => {
await sendDelReq();
history.push("/");
history.go(0);
};
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Managment</Typography>
</AccordionSummary>
<AccordionDetails>
<Button
variant="contained"
color="secondary"
startIcon={<DeleteIcon />}
onClick={handleClickOpen}
>
Delete Network
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>
{"Are you sure you want to delete this network?"}
</DialogTitle>
<DialogContent>
<DialogContentText>This action cannot be undone.</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={deleteNetwork} color="secondary">
Delete
</Button>
</DialogActions>
</Dialog>
</AccordionDetails>
</Accordion>
);
}
export default NetworkManagment;

View file

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

View file

@ -0,0 +1,185 @@
import { useState, useEffect, useCallback } from "react";
import { useParams } from "react-router-dom";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Checkbox,
Grid,
Typography,
IconButton,
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import RefreshIcon from "@material-ui/icons/Refresh";
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 API from "utils/API";
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
function NetworkMembers() {
const { nwid } = useParams();
const [members, setMembers] = useState([]);
const fetchData = useCallback(async () => {
try {
const members = await API.get("network/" + nwid + "/member");
setMembers(members.data);
console.log("Members:", members.data);
} catch (err) {
console.error(err);
}
}, [nwid]);
useEffect(() => {
fetchData();
const timer = setInterval(() => fetchData(), 30000);
return () => clearInterval(timer);
}, [nwid, fetchData]);
const sendReq = async (mid, data) => {
const req = await API.post("/network/" + nwid + "/member/" + mid, data);
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 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 data = setValue({}, key1, key2, value);
sendReq(member["config"]["id"], data);
};
const columns = [
{
id: "auth",
name: "Authorized",
minWidth: "80px",
cell: (row) => (
<Checkbox
checked={row.config.authorized}
color="primary"
onChange={handleChange(row, "config", "authorized", "checkbox")}
/>
),
},
{
id: "address",
name: "Address",
minWidth: "150px",
cell: (row) => (
<Typography variant="body2">{row.config.address}</Typography>
),
},
{
id: "name",
name: "Name/Description",
minWidth: "250px",
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
},
{
id: "ips",
name: "Managed IPs",
minWidth: "220px",
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
},
{
***REMOVED***
id: "status",
name: "Peer status",
minWidth: "100px",
cell: (row) =>
row.online ? (
<Typography style={{ color: "#008000" }}>
{"ONLINE (v" +
row.config.vMajor +
"." +
row.config.vMinor +
"." +
row.config.vRev +
")"}
</Typography>
) : (
<Typography color="error">OFFLINE</Typography>
),
},
{
id: "delete",
name: "",
minWidth: "50px",
right: true,
cell: (row) => (
<>
<MemberSettings member={row} handleChange={handleChange} />
<DeleteMember nwid={nwid} mid={row.config.id} callback={fetchData} />
</>
),
},
];
return (
<Accordion defaultExpanded={true}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Members</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"
justify="center"
style={{
minHeight: "50vh",
}}
>
<Typography variant="h6" style={{ padding: "10%" }}>
No devices have joined this network. Use the app on your
devices to join <b>{nwid}</b>.
</Typography>
</Grid>
)}
</Grid>
<Grid item>
<AddMember nwid={nwid} callback={fetchData} />
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
}
export default NetworkMembers;

View file

@ -0,0 +1,55 @@
import { useState } from "react";
import { List, Typography, IconButton, TextField } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import API from "utils/API";
function AddMember({ nwid, callback }) {
const [member, setMember] = useState("");
const handleInput = (event) => {
setMember(event.target.value);
};
const addMemberReq = async () => {
if (member.length === 10) {
const req = await API.post("/network/" + nwid + "/member/" + member, {
config: { authorized: true },
hidden: false,
});
console.log("Action:", req);
callback();
}
setMember("");
};
return (
<>
<Typography>Manually Add Member</Typography>
<List
disablePadding={true}
style={{
display: "flex",
flexDirection: "row",
}}
>
<TextField
value={member}
onChange={handleInput}
placeholder={"##########"}
/>
<IconButton size="small" color="primary" onClick={addMemberReq}>
<AddIcon
style={{
fontSize: 16,
}}
/>
</IconButton>
</List>
</>
);
}
export default AddMember;

View file

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

View file

@ -0,0 +1,59 @@
import { useState } from "react";
import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
IconButton,
} from "@material-ui/core";
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
import API from "utils/API";
function DeleteMember({ nwid, mid, callback }) {
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const deleteMemberReq = async () => {
const req = await API.delete("/network/" + nwid + "/member/" + mid);
console.log("Action:", req);
setOpen(false);
callback();
};
return (
<>
<IconButton color="primary" onClick={handleClickOpen}>
<DeleteOutlineIcon color="secondary" style={{ fontSize: 20 }} />
</IconButton>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>
{"Are you sure you want to delete this member?"}
</DialogTitle>
<DialogContent>
<DialogContentText>This action cannot be undone.</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={deleteMemberReq} color="secondary">
Delete
</Button>
</DialogActions>
</Dialog>
</>
);
}
export default DeleteMember;

View file

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

View file

@ -0,0 +1,76 @@
import { useState } from "react";
import { Grid, List, TextField, IconButton } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
import { validateIP, normilizeIP } from "utils/IP";
function ManagedIP({ member, handleChange }) {
const [ipInput, setIpInput] = useState();
const [normolizedInput, setNormolizedInput] = useState();
const handleInput = (event) => {
const ip = event.target.value;
setIpInput(ip);
if (validateIP(ip)) {
setNormolizedInput(normilizeIP(ip));
}
};
return (
<Grid>
{member.config.ipAssignments.map((value, i) => (
<List
key={i + "_ips"}
disablePadding={true}
style={{ display: "flex", flexDirection: "row" }}
>
<IconButton
size="small"
color="secondary"
onClick={handleChange(
member,
"config",
"ipAssignments",
"arrayDel",
i
)}
>
<DeleteOutlineIcon style={{ fontSize: 14 }} />
</IconButton>
{value}
</List>
))}
<List
disablePadding={true}
style={{
display: "flex",
flexDirection: "row",
}}
>
<IconButton
size="small"
color="primary"
onClick={handleChange(
member,
"config",
"ipAssignments",
"arrayAdd",
normolizedInput
)}
>
<AddIcon
style={{
fontSize: 14,
}}
/>
</IconButton>
<TextField value={ipInput} onChange={handleInput} />
</List>
</Grid>
);
}
export default ManagedIP;

View file

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

View file

@ -0,0 +1,28 @@
import { Grid, TextField } from "@material-ui/core";
function MemberName({ member, handleChange }) {
return (
<Grid>
<TextField
value={member.name}
onChange={handleChange(member, "name")}
label="Name"
variant="filled"
InputLabelProps={{
shrink: true,
}}
/>
<TextField
value={member.description}
onChange={handleChange(member, "description")}
label="Description"
variant="filled"
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
);
}
export default MemberName;

View file

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

View file

@ -0,0 +1,64 @@
import { useState } from "react";
import {
Checkbox,
Dialog,
DialogTitle,
DialogContent,
Grid,
IconButton,
} from "@material-ui/core";
import BuildIcon from "@material-ui/icons/Build";
function MemberSettings({ member, handleChange }) {
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<IconButton color="primary" onClick={handleClickOpen}>
<BuildIcon style={{ fontSize: 20 }} />
</IconButton>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{"Member " + member.config.id + " settings"}</DialogTitle>
<DialogContent>
<Grid item>
<Checkbox
checked={member["config"]["activeBridge"]}
color="primary"
onChange={handleChange(
member,
"config",
"activeBridge",
"checkbox"
)}
/>
<span>Allow Ethernet Bridging</span>
</Grid>
<Grid item>
<Checkbox
checked={member["config"]["noAutoAssignIps"]}
color="primary"
onChange={handleChange(
member,
"config",
"noAutoAssignIps",
"checkbox"
)}
/>
<span>Do Not Auto-Assign IPs</span>
</Grid>
</DialogContent>
</Dialog>
</>
);
}
export default MemberSettings;

View file

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

View file

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

View file

@ -0,0 +1,132 @@
import { useState } from "react";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Button,
Divider,
Snackbar,
Hidden,
Grid,
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 API from "utils/API";
function NetworkRules({ network }) {
const [editor, setEditor] = useState(null);
const [flowData, setFlowData] = useState({
rules: [...network.config.rules],
capabilities: [...network.config.capabilities],
tags: [...network.config.tags],
});
const [errors, setErrors] = useState([]);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const saveChanges = async () => {
if (editor) {
const req = await API.post("/network/" + network["config"]["id"], {
config: { ...flowData },
rulesSource: editor.getValue(),
});
console.log("Action", req);
setSnackbarOpen(true);
const timer = setTimeout(() => {
setSnackbarOpen(false);
}, 1500);
return () => clearTimeout(timer);
}
};
const onChange = debounce((event) => {
const src = event.getValue();
setEditor(event);
let rules = [],
caps = [],
tags = [];
const res = compile(src, rules, caps, tags);
if (!res) {
setFlowData({
rules: [...rules],
capabilities: [...caps],
tags: [...tags],
});
setErrors([]);
} else {
setErrors(res);
}
}, 100);
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Flow Rules</Typography>
</AccordionSummary>
<AccordionDetails>
{/* Important note: value in CodeMirror instance means INITAIL VALUE
or it could be used to replace editor state with the new value.
No need to update on every user character input
*/}
<CodeMirror
value={network["rulesSource"]}
onChange={onChange}
options={{ tabSize: 2, lineWrapping: true }}
/>
<Hidden mdDown>
<div>
<CodeMirror
value={JSON.stringify(flowData, null, 2)}
width="100%"
height="50%"
options={{
theme: "3024-day",
readOnly: true,
lineNumbers: false,
lineWrapping: true,
}}
/>
</div>
</Hidden>
<Divider />
<Grid
item
style={{
margin: "1%",
display: "block",
overflowWrap: "break-word",
width: "250px",
}}
>
{!!errors.length ? (
<Typography color="error">
{"[" + errors[0] + ":" + errors[1] + "] " + errors[2]}
</Typography>
) : (
<Button variant="contained" color="primary" onClick={saveChanges}>
Save Changes
</Button>
)}
</Grid>
<Snackbar
open={snackbarOpen}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
message="Saved"
/>
</AccordionDetails>
</Accordion>
);
}
export default NetworkRules;

View file

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

View file

@ -0,0 +1,141 @@
import {
Accordion,
AccordionSummary,
AccordionDetails,
Checkbox,
Divider,
Grid,
Typography,
TextField,
Select,
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ManagedRoutes from "./components/ManagedRoutes";
import IPv4AutoAssign from "./components/IPv4AutoAssign";
import API from "utils/API";
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
function NetworkSettings({ network, setNetwork }) {
const sendReq = async (data) => {
try {
const req = await API.post("/network/" + network["config"]["id"], data);
console.log("Action", req);
} catch (err) {
console.error(err);
}
};
const handleChange = (key1, key2, mode = "text", additionalData = null) => (
event
) => {
const value = parseValue(event, mode, additionalData);
let updatedNetwork = replaceValue({ ...network }, key1, key2, value);
setNetwork(updatedNetwork);
let data = setValue({}, key1, key2, value);
sendReq(data);
};
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>General settings</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container direction="column" spacing={3}>
<Grid item>
<Typography>Network ID</Typography>
<Typography variant="h5">
<span>{network["config"]["id"]}</span>
</Typography>
</Grid>
<Grid item>
<TextField
value={network["config"]["name"]}
onChange={handleChange("config", "name")}
label="Name"
variant="filled"
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item>
<TextField
value={network["description"]}
onChange={handleChange("description")}
multiline
rows={2}
rowsMax={Infinity}
label="Description"
variant="filled"
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Divider />
<Grid item>
<Typography>Access Control</Typography>
<Select
native
value={network["config"]["private"]}
onChange={handleChange("config", "private", "json")}
>
<option value={true}>Private</option>
<option value={false}>Public</option>
</Select>
</Grid>
<Divider />
<Grid item>
<ManagedRoutes
routes={network["config"]["routes"]}
handleChange={handleChange}
/>
</Grid>
<Divider />
<Grid item>
<IPv4AutoAssign
ipAssignmentPools={network["config"]["ipAssignmentPools"]}
handleChange={handleChange}
/>
</Grid>
{/* TODO: */}
{/* <Grid item>
<Typography>IPv6 Auto-Assign</Typography>
</Grid> */}
<Divider />
<Grid item>
<TextField
label="Multicast Recipient Limit"
type="number"
value={network["config"]["multicastLimit"]}
onChange={handleChange("config", "multicastLimit", "json")}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item>
<Checkbox
checked={network["config"]["enableBroadcast"]}
color="primary"
onChange={handleChange("config", "enableBroadcast", "checkbox")}
/>
<span>Enable Broadcast</span>
</Grid>
{/* TODO: */}
{/* <Grid item>
<Typography>DNS</Typography>
</Grid> */}
</Grid>
</AccordionDetails>
</Accordion>
);
}
export default NetworkSettings;

View file

@ -0,0 +1,177 @@
import { useState } from "react";
import {
Button,
Box,
Divider,
Grid,
List,
Typography,
TextField,
IconButton,
} from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
import DataTable from "react-data-table-component";
import { addressPool } from "utils/NetworkConfig";
import { getCIDRAddress, validateIP, normilizeIP } from "utils/IP";
function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
const [start, setStart] = useState("");
const [end, setEnd] = useState("");
const handleStartInput = (event) => {
setStart(event.target.value);
};
const handleEndInput = (event) => {
setEnd(event.target.value);
};
const setDefaultPool = (index) => {
addPoolReq(addressPool[index]["start"], addressPool[index]["end"], true);
handleChange("config", "routes", "custom", [
{
target: getCIDRAddress(
addressPool[index]["start"],
addressPool[index]["end"]
),
},
])(null);
};
const addPoolReq = (localStart, localEnd, reset = false) => {
let data = {};
console.log(localStart, localEnd);
if (validateIP(localStart) && validateIP(localEnd)) {
data["ipRangeStart"] = normilizeIP(localStart);
data["ipRangeEnd"] = normilizeIP(localEnd);
} else {
return;
}
let newPool = [];
if (ipAssignmentPools && !reset) {
newPool = [...ipAssignmentPools];
}
newPool.push(data);
console.log(newPool);
handleChange("config", "ipAssignmentPools", "custom", newPool)(null);
setStart("");
setEnd("");
};
const removePoolReq = (index) => {
let newPool = [...ipAssignmentPools];
newPool.splice(index, 1);
handleChange("config", "ipAssignmentPools", "custom", newPool)(null);
};
const columns = [
{
id: "remove",
width: "10px",
cell: (_, index) => (
<IconButton
size="small"
color="secondary"
onClick={() => removePoolReq(index)}
>
<DeleteOutlineIcon style={{ fontSize: 14 }} />
</IconButton>
),
},
{
id: "Start",
name: "Start",
cell: (row) => row["ipRangeStart"],
},
{
id: "End",
name: "End",
cell: (row) => row["ipRangeEnd"],
},
];
return (
<>
<Typography>IPv4 Auto-Assign</Typography>
<div
style={{
padding: "30px",
}}
>
<Grid container spacing={1}>
{addressPool.map((item, index) => (
<Grid item xs={3} key={item["name"]}>
<Button
variant="contained"
fullWidth={true}
onClick={() => setDefaultPool(index)}
>
{item["name"]}
</Button>
</Grid>
))}
</Grid>
</div>
<Typography style={{ paddingBottom: "10px" }}>
Auto-Assign Pools
</Typography>
<Box border={1} borderColor="grey.300">
<Grid item style={{ margin: "10px" }}>
<DataTable
noHeader={true}
columns={columns}
data={ipAssignmentPools}
/>
<Divider />
<Typography>Add IPv4 Pool</Typography>
<List
style={{
display: "flex",
flexDirection: "row",
}}
>
<TextField
value={start}
onChange={handleStartInput}
placeholder={"Start"}
/>
<Divider
orientation="vertical"
style={{
margin: "10px",
}}
flexItem
/>
<TextField
value={end}
onChange={handleEndInput}
placeholder={"End"}
/>
<IconButton
size="small"
color="primary"
onClick={() => addPoolReq(start, end)}
>
<AddIcon
style={{
fontSize: 16,
}}
/>
</IconButton>
</List>
</Grid>
</Box>
</>
);
}
export default IPv4AutoAssign;

View file

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

View file

@ -0,0 +1,131 @@
import { useState } from "react";
import {
Box,
Divider,
Grid,
List,
Typography,
TextField,
IconButton,
} from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
import DataTable from "react-data-table-component";
import { validateIP, normilizeIP, validateCIDR } from "utils/IP";
function ManagedRoutes({ routes, handleChange }) {
const [destination, setDestination] = useState("");
const [via, setVia] = useState("");
const handleDestinationInput = (event) => {
setDestination(event.target.value);
};
const handleViaInput = (event) => {
setVia(event.target.value);
};
const addRouteReq = () => {
let data = {};
if (validateCIDR(destination)) {
data["target"] = destination;
} else {
return;
}
if (via && validateIP(via)) {
data["via"] = normilizeIP(via);
}
let newRoutes = [...routes];
newRoutes.push(data);
handleChange("config", "routes", "custom", newRoutes)(null);
setDestination("");
setVia("");
};
const removeRouteReq = (index) => {
let newRoutes = [...routes];
newRoutes.splice(index, 1);
handleChange("config", "routes", "custom", newRoutes)(null);
};
const columns = [
{
id: "remove",
width: "10px",
cell: (_, index) => (
<IconButton
size="small"
color="secondary"
onClick={() => removeRouteReq(index)}
>
<DeleteOutlineIcon style={{ fontSize: 14 }} />
</IconButton>
),
},
{
id: "target",
name: "Target",
cell: (row) => row["target"],
},
{
id: "via",
name: "via",
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
},
];
return (
<>
<Typography style={{ paddingBottom: "10px" }}>
Managed Routes ({routes.length + "/32"})
</Typography>
<Box border={1} borderColor="grey.300">
<Grid item style={{ margin: "10px" }}>
<DataTable noHeader={true} columns={columns} data={routes} />
<Divider />
<Typography>Add Routes</Typography>
<List
style={{
display: "flex",
flexDirection: "row",
}}
>
<TextField
value={destination}
onChange={handleDestinationInput}
placeholder={"Destination (CIDR)"}
/>
<Divider
orientation="vertical"
style={{
margin: "10px",
}}
flexItem
/>
<TextField
value={via}
onChange={handleViaInput}
placeholder={"Via (Optional)"}
/>
<IconButton size="small" color="primary" onClick={addRouteReq}>
<AddIcon
style={{
fontSize: 16,
}}
/>
</IconButton>
</List>
</Grid>
</Box>
</>
);
}
export default ManagedRoutes;

View file

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

View file

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

View file

@ -0,0 +1,21 @@
import { ThemeProvider } from "@material-ui/styles";
import { createMuiTheme } from "@material-ui/core/styles";
import { red, amber } from "@material-ui/core/colors";
const theme = createMuiTheme({
palette: {
primary: {
main: amber[500],
},
secondary: {
main: red[500],
},
type: "light",
},
});
function Theme({ children }) {
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
export default Theme;

View file

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