mirror of
https://github.com/dec0dOS/zero-ui.git
synced 2025-07-10 23:33:03 -07:00
refactor: squash commits
This commit is contained in:
parent
63ebcb5915
commit
1e6e237aa3
107 changed files with 20077 additions and 0 deletions
144
frontend/src/components/Bar/Bar.jsx
Normal file
144
frontend/src/components/Bar/Bar.jsx
Normal 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;
|
BIN
frontend/src/components/Bar/assets/logo.png
Normal file
BIN
frontend/src/components/Bar/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
1
frontend/src/components/Bar/index.jsx
Normal file
1
frontend/src/components/Bar/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./Bar";
|
71
frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
Normal file
71
frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
Normal 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;
|
16
frontend/src/components/HomeLoggedIn/HomeLoggedIn.styles.jsx
Normal file
16
frontend/src/components/HomeLoggedIn/HomeLoggedIn.styles.jsx
Normal 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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkButton";
|
1
frontend/src/components/HomeLoggedIn/index.jsx
Normal file
1
frontend/src/components/HomeLoggedIn/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./HomeLoggedIn";
|
31
frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
Normal file
31
frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
Normal 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;
|
1
frontend/src/components/HomeLoggedOut/index.jsx
Normal file
1
frontend/src/components/HomeLoggedOut/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./HomeLoggedOut";
|
20
frontend/src/components/LogIn/LogIn.jsx
Normal file
20
frontend/src/components/LogIn/LogIn.jsx
Normal 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;
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./LogInToken";
|
123
frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx
Normal file
123
frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx
Normal 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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./LogInUser";
|
1
frontend/src/components/LogIn/index.jsx
Normal file
1
frontend/src/components/LogIn/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./LogIn";
|
17
frontend/src/components/NetworkHeader/NetworkHeader.jsx
Normal file
17
frontend/src/components/NetworkHeader/NetworkHeader.jsx
Normal 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;
|
1
frontend/src/components/NetworkHeader/index.jsx
Normal file
1
frontend/src/components/NetworkHeader/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkHeader";
|
|
@ -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;
|
1
frontend/src/components/NetworkManagment/index.jsx
Normal file
1
frontend/src/components/NetworkManagment/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkManagment";
|
185
frontend/src/components/NetworkMembers/NetworkMembers.jsx
Normal file
185
frontend/src/components/NetworkMembers/NetworkMembers.jsx
Normal 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;
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./AddMember";
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./DeleteMember";
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./ManagedIP";
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./MemberName";
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./MemberSettings";
|
1
frontend/src/components/NetworkMembers/index.jsx
Normal file
1
frontend/src/components/NetworkMembers/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkMembers";
|
132
frontend/src/components/NetworkRules/NetworkRules.jsx
Normal file
132
frontend/src/components/NetworkRules/NetworkRules.jsx
Normal 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;
|
1
frontend/src/components/NetworkRules/index.jsx
Normal file
1
frontend/src/components/NetworkRules/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkRules";
|
141
frontend/src/components/NetworkSettings/NetworkSettings.jsx
Normal file
141
frontend/src/components/NetworkSettings/NetworkSettings.jsx
Normal 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;
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./IPv4AutoAssign";
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./ManagedRoutes";
|
1
frontend/src/components/NetworkSettings/index.jsx
Normal file
1
frontend/src/components/NetworkSettings/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./NetworkSettings";
|
21
frontend/src/components/Theme/Theme.jsx
Normal file
21
frontend/src/components/Theme/Theme.jsx
Normal 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;
|
1
frontend/src/components/Theme/index.jsx
Normal file
1
frontend/src/components/Theme/index.jsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./Theme";
|
Loading…
Add table
Add a link
Reference in a new issue