mirror of
https://github.com/dec0dOS/zero-ui.git
synced 2025-07-06 13:01:46 -07:00
Compare commits
No commits in common. "main" and "v1.5.8" have entirely different histories.
66 changed files with 3488 additions and 3575 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -150,9 +150,15 @@ sketch
|
||||||
|
|
||||||
.yarn/*
|
.yarn/*
|
||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
!.yarn/plugins
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
|
# if you are NOT using Zero-installs, then:
|
||||||
|
# comment the following lines
|
||||||
|
!.yarn/cache
|
||||||
|
|
||||||
|
# and uncomment the following lines
|
||||||
|
# .pnp.*
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node
|
# End of https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node
|
893
.yarn/releases/yarn-4.0.0-rc.53.cjs
vendored
Executable file
893
.yarn/releases/yarn-4.0.0-rc.53.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,7 @@
|
||||||
enableGlobalCache: true
|
compressionLevel: mixed
|
||||||
|
|
||||||
enableTelemetry: false
|
enableGlobalCache: false
|
||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.0.0-rc.53.cjs
|
||||||
|
|
11
README.md
11
README.md
|
@ -165,12 +165,9 @@ Advanced manual setups are also supported. Check the following environment varia
|
||||||
| ZU_DEFAULT_USERNAME | unset (`docker-compose.yml`: admin) | Default username that will be set on the first run |
|
| ZU_DEFAULT_USERNAME | unset (`docker-compose.yml`: admin) | Default username that will be set on the first run |
|
||||||
| ZU_DEFAULT_PASSWORD | unset (`docker-compose.yml`: zero-ui) | Default password that will be set on the first run |
|
| ZU_DEFAULT_PASSWORD | unset (`docker-compose.yml`: zero-ui) | Default password that will be set on the first run |
|
||||||
| ZU_DATAPATH | `data/db.json` | ZeroUI data storage path |
|
| ZU_DATAPATH | `data/db.json` | ZeroUI data storage path |
|
||||||
| ZU_DISABLE_AUTH | `false` | If set to true, automatically log in all users. This is useful if ZeroUI is protected by an authentication proxy. Note that when this value is changed, the localStorage of instances of logged-in panels should be cleared |
|
| ZU_DISABLE_AUTH | unset | If set to true, automatically log in all users. This is useful if ZeroUI is protected by an authentication proxy. Note that when this value is changed, the localStorage of instances of logged-in panels should be cleared |
|
||||||
| ZU_LAST_SEEN_FETCH | `true`| Enables [Last Seen feature](https://github.com/dec0dOS/zero-ui/issues/40) |
|
| ZU_LAST_SEEN_FETCH | `true`| Enables [Last Seen feature](https://github.com/dec0dOS/zero-ui/issues/40) |
|
||||||
| ZU_LAST_SEEN_SCHEDULE | `*/5 * * * *` | Last Seen cron-like schedule |
|
| ZU_LAST_SEEN_SCHEDULE | `*/5 * * * *` | Last Seen cron-like schedule |
|
||||||
| ZU_LOGIN_LIMIT | `false` | Enable rate limiter for /login endpoint |
|
|
||||||
| ZU_LOGIN_LIMIT_WINDOW | 30 | The duration of the IP ban in minutes |
|
|
||||||
| ZU_LOGIN_LIMIT_ATTEMPTS | 50 | Login attemps before ban |
|
|
||||||
|
|
||||||
ZeroUI could be deployed as a regular nodejs web application, but it requires a ZeroTier controller that is installed with the `zerotier-one` package. For more info about the network controller, you could read [here](https://github.com/zerotier/ZeroTierOne/tree/master/controller/#readme).
|
ZeroUI could be deployed as a regular nodejs web application, but it requires a ZeroTier controller that is installed with the `zerotier-one` package. For more info about the network controller, you could read [here](https://github.com/zerotier/ZeroTierOne/tree/master/controller/#readme).
|
||||||
|
|
||||||
|
@ -200,7 +197,7 @@ For more information, please refer to this [discussion](https://github.com/dec0d
|
||||||
|
|
||||||
After installation, log in with the credentials declared with `ZU_DEFAULT_USERNAME` and `ZU_DEFAULT_PASSWORD`.
|
After installation, log in with the credentials declared with `ZU_DEFAULT_USERNAME` and `ZU_DEFAULT_PASSWORD`.
|
||||||
|
|
||||||
Currently, some main ZeroTier Central features are missing. Refer to the [roadmap](#roadmap) for more information.
|
Currently, almost all main ZeroTier Central features are available. Refer to the [roadmap](#roadmap) for more information.
|
||||||
|
|
||||||
_For screenshots, please refer to the [screenshots](docs/SCREENSHOTS.md) section._
|
_For screenshots, please refer to the [screenshots](docs/SCREENSHOTS.md) section._
|
||||||
|
|
||||||
|
@ -212,7 +209,7 @@ To get the latest version, simply run
|
||||||
docker-compose pull && docker-compose up -d --no-build
|
docker-compose pull && docker-compose up -d --no-build
|
||||||
```
|
```
|
||||||
|
|
||||||
in the folder where `docker-compose.yml` is located. Backups may not be necessary since most of your data is usually saved at the controller level, but it's still a good idea to consider them as a precautionary measure.
|
in the folder where `docker-compose.yml` is located. While backups are not required as your data is saved in Docker volumes, it is recommended.
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
|
@ -291,7 +288,7 @@ Afterward, you can start the ZeroUI development environment:
|
||||||
ZU_CONTROLLER_TOKEN=TOKEN_FROM_authtoken.secret yarn dev
|
ZU_CONTROLLER_TOKEN=TOKEN_FROM_authtoken.secret yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
For other platforms, please refer to the [ZeroTier manual](https://docs.zerotier.com/service/v1/).
|
For other platforms, please refer to the [ZeroTier manual](https://www.zerotier.com/manual/#4).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
import path from "path";
|
const express = require("express");
|
||||||
import * as url from "url";
|
const path = require("path");
|
||||||
import express from "express";
|
const logger = require("morgan");
|
||||||
import logger from "morgan";
|
const compression = require("compression");
|
||||||
import compression from "compression";
|
const bearerToken = require("express-bearer-token");
|
||||||
import bearerToken from "express-bearer-token";
|
const helmet = require("helmet");
|
||||||
import helmet from "helmet";
|
const cron = require("node-cron");
|
||||||
import { Cron } from "croner";
|
|
||||||
|
|
||||||
import { db } from "./utils/db.js";
|
const db = require("./utils/db");
|
||||||
import { initAdmin } from "./utils/init-admin.js";
|
const initAdmin = require("./utils/init-admin");
|
||||||
import { pingAll } from "./utils/ping.js";
|
const pingAll = require("./utils/ping");
|
||||||
|
|
||||||
import authRoutes from "./routes/auth.js";
|
const authRoutes = require("./routes/auth");
|
||||||
import networkRoutes from "./routes/network.js";
|
const networkRoutes = require("./routes/network");
|
||||||
import memberRoutes from "./routes/member.js";
|
const memberRoutes = require("./routes/member");
|
||||||
import controllerRoutes from "./routes/controller.js";
|
const controllerRoutes = require("./routes/controller");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
||||||
|
|
||||||
app.use(logger("dev"));
|
app.use(logger("dev"));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
@ -51,10 +49,6 @@ if (
|
||||||
["/app", "/app/*"],
|
["/app", "/app/*"],
|
||||||
express.static(path.join(__dirname, "..", "frontend", "build"))
|
express.static(path.join(__dirname, "..", "frontend", "build"))
|
||||||
);
|
);
|
||||||
app.use(
|
|
||||||
["/locales", "/locales/*"],
|
|
||||||
express.static(path.join(__dirname, "..", "frontend", "build", "locales"))
|
|
||||||
);
|
|
||||||
app.get(["/app/network/*"], function (req, res) {
|
app.get(["/app/network/*"], function (req, res) {
|
||||||
res.sendFile(path.join(__dirname, "..", "frontend", "build", "index.html"));
|
res.sendFile(path.join(__dirname, "..", "frontend", "build", "index.html"));
|
||||||
});
|
});
|
||||||
|
@ -69,7 +63,7 @@ initAdmin().then(function (admin) {
|
||||||
|
|
||||||
if (process.env.ZU_LAST_SEEN_FETCH !== "false") {
|
if (process.env.ZU_LAST_SEEN_FETCH !== "false") {
|
||||||
let schedule = process.env.ZU_LAST_SEEN_SCHEDULE || "*/5 * * * *";
|
let schedule = process.env.ZU_LAST_SEEN_SCHEDULE || "*/5 * * * *";
|
||||||
Cron(schedule, () => {
|
cron.schedule(schedule, () => {
|
||||||
console.debug("Running scheduled job");
|
console.debug("Running scheduled job");
|
||||||
const networks = db.get("networks").value();
|
const networks = db.get("networks").value();
|
||||||
networks.forEach(async (network) => {
|
networks.forEach(async (network) => {
|
||||||
|
@ -99,4 +93,4 @@ app.use(function (err, req, res, next) {
|
||||||
res.status(500).json({ error: "500 Internal server error" });
|
res.status(500).json({ error: "500 Internal server error" });
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
module.exports = app;
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import dotenv from "dotenv";
|
require("dotenv").config();
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import app from "../app.js";
|
var app = require("../app");
|
||||||
|
var debug = require("debug")("zero-ui:server");
|
||||||
console.log("zero-ui:server");
|
var http = require("http");
|
||||||
import http from "node:http";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
|
@ -84,6 +82,6 @@ function onError(error) {
|
||||||
|
|
||||||
function onListening() {
|
function onListening() {
|
||||||
var addr = server.address();
|
var addr = server.address();
|
||||||
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr?.port;
|
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
|
||||||
console.log("Listening on " + bind);
|
debug("Listening on " + bind);
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www",
|
||||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
||||||
|
@ -10,27 +9,22 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"croner": "^7.0.2",
|
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-bearer-token": "^2.4.0",
|
"express-bearer-token": "^2.4.0",
|
||||||
"express-rate-limit": "^7.1.1",
|
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lowdb": "^1.0.0",
|
"lowdb": "^1.0.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
"node-cron": "^3.0.2",
|
||||||
"pbkdf2-wrapper": "^1.3.4"
|
"pbkdf2-wrapper": "^1.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.7.3",
|
|
||||||
"@types/debug": "^4.1.9",
|
|
||||||
"@types/express": "^4.17.18",
|
"@types/express": "^4.17.18",
|
||||||
"@types/lodash": "^4.14.199",
|
|
||||||
"@types/morgan": "^1.9.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||||
"@typescript-eslint/parser": "^6.7.4",
|
"@typescript-eslint/parser": "^6.7.4",
|
||||||
"eslint": "^8.51.0",
|
"eslint": "^8.50.0",
|
||||||
"eslint-plugin-import": "^2.28.1",
|
"eslint-plugin-import": "^2.28.1",
|
||||||
"eslint-plugin-jsdoc": "^46.8.2",
|
"eslint-plugin-jsdoc": "^46.8.2",
|
||||||
"eslint-plugin-n": "^16.1.0",
|
"eslint-plugin-n": "^16.1.0",
|
||||||
|
|
|
@ -1,29 +1,7 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
import rateLimit from "express-rate-limit";
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
|
|
||||||
const loginLimiter = rateLimit({
|
|
||||||
windowMs: (Number(process.env.ZU_LOGIN_LIMIT_WINDOW) || 30) * 60 * 1000, // 30 minutes
|
|
||||||
max: Number(process.env.ZU_LOGIN_LIMIT_ATTEMPTS) || 50, // limit each IP to 50 requests per windowMs
|
|
||||||
message: {
|
|
||||||
status: 429,
|
|
||||||
error: "tooManyAttempts",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const loginLimiterWrapper = (req, res, next) => {
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === "production" &&
|
|
||||||
process.env.ZU_LOGIN_LIMIT === "true"
|
|
||||||
) {
|
|
||||||
return loginLimiter(req, res, next);
|
|
||||||
} else {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
router.get("/login", async function (req, res) {
|
router.get("/login", async function (req, res) {
|
||||||
if (process.env.ZU_DISABLE_AUTH === "true") {
|
if (process.env.ZU_DISABLE_AUTH === "true") {
|
||||||
|
@ -33,7 +11,7 @@ router.get("/login", async function (req, res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/login", loginLimiterWrapper, async function (req, res) {
|
router.post("/login", async function (req, res) {
|
||||||
if (req.body.username && req.body.password) {
|
if (req.body.username && req.body.password) {
|
||||||
auth.authorize(req.body.username, req.body.password, function (err, user) {
|
auth.authorize(req.body.username, req.body.password, function (err, user) {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -49,4 +27,4 @@ router.post("/login", loginLimiterWrapper, async function (req, res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
router.get("/status", auth.isAuthorized, async function (req, res) {
|
router.get("/status", auth.isAuthorized, async function (req, res) {
|
||||||
api.get("status").then(function (controllerRes) {
|
api.get("status").then(function (controllerRes) {
|
||||||
|
@ -10,4 +10,4 @@ router.get("/status", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router({ mergeParams: true });
|
const router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import * as member from "../services/member.js";
|
const member = require("../services/member");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
// get all members
|
// get all members
|
||||||
router.get("/", auth.isAuthorized, async function (req, res) {
|
router.get("/", auth.isAuthorized, async function (req, res) {
|
||||||
|
@ -86,4 +86,4 @@ router.delete("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import * as network from "../services/network.js";
|
const network = require("../services/network");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { defaultRules } from "../utils/constants.js";
|
const constants = require("../utils/constants");
|
||||||
import { getZTAddress } from "../utils/zt-address.js";
|
const getZTAddress = require("../utils/zt-address");
|
||||||
|
|
||||||
let ZT_ADDRESS = null;
|
let ZT_ADDRESS = null;
|
||||||
getZTAddress().then(function (address) {
|
getZTAddress().then(function (address) {
|
||||||
|
@ -40,7 +40,7 @@ router.post("/", auth.isAuthorized, async function (req, res) {
|
||||||
const config = reqData.config;
|
const config = reqData.config;
|
||||||
delete reqData.config;
|
delete reqData.config;
|
||||||
reqData = config;
|
reqData = config;
|
||||||
reqData.rules = JSON.parse(defaultRules);
|
reqData.rules = JSON.parse(constants.defaultRules);
|
||||||
} else {
|
} else {
|
||||||
res.status(400).send({ error: "Bad request" });
|
res.status(400).send({ error: "Bad request" });
|
||||||
}
|
}
|
||||||
|
@ -87,4 +87,4 @@ router.delete("/:nwid", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import verifyHash from "pbkdf2-wrapper/verifyHash.js";
|
const verifyHash = require("pbkdf2-wrapper/verifyHash");
|
||||||
|
|
||||||
export async function authorize(username, password, callback) {
|
exports.authorize = authorize;
|
||||||
|
async function authorize(username, password, callback) {
|
||||||
try {
|
try {
|
||||||
var users = await db.get("users");
|
var users = await db.get("users");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const user = users.find({ username: username });
|
const user = users.find({ username: username });
|
||||||
if (!user.value()) return callback(new Error("logInFailed")); // If return "user not found" someone can do a user listing
|
if (!user.value()) return callback(new Error("Cannot find user"));
|
||||||
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("logInFailed"));
|
return callback(new Error("Invalid password"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isAuthorized(req, res, next) {
|
exports.isAuthorized = isAuthorized;
|
||||||
|
async function isAuthorized(req, res, next) {
|
||||||
if (process.env.ZU_DISABLE_AUTH === "true") {
|
if (process.env.ZU_DISABLE_AUTH === "true") {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import _ from "lodash";
|
const _ = require("lodash");
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import { getZTAddress } from "../utils/zt-address.js";
|
const getZTAddress = require("../utils/zt-address");
|
||||||
|
|
||||||
let ZT_ADDRESS = null;
|
let ZT_ADDRESS = null;
|
||||||
getZTAddress().then(function (address) {
|
getZTAddress().then(function (address) {
|
||||||
|
@ -91,7 +91,8 @@ async function filterDeleted(nwid, mid) {
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMembersData(nwid, mids) {
|
exports.getMembersData = getMembersData;
|
||||||
|
async function getMembersData(nwid, mids) {
|
||||||
const prefix = "/controller/network/" + nwid + "/member/";
|
const prefix = "/controller/network/" + nwid + "/member/";
|
||||||
const filtered = (
|
const filtered = (
|
||||||
await Promise.all(mids.map(async (mid) => await filterDeleted(nwid, mid)))
|
await Promise.all(mids.map(async (mid) => await filterDeleted(nwid, mid)))
|
||||||
|
@ -118,7 +119,8 @@ export async function getMembersData(nwid, mids) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateMemberAdditionalData(nwid, mid, data) {
|
exports.updateMemberAdditionalData = updateMemberAdditionalData;
|
||||||
|
async function updateMemberAdditionalData(nwid, mid, data) {
|
||||||
if (data.config && data.config.authorized) {
|
if (data.config && data.config.authorized) {
|
||||||
db.get("networks")
|
db.get("networks")
|
||||||
.filter({ id: nwid })
|
.filter({ id: nwid })
|
||||||
|
@ -170,7 +172,8 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMemberAdditionalData(nwid, mid) {
|
exports.deleteMemberAdditionalData = deleteMemberAdditionalData;
|
||||||
|
async function deleteMemberAdditionalData(nwid, mid) {
|
||||||
// ZT controller bug
|
// ZT controller bug
|
||||||
/* db.get("networks")
|
/* db.get("networks")
|
||||||
.find({ id: nwid })
|
.find({ id: nwid })
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import _ from "lodash";
|
const _ = require("lodash");
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import { defaultRulesSource } from "../utils/constants.js";
|
const constants = require("../utils/constants");
|
||||||
|
|
||||||
export async function getNetworkAdditionalData(data) {
|
async function getNetworkAdditionalData(data) {
|
||||||
let additionalData = db
|
let additionalData = db
|
||||||
.get("networks")
|
.get("networks")
|
||||||
.find({ id: data.id })
|
.find({ id: data.id })
|
||||||
|
@ -29,7 +29,8 @@ export async function getNetworkAdditionalData(data) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNetworksData(nwids) {
|
exports.getNetworksData = getNetworksData;
|
||||||
|
async function getNetworksData(nwids) {
|
||||||
const prefix = "/controller/network/";
|
const prefix = "/controller/network/";
|
||||||
const links = nwids.map((nwid) => prefix + nwid);
|
const links = nwids.map((nwid) => prefix + nwid);
|
||||||
|
|
||||||
|
@ -53,12 +54,13 @@ export async function getNetworksData(nwids) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNetworkAdditionalData(nwid) {
|
exports.createNetworkAdditionalData = createNetworkAdditionalData;
|
||||||
|
async function createNetworkAdditionalData(nwid) {
|
||||||
const saveData = {
|
const saveData = {
|
||||||
id: nwid,
|
id: nwid,
|
||||||
additionalConfig: {
|
additionalConfig: {
|
||||||
description: "",
|
description: "",
|
||||||
rulesSource: defaultRulesSource,
|
rulesSource: constants.defaultRulesSource,
|
||||||
tagsByName: {},
|
tagsByName: {},
|
||||||
capabilitiesByName: {},
|
capabilitiesByName: {},
|
||||||
},
|
},
|
||||||
|
@ -68,7 +70,8 @@ export async function createNetworkAdditionalData(nwid) {
|
||||||
db.get("networks").push(saveData).write();
|
db.get("networks").push(saveData).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateNetworkAdditionalData(nwid, data) {
|
exports.updateNetworkAdditionalData = updateNetworkAdditionalData;
|
||||||
|
async function updateNetworkAdditionalData(nwid, data) {
|
||||||
let additionalData = {};
|
let additionalData = {};
|
||||||
|
|
||||||
if (data.hasOwnProperty("description")) {
|
if (data.hasOwnProperty("description")) {
|
||||||
|
@ -93,6 +96,7 @@ export async function updateNetworkAdditionalData(nwid, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteNetworkAdditionalData(nwid) {
|
exports.deleteNetworkAdditionalData = deleteNetworkAdditionalData;
|
||||||
|
async function deleteNetworkAdditionalData(nwid) {
|
||||||
db.get("networks").remove({ id: nwid }).write();
|
db.get("networks").remove({ id: nwid }).write();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "NodeNext",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "NodeNext"
|
"moduleResolution": "Node"
|
||||||
},
|
},
|
||||||
"include": ["."]
|
"include": ["."]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const defaultRulesSource = `
|
exports.defaultRulesSource = `
|
||||||
# This is a default rule set that allows IPv4 and IPv6 traffic but otherwise
|
# This is a default rule set that allows IPv4 and IPv6 traffic but otherwise
|
||||||
# behaves like a standard Ethernet switch.
|
# behaves like a standard Ethernet switch.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ drop
|
||||||
accept;
|
accept;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const defaultRules = `
|
exports.defaultRules = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "MATCH_ETHERTYPE",
|
"type": "MATCH_ETHERTYPE",
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
import fs from "node:fs";
|
const fs = require("fs");
|
||||||
import os from "node:os";
|
|
||||||
|
|
||||||
const baseURL = process.env.ZU_CONTROLLER_ENDPOINT || "http://localhost:9993/";
|
const baseURL = process.env.ZU_CONTROLLER_ENDPOINT || "http://localhost:9993/";
|
||||||
|
|
||||||
var token;
|
var token;
|
||||||
if (process.env.ZU_CONTROLLER_TOKEN) {
|
if (process.env.ZU_CONTROLLER_TOKEN) {
|
||||||
token = process.env.ZU_CONTROLLER_TOKEN;
|
token = process.env.ZU_CONTROLLER_TOKEN;
|
||||||
} else if (os.platform() === "linux") {
|
|
||||||
token = fs.readFileSync("/var/lib/zerotier-one/authtoken.secret", "utf8");
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Please provide ZU_CONTROLLER_TOKEN in environment");
|
token = fs.readFileSync("/var/lib/zerotier-one/authtoken.secret", "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = axios.create({
|
module.exports = axios.create({
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import low from "lowdb";
|
const low = require("lowdb");
|
||||||
import FileSync from "lowdb/adapters/FileSync.js";
|
const FileSync = require("lowdb/adapters/FileSync");
|
||||||
|
|
||||||
const adapter = new FileSync(process.env.ZU_DATAPATH || "data/db.json");
|
const adapter = new FileSync(process.env.ZU_DATAPATH || "data/db.json");
|
||||||
|
|
||||||
export const db = low(adapter);
|
const db = low(adapter);
|
||||||
|
|
||||||
|
module.exports = db;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import crypto from "crypto";
|
const crypto = require("crypto");
|
||||||
import hashPassword from "pbkdf2-wrapper/hashText.js";
|
const hashPassword = require("pbkdf2-wrapper/hashText");
|
||||||
|
|
||||||
export async function initAdmin() {
|
module.exports = async function () {
|
||||||
if (!process.env.ZU_DEFAULT_PASSWORD || !process.env.ZU_DEFAULT_USERNAME) {
|
if (!process.env.ZU_DEFAULT_PASSWORD || !process.env.ZU_DEFAULT_USERNAME) {
|
||||||
console.error("ZU_DEFAULT_PASSWORD or ZU_DEFAULT_USERNAME not found!");
|
console.error("ZU_DEFAULT_PASSWORD or ZU_DEFAULT_USERNAME not found!");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -13,4 +13,4 @@ export async function initAdmin() {
|
||||||
password_hash: hash,
|
password_hash: hash,
|
||||||
token: crypto.randomBytes(16).toString("hex"),
|
token: crypto.randomBytes(16).toString("hex"),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import _ from "lodash";
|
const _ = require("lodash");
|
||||||
|
|
||||||
import { api } from "./controller-api.js";
|
const api = require("./controller-api");
|
||||||
import { db } from "./db.js";
|
const db = require("./db");
|
||||||
|
|
||||||
export async function pingAll(network) {
|
async function pingAll(network) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
network.members.map(async (member) => {
|
network.members.map(async (member) => {
|
||||||
console.debug("Processing member " + member.id);
|
console.debug("Processing member " + member.id);
|
||||||
|
@ -29,3 +29,5 @@ export async function pingAll(network) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = pingAll;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
export async function getZTAddress() {
|
module.exports = async function () {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("status");
|
const res = await api.get("status");
|
||||||
return res.data.address;
|
return res.data.address;
|
||||||
|
@ -10,4 +10,4 @@ export async function getZTAddress() {
|
||||||
"Couldn't connect to the controller on " + err.config.baseURL
|
"Couldn't connect to the controller on " + err.config.baseURL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,68 +1,36 @@
|
||||||
# Stage 1: Build frontend
|
FROM --platform=$BUILDPLATFORM node:lts-alpine as frontend-build
|
||||||
FROM --platform=$BUILDPLATFORM node:lts-alpine AS frontend-build
|
|
||||||
|
|
||||||
ENV GENERATE_SOURCEMAP=false
|
ENV GENERATE_SOURCEMAP=false
|
||||||
|
|
||||||
# Enable corepack and create necessary directories in one layer
|
RUN mkdir -p /app/frontend
|
||||||
RUN corepack enable && mkdir -p /app/frontend
|
WORKDIR /app/
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package-related files and install dependencies
|
|
||||||
COPY tsconfig.json package.json yarn.lock* .yarnrc.yml ./
|
COPY tsconfig.json package.json yarn.lock* .yarnrc.yml ./
|
||||||
COPY .yarn/ ./.yarn
|
COPY .yarn/ ./.yarn
|
||||||
|
WORKDIR /app/frontend/
|
||||||
# Set working directory to frontend and copy package files
|
COPY ./frontend/package*.json /app/frontend
|
||||||
WORKDIR /app/frontend
|
|
||||||
COPY ./frontend/package*.json /app/frontend/
|
|
||||||
|
|
||||||
# Install frontend dependencies and build the frontend
|
|
||||||
RUN yarn workspaces focus frontend
|
RUN yarn workspaces focus frontend
|
||||||
COPY ./frontend /app/frontend/
|
COPY ./frontend /app/frontend
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
# Stage 2: Build backend
|
|
||||||
FROM node:lts-alpine AS backend-build
|
|
||||||
|
|
||||||
# Enable corepack and create necessary directories in one layer
|
|
||||||
RUN corepack enable && mkdir -p /app/backend
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package-related files and install dependencies
|
|
||||||
COPY package.json yarn.lock* .yarnrc.yml ./
|
|
||||||
COPY .yarn/ ./.yarn
|
|
||||||
|
|
||||||
# Set working directory to backend and copy package files
|
|
||||||
WORKDIR /app/backend
|
|
||||||
COPY ./backend/package*.json /app/backend/
|
|
||||||
|
|
||||||
# Install backend dependencies
|
|
||||||
RUN yarn workspaces focus --production backend && yarn cache clean
|
|
||||||
|
|
||||||
# Copy the backend source files
|
|
||||||
COPY ./backend /app/backend
|
|
||||||
|
|
||||||
# Final Stage: Production
|
|
||||||
FROM node:lts-alpine
|
FROM node:lts-alpine
|
||||||
|
|
||||||
# Set the working directory to /app/backend
|
WORKDIR /app/frontend/build
|
||||||
WORKDIR /app/backend
|
COPY --from=frontend-build /app/frontend/build /app/frontend/build/
|
||||||
|
|
||||||
# Copy the built frontend from the frontend-build stage
|
RUN mkdir -p /app/backend
|
||||||
COPY --from=frontend-build /app/frontend/build /app/frontend/build
|
WORKDIR /app/
|
||||||
|
COPY package.json yarn.lock* .yarnrc.yml ./
|
||||||
|
COPY .yarn/ ./.yarn
|
||||||
|
WORKDIR /app/backend/
|
||||||
|
COPY ./backend/package*.json /app/backend
|
||||||
|
RUN yarn workspaces focus --production backend && yarn cache clean
|
||||||
|
|
||||||
# Copy the backend files from the backend-build stage
|
COPY ./backend /app/backend
|
||||||
COPY --from=backend-build /app/backend /app/backend
|
|
||||||
COPY --from=backend-build /app/node_modules /app/backend/node_modules
|
|
||||||
|
|
||||||
# Environment variables
|
EXPOSE 4000
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV ZU_SECURE_HEADERS=true
|
ENV ZU_SECURE_HEADERS=true
|
||||||
ENV ZU_SERVE_FRONTEND=true
|
ENV ZU_SERVE_FRONTEND=true
|
||||||
|
|
||||||
# Expose the application port
|
CMD [ "node", "./bin/www" ]
|
||||||
EXPOSE 4000
|
|
||||||
|
|
||||||
# Start the application
|
|
||||||
CMD ["node", "bin/www.js"]
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 162 KiB |
Binary file not shown.
Before Width: | Height: | Size: 477 KiB After Width: | Height: | Size: 834 KiB |
|
@ -1,4 +1,4 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
|
@ -11,38 +11,28 @@
|
||||||
"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",
|
||||||
"styled-components": "^5.3.11"
|
"styled-components": "^5.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/codemirror": "^5.60.10",
|
"@types/react": "^18.2.24",
|
||||||
"@types/lodash": "^4.14.199",
|
"@types/react-dom": "^18.2.9",
|
||||||
"@types/react": "^17.0.67",
|
|
||||||
"@types/react-dom": "^17.0.21",
|
|
||||||
"@types/react-is": "^17.0.5",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
|
||||||
"@types/styled-components": "^5.1.28",
|
|
||||||
"@vitejs/plugin-react": "^4.1.0",
|
"@vitejs/plugin-react": "^4.1.0",
|
||||||
"eslint": "^8.51.0",
|
"eslint": "^8.50.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
"eslint-plugin-react-refresh": "^0.4.3",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.4.11",
|
"vite": "^4.4.10"
|
||||||
"vite-plugin-static-copy": "^1.0.6"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
@ -51,6 +41,19 @@
|
||||||
"clean": "rimraf build",
|
"clean": "rimraf build",
|
||||||
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"typecheck": "tsc --pretty --noEmit -p tsconfig.json",
|
"typecheck": "tsc --pretty --noEmit -p tsconfig.json",
|
||||||
"analyze": "vite build --sourcemap true && source-map-explorer 'build/assets/*.js' --no-border-checks"
|
"analyze": "source-map-explorer 'build/static/js/*.js'"
|
||||||
|
},
|
||||||
|
"homepage": "/app",
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"languageName": "English"
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"languageName": "Español"
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "Правила потока",
|
|
||||||
"createNetwork": "Создать сеть",
|
|
||||||
"createOneNetwork": "Пожалуйста, создайте хотя бы одну сеть",
|
|
||||||
"controllerNetworks": "Сети контроллера",
|
|
||||||
"network_one": "Сеть",
|
|
||||||
"network_few": "Сети",
|
|
||||||
"network_many": "Сетей",
|
|
||||||
"network_other": "Сетей",
|
|
||||||
"controllerAddress": "Адрес контроллера сети",
|
|
||||||
"loginToContinue": "Пожалуйста, войдите, чтобы продолжить",
|
|
||||||
"zerouiDesc": "ZeroUI - Веб-интерфейс контроллера ZeroTier - это веб-интерфейс для самостоятельного хостинга контроллера сети ZeroTier.",
|
|
||||||
"logIn": "Войти",
|
|
||||||
"logInToken": "Войти по токену",
|
|
||||||
"cancel": "Отмена",
|
|
||||||
"management": "Управление",
|
|
||||||
"deleteNetwork": "Удалить сеть",
|
|
||||||
"deleteAlert": "Это действие не может быть отменено.",
|
|
||||||
"deleteNetworkConfirm": "Вы уверены, что хотите удалить эту сеть?",
|
|
||||||
"deleteMemberConfirm": "Вы уверены, что хотите удалить это устройство?",
|
|
||||||
"delete": "Удалить",
|
|
||||||
"logOut": "Выйти",
|
|
||||||
"advancedFeature": "РАСШИРЕННАЯ ФУНКЦИЯ",
|
|
||||||
"noDevices": "Ни одно устройство не присоединилось к этой сети. Используйте приложение на ваших устройствах для подключения",
|
|
||||||
"member_one": "Устройство",
|
|
||||||
"member_few": "Устройства",
|
|
||||||
"member_many": "Устройства",
|
|
||||||
"member_other": "Устройств",
|
|
||||||
"addMemberManually": "Добавить устройство вручную",
|
|
||||||
"name": "Имя",
|
|
||||||
"description": "Описание",
|
|
||||||
"allowBridging": "Разрешить Ethernet мост",
|
|
||||||
"noAutoIP": "Не назначать IP автоматически",
|
|
||||||
"capabilities": "Возможности",
|
|
||||||
"noCapDef": "Нет определенных возможностей",
|
|
||||||
"tags": "Теги",
|
|
||||||
"noTagDef": "Нет определенных тегов",
|
|
||||||
"authorized": "Авторизован",
|
|
||||||
"address": "Адрес",
|
|
||||||
"managedIPs": "Управляемые IP",
|
|
||||||
"lastSeen": "Последний раз был онлайн",
|
|
||||||
"version": "Версия",
|
|
||||||
"physIp": "Физический IP",
|
|
||||||
"latency": "Задержка",
|
|
||||||
"settings": "Настройки",
|
|
||||||
"generalSettings": "Общие настройки",
|
|
||||||
"networkId": "ID сети",
|
|
||||||
"accessControl": "Контроль доступа",
|
|
||||||
"public": "Публичный",
|
|
||||||
"private": "Частный",
|
|
||||||
"managedRoutes": "Управляемые маршруты",
|
|
||||||
"addRoute": "Добавить маршрут",
|
|
||||||
"target": "Цель",
|
|
||||||
"via": "Через",
|
|
||||||
"start": "Начало",
|
|
||||||
"end": "Конец",
|
|
||||||
"ipv4AutoAssign": "Автоматическое назначение IPv4",
|
|
||||||
"autoAssignPool": "Автоматическое назначение IPv4",
|
|
||||||
"addIPv4Pool": "Добавить пул IPv4",
|
|
||||||
"multicastLimit": "Ограничение получателей мультикаста",
|
|
||||||
"enableBroadcast": "Включить широковещание",
|
|
||||||
"logInFailed": "Неверное имя пользователя или пароль",
|
|
||||||
"tooManyAttempts": "Слишком много попыток входа, попробуйте снова через 15 минут.",
|
|
||||||
"language": "Язык",
|
|
||||||
"notAuthorized": "Вы не авторизованы. Пожалуйста, войдите.",
|
|
||||||
"saveChanges": "Сохранить изменения",
|
|
||||||
"optional": "Необязательно",
|
|
||||||
"destination": "Назначение",
|
|
||||||
"username": "Имя пользователя",
|
|
||||||
"password": "Пароль",
|
|
||||||
"languageName": "Русский"
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "流量规则",
|
|
||||||
"createNetwork": "创建网络",
|
|
||||||
"createOneNetwork": "请至少创建一个网络",
|
|
||||||
"controllerNetworks": "控制器网络",
|
|
||||||
"network_one": "网络",
|
|
||||||
"network_other": "网络们",
|
|
||||||
"controllerAddress": "网络控制器地址",
|
|
||||||
"loginToContinue": "请登录以继续",
|
|
||||||
"zerouiDesc": "ZeroUI - ZeroTier 控制器Web界面 - 是一个用于自托管ZeroTier网络控制器的Web用户界面。",
|
|
||||||
"logIn": "登录",
|
|
||||||
"logInToken": "令牌登录",
|
|
||||||
"cancel": "取消",
|
|
||||||
"management": "管理",
|
|
||||||
"deleteNetwork": "删除网络",
|
|
||||||
"deleteAlert": "此操作无法撤销。",
|
|
||||||
"deleteNetworkConfirm": "您确定要删除此网络吗?",
|
|
||||||
"deleteMemberConfirm": "您确定要删除此成员吗?",
|
|
||||||
"delete": "删除",
|
|
||||||
"logOut": "登出",
|
|
||||||
"advancedFeature": "高级功能",
|
|
||||||
"noDevices": "没有设备加入此网络。请使用您的设备上的应用程序加入。",
|
|
||||||
"member_one": "成员",
|
|
||||||
"member_other": "成员们",
|
|
||||||
"addMemberManually": "手动添加成员",
|
|
||||||
"name": "名称",
|
|
||||||
"description": "描述",
|
|
||||||
"allowBridging": "允许以太网桥接",
|
|
||||||
"noAutoIP": "不自动分配IP",
|
|
||||||
"capabilities": "能力",
|
|
||||||
"noCapDef": "未定义能力",
|
|
||||||
"tags": "标签",
|
|
||||||
"noTagDef": "未定义标签",
|
|
||||||
"authorized": "已授权",
|
|
||||||
"address": "地址",
|
|
||||||
"managedIPs": "管理的IP地址",
|
|
||||||
"lastSeen": "最后看到的时间",
|
|
||||||
"version": "版本",
|
|
||||||
"physIp": "物理IP",
|
|
||||||
"latency": "延迟",
|
|
||||||
"settings": "设置",
|
|
||||||
"generalSettings": "常规设置",
|
|
||||||
"networkId": "网络ID",
|
|
||||||
"accessControl": "访问控制",
|
|
||||||
"public": "公共",
|
|
||||||
"private": "私有",
|
|
||||||
"managedRoutes": "管理的路由",
|
|
||||||
"addRoute": "添加路由",
|
|
||||||
"target": "目标",
|
|
||||||
"via": "通过",
|
|
||||||
"start": "开始",
|
|
||||||
"end": "结束",
|
|
||||||
"ipv4AutoAssign": "IPv4 自动分配",
|
|
||||||
"autoAssignPool": "IPv4 自动分配池",
|
|
||||||
"addIPv4Pool": "添加IPv4池",
|
|
||||||
"multicastLimit": "多播接收者限制",
|
|
||||||
"enableBroadcast": "启用广播",
|
|
||||||
"logInFailed": "无效的用户名或密码",
|
|
||||||
"tooManyAttempts": "尝试登录次数过多,请15分钟后重试。",
|
|
||||||
"language": "语言",
|
|
||||||
"notAuthorized": "您没有权限。请登录。",
|
|
||||||
"saveChanges": "保存更改",
|
|
||||||
"optional": "可选",
|
|
||||||
"destination": "目的地",
|
|
||||||
"username": "用户名",
|
|
||||||
"password": "密码",
|
|
||||||
"languageName": "中文"
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import "@fontsource/roboto";
|
import "@fontsource/roboto";
|
||||||
|
|
||||||
import { Suspense } from "react";
|
|
||||||
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||||
|
|
||||||
import Theme from "./components/Theme";
|
import Theme from "./components/Theme";
|
||||||
|
@ -9,27 +8,19 @@ 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";
|
|
||||||
|
|
||||||
import Loading from "./components/Loading";
|
|
||||||
|
|
||||||
import "./i18n";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Theme>
|
<Theme>
|
||||||
<Suspense fallback={<Loading />}>
|
|
||||||
<BrowserRouter basename="/app">
|
<BrowserRouter basename="/app">
|
||||||
<Bar />
|
<Bar />
|
||||||
<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>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</Suspense>
|
|
||||||
</Theme>
|
</Theme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ 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);
|
||||||
|
@ -43,18 +41,16 @@ 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: t("settings"),
|
// name: "Settings",
|
||||||
to: "/settings",
|
// to: "/settings",
|
||||||
},
|
// },
|
||||||
...(!disabledAuth
|
...(!disabledAuth
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: t("logOut"),
|
name: "Log out",
|
||||||
divide: true,
|
divide: true,
|
||||||
onClick: onLogOutClick,
|
onClick: onLogOutClick,
|
||||||
},
|
},
|
||||||
|
@ -119,6 +115,7 @@ function Bar() {
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
menuItem.onClick();
|
menuItem.onClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,8 +9,6 @@ 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([]);
|
||||||
|
|
||||||
|
@ -32,8 +30,6 @@ function HomeLoggedIn() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -42,19 +38,19 @@ function HomeLoggedIn() {
|
||||||
className={classes.createBtn}
|
className={classes.createBtn}
|
||||||
onClick={createNetwork}
|
onClick={createNetwork}
|
||||||
>
|
>
|
||||||
{t("createNetwork")}
|
Create A Network
|
||||||
</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">{t("controllerNetworks")}</Typography>
|
<Typography variant="h5">Controller networks</Typography>
|
||||||
{networks[0] && t("controllerAddress")}
|
{networks[0] && "Network controller address"}
|
||||||
<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>{t("network", { count: networks.length })}</Typography>
|
<Typography>Networks</Typography>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{networks[0] ? (
|
{networks[0] ? (
|
||||||
networks.map((network) => (
|
networks.map((network) => (
|
||||||
|
@ -63,7 +59,7 @@ function HomeLoggedIn() {
|
||||||
</Grid>
|
</Grid>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div>{t("createOneNetwork")}</div>
|
<div>Please create at least one network</div>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -3,8 +3,6 @@ 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() {
|
||||||
|
@ -31,8 +29,6 @@ function HomeLoggedOut() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [history, setDisableAuth, setLoggedIn, setToken]);
|
}, [history, setDisableAuth, setLoggedIn, setToken]);
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
@ -46,11 +42,14 @@ function HomeLoggedOut() {
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>{t("zerouiDesc")}</span>
|
<span>
|
||||||
|
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
|
||||||
|
self-hosted ZeroTier network controller.
|
||||||
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography>
|
<Typography>
|
||||||
<span>{t("loginToContinue")}</span>
|
<span>Please Log In to continue</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { Typography, Box, CircularProgress } from "@material-ui/core";
|
|
||||||
|
|
||||||
import useStyles from "./Loading.styles";
|
|
||||||
|
|
||||||
function Loading() {
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.root}>
|
|
||||||
<CircularProgress color="primary" />
|
|
||||||
<Typography variant="h6" component="div" className={classes.loadingText}>
|
|
||||||
Loading
|
|
||||||
<span className="loadingDots"></span>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Loading;
|
|
|
@ -1,32 +0,0 @@
|
||||||
// Loading.styles.jsx
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
root: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100vh",
|
|
||||||
},
|
|
||||||
loadingText: {
|
|
||||||
marginTop: "16px",
|
|
||||||
position: "relative",
|
|
||||||
"& .loadingDots::after": {
|
|
||||||
content: '"."',
|
|
||||||
position: "absolute",
|
|
||||||
left: "100%",
|
|
||||||
marginLeft: "4px",
|
|
||||||
animation: "$loadingDots 1s infinite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"@keyframes loadingDots": {
|
|
||||||
"0%": { content: '"."' },
|
|
||||||
"25%": { content: '".."' },
|
|
||||||
"50%": { content: '"..."' },
|
|
||||||
"75%": { content: '"...."' },
|
|
||||||
"100%": { content: '"."' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Loading.jsx";
|
|
|
@ -12,8 +12,6 @@ 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("");
|
||||||
|
@ -43,8 +41,6 @@ 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");
|
||||||
|
@ -59,12 +55,12 @@ function LogInToken() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
||||||
{t("logInToken")}
|
Token Log In
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>{t("logIn")}</DialogTitle>
|
<DialogTitle>Log In</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("advancedFeature")}</DialogContentText>
|
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
value={token}
|
value={token}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -80,10 +76,10 @@ function LogInToken() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -13,14 +13,10 @@ 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);
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
@ -69,20 +65,17 @@ function LogInUser() {
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
setPassword("");
|
setPassword("");
|
||||||
setSnackbarOpen(true);
|
setSnackbarOpen(true);
|
||||||
setError(error.response.data.error);
|
console.error(error);
|
||||||
// console.error(error.response.data.error);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>{t("logIn")}</DialogTitle>
|
<DialogTitle>Log In</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
|
@ -91,7 +84,7 @@ function LogInUser() {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label={t("username")}
|
label="username"
|
||||||
type="username"
|
type="username"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
@ -101,17 +94,17 @@ function LogInUser() {
|
||||||
setPassword(e.target.value);
|
setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label={t("password")}
|
label="password"
|
||||||
type="password"
|
type="password"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -121,7 +114,7 @@ function LogInUser() {
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "center",
|
horizontal: "center",
|
||||||
}}
|
}}
|
||||||
message={t(error)}
|
message="Invalid username or password"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,8 +18,6 @@ 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();
|
||||||
|
@ -44,12 +42,10 @@ function NetworkManagement() {
|
||||||
history.go(0);
|
history.go(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("management")}</Typography>
|
<Typography>Management</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Button
|
<Button
|
||||||
|
@ -58,19 +54,21 @@ function NetworkManagement() {
|
||||||
startIcon={<DeleteIcon />}
|
startIcon={<DeleteIcon />}
|
||||||
onClick={handleClickOpen}
|
onClick={handleClickOpen}
|
||||||
>
|
>
|
||||||
{t("deleteNetwork")}
|
Delete Network
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>{t("deleteNetworkConfirm")}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{"Are you sure you want to delete this network?"}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
<DialogContentText>This action cannot be undone.</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteNetwork} color="secondary">
|
<Button onClick={deleteNetwork} color="secondary">
|
||||||
{t("delete")}
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -21,8 +21,6 @@ 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([]);
|
||||||
|
@ -48,8 +46,6 @@ 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) => {
|
||||||
|
@ -71,7 +67,7 @@ function NetworkMembers({ network }) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
id: "auth",
|
id: "auth",
|
||||||
name: t("authorized"),
|
name: "Authorized",
|
||||||
minWidth: "80px",
|
minWidth: "80px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -83,7 +79,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "address",
|
id: "address",
|
||||||
name: t("address"),
|
name: "Address",
|
||||||
minWidth: "150px",
|
minWidth: "150px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Typography variant="body2">{row.config.address}</Typography>
|
<Typography variant="body2">{row.config.address}</Typography>
|
||||||
|
@ -91,19 +87,19 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "name",
|
id: "name",
|
||||||
name: t("name") + "/" + t("description"),
|
name: "Name / Description",
|
||||||
minWidth: "250px",
|
minWidth: "250px",
|
||||||
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ips",
|
id: "ips",
|
||||||
name: t("managedIPs"),
|
name: "Managed IPs",
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "lastSeen",
|
id: "status",
|
||||||
name: t("lastSeen"),
|
name: "Last Seen",
|
||||||
minWidth: "100px",
|
minWidth: "100px",
|
||||||
cell: (row) =>
|
cell: (row) =>
|
||||||
row.online === 1 ? (
|
row.online === 1 ? (
|
||||||
|
@ -125,7 +121,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "physicalip",
|
id: "physicalip",
|
||||||
name: t("version") + " / " + t("physIp") + " / " + t("latency"),
|
name: "Version / Physical IP / Latency",
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
cell: (row) =>
|
cell: (row) =>
|
||||||
row.online === 1 ? (
|
row.online === 1 ? (
|
||||||
|
@ -147,7 +143,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "delete",
|
id: "delete",
|
||||||
name: t("settings"),
|
name: "",
|
||||||
minWidth: "50px",
|
minWidth: "50px",
|
||||||
right: true,
|
right: true,
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
|
@ -166,7 +162,7 @@ function NetworkMembers({ network }) {
|
||||||
return (
|
return (
|
||||||
<Accordion defaultExpanded={true}>
|
<Accordion defaultExpanded={true}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("member", { count: members.length })}</Typography>
|
<Typography>Members</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
|
@ -192,7 +188,8 @@ function NetworkMembers({ network }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" style={{ padding: "10%" }}>
|
<Typography variant="h6" style={{ padding: "10%" }}>
|
||||||
{t("noDevices")} <b>{nwid}</b>.
|
No devices have joined this network. Use the app on your
|
||||||
|
devices to join <b>{nwid}</b>.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,8 +5,6 @@ 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("");
|
||||||
|
|
||||||
|
@ -26,11 +24,9 @@ function AddMember({ nwid, callback }) {
|
||||||
setMember("");
|
setMember("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>{t("addMemberManually")}</Typography>
|
<Typography>Manually Add Member</Typography>
|
||||||
<List
|
<List
|
||||||
disablePadding={true}
|
disablePadding={true}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -12,10 +12,8 @@ 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 = () => {
|
||||||
|
@ -39,16 +37,18 @@ 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>{t("deleteMemberConfirm")}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{"Are you sure you want to delete this member?"}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
<DialogContentText>This action cannot be undone.</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteMemberReq} color="secondary">
|
<Button onClick={deleteMemberReq} color="secondary">
|
||||||
{t("delete")}
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
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={t("name")}
|
label="Name"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -17,7 +15,7 @@ function MemberName({ member, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={member.description}
|
value={member.description}
|
||||||
onChange={handleChange(member, "description")}
|
onChange={handleChange(member, "description")}
|
||||||
label={t("description")}
|
label="Description"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
|
|
@ -13,10 +13,7 @@ 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 = () => {
|
||||||
|
@ -33,9 +30,7 @@ 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>
|
<DialogTitle>{"Member " + member.config.id + " settings"}</DialogTitle>
|
||||||
{t("member") + member.config.id + t("settings")}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -48,7 +43,7 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>{t("allowBridging")}</span>
|
<span>Allow Ethernet Bridging</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -61,17 +56,17 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>{t("noAutoIP")}</span>
|
<span>Do Not Auto-Assign IPs</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6">{t("capabilities")}</Typography>
|
<Typography variant="h6">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
|
||||||
? t("noCapDef")
|
? "No capabilities defined"
|
||||||
: ""}
|
: ""}
|
||||||
{Object.entries(network["capabilitiesByName"] || []).map(
|
{Object.entries(network["capabilitiesByName"] || []).map(
|
||||||
([capName, capId]) => (
|
([capName, capId]) => (
|
||||||
|
@ -101,11 +96,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">{t("tags")}</Typography>
|
<Typography variant="h6">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 }}>{t("noTagDef")}</Paper>
|
<Paper style={{ padding: 20 }}>No tags defined</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
@ -17,11 +17,7 @@ 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 } 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],
|
||||||
|
@ -91,12 +87,12 @@ function NetworkRules({ network, callback }) {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("flowRules")}</Typography>
|
<Typography>Flow Rules</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 Flow Rules
|
No need to update on every user character input
|
||||||
*/}
|
*/}
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={network["rulesSource"]}
|
value={network["rulesSource"]}
|
||||||
|
@ -134,7 +130,7 @@ function NetworkRules({ network, callback }) {
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="contained" color="primary" onClick={saveChanges}>
|
<Button variant="contained" color="primary" onClick={saveChanges}>
|
||||||
{t("saveChanges")}
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -17,10 +17,7 @@ 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);
|
||||||
|
@ -46,12 +43,12 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("generalSettings")}</Typography>
|
<Typography>General settings</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>{t("networkId")}</Typography>
|
<Typography>Network ID</Typography>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>{network["config"]["id"]}</span>
|
<span>{network["config"]["id"]}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -60,7 +57,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={network["config"]["name"]}
|
value={network["config"]["name"]}
|
||||||
onChange={handleChange("config", "name")}
|
onChange={handleChange("config", "name")}
|
||||||
label={t("name")}
|
label="Name"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -74,7 +71,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
multiline
|
multiline
|
||||||
minRows={2}
|
minRows={2}
|
||||||
maxRows={Infinity}
|
maxRows={Infinity}
|
||||||
label={t("description")}
|
label="Description"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -83,14 +80,14 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>{t("accessControl")}</Typography>
|
<Typography>Access Control</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={1}>{t("private")}</option>
|
<option value={true}>Private</option>
|
||||||
<option value={0}>{t("public")}</option>
|
<option value={false}>Public</option>
|
||||||
</Select>
|
</Select>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -114,7 +111,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<TextField
|
<TextField
|
||||||
label={t("multicastLimit")}
|
label="Multicast Recipient Limit"
|
||||||
type="number"
|
type="number"
|
||||||
value={network["config"]["multicastLimit"]}
|
value={network["config"]["multicastLimit"]}
|
||||||
onChange={handleChange("config", "multicastLimit", "json")}
|
onChange={handleChange("config", "multicastLimit", "json")}
|
||||||
|
@ -129,7 +126,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
color="primary"
|
color="primary"
|
||||||
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
||||||
/>
|
/>
|
||||||
<span>{t("enableBroadcast")}</span>
|
<span>Enable Broadcast</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* TODO: */}
|
{/* TODO: */}
|
||||||
{/* <Grid item>
|
{/* <Grid item>
|
||||||
|
|
|
@ -18,10 +18,7 @@ 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("");
|
||||||
|
|
||||||
|
@ -92,19 +89,19 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Start",
|
id: "Start",
|
||||||
name: t("start"),
|
name: "Start",
|
||||||
cell: (row) => row["ipRangeStart"],
|
cell: (row) => row["ipRangeStart"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "End",
|
id: "End",
|
||||||
name: t("end"),
|
name: "End",
|
||||||
cell: (row) => row["ipRangeEnd"],
|
cell: (row) => row["ipRangeEnd"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>{t("ipv4AutoAssign")}</Typography>
|
<Typography>IPv4 Auto-Assign</Typography>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "30px",
|
padding: "30px",
|
||||||
|
@ -125,7 +122,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
{t("autoAssignPool")}
|
Auto-Assign Pools
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box border={1} borderColor="grey.300">
|
<Box border={1} borderColor="grey.300">
|
||||||
<Grid item style={{ margin: "10px" }}>
|
<Grid item style={{ margin: "10px" }}>
|
||||||
|
@ -135,7 +132,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
data={ipAssignmentPools}
|
data={ipAssignmentPools}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography>{t("addIPv4Pool")}</Typography>
|
<Typography>Add IPv4 Pool</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -145,7 +142,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={start}
|
value={start}
|
||||||
onChange={handleStartInput}
|
onChange={handleStartInput}
|
||||||
placeholder={t("start")}
|
placeholder={"Start"}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -157,7 +154,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={end}
|
value={end}
|
||||||
onChange={handleEndInput}
|
onChange={handleEndInput}
|
||||||
placeholder={t("end")}
|
placeholder={"End"}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -16,10 +16,7 @@ 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("");
|
||||||
|
|
||||||
|
@ -74,12 +71,12 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "target",
|
id: "target",
|
||||||
name: t("target"),
|
name: "Target",
|
||||||
cell: (row) => row["target"],
|
cell: (row) => row["target"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "via",
|
id: "via",
|
||||||
name: t("via"),
|
name: "via",
|
||||||
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -87,13 +84,13 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
{t("managedRoutes")} ({routes.length + "/128"})
|
Managed Routes ({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>{t("addRoute")}</Typography>
|
<Typography>Add Routes</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -103,7 +100,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={destination}
|
value={destination}
|
||||||
onChange={handleDestinationInput}
|
onChange={handleDestinationInput}
|
||||||
placeholder={t("destination") + " (CIDR)"}
|
placeholder={"Destination (CIDR)"}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -115,7 +112,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={via}
|
value={via}
|
||||||
onChange={handleViaInput}
|
onChange={handleViaInput}
|
||||||
placeholder={t("via") + " (" + t("optional") + ")"}
|
placeholder={"Via (Optional)"}
|
||||||
/>
|
/>
|
||||||
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
||||||
<AddIcon
|
<AddIcon
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionSummary,
|
|
||||||
AccordionDetails,
|
|
||||||
Grid,
|
|
||||||
Typography,
|
|
||||||
Select,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import localesList from "generated/localesList.json";
|
|
||||||
|
|
||||||
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()}>
|
|
||||||
{localesList.map((locale) => (
|
|
||||||
<option key={locale.code} value={locale.code}>
|
|
||||||
{locale.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Grid>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Settings;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Settings";
|
|
|
@ -1,18 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"code": "en",
|
|
||||||
"name": "English"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "es-ES",
|
|
||||||
"name": "Español"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ru-RU",
|
|
||||||
"name": "Русский"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "zh_CN",
|
|
||||||
"name": "中文"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,35 +0,0 @@
|
||||||
import i18n from "i18next";
|
|
||||||
import languageDetector from "i18next-browser-languagedetector";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import Backend from "i18next-http-backend";
|
|
||||||
|
|
||||||
import localesList from "./utils/localesList.json";
|
|
||||||
const supportedLngs = localesList.map((locale) => locale.code);
|
|
||||||
|
|
||||||
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,
|
|
||||||
backend: {
|
|
||||||
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
|
||||||
},
|
|
||||||
ns: ["common"],
|
|
||||||
defaultNS: "common",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
|
@ -1,17 +1,8 @@
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
font-family:
|
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen",
|
||||||
"Roboto",
|
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
"Oxygen",
|
|
||||||
"Ubuntu",
|
|
||||||
"Cantarell",
|
|
||||||
"Fira Sans",
|
|
||||||
"Droid Sans",
|
|
||||||
"Helvetica Neue",
|
|
||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
|
@ -11,10 +11,7 @@ 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,12 +39,10 @@ function Network() {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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>
|
||||||
{t("network", { count: 2 })}
|
Networks
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
{network["config"] && (
|
{network["config"] && (
|
||||||
<>
|
<>
|
||||||
|
@ -76,7 +71,9 @@ function Network() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">{t("notAuthorized")}</Typography>
|
<Typography variant="h5">
|
||||||
|
You are not authorized. Please Log In
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,11 +5,7 @@ const useStyles = makeStyles((theme) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
margin: "3%",
|
margin: "1%",
|
||||||
},
|
|
||||||
breadcrumbs: {
|
|
||||||
paddingTop: "2%",
|
|
||||||
paddingLeft: "2%",
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
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;
|
|
|
@ -1,16 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
export { default } from "./Settings";
|
|
|
@ -1,14 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"code": "en",
|
|
||||||
"name": "English"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "es-ES",
|
|
||||||
"name": "Español"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ru-RU",
|
|
||||||
"name": "Русский"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -5,8 +5,7 @@
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve"
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import * as url from "url";
|
|
||||||
|
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
||||||
|
|
||||||
export default function generateLocalesPlugin() {
|
|
||||||
return {
|
|
||||||
name: "generate-locales",
|
|
||||||
buildStart() {
|
|
||||||
const localesDir = path.resolve(__dirname, "public", "locales");
|
|
||||||
|
|
||||||
if (fs.existsSync(localesDir)) {
|
|
||||||
const localesList = fs
|
|
||||||
.readdirSync(localesDir)
|
|
||||||
.filter((file) => {
|
|
||||||
return fs.statSync(path.join(localesDir, file)).isDirectory();
|
|
||||||
})
|
|
||||||
.map((locale) => {
|
|
||||||
const commonFilePath = path.join(localesDir, locale, "common.json");
|
|
||||||
if (fs.existsSync(commonFilePath)) {
|
|
||||||
const commonFile = JSON.parse(
|
|
||||||
fs.readFileSync(commonFilePath, "utf-8")
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
code: locale,
|
|
||||||
name: commonFile.languageName || locale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
code: locale,
|
|
||||||
name: locale,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save the array to a JSON file
|
|
||||||
const outputPath = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"src",
|
|
||||||
"generated",
|
|
||||||
"localesList.json"
|
|
||||||
);
|
|
||||||
fs.writeFileSync(outputPath, JSON.stringify(localesList, null, 2));
|
|
||||||
|
|
||||||
console.log(`Locales list saved to ${outputPath}`);
|
|
||||||
} else {
|
|
||||||
console.error("Locales directory not found.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
27
frontend/vite.config.js
Normal file
27
frontend/vite.config.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
base: "/app",
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
strictPort: true,
|
||||||
|
proxy: {
|
||||||
|
"/auth": "http://127.0.0.1:4000",
|
||||||
|
"/api": "http://127.0.0.1:4000",
|
||||||
|
"/controller": "http://127.0.0.1:4000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
components: "/src/components",
|
||||||
|
utils: "/src/utils",
|
||||||
|
external: "/src/external",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "build",
|
||||||
|
chunkSizeWarningLimit: 1000,
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
|
@ -1,45 +0,0 @@
|
||||||
import process from "node:process";
|
|
||||||
import { defineConfig, searchForWorkspaceRoot } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
|
||||||
import generateLocalesPlugin from "./vite-plugin-generate-locales.js";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
base: "/app",
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
strictPort: true,
|
|
||||||
proxy: {
|
|
||||||
"/auth": "http://127.0.0.1:4000",
|
|
||||||
"/api": "http://127.0.0.1:4000",
|
|
||||||
"/controller": "http://127.0.0.1:4000",
|
|
||||||
},
|
|
||||||
fs: {
|
|
||||||
allow: [searchForWorkspaceRoot(process.cwd()), "../node_modules"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
components: "/src/components",
|
|
||||||
utils: "/src/utils",
|
|
||||||
external: "/src/external",
|
|
||||||
generated: "/src/generated",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
outDir: "build",
|
|
||||||
chunkSizeWarningLimit: 1000,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
react(),
|
|
||||||
generateLocalesPlugin(),
|
|
||||||
viteStaticCopy({
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
src: "public/locales",
|
|
||||||
dest: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
|
@ -10,14 +10,12 @@
|
||||||
"upgrade:deps": "yarn upgrade-interactive",
|
"upgrade:deps": "yarn upgrade-interactive",
|
||||||
"upgrade:yarn": "yarn set version latest",
|
"upgrade:yarn": "yarn set version latest",
|
||||||
"clean:deps": "cd frontend && rimraf node_modules && cd ../backend && rimraf node_modules && cd .. && rimraf node_modules",
|
"clean:deps": "cd frontend && rimraf node_modules && cd ../backend && rimraf node_modules && cd .. && rimraf node_modules",
|
||||||
"format": "yarn prettier --check .",
|
"format": "yarn prettier",
|
||||||
"format:fix": "yarn prettier --write .",
|
"format:fix": "yarn prettier --write .",
|
||||||
"lint": "yarn workspaces foreach --all --parallel run lint",
|
"lint": "yarn workspaces foreach --all --parallel run lint",
|
||||||
"dev": "concurrently \"cd frontend && cross-env FAST_REFRESH=true yarn start\" \"cd backend && cross-env NODE_ENV=development ZU_DEFAULT_USERNAME=admin ZU_DEFAULT_PASSWORD=zero-ui nodemon ./bin/www --ignore data/db.json\"",
|
"dev": "concurrently \"cd frontend && cross-env FAST_REFRESH=true yarn start\" \"cd backend && cross-env NODE_ENV=development ZU_DEFAULT_USERNAME=admin ZU_DEFAULT_PASSWORD=zero-ui nodemon ./bin/www --ignore data/db.json\"",
|
||||||
"build": "cd frontend && cross-env GENERATE_SOURCEMAP=false yarn build",
|
"build": "cd frontend && cross-env GENERATE_SOURCEMAP=false yarn build",
|
||||||
"prod": "cd backend && cross-env NODE_ENV=production ZU_SECURE_HEADERS=false yarn start",
|
"prod": "cd backend && cross-env NODE_ENV=production ZU_SECURE_HEADERS=false yarn start",
|
||||||
"docker:build": "docker build . -t dec0dos/zero-ui -f docker/zero-ui/Dockerfile --progress=plain",
|
|
||||||
"docker:run": "docker run --rm --env-file .env -e ZU_CONTROLLER_ENDPOINT=http://host.docker.internal:9993 -p 4000:4000 --name zero-ui dec0dos/zero-ui",
|
|
||||||
"release": "standard-version && git push --follow-tags origin main && git add CHANGELOG.md",
|
"release": "standard-version && git push --follow-tags origin main && git add CHANGELOG.md",
|
||||||
"commit": "yarn git-cz"
|
"commit": "yarn git-cz"
|
||||||
},
|
},
|
||||||
|
@ -56,5 +54,5 @@
|
||||||
"yarn prettier --write"
|
"yarn prettier --write"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.3.1"
|
"packageManager": "yarn@4.0.0-rc.53"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue