mirror of
https://github.com/dec0dOS/zero-ui.git
synced 2024-09-20 06:56:05 +08:00
commit
4a5f8469b3
|
@ -10,7 +10,7 @@ const loginLimiter = rateLimit({
|
||||||
max: Number(process.env.ZU_LOGIN_LIMIT_ATTEMPTS) || 50, // limit each IP to 50 requests per windowMs
|
max: Number(process.env.ZU_LOGIN_LIMIT_ATTEMPTS) || 50, // limit each IP to 50 requests per windowMs
|
||||||
message: {
|
message: {
|
||||||
status: 429,
|
status: 429,
|
||||||
error: "Too many login attempts, please try again in 15 minutes.",
|
error: "tooManyAttempts",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ export async function authorize(username, password, callback) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const user = users.find({ username: username });
|
const user = users.find({ username: username });
|
||||||
if (!user.value()) return callback(new Error("Invalid username or password")); // If return "user not found" someone can do a user listing
|
if (!user.value()) return callback(new Error("logInFailed")); // If return "user not found" someone can do a user listing
|
||||||
const verified = await verifyHash(password, user.value()["password_hash"]);
|
const verified = await verifyHash(password, user.value()["password_hash"]);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
return callback(null, user.value());
|
return callback(null, user.value());
|
||||||
} else {
|
} else {
|
||||||
return callback(new Error("Invalid username or password"));
|
return callback(new Error("logInFailed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,15 @@
|
||||||
"codemirror": "^5.62.3",
|
"codemirror": "^5.62.3",
|
||||||
"date-fns": "^2.29.2",
|
"date-fns": "^2.29.2",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
"i18next": "^23.5.1",
|
||||||
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
|
"i18next-http-backend": "^2.2.2",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-data-table-component": "^6.11.8",
|
"react-data-table-component": "^6.11.8",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-i18next": "^13.3.0",
|
||||||
"react-is": "^17.0.2",
|
"react-is": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
|
|
67
frontend/public/locales/en/common.json
Normal file
67
frontend/public/locales/en/common.json
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"flowRules": "Flow Rules",
|
||||||
|
"createNetwork": "Create A Network",
|
||||||
|
"createOneNetwork": "Please create at least one network",
|
||||||
|
"controllerNetworks": "Controller networks",
|
||||||
|
"network_one": "Network",
|
||||||
|
"network_other": "Networks",
|
||||||
|
"controllerAddress": "Network controller address",
|
||||||
|
"loginToContinue": "Please, Log In to continue",
|
||||||
|
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.",
|
||||||
|
"logIn": "Log In",
|
||||||
|
"logInToken": "Token Log In",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"management": "Management",
|
||||||
|
"deleteNetwork": "Delete Network",
|
||||||
|
"deleteAlert": "This action cannot be undone.",
|
||||||
|
"deleteNetworkConfirm": "Are you sure you want to delete this network?",
|
||||||
|
"deleteMemberConfirm": "Are you sure you want to delete this member?",
|
||||||
|
"delete": "Delete",
|
||||||
|
"logOut": "Log out",
|
||||||
|
"advancedFeature": "ADVANCED FEATURE",
|
||||||
|
"noDevices": "No devices have joined this network. Use the app on your devices to join",
|
||||||
|
"member_one": "Member",
|
||||||
|
"member_other": "Members",
|
||||||
|
"addMemberManually": "Manually Add Member",
|
||||||
|
"name": "Name",
|
||||||
|
"description": "Description",
|
||||||
|
"allowBridging": "Allow Ethernet Bridging",
|
||||||
|
"noAutoIP": "Do Not Auto-Assign IPs",
|
||||||
|
"capabilities": "Capabilities",
|
||||||
|
"noCapDef": "No capabilities defined",
|
||||||
|
"tags": "Tags",
|
||||||
|
"noTagDef": "No tags defined",
|
||||||
|
"authorized": "Authorized",
|
||||||
|
"address": "Address",
|
||||||
|
"managedIPs": "Managed IPs",
|
||||||
|
"lastSeen": "Last seen",
|
||||||
|
"version": "Version",
|
||||||
|
"physIp": "Physical IP",
|
||||||
|
"latency": "Latency",
|
||||||
|
"settings": "Settings",
|
||||||
|
"generalSettings": "General settings",
|
||||||
|
"networkId": "Network ID",
|
||||||
|
"accessControl": "Access control",
|
||||||
|
"public": "Public",
|
||||||
|
"private": "Private",
|
||||||
|
"managedRoutes": "Managed routes",
|
||||||
|
"addRoute": "Add route",
|
||||||
|
"target": "Target",
|
||||||
|
"via": "Via",
|
||||||
|
"start": "Start",
|
||||||
|
"end": "End",
|
||||||
|
"ipv4AutoAssign": "IPv4 Auto-Assign",
|
||||||
|
"autoAssignPool": "IPv4 Auto-Assign",
|
||||||
|
"addIPv4Pool": "Add IPv4 Pool",
|
||||||
|
"multicastLimit": "Multicast Recipient Limit",
|
||||||
|
"enableBroadcast": "Enable Broadcast",
|
||||||
|
"logInFailed": "Invalid username or password",
|
||||||
|
"tooManyAttempts": "Too many login attempts, please try again in 15 minutes.",
|
||||||
|
"language": "Language",
|
||||||
|
"notAuthorized": "You are not authorized. Please Log In.",
|
||||||
|
"saveChanges": "Save changes",
|
||||||
|
"optional": "Optional",
|
||||||
|
"destination": "Destination",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password"
|
||||||
|
}
|
67
frontend/public/locales/es-ES/common.json
Normal file
67
frontend/public/locales/es-ES/common.json
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"flowRules": "Reglas de flujo",
|
||||||
|
"createNetwork": "Crear una red",
|
||||||
|
"createOneNetwork": "Por favor, crea al menos una red",
|
||||||
|
"controllerNetworks": "Controlador de redes",
|
||||||
|
"network_one": "Red",
|
||||||
|
"network_other": "Redes",
|
||||||
|
"controllerAddress": "Dirección del controlador",
|
||||||
|
"loginToContinue": "Por favor, inicia sesión para continuar",
|
||||||
|
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - es una interfaz de usuario web para un controlador de red ZeroTier self-hosted.",
|
||||||
|
"logIn": "Iniciar sesión",
|
||||||
|
"logInToken": "Iniciar sesión con token",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"management": "Gestión",
|
||||||
|
"deleteNetwork": "Borrar red",
|
||||||
|
"deleteAlert": "Esta acción no puede ser revertida.",
|
||||||
|
"deleteNetworkConfirm": "¿Seguro que deseas borrar esta red?",
|
||||||
|
"deleteMemberConfirm": "¿Seguro que deseas borrar este usuario?",
|
||||||
|
"delete": "Borrar",
|
||||||
|
"logOut": "Cerrar sesión",
|
||||||
|
"advancedFeature": "CARACTERÍSTICA AVANZADA",
|
||||||
|
"noDevices": "Ningún dispositivo se ha unido a esta red. Utilice la aplicación en sus dispositivos para unirse",
|
||||||
|
"member_one": "Miembro",
|
||||||
|
"member_other": "Miembros",
|
||||||
|
"addMemberManually": "Añadir miembro manualmente",
|
||||||
|
"name": "Nombre",
|
||||||
|
"description": "Descripción",
|
||||||
|
"allowBridging": "Permitir puente Ethernet",
|
||||||
|
"noAutoIP": "No autoasignar IPs",
|
||||||
|
"capabilities": "Permisos",
|
||||||
|
"noCapDef": "No hay permisos definidos",
|
||||||
|
"tags": "Etiquetas",
|
||||||
|
"noTagDef": "No hay etiquetas definidas",
|
||||||
|
"authorized": "Autorizado",
|
||||||
|
"address": "Dirección",
|
||||||
|
"managedIPs": "IPs asignadas",
|
||||||
|
"lastSeen": "Visto por última vez",
|
||||||
|
"version": "Versión",
|
||||||
|
"physIp": "IP pública",
|
||||||
|
"latency": "Latencia",
|
||||||
|
"settings": "Ajustes",
|
||||||
|
"generalSettings": "Ajustes generales",
|
||||||
|
"networkId": "ID de red",
|
||||||
|
"accessControl": "Control de acceso",
|
||||||
|
"public": "Público",
|
||||||
|
"private": "Privado",
|
||||||
|
"managedRoutes": "Rutas gestionadas",
|
||||||
|
"addRoute": "Añadir ruta",
|
||||||
|
"target": "Objetivo",
|
||||||
|
"via": "Vía",
|
||||||
|
"start": "Inicio",
|
||||||
|
"end": "Final",
|
||||||
|
"autoAssignPool": "Rango de IPv4 autoasignables",
|
||||||
|
"ipv4AutoAssign": "Rangos de IPv4 automáticos",
|
||||||
|
"addIPv4Pool": "Añadir rango IPv4",
|
||||||
|
"multicastLimit": "Límite de destinatarios multicast",
|
||||||
|
"enableBroadcast": "Habilitar broadcast",
|
||||||
|
"logInFailed": "Nombre de usuario o contraseña incorrecto",
|
||||||
|
"tooManyAttempts": "Demasiados intentos de inicio de sesión. Vuelvee a intentarlo en 15 minutos",
|
||||||
|
"language": "Idioma",
|
||||||
|
"notAuthorized": "No estás autorizado. Por favor, inicia sesión.",
|
||||||
|
"saveChanges": "Guardar cambios",
|
||||||
|
"optional": "Opcional",
|
||||||
|
"destination": "Destino",
|
||||||
|
"username": "Nombre de usuario",
|
||||||
|
"password": "Contraseña"
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import Bar from "./components/Bar";
|
||||||
import Home from "./routes/Home";
|
import Home from "./routes/Home";
|
||||||
import NotFound from "./routes/NotFound";
|
import NotFound from "./routes/NotFound";
|
||||||
import Network from "./routes/Network/Network";
|
import Network from "./routes/Network/Network";
|
||||||
|
import Settings from "./routes/Settings";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -17,6 +18,7 @@ function App() {
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path="/network/:nwid" component={Network} />
|
<Route path="/network/:nwid" component={Network} />
|
||||||
|
<Route path="/settings" component={Settings} />
|
||||||
<Route path="/404" component={NotFound} />
|
<Route path="/404" component={NotFound} />
|
||||||
<Redirect to="/404" />
|
<Redirect to="/404" />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -19,6 +19,8 @@ import MenuIcon from "@material-ui/icons/Menu";
|
||||||
|
|
||||||
import LogIn from "components/LogIn";
|
import LogIn from "components/LogIn";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function Bar() {
|
function Bar() {
|
||||||
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
|
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
|
||||||
const [disabledAuth] = useLocalStorage("disableAuth", false);
|
const [disabledAuth] = useLocalStorage("disableAuth", false);
|
||||||
|
@ -41,16 +43,18 @@ function Bar() {
|
||||||
history.go(0);
|
history.go(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
// TODO: add settings page
|
// TODO: add settings page
|
||||||
// {
|
{
|
||||||
// name: "Settings",
|
name: t("settings"),
|
||||||
// to: "/settings",
|
to: "/settings",
|
||||||
// },
|
},
|
||||||
...(!disabledAuth
|
...(!disabledAuth
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: "Log out",
|
name: t("logOut"),
|
||||||
divide: true,
|
divide: true,
|
||||||
onClick: onLogOutClick,
|
onClick: onLogOutClick,
|
||||||
},
|
},
|
||||||
|
@ -115,7 +119,6 @@ function Bar() {
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
menuItem.onClick();
|
menuItem.onClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,6 +9,8 @@ import NetworkButton from "./components/NetworkButton";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { generateNetworkConfig } from "utils/NetworkConfig";
|
import { generateNetworkConfig } from "utils/NetworkConfig";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function HomeLoggedIn() {
|
function HomeLoggedIn() {
|
||||||
const [networks, setNetworks] = useState([]);
|
const [networks, setNetworks] = useState([]);
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ function HomeLoggedIn() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -38,19 +42,19 @@ function HomeLoggedIn() {
|
||||||
className={classes.createBtn}
|
className={classes.createBtn}
|
||||||
onClick={createNetwork}
|
onClick={createNetwork}
|
||||||
>
|
>
|
||||||
Create A Network
|
{t("createNetwork")}
|
||||||
</Button>
|
</Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid container spacing={3} className={classes.container}>
|
<Grid container spacing={3} className={classes.container}>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<Typography variant="h5">Controller networks</Typography>
|
<Typography variant="h5">{t("controllerNetworks")}</Typography>
|
||||||
{networks[0] && "Network controller address"}
|
{networks[0] && t("controllerAddress")}
|
||||||
<Box fontWeight="fontWeightBold">
|
<Box fontWeight="fontWeightBold">
|
||||||
{networks[0] && networks[0]["id"].slice(0, 10)}
|
{networks[0] && networks[0]["id"].slice(0, 10)}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs="auto">
|
<Grid item xs="auto">
|
||||||
<Typography>Networks</Typography>
|
<Typography>{t("network", { count: networks.length })}</Typography>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{networks[0] ? (
|
{networks[0] ? (
|
||||||
networks.map((network) => (
|
networks.map((network) => (
|
||||||
|
@ -59,7 +63,7 @@ function HomeLoggedIn() {
|
||||||
</Grid>
|
</Grid>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div>Please create at least one network</div>
|
<div>{t("createOneNetwork")}</div>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Grid, Typography } from "@material-ui/core";
|
||||||
import { useLocalStorage } from "react-use";
|
import { useLocalStorage } from "react-use";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
function HomeLoggedOut() {
|
function HomeLoggedOut() {
|
||||||
|
@ -29,6 +31,8 @@ function HomeLoggedOut() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [history, setDisableAuth, setLoggedIn, setToken]);
|
}, [history, setDisableAuth, setLoggedIn, setToken]);
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
@ -42,14 +46,11 @@ function HomeLoggedOut() {
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>
|
<span>{t("zerouiDesc")}</span>
|
||||||
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
|
|
||||||
self-hosted ZeroTier network controller.
|
|
||||||
</span>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography>
|
<Typography>
|
||||||
<span>Please Log In to continue</span>
|
<span>{t("loginToContinue")}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function LogInToken() {
|
function LogInToken() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [errorText, setErrorText] = useState("");
|
const [errorText, setErrorText] = useState("");
|
||||||
|
@ -41,6 +43,8 @@ function LogInToken() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const LogIn = () => {
|
const LogIn = () => {
|
||||||
if (token.length !== 32) {
|
if (token.length !== 32) {
|
||||||
setErrorText("Token length error");
|
setErrorText("Token length error");
|
||||||
|
@ -55,12 +59,12 @@ function LogInToken() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
||||||
Token Log In
|
{t("logInToken")}
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>Log In</DialogTitle>
|
<DialogTitle>{t("logIn")}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
|
<DialogContentText>{t("advancedFeature")}</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
value={token}
|
value={token}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -76,10 +80,10 @@ function LogInToken() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
Log In
|
{t("logIn")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function LogInUser() {
|
function LogInUser() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
@ -72,13 +74,15 @@ function LogInUser() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
||||||
Log In
|
{t("logIn")}
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>Log In</DialogTitle>
|
<DialogTitle>{t("logIn")}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
|
@ -87,7 +91,7 @@ function LogInUser() {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="username"
|
label={t("username")}
|
||||||
type="username"
|
type="username"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
@ -97,17 +101,17 @@ function LogInUser() {
|
||||||
setPassword(e.target.value);
|
setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="password"
|
label={t("password")}
|
||||||
type="password"
|
type="password"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
Log In
|
{t("logIn")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -117,7 +121,7 @@ function LogInUser() {
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "center",
|
horizontal: "center",
|
||||||
}}
|
}}
|
||||||
message={error}
|
message={t(error)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,6 +18,8 @@ import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function NetworkManagement() {
|
function NetworkManagement() {
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -42,10 +44,12 @@ function NetworkManagement() {
|
||||||
history.go(0);
|
history.go(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>Management</Typography>
|
<Typography>{t("management")}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Button
|
<Button
|
||||||
|
@ -54,21 +58,19 @@ function NetworkManagement() {
|
||||||
startIcon={<DeleteIcon />}
|
startIcon={<DeleteIcon />}
|
||||||
onClick={handleClickOpen}
|
onClick={handleClickOpen}
|
||||||
>
|
>
|
||||||
Delete Network
|
{t("deleteNetwork")}
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>
|
<DialogTitle>{t("deleteNetworkConfirm")}</DialogTitle>
|
||||||
{"Are you sure you want to delete this network?"}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>This action cannot be undone.</DialogContentText>
|
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteNetwork} color="secondary">
|
<Button onClick={deleteNetwork} color="secondary">
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -21,6 +21,8 @@ import ManagedIP from "./components/ManagedIP";
|
||||||
import MemberName from "./components/MemberName";
|
import MemberName from "./components/MemberName";
|
||||||
import MemberSettings from "./components/MemberSettings";
|
import MemberSettings from "./components/MemberSettings";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function NetworkMembers({ network }) {
|
function NetworkMembers({ network }) {
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
|
@ -46,6 +48,8 @@ function NetworkMembers({ network }) {
|
||||||
console.log("Action:", req);
|
console.log("Action:", req);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const handleChange =
|
const handleChange =
|
||||||
(member, key1, key2 = null, mode = "text", id = null) =>
|
(member, key1, key2 = null, mode = "text", id = null) =>
|
||||||
(event) => {
|
(event) => {
|
||||||
|
@ -67,7 +71,7 @@ function NetworkMembers({ network }) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
id: "auth",
|
id: "auth",
|
||||||
name: "Authorized",
|
name: t("authorized"),
|
||||||
minWidth: "80px",
|
minWidth: "80px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -79,7 +83,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "address",
|
id: "address",
|
||||||
name: "Address",
|
name: t("address"),
|
||||||
minWidth: "150px",
|
minWidth: "150px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Typography variant="body2">{row.config.address}</Typography>
|
<Typography variant="body2">{row.config.address}</Typography>
|
||||||
|
@ -87,19 +91,19 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "name",
|
id: "name",
|
||||||
name: "Name / Description",
|
name: t("name") + "/" + t("description"),
|
||||||
minWidth: "250px",
|
minWidth: "250px",
|
||||||
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ips",
|
id: "ips",
|
||||||
name: "Managed IPs",
|
name: t("managedIPs"),
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "status",
|
id: "lastSeen",
|
||||||
name: "Last Seen",
|
name: t("lastSeen"),
|
||||||
minWidth: "100px",
|
minWidth: "100px",
|
||||||
cell: (row) =>
|
cell: (row) =>
|
||||||
row.online === 1 ? (
|
row.online === 1 ? (
|
||||||
|
@ -121,7 +125,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "physicalip",
|
id: "physicalip",
|
||||||
name: "Version / Physical IP / Latency",
|
name: t("version") + " / " + t("physIp") + " / " + t("latency"),
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
cell: (row) =>
|
cell: (row) =>
|
||||||
row.online === 1 ? (
|
row.online === 1 ? (
|
||||||
|
@ -143,7 +147,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "delete",
|
id: "delete",
|
||||||
name: "",
|
name: t("settings"),
|
||||||
minWidth: "50px",
|
minWidth: "50px",
|
||||||
right: true,
|
right: true,
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
|
@ -162,7 +166,7 @@ function NetworkMembers({ network }) {
|
||||||
return (
|
return (
|
||||||
<Accordion defaultExpanded={true}>
|
<Accordion defaultExpanded={true}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>Members</Typography>
|
<Typography>{t("member", { count: members.length })}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
|
@ -188,8 +192,7 @@ function NetworkMembers({ network }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" style={{ padding: "10%" }}>
|
<Typography variant="h6" style={{ padding: "10%" }}>
|
||||||
No devices have joined this network. Use the app on your
|
{t("noDevices")} <b>{nwid}</b>.
|
||||||
devices to join <b>{nwid}</b>.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import AddIcon from "@material-ui/icons/Add";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function AddMember({ nwid, callback }) {
|
function AddMember({ nwid, callback }) {
|
||||||
const [member, setMember] = useState("");
|
const [member, setMember] = useState("");
|
||||||
|
|
||||||
|
@ -24,9 +26,11 @@ function AddMember({ nwid, callback }) {
|
||||||
setMember("");
|
setMember("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>Manually Add Member</Typography>
|
<Typography>{t("addMemberManually")}</Typography>
|
||||||
<List
|
<List
|
||||||
disablePadding={true}
|
disablePadding={true}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -12,8 +12,10 @@ import {
|
||||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function DeleteMember({ nwid, mid, callback }) {
|
function DeleteMember({ nwid, mid, callback }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
|
@ -37,18 +39,16 @@ function DeleteMember({ nwid, mid, callback }) {
|
||||||
<DeleteOutlineIcon color="secondary" style={{ fontSize: 20 }} />
|
<DeleteOutlineIcon color="secondary" style={{ fontSize: 20 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>
|
<DialogTitle>{t("deleteMemberConfirm")}</DialogTitle>
|
||||||
{"Are you sure you want to delete this member?"}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>This action cannot be undone.</DialogContentText>
|
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteMemberReq} color="secondary">
|
<Button onClick={deleteMemberReq} color="secondary">
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { Grid, TextField } from "@material-ui/core";
|
import { Grid, TextField } from "@material-ui/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function MemberName({ member, handleChange }) {
|
function MemberName({ member, handleChange }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
value={member.name}
|
value={member.name}
|
||||||
onChange={handleChange(member, "name")}
|
onChange={handleChange(member, "name")}
|
||||||
label="Name"
|
label={t("name")}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -15,7 +17,7 @@ function MemberName({ member, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={member.description}
|
value={member.description}
|
||||||
onChange={handleChange(member, "description")}
|
onChange={handleChange(member, "description")}
|
||||||
label="Description"
|
label={t("description")}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
|
|
@ -13,7 +13,10 @@ import BuildIcon from "@material-ui/icons/Build";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Tag from "./components/Tag";
|
import Tag from "./components/Tag";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function MemberSettings({ member, network, handleChange }) {
|
function MemberSettings({ member, network, handleChange }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
|
@ -30,7 +33,9 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
<BuildIcon style={{ fontSize: 20 }} />
|
<BuildIcon style={{ fontSize: 20 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>{"Member " + member.config.id + " settings"}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{t("member") + member.config.id + t("settings")}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -43,7 +48,7 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>Allow Ethernet Bridging</span>
|
<span>{t("allowBridging")}</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -56,17 +61,17 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>Do Not Auto-Assign IPs</span>
|
<span>{t("noAutoIP")}</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6">Capabilities</Typography>
|
<Typography variant="h6">{t("capabilities")}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Paper style={{ padding: 20 }}>
|
<Paper style={{ padding: 20 }}>
|
||||||
{Object.entries(network["capabilitiesByName"] || []).length ===
|
{Object.entries(network["capabilitiesByName"] || []).length ===
|
||||||
0
|
0
|
||||||
? "No capabilities defined"
|
? t("noCapDef")
|
||||||
: ""}
|
: ""}
|
||||||
{Object.entries(network["capabilitiesByName"] || []).map(
|
{Object.entries(network["capabilitiesByName"] || []).map(
|
||||||
([capName, capId]) => (
|
([capName, capId]) => (
|
||||||
|
@ -96,11 +101,11 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6">Tags</Typography>
|
<Typography variant="h6">{t("tags")}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
{Object.entries(network["tagsByName"] || []).length === 0 ? (
|
{Object.entries(network["tagsByName"] || []).length === 0 ? (
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Paper style={{ padding: 20 }}>No tags defined</Paper>
|
<Paper style={{ padding: 20 }}>{t("noTagDef")}</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
@ -17,7 +17,11 @@ import debounce from "lodash/debounce";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
|
|
||||||
function NetworkRules({ network, callback }) {
|
function NetworkRules({ network, callback }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const [editor, setEditor] = useState(null);
|
const [editor, setEditor] = useState(null);
|
||||||
const [flowData, setFlowData] = useState({
|
const [flowData, setFlowData] = useState({
|
||||||
rules: [...network.config.rules],
|
rules: [...network.config.rules],
|
||||||
|
@ -87,12 +91,12 @@ function NetworkRules({ network, callback }) {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>Flow Rules</Typography>
|
<Typography>{t("flowRules")}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
{/* Important note: value in CodeMirror instance means INITAIL VALUE
|
{/* Important note: value in CodeMirror instance means INITAIL VALUE
|
||||||
or it could be used to replace editor state with the new value.
|
or it could be used to replace editor state with the new value.
|
||||||
No need to update on every user character input
|
No need to update on every user character input Flow Rules
|
||||||
*/}
|
*/}
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={network["rulesSource"]}
|
value={network["rulesSource"]}
|
||||||
|
@ -130,7 +134,7 @@ function NetworkRules({ network, callback }) {
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="contained" color="primary" onClick={saveChanges}>
|
<Button variant="contained" color="primary" onClick={saveChanges}>
|
||||||
Save Changes
|
{t("saveChanges")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -17,7 +17,10 @@ import IPv4AutoAssign from "./components/IPv4AutoAssign";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function NetworkSettings({ network, setNetwork }) {
|
function NetworkSettings({ network, setNetwork }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const sendReq = async (data) => {
|
const sendReq = async (data) => {
|
||||||
try {
|
try {
|
||||||
const req = await API.post("/network/" + network["config"]["id"], data);
|
const req = await API.post("/network/" + network["config"]["id"], data);
|
||||||
|
@ -43,12 +46,12 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>General settings</Typography>
|
<Typography>{t("generalSettings")}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>Network ID</Typography>
|
<Typography>{t("networkId")}</Typography>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>{network["config"]["id"]}</span>
|
<span>{network["config"]["id"]}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -57,7 +60,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={network["config"]["name"]}
|
value={network["config"]["name"]}
|
||||||
onChange={handleChange("config", "name")}
|
onChange={handleChange("config", "name")}
|
||||||
label="Name"
|
label={t("name")}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -71,7 +74,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
multiline
|
multiline
|
||||||
minRows={2}
|
minRows={2}
|
||||||
maxRows={Infinity}
|
maxRows={Infinity}
|
||||||
label="Description"
|
label={t("description")}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -80,14 +83,14 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>Access Control</Typography>
|
<Typography>{t("accessControl")}</Typography>
|
||||||
<Select
|
<Select
|
||||||
native
|
native
|
||||||
value={network["config"]["private"]}
|
value={network["config"]["private"]}
|
||||||
onChange={handleChange("config", "private", "json")}
|
onChange={handleChange("config", "private", "json")}
|
||||||
>
|
>
|
||||||
<option value={true}>Private</option>
|
<option value={1}>{t("private")}</option>
|
||||||
<option value={false}>Public</option>
|
<option value={0}>{t("public")}</option>
|
||||||
</Select>
|
</Select>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -111,7 +114,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<TextField
|
<TextField
|
||||||
label="Multicast Recipient Limit"
|
label={t("multicastLimit")}
|
||||||
type="number"
|
type="number"
|
||||||
value={network["config"]["multicastLimit"]}
|
value={network["config"]["multicastLimit"]}
|
||||||
onChange={handleChange("config", "multicastLimit", "json")}
|
onChange={handleChange("config", "multicastLimit", "json")}
|
||||||
|
@ -126,7 +129,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
color="primary"
|
color="primary"
|
||||||
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
||||||
/>
|
/>
|
||||||
<span>Enable Broadcast</span>
|
<span>{t("enableBroadcast")}</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* TODO: */}
|
{/* TODO: */}
|
||||||
{/* <Grid item>
|
{/* <Grid item>
|
||||||
|
|
|
@ -18,7 +18,10 @@ import DataTable from "react-data-table-component";
|
||||||
import { addressPool } from "utils/NetworkConfig";
|
import { addressPool } from "utils/NetworkConfig";
|
||||||
import { getCIDRAddress, validateIP, normilizeIP } from "utils/IP";
|
import { getCIDRAddress, validateIP, normilizeIP } from "utils/IP";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [start, setStart] = useState("");
|
const [start, setStart] = useState("");
|
||||||
const [end, setEnd] = useState("");
|
const [end, setEnd] = useState("");
|
||||||
|
|
||||||
|
@ -89,19 +92,19 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Start",
|
id: "Start",
|
||||||
name: "Start",
|
name: t("start"),
|
||||||
cell: (row) => row["ipRangeStart"],
|
cell: (row) => row["ipRangeStart"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "End",
|
id: "End",
|
||||||
name: "End",
|
name: t("end"),
|
||||||
cell: (row) => row["ipRangeEnd"],
|
cell: (row) => row["ipRangeEnd"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>IPv4 Auto-Assign</Typography>
|
<Typography>{t("ipv4AutoAssign")}</Typography>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "30px",
|
padding: "30px",
|
||||||
|
@ -122,7 +125,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
Auto-Assign Pools
|
{t("autoAssignPool")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box border={1} borderColor="grey.300">
|
<Box border={1} borderColor="grey.300">
|
||||||
<Grid item style={{ margin: "10px" }}>
|
<Grid item style={{ margin: "10px" }}>
|
||||||
|
@ -132,7 +135,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
data={ipAssignmentPools}
|
data={ipAssignmentPools}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography>Add IPv4 Pool</Typography>
|
<Typography>{t("addIPv4Pool")}</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -142,7 +145,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={start}
|
value={start}
|
||||||
onChange={handleStartInput}
|
onChange={handleStartInput}
|
||||||
placeholder={"Start"}
|
placeholder={t("start")}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -154,7 +157,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={end}
|
value={end}
|
||||||
onChange={handleEndInput}
|
onChange={handleEndInput}
|
||||||
placeholder={"End"}
|
placeholder={t("end")}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -16,7 +16,10 @@ import DataTable from "react-data-table-component";
|
||||||
|
|
||||||
import { validateIP, normilizeIP, validateCIDR } from "utils/IP";
|
import { validateIP, normilizeIP, validateCIDR } from "utils/IP";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function ManagedRoutes({ routes, handleChange }) {
|
function ManagedRoutes({ routes, handleChange }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [destination, setDestination] = useState("");
|
const [destination, setDestination] = useState("");
|
||||||
const [via, setVia] = useState("");
|
const [via, setVia] = useState("");
|
||||||
|
|
||||||
|
@ -71,12 +74,12 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "target",
|
id: "target",
|
||||||
name: "Target",
|
name: t("target"),
|
||||||
cell: (row) => row["target"],
|
cell: (row) => row["target"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "via",
|
id: "via",
|
||||||
name: "via",
|
name: t("via"),
|
||||||
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -84,13 +87,13 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
Managed Routes ({routes.length + "/128"})
|
{t("managedRoutes")} ({routes.length + "/128"})
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box border={1} borderColor="grey.300">
|
<Box border={1} borderColor="grey.300">
|
||||||
<Grid item style={{ margin: "10px" }}>
|
<Grid item style={{ margin: "10px" }}>
|
||||||
<DataTable noHeader={true} columns={columns} data={routes} />
|
<DataTable noHeader={true} columns={columns} data={routes} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography>Add Routes</Typography>
|
<Typography>{t("addRoute")}</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -100,7 +103,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={destination}
|
value={destination}
|
||||||
onChange={handleDestinationInput}
|
onChange={handleDestinationInput}
|
||||||
placeholder={"Destination (CIDR)"}
|
placeholder={t("destination") + " (CIDR)"}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -112,7 +115,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={via}
|
value={via}
|
||||||
onChange={handleViaInput}
|
onChange={handleViaInput}
|
||||||
placeholder={"Via (Optional)"}
|
placeholder={t("via") + " (" + t("optional") + ")"}
|
||||||
/>
|
/>
|
||||||
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
||||||
<AddIcon
|
<AddIcon
|
||||||
|
|
43
frontend/src/components/Settings/Settings.jsx
Normal file
43
frontend/src/components/Settings/Settings.jsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
Checkbox,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Select,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||||
|
|
||||||
|
import API from "utils/API";
|
||||||
|
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function Settings() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
const handleChange = () => (event) => {
|
||||||
|
i18n.changeLanguage(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
|
<Typography>{t("language")}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Grid item>
|
||||||
|
<Select native value={i18n.language} onChange={handleChange()}>
|
||||||
|
<option value={"en"}>English</option>
|
||||||
|
<option value={"es-ES"}>Español</option>
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
1
frontend/src/components/Settings/index.jsx
Normal file
1
frontend/src/components/Settings/index.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./Settings";
|
34
frontend/src/i18n.js
Normal file
34
frontend/src/i18n.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import i18n from "i18next";
|
||||||
|
import languageDetector from "i18next-browser-languagedetector";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import Backend from "i18next-http-backend";
|
||||||
|
|
||||||
|
const userLanguage = window.navigator.language;
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(languageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.use(Backend)
|
||||||
|
.init({
|
||||||
|
compatibilityJSON: "v4",
|
||||||
|
fallbackLng: "en",
|
||||||
|
detection: {
|
||||||
|
order: ["path", "cookie", "localStorage", "htmlTag"],
|
||||||
|
caches: ["localStorage", "cookie"],
|
||||||
|
},
|
||||||
|
debug: true,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: true,
|
||||||
|
},
|
||||||
|
react: {
|
||||||
|
useSuspense: true,
|
||||||
|
},
|
||||||
|
supportedLngs: ["en", "es-ES"],
|
||||||
|
backend: {
|
||||||
|
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
||||||
|
},
|
||||||
|
ns: ["common"],
|
||||||
|
defaultNS: "common",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
|
@ -1,13 +1,17 @@
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import React from "react";
|
import React, { Suspense } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
|
import "./i18n";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<App />
|
<App />
|
||||||
|
</Suspense>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById("root")
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,10 @@ import { useLocalStorage } from "react-use";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import useStyles from "./Network.styles";
|
import useStyles from "./Network.styles";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function Network() {
|
function Network() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const [loggedIn] = useLocalStorage("loggedIn", false);
|
const [loggedIn] = useLocalStorage("loggedIn", false);
|
||||||
const [network, setNetwork] = useState({});
|
const [network, setNetwork] = useState({});
|
||||||
|
@ -42,7 +45,7 @@ function Network() {
|
||||||
<div className={classes.breadcrumbs}>
|
<div className={classes.breadcrumbs}>
|
||||||
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
||||||
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
||||||
Networks
|
{t("network", { count: 2 })}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
|
@ -73,9 +76,7 @@ function Network() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">{t("notAuthorized")}</Typography>
|
||||||
You are not authorized. Please Log In
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|
52
frontend/src/routes/Settings/Settings.jsx
Normal file
52
frontend/src/routes/Settings/Settings.jsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { Grid, Link, Typography } from "@material-ui/core";
|
||||||
|
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
|
||||||
|
import SettingsComponent from "components/Settings";
|
||||||
|
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
import { useLocalStorage } from "react-use";
|
||||||
|
|
||||||
|
import useStyles from "./Settings.styles";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function Settings() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const [loggedIn] = useLocalStorage("loggedIn", false);
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
if (loggedIn) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classes.breadcrumbs}>
|
||||||
|
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
||||||
|
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
||||||
|
{t("settings")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={classes.container}>
|
||||||
|
<SettingsComponent />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={0}
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
justify="center"
|
||||||
|
style={{
|
||||||
|
minHeight: "50vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid item xs={10}>
|
||||||
|
<Typography variant="h5">{t("notAuthorized")}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
16
frontend/src/routes/Settings/Settings.styles.jsx
Normal file
16
frontend/src/routes/Settings/Settings.styles.jsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
backIcon: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
margin: "3%",
|
||||||
|
},
|
||||||
|
breadcrumbs: {
|
||||||
|
paddingTop: "2%",
|
||||||
|
paddingLeft: "2%",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useStyles;
|
1
frontend/src/routes/Settings/index.jsx
Normal file
1
frontend/src/routes/Settings/index.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./Settings";
|
121
yarn.lock
121
yarn.lock
|
@ -265,6 +265,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.22.5":
|
||||||
|
version: 7.23.2
|
||||||
|
resolution: "@babel/runtime@npm:7.23.2"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: "npm:^0.14.0"
|
||||||
|
checksum: abdcbdd590c7e31762e1bdab94dd466823c8bcedd3ff2fde85eeb94dac7cccaef151ac37c428bda7018ededd27c9a82b4dfeb621f978ad934232475a902f8e3a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/template@npm:^7.22.15":
|
"@babel/template@npm:^7.22.15":
|
||||||
version: 7.22.15
|
version: 7.22.15
|
||||||
resolution: "@babel/template@npm:7.22.15"
|
resolution: "@babel/template@npm:7.22.15"
|
||||||
|
@ -2992,6 +3001,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cross-fetch@npm:3.1.6":
|
||||||
|
version: 3.1.6
|
||||||
|
resolution: "cross-fetch@npm:3.1.6"
|
||||||
|
dependencies:
|
||||||
|
node-fetch: "npm:^2.6.11"
|
||||||
|
checksum: e08325b813da37f2d5312b3e630af992c35681c1737707b029e8ef1c48ea034bda8b960000fc8bee6e0485e133347198aa6ecccadb530b06c47472f6c76bc27b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
|
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
resolution: "cross-spawn@npm:7.0.3"
|
resolution: "cross-spawn@npm:7.0.3"
|
||||||
|
@ -4344,11 +4362,15 @@ __metadata:
|
||||||
eslint-plugin-react-hooks: "npm:^4.6.0"
|
eslint-plugin-react-hooks: "npm:^4.6.0"
|
||||||
eslint-plugin-react-refresh: "npm:^0.4.3"
|
eslint-plugin-react-refresh: "npm:^0.4.3"
|
||||||
history: "npm:^5.3.0"
|
history: "npm:^5.3.0"
|
||||||
|
i18next: "npm:^23.5.1"
|
||||||
|
i18next-browser-languagedetector: "npm:^7.1.0"
|
||||||
|
i18next-http-backend: "npm:^2.2.2"
|
||||||
ipaddr.js: "npm:^2.0.1"
|
ipaddr.js: "npm:^2.0.1"
|
||||||
lodash: "npm:^4.17.21"
|
lodash: "npm:^4.17.21"
|
||||||
react: "npm:^17.0.2"
|
react: "npm:^17.0.2"
|
||||||
react-data-table-component: "npm:^6.11.8"
|
react-data-table-component: "npm:^6.11.8"
|
||||||
react-dom: "npm:^17.0.2"
|
react-dom: "npm:^17.0.2"
|
||||||
|
react-i18next: "npm:^13.3.0"
|
||||||
react-is: "npm:^17.0.2"
|
react-is: "npm:^17.0.2"
|
||||||
react-router-dom: "npm:^5.2.0"
|
react-router-dom: "npm:^5.2.0"
|
||||||
react-use: "npm:^17.4.0"
|
react-use: "npm:^17.4.0"
|
||||||
|
@ -4888,6 +4910,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"html-parse-stringify@npm:^3.0.1":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "html-parse-stringify@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
void-elements: "npm:3.1.0"
|
||||||
|
checksum: 8743b76cc50e46d1956c1ad879d18eb9613b0d2d81e24686d633f9f69bb26b84676f64a926973de793cca479997017a63219278476d617b6c42d68246d7c07fe
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"http-cache-semantics@npm:^4.1.1":
|
"http-cache-semantics@npm:^4.1.1":
|
||||||
version: 4.1.1
|
version: 4.1.1
|
||||||
resolution: "http-cache-semantics@npm:4.1.1"
|
resolution: "http-cache-semantics@npm:4.1.1"
|
||||||
|
@ -4968,6 +4999,33 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"i18next-browser-languagedetector@npm:^7.1.0":
|
||||||
|
version: 7.1.0
|
||||||
|
resolution: "i18next-browser-languagedetector@npm:7.1.0"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.19.4"
|
||||||
|
checksum: 3b06c8a5df09092cffc0b6637b542bb572e8a25dcba97d0d8a5e5dd7539b90bf00000f3a279654693f4b5908c5fc4d1d4f3766dfb461dacab46be3d071266384
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"i18next-http-backend@npm:^2.2.2":
|
||||||
|
version: 2.2.2
|
||||||
|
resolution: "i18next-http-backend@npm:2.2.2"
|
||||||
|
dependencies:
|
||||||
|
cross-fetch: "npm:3.1.6"
|
||||||
|
checksum: dbf09f2f309cb6070e691d0d382ccff3e94d2cfa9f30315dc4c03faa5d7d1f3b408d48c46c766b7e07527ec10c0542fde19240845905314ce0134ac10d6a6adb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"i18next@npm:^23.5.1":
|
||||||
|
version: 23.5.1
|
||||||
|
resolution: "i18next@npm:23.5.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.22.5"
|
||||||
|
checksum: 38e62d582b0f67eb2eee4f079c9cd512246496f2fb970f50a0be26c7c5e6ac5e772de9763ac1943919ecd816b2c0375f4b2071c67b1485a6a980c4d37348408f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
|
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
|
||||||
version: 0.4.24
|
version: 0.4.24
|
||||||
resolution: "iconv-lite@npm:0.4.24"
|
resolution: "iconv-lite@npm:0.4.24"
|
||||||
|
@ -6532,6 +6590,20 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-fetch@npm:^2.6.11":
|
||||||
|
version: 2.7.0
|
||||||
|
resolution: "node-fetch@npm:2.7.0"
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: "npm:^5.0.0"
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
checksum: b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp@npm:latest":
|
"node-gyp@npm:latest":
|
||||||
version: 9.4.0
|
version: 9.4.0
|
||||||
resolution: "node-gyp@npm:9.4.0"
|
resolution: "node-gyp@npm:9.4.0"
|
||||||
|
@ -7288,6 +7360,24 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-i18next@npm:^13.3.0":
|
||||||
|
version: 13.3.0
|
||||||
|
resolution: "react-i18next@npm:13.3.0"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.22.5"
|
||||||
|
html-parse-stringify: "npm:^3.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
i18next: ">= 23.2.3"
|
||||||
|
react: ">= 16.8.0"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
checksum: 2ef46245ba1ba9fca8c43dbe1bcab4ac63ca68e67de7159cb5f93cd7fd10407599bcdf1999f6567091987be7b9aaba77cdb515a70d5ee2b935b985f4c40d6d9d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0":
|
"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0":
|
||||||
version: 16.13.1
|
version: 16.13.1
|
||||||
resolution: "react-is@npm:16.13.1"
|
resolution: "react-is@npm:16.13.1"
|
||||||
|
@ -8654,6 +8744,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tr46@npm:~0.0.3":
|
||||||
|
version: 0.0.3
|
||||||
|
resolution: "tr46@npm:0.0.3"
|
||||||
|
checksum: 8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tree-kill@npm:^1.2.2":
|
"tree-kill@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "tree-kill@npm:1.2.2"
|
resolution: "tree-kill@npm:1.2.2"
|
||||||
|
@ -9046,6 +9143,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"void-elements@npm:3.1.0":
|
||||||
|
version: 3.1.0
|
||||||
|
resolution: "void-elements@npm:3.1.0"
|
||||||
|
checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"wcwidth@npm:^1.0.1":
|
"wcwidth@npm:^1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "wcwidth@npm:1.0.1"
|
resolution: "wcwidth@npm:1.0.1"
|
||||||
|
@ -9055,6 +9159,23 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"webidl-conversions@npm:^3.0.0":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "webidl-conversions@npm:3.0.1"
|
||||||
|
checksum: b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"whatwg-url@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "whatwg-url@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
tr46: "npm:~0.0.3"
|
||||||
|
webidl-conversions: "npm:^3.0.0"
|
||||||
|
checksum: f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"which-boxed-primitive@npm:^1.0.2":
|
"which-boxed-primitive@npm:^1.0.2":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "which-boxed-primitive@npm:1.0.2"
|
resolution: "which-boxed-primitive@npm:1.0.2"
|
||||||
|
|
Loading…
Reference in a new issue