diff --git a/client/src/pages/Home/components/LatestTest/utils/dialogs.jsx b/client/src/pages/Home/components/LatestTest/utils/dialogs.jsx
index 30208561..589dd3cd 100644
--- a/client/src/pages/Home/components/LatestTest/utils/dialogs.jsx
+++ b/client/src/pages/Home/components/LatestTest/utils/dialogs.jsx
@@ -9,8 +9,8 @@ export const uploadInfo = () => ({title: t("info.up.title"), description: t("inf
export const latestTestInfo = (latest) => ({
title: t("info.latest.title"),
- description:
}} values={{date: new Date(latest.created).toLocaleDateString(),
+ description: latest.created ?
}} values={{date: new Date(latest.created).toLocaleDateString(),
time: new Date(latest.created).toLocaleTimeString(undefined, {hour: "2-digit", minute: "2-digit"})}}>
- info.latest.description,
+ info.latest.description : t("test.no_latest"),
buttonText: t("dialog.okay")
});
\ No newline at end of file
diff --git a/client/src/pages/Nodes/Nodes.jsx b/client/src/pages/Nodes/Nodes.jsx
index 1c0a4ac2..86467a43 100644
--- a/client/src/pages/Nodes/Nodes.jsx
+++ b/client/src/pages/Nodes/Nodes.jsx
@@ -19,7 +19,7 @@ export const Nodes = (props) => {
{createDialogOpen &&
setCreateDialogOpen(false)}/>}
-
{nodes.map(node => )}
diff --git a/client/src/pages/Nodes/styles.sass b/client/src/pages/Nodes/styles.sass
index 5481532d..6e74d17e 100644
--- a/client/src/pages/Nodes/styles.sass
+++ b/client/src/pages/Nodes/styles.sass
@@ -25,6 +25,7 @@
border: 2px dashed #696C73
border-radius: 15px
cursor: pointer
+ user-select: none
.node-add h1
font-size: 24pt
diff --git a/package-lock.json b/package-lock.json
index d48bec63..62b345d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "myspeed",
- "version": "1.0.6",
+ "version": "1.0.7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "myspeed",
- "version": "1.0.6",
+ "version": "1.0.7",
"dependencies": {
"axios": "^1.3.5",
"bcrypt": "^5.1.0",
diff --git a/package.json b/package.json
index 467bb159..0d98a175 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "myspeed",
- "version": "1.0.6",
+ "version": "1.0.7",
"scripts": {
"client": "cd client && npm run dev",
"server": "nodemon server",
diff --git a/server/controller/node.js b/server/controller/node.js
index 56a880fb..b5726194 100644
--- a/server/controller/node.js
+++ b/server/controller/node.js
@@ -1,8 +1,9 @@
+const axios = require('axios');
const nodes = require('../models/Node');
// Gets all node entries
-module.exports.list = async (excludePassword) => {
- return await nodes.findAll({attributes: {exclude: excludePassword ? ['password'] : []}});
+module.exports.list = async () => {
+ return await nodes.findAll().then((result) => result.map((node) => ({...node, password: node.password !== null})));
}
// Create a new node entry
@@ -28,4 +29,37 @@ module.exports.updateName = async (nodeId, name) => {
// Update the password of the node entry
module.exports.updatePassword = async (nodeId, password) => {
return await nodes.update({password: password}, {where: {id: nodeId}});
+}
+
+module.exports.checkNode = async (url, password) => {
+ if (password === "none") password = undefined;
+ const api = await axios.get(url + "/api/config", {headers: {password: password}}).catch(() => {
+ return "INVALID_URL";
+ });
+
+ if (api === "INVALID_URL" || api.status !== 200) return "INVALID_URL";
+
+ if (!api.data.ping) return "INVALID_URL";
+
+ if (api.data.viewMode) return "PASSWORD_REQUIRED";
+
+ return "NODE_VALID";
+}
+
+module.exports.proxyRequest = async (url, req, res) => {
+ const response = await axios(url, {
+ method: req.method,
+ headers: req.headers,
+ data: req.method === "GET" ? undefined : JSON.stringify(req.body),
+ signal: req.signal,
+ validateStatus: (status) => status >= 200 && status < 400
+ }).catch(() => "INVALID_URL");
+
+ if (response === "INVALID_URL")
+ return res.status(500).json({message: "Internal server error"});
+
+ if (response.headers["content-disposition"])
+ res.setHeader("content-disposition", response.headers["content-disposition"]);
+
+ res.status(response.status).json(response.data);
}
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index f005e99f..5dfb8d45 100755
--- a/server/index.js
+++ b/server/index.js
@@ -4,6 +4,9 @@ const timerTask = require('./tasks/timer');
const healthCheckTask = require('./tasks/healthchecks');
const app = express();
+
+app.disable('x-powered-by');
+
const port = process.env.port || 5216;
// Create the data folder and the servers file
diff --git a/server/routes/export.js b/server/routes/export.js
index beacbea3..32268e6b 100644
--- a/server/routes/export.js
+++ b/server/routes/export.js
@@ -10,6 +10,8 @@ app.get("/json", password(false), async (req, res) => {
app.get("/csv", password(false), async (req, res) => {
res.set({"Content-Disposition": "attachment; filename=\"speedtests.csv\""});
let list = await tests.list();
+
+ if (list.length === 0) return res.send("");
let fields = Object.keys(list[0]);
let replacer = (key, value) => value === null ? '' : value;
diff --git a/server/routes/nodes.js b/server/routes/nodes.js
index 42487df1..b3b89172 100644
--- a/server/routes/nodes.js
+++ b/server/routes/nodes.js
@@ -1,10 +1,11 @@
const app = require('express').Router();
const nodes = require('../controller/node');
const password = require("../middlewares/password");
+const {checkNode, proxyRequest} = require("../controller/node");
// List all nodes
app.get("/", password(false), async (req, res) => {
- return res.json(await nodes.list(true));
+ return res.json(await nodes.list());
});
// Create a node
@@ -13,18 +14,14 @@ app.put("/", password(false), async (req, res) => {
const url = req.body.url.replace(/\/+$/, "");
- const headers = req.body.password ? {password: req.body.password} : {};
-
- fetch(url + "/api/config", {headers}).then(async api => {
- if (api.status !== 200)
+ checkNode(url, req.body.password).then(async (result) => {
+ if (result === "INVALID_URL")
return res.status(400).json({message: "Invalid URL", type: "INVALID_URL"});
- if ((await api.json()).viewMode)
+ if (result === "PASSWORD_REQUIRED")
return res.status(400).json({message: "Invalid password", type: "PASSWORD_REQUIRED"});
res.json({id: (await nodes.create(req.body.name, url, req.body.password)).id, type: "NODE_CREATED"});
- }).catch(async () => {
- res.status(400).json({message: "Invalid URL", type: "INVALID_URL"});
});
});
@@ -55,17 +52,15 @@ app.patch("/:nodeId/password", password(false), async (req, res) => {
const node = await nodes.get(req.params.nodeId);
if (node === null) return res.status(404).json({message: "Node not found"});
- fetch(node.url + "/api/config", {headers: {password: req.body.password}}).then(async api => {
- if (api.status !== 200)
+ checkNode(node.url, req.body.password).then(async (result) => {
+ if (result === "INVALID_URL")
return res.status(400).json({message: "Invalid URL", type: "INVALID_URL"});
- if ((await api.json()).viewMode)
+ if (result === "PASSWORD_REQUIRED")
return res.status(400).json({message: "Invalid password", type: "PASSWORD_REQUIRED"});
- await nodes.updatePassword(req.params.nodeId, req.body.password);
+ await nodes.updatePassword(req.params.nodeId, req.body.password === "none" ? null : req.body.password);
res.json({message: "Node password successfully updated", type: "PASSWORD_UPDATED"});
- }).catch(async () => {
- res.status(400).json({message: "Invalid URL", type: "INVALID_URL"});
});
});
@@ -79,19 +74,7 @@ app.all("/:nodeId/*", password(false), async (req, res) => {
req.headers['password'] = node.password;
delete req.headers['host'];
- fetch(url, {
- method: req.method,
- headers: req.headers,
- body: req.method === "GET" ? undefined : JSON.stringify(req.body),
- signal: req.signal
- }).then(async api => {
- if (api.headers.get("content-disposition"))
- res.setHeader("content-disposition", api.headers.get("content-disposition"));
-
- res.status(api.status).json(await api.json());
- }).catch(() => {
- res.status(500).json({message: "Internal server error"});
- });
+ await proxyRequest(url, req, res);
});
module.exports = app;
\ No newline at end of file