From e6c47ff8724cfb9b62f5c33e7ee35237d92b8abc Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 15:40:35 +0200 Subject: [PATCH 01/68] Made the chooser dialog wrap in the ViewDialog styles.sass --- client/src/common/components/ViewDialog/styles.sass | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/common/components/ViewDialog/styles.sass b/client/src/common/components/ViewDialog/styles.sass index 5bf55062..f44bfaf1 100644 --- a/client/src/common/components/ViewDialog/styles.sass +++ b/client/src/common/components/ViewDialog/styles.sass @@ -3,6 +3,7 @@ .chooser-dialog display: flex justify-content: center + flex-wrap: wrap align-items: center padding: 2rem 3.5rem gap: 1rem From 0a1858be403950cec723c2371bc9d4ab903beae1 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 15:43:56 +0200 Subject: [PATCH 02/68] Uploaded a node view placeholder image --- .../components/ViewDialog/images/node.png | Bin 0 -> 13652 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/src/common/components/ViewDialog/images/node.png diff --git a/client/src/common/components/ViewDialog/images/node.png b/client/src/common/components/ViewDialog/images/node.png new file mode 100644 index 0000000000000000000000000000000000000000..a1ec71802b684813e23fe20fc592dfa1bcd56344 GIT binary patch literal 13652 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 Date: Sat, 1 Apr 2023 15:44:10 +0200 Subject: [PATCH 03/68] Implemented the node view into the ViewDialog.jsx --- client/src/common/components/ViewDialog/ViewDialog.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/common/components/ViewDialog/ViewDialog.jsx b/client/src/common/components/ViewDialog/ViewDialog.jsx index b17aa6e9..d50b3b0e 100644 --- a/client/src/common/components/ViewDialog/ViewDialog.jsx +++ b/client/src/common/components/ViewDialog/ViewDialog.jsx @@ -5,6 +5,7 @@ import React, {useContext, useState} from "react"; import {ViewContext} from "@/common/contexts/View"; import ListImage from "./images/list.png"; import StatisticImage from "./images/statistic.png"; +import NodeImage from "./images/node.png"; import {t} from "i18next"; import "./styles.sass"; import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; @@ -38,6 +39,11 @@ export const Dialog = () => { className={"dialog-thumbnail" + (selected === 1 ? " thumbnail-selected" : "")}/>

{t("test.views.statistic")}

+
setSelected(2)}> + {t("test.views.node")} +

{t("test.views.node")}

+
From 45fe1419e996bd869bd727ddc69ee258f774b499 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 15:44:30 +0200 Subject: [PATCH 04/68] Created the Nodes page --- client/src/pages/Nodes/Nodes.jsx | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 client/src/pages/Nodes/Nodes.jsx diff --git a/client/src/pages/Nodes/Nodes.jsx b/client/src/pages/Nodes/Nodes.jsx new file mode 100644 index 00000000..e4f28804 --- /dev/null +++ b/client/src/pages/Nodes/Nodes.jsx @@ -0,0 +1,3 @@ +export const Nodes = () => { + +} \ No newline at end of file From b73670d73d8bcc3a5ce6701f316c352b0f2edc1c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 15:44:48 +0200 Subject: [PATCH 05/68] Created the Nodes page index --- client/src/pages/Nodes/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/src/pages/Nodes/index.js diff --git a/client/src/pages/Nodes/index.js b/client/src/pages/Nodes/index.js new file mode 100644 index 00000000..2cdb6a05 --- /dev/null +++ b/client/src/pages/Nodes/index.js @@ -0,0 +1 @@ +export {Nodes as default} from './Nodes'; \ No newline at end of file From 853351fb6d25df3671b78f4ced5c90e8705ec62c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 15:45:31 +0200 Subject: [PATCH 06/68] Implemented the Nodes view into the App.jsx --- client/src/App.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index 08749b3b..1eef9727 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -14,6 +14,7 @@ import {ViewContext, ViewProvider} from "@/common/contexts/View"; import Statistics from "@/pages/Statistics"; import {t} from "i18next"; import {ToastNotificationProvider} from "@/common/contexts/ToastNotification"; +import Nodes from "@/pages/Nodes"; const MainContent = () => { const [view] = useContext(ViewContext); @@ -21,7 +22,8 @@ const MainContent = () => {
{view === 0 && } {view === 1 && } - {view !== 0 && view !== 1 && } + {view === 2 && } + {view !== 0 && view !== 1 && view !== 2 && }
); } From cf5ae01dc3a1f1a78699724139a63a10b894231c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 19:45:35 +0200 Subject: [PATCH 07/68] Implemented the start & limit parameters in the speedtest controller --- server/controller/speedtests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/controller/speedtests.js b/server/controller/speedtests.js index cf93cbb4..5ffe9634 100644 --- a/server/controller/speedtests.js +++ b/server/controller/speedtests.js @@ -15,8 +15,10 @@ module.exports.get = async (id) => { } // Lists all speedtests from the database -module.exports.list = async (hours = 24) => { - let dbEntries = (await tests.findAll({order: [["created", "DESC"]]})) +module.exports.list = async (hours = 24, start, limit) => { + const whereClause = start ? {id: {[Op.lt]: start}} : undefined; + + let dbEntries = (await tests.findAll({where: whereClause, order: [["created", "DESC"]], limit})) .filter((entry) => new Date(entry.created) > new Date().getTime() - hours * 3600000); for (let dbEntry of dbEntries) From 50a5293d9f56085861ab02b54dc192ca1daa131c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 19:46:09 +0200 Subject: [PATCH 08/68] Integrated the new start & limit parameters into the GET /api/speedtests route --- server/routes/speedtests.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/routes/speedtests.js b/server/routes/speedtests.js index e81f8347..2ee053d0 100644 --- a/server/routes/speedtests.js +++ b/server/routes/speedtests.js @@ -7,7 +7,10 @@ const password = require('../middlewares/password'); // List all speedtests app.get("/", password(true), async (req, res) => { - res.json(await tests.list(req.query.hours || 24)); + if (req.query.limit && isNaN(req.query.limit)) + return res.status(400).json({message: "You need to provide a correct number in the limit parameter"}); + + res.json(await tests.list(req.query.hours || 24, req.query.start, req.query.limit)); }); // List all speedtests by average From 1be6a879db3c0fadd6493b3cee506123f615ad36 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Sat, 1 Apr 2023 19:52:20 +0200 Subject: [PATCH 09/68] Reverted the changes in the view dialog --- client/src/App.jsx | 3 +-- .../common/components/ViewDialog/ViewDialog.jsx | 6 ------ .../components/ViewDialog/images/node.png | Bin 13652 -> 0 bytes 3 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 client/src/common/components/ViewDialog/images/node.png diff --git a/client/src/App.jsx b/client/src/App.jsx index 1eef9727..85fb9b3e 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -22,8 +22,7 @@ const MainContent = () => {
{view === 0 && } {view === 1 && } - {view === 2 && } - {view !== 0 && view !== 1 && view !== 2 && } + {view !== 0 && view !== 1 && }
); } diff --git a/client/src/common/components/ViewDialog/ViewDialog.jsx b/client/src/common/components/ViewDialog/ViewDialog.jsx index d50b3b0e..b17aa6e9 100644 --- a/client/src/common/components/ViewDialog/ViewDialog.jsx +++ b/client/src/common/components/ViewDialog/ViewDialog.jsx @@ -5,7 +5,6 @@ import React, {useContext, useState} from "react"; import {ViewContext} from "@/common/contexts/View"; import ListImage from "./images/list.png"; import StatisticImage from "./images/statistic.png"; -import NodeImage from "./images/node.png"; import {t} from "i18next"; import "./styles.sass"; import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; @@ -39,11 +38,6 @@ export const Dialog = () => { className={"dialog-thumbnail" + (selected === 1 ? " thumbnail-selected" : "")}/>

{t("test.views.statistic")}

-
setSelected(2)}> - {t("test.views.node")} -

{t("test.views.node")}

-
diff --git a/client/src/common/components/ViewDialog/images/node.png b/client/src/common/components/ViewDialog/images/node.png deleted file mode 100644 index a1ec71802b684813e23fe20fc592dfa1bcd56344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13652 zcmeI2X;hQfy2oEcr7A*gtx!gLv?wZ|f`Eh&tVKkDsEk5LsDMmDkRc=?A>48*PC#X{ zfDn#|$RNlV!W4xIlt2iBh?0as4hbMLBoH8QcG~M&z4zSD_rqP;Uw9X5z0cnJ+5hMF z{P(+Z?~1+k-rZ`uAqd)QbMeP(5Tpn{f)Ij)uk<>Gzt_G zL+RI+K0vS82QB5L4_|&K`zym^mq;YU?GRM?MK=Ud z9-uNd4(;mxMotZiG5In_4%$3GHxgTam2-TX5d>kS-OVGjRS0-aVt9!PpW|DHi0l%CfSu`g&9}k&YwD7Znx3V6Y=cjzmUA3VkGsJ7aTm zb6>uExzIW0-x0|j9yXw@tggCZx|?Tag6Mgted!IS-P0><(R?n41Kw1RzaZT4we7C% z@mA(c5EffnT>KDQw{e%nVl8?wPoLrAe<@FBYisjKIxYD4@na@-J*Wuf{0g;N{$)#? zvNrP=m&>hxD1HPpCfT{3;)F5rU%4qQkAW22sngLkZ?rKMR=Ho1uN zkJX0;2Mc}0LrqOh3kwUYZ|qHUEg!=$Xms$VL!SA4mmS^ZUxIW9PYfTYk;B8^9(mh{ zOYL|$J3DJ^P%u3+6R$nM*|v;Cr5Iou>rvODO0%{loPN!7*d9nV` z3?x}yU0oP!C<=;F)eK}zOlT% zo(pnXU}&kWrDcqG;(hye8M|wFvEgRxhv#{DdFd2?eE%^F3YFp>lrRSl@oKPeuiTnI z{}RknTU*;iXSHi^(2Wd-AjZ#9u_aW>FAoi&my##JnLD!IcO&c?r{auRY&H@Rd&5+t zi#x+{)=%%v>2M%A^=M_LcW~s^oX$r0`}?n1Of2R+pL;gzm>ohdBbMCREF$4>?=;Tx zT3cILEISZW&aCXHQ;hzK;W+j%P>)auK^wcJ`h{JyVoy=lR~N@G!zWGPecpyk4R7AP zbB2$ZBqk<;{R($TZeg(lePlz)_ItW~fdJGSoM+PCOeJ_;<8gpn5 z9y}nnVu>Z8;*SnfLHPkkHkY9+*AzaM55$x*oOilCh2aW8^NhoPB;INHmN7FmHI+?8 zP$X-@(b1ob)I2a4kkACouJ?`>>6HuB$cz()G`2UW6xi9bbTAkU3dwmhmupFS)5v^EMx2J+ywu>)l^a7csXvH9z<624xU1kMbG}<@gLPs)QLXYNwQG{L_NGVZ& zDEX;ib2HjEsjjXra85qUap(H8cCcZSw*8>aggJ9n29w-h;2KICdOf%IS)^iiR+iJ4 ziLr6vsob6yY8AD$W|H~wP;CGA$&H=@w@#o3GtNIwTv@jibd8;6CYRnx^@nK>zV|%o z=Q3#H-n3pEy1vud*;ywm>-6N42^+>uy49p&cU)YYutv~a^(hg8R;{F*ihO2B#OYM4 zC@U*F#b*DJ>Eh>uJl;#|@PaP)3G@54lE#`Yb-#ef-T)3(7|&!SUdZLg*t@v6u=tWHvuXF9#XS1b}$YYh-}vXtp=<6w-2 zxGD#zD(;*?XxZ-Wrd#OG4B0&*s$B*B7@|3Q1}`(p5}Slp$yYF6|aWk8u3V@RL7` zkBsCHoa&-1gcp;M?1*v_`ldSuLk<7^ygR*}-#J4??*-(hHjay0K79B9=!lDqMB3Yz zl$K@+B2u=3y6808Kuz!+aPa)9EcFIXU=Mw)<{Hn5@UTB->fkgGe!%Pf{Z-@bZx0cR zLkUSNxf$4#XJ$K}8CqJa|*t|exeC*Wpr0C9v$qe-f*zu6616ftHna#~cLjTlMz?q!EK95GLGoJSY7N7DK z!2X5K&0Va;tU`sMZ*y`VT-z}rs@MUj{dp#l`IDcD&B!(6* zYwq|g5dfR7$U0xv2Bu#Up(EtS`I!T_Kj*LAx(^7nXBTMma~HpXa}-Y4metj9>Z>Ku z)#?7_p%e-wG;|SkgMhwS+Zm{#tgJ_)n=&x7zu$c?iYV;`}~Z|K##oh)&;0=YUHkH;UM;;&mNIF3Wai90?D;+NCy47|0)k_+EPROWu&L) z>KV_qW6E*Od`30tKLan6ecN z1b|t*?{GMrfY<6LPMk0jY@FN5gghEvF6@=7C3XxiEZmlc-|~zJF7cAamHL=@*yjy^ zY9#)rH)!Cjz~OmfZN5OS7OsBd+v>)~Mg{{jD_&eI7b8K|==2X7^9~6kFOCt`^?pVi z-Ir7J^~vasFA_wj_V>An6%t4MvS0N`)hDIoM#5(`ln&~mtcEf(GXq{P8(lZIumI&` z()JLvLBQA9>1hp3&8+NfG#c&f>j!qkU&X}?0Vyi2>Kc4koFMe03~Q%+(QW0$9B7aa z0gbElY_oFX-$Q0o+v<&w@BcUyshx|q{@naT54PvO!0FQ>k!;&!fk42L3u8+vxxJki z2xKy^zECr#Xzbit8&|h*WV0{K`zWZla8l6IpzJ2|fz(A%OZ|5_Sj>}W#Mm4Eo-Hj6 z4RG8wbhIXqrO}==LT%wjr^>!OrrH(UL)plL*A{QuxSJ(@l0mO^x5CLwbc`Fv^S- zVv0hewIXLKDk^ZTZ(TmCpLdRut- zRjGD{AAL4az_l%V{cN4v1MJmOl1-GG@ALL!n$?()0b@X>a#YHCK^q*dsjcnOY<8UB z01I--EH5Gthe>L2>)%CIzr7NpZ)|L=_Vrd|=6t91RcE>+`uDD}Oy814hfk`%md|E6 zG}JH{rMNb>9o`SwZuJ*A^>&4XvB@F5{;D(@tsged*NGbg+0O_7CXJ~x0s1{?{4OkF zaYYQxWGHP*ZRZwoB4vW@_k{J#oRlyK8OxM7|*R*r3Akl~WS?wcNT%5s=g()5ZljwKKyiz<3Kr6_Zn_u#zEbYikx87>OwYgMi0Rn8(~mnwgo=6%vX& zKM)*S_Kz1R#WfS3-VwP0woH7RL(L8&QCrAmm6b-5M)N9g7sB-HYza~ajY4TqS9=ha zh#miJYTsoNi3F~S*yy3kSFb|wcvHh3iv}IL?Cfm&vmXh!KuhBebzI%0qN2j0@CFCh zqM3u>9wZoL*lAmkehW1+m%q z<t=I?-@U|9YjT^|G;{IB2M$X=JF zPO1l)Y-A-P)qxBbGE@Q?$l8RgMM`lXvj;MJAk~4)9?0y0R0lG9AhQQR1~Pjfvj Date: Mon, 3 Apr 2023 13:41:27 +0200 Subject: [PATCH 10/68] Fixed the error page style --- client/src/pages/Error/styles.sass | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Error/styles.sass b/client/src/pages/Error/styles.sass index 3e74f78a..11e798f2 100644 --- a/client/src/pages/Error/styles.sass +++ b/client/src/pages/Error/styles.sass @@ -1,15 +1,14 @@ @import "@/common/styles/colors" .error-page - width: 100vw - height: 100vh - position: absolute + min-height: 100vh display: flex flex-direction: column justify-content: center align-items: center color: $white text-align: center + margin: 0 .no-reload width: unset From defec625e11b2e80e0793a32e8b5d0539c8011db Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:20:37 +0200 Subject: [PATCH 11/68] Created the Node model --- server/models/Node.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 server/models/Node.js diff --git a/server/models/Node.js b/server/models/Node.js new file mode 100644 index 00000000..a85dc423 --- /dev/null +++ b/server/models/Node.js @@ -0,0 +1,17 @@ +const Sequelize = require('sequelize'); +const db = require("../config/database"); + +module.exports = db.define("nodes", { + name: { + type: Sequelize.STRING, + defaultValue: "MySpeed Server" + }, + url: { + type: Sequelize.STRING, + allowNull: false + }, + password: { + type: Sequelize.STRING, + allowNull: false + } +}, {createdAt: false, updatedAt: false}); \ No newline at end of file From caf4daf955462837513ede1b9bc6802124247e9d Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:21:22 +0200 Subject: [PATCH 12/68] Created the node controller --- server/controller/node.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 server/controller/node.js diff --git a/server/controller/node.js b/server/controller/node.js new file mode 100644 index 00000000..cd026628 --- /dev/null +++ b/server/controller/node.js @@ -0,0 +1,21 @@ +const nodes = require('../models/Node'); + +// Gets all node entries +module.exports.list = async (excludePassword) => { + return await nodes.findAll({attributes: {exclude: excludePassword ? ['password'] : []}}); +} + +// Create a new node entry +module.exports.create = async (name, url, password) => { + return await nodes.create({name: name, url: url, password: password}); +} + +// Delete a node entry +module.exports.delete = async (nodeId) => { + return await nodes.destroy({where: {id: nodeId}}); +} + +// Get a specific node entry +module.exports.get = async (nodeId) => { + return await nodes.findOne({where: {id: nodeId}}); +} \ No newline at end of file From 10ac12210481bc070560cd67190b3ccbbd7cdbbb Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:23:29 +0200 Subject: [PATCH 13/68] Created all node routes --- server/routes/nodes.js | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 server/routes/nodes.js diff --git a/server/routes/nodes.js b/server/routes/nodes.js new file mode 100644 index 00000000..24dbaafe --- /dev/null +++ b/server/routes/nodes.js @@ -0,0 +1,58 @@ +const app = require('express').Router(); +const nodes = require('../controller/node'); + +// List all nodes +app.get("/", async (req, res) => { + return res.json(await nodes.list(true)); +}); + +// Create a node +app.put("/", async (req, res) => { + if (!req.body.name || !req.body.url || !req.body.password) return res.status(400).json({message: "Missing parameters"}); + + const url = req.body.url.replace(/\/+$/, ""); + + fetch(url + "/api/config", {headers: {password: req.body.password}}).then(async api => { + if (api.status !== 200) + return res.status(400).json({message: "Invalid URL or password"}); + + if ((await api.json()).viewMode) + return res.status(400).json({message: "Invalid URL or password"}); + + res.json({id: (await nodes.create(req.body.name, url, req.body.password)).id}); + }).catch(async () => { + res.status(400).json({message: "Invalid URL or password"}); + }); +}); + +// Delete a node +app.delete("/:nodeId", async (req, res) => { + const node = await nodes.get(req.params.nodeId); + if (node === null) return res.status(404).json({message: "Node not found"}); + + await nodes.delete(req.params.nodeId); + res.json({message: "Node successfully deleted"}); +}); + +// Get information from the node +app.get("/:nodeId/*", async (req, res) => { + const node = await nodes.get(req.params.nodeId); + if (node === null) return res.status(404).json({message: "Node not found"}); + + const url = node.url + req.originalUrl.replace("/api/nodes/" + req.params.nodeId, "/api"); + + req.headers['password'] = node.password; + delete req.headers['host']; + + fetch(url, { + method: req.method, + headers: req.headers, + body: req.method === "GET" ? undefined : req.body + }).then(async api => { + res.status(api.status).json(await api.json()); + }).catch(err => { + res.status(500).json({message: "Internal server error", err}); + }); +}); + +module.exports = app; \ No newline at end of file From 750d869e176f94e4de96d48c5a596dd323c6a40f Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:23:52 +0200 Subject: [PATCH 14/68] Integrated the node routes into the index.js --- server/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/index.js b/server/index.js index 3c37ff80..f005e99f 100755 --- a/server/index.js +++ b/server/index.js @@ -22,6 +22,7 @@ app.use("/api/speedtests", require('./routes/speedtests')); app.use("/api/info", require('./routes/system')); app.use("/api/export", require('./routes/export')); app.use("/api/recommendations", require('./routes/recommendations')); +app.use("/api/nodes", require('./routes/nodes')); app.use("/api*", (req, res) => res.status(404).json({message: "Route not found"})); // Enable production From 823bd4a094565618d5e98d4db23a518828546b32 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:25:45 +0200 Subject: [PATCH 15/68] The RequestUtil.js now supports loading information from a specific node --- client/src/common/utils/RequestUtil.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/common/utils/RequestUtil.js b/client/src/common/utils/RequestUtil.js index b0b0b786..cc54973f 100644 --- a/client/src/common/utils/RequestUtil.js +++ b/client/src/common/utils/RequestUtil.js @@ -1,4 +1,8 @@ -const API_ROOT = "/api"; +const getApiRoot = () => { + if (localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0") { + return "/api/nodes/" + localStorage.getItem("currentNode"); + } else return "/api"; +} // Get the default headers of the request const getHeaders = () => { @@ -8,9 +12,17 @@ const getHeaders = () => { return headers; } +// Run a plain request with all default values using the base path +export const baseRequest = async (path, method = "GET", body = {}, headers = {}) => { + return await fetch("/api" + path, { + headers: {...getHeaders(), ...headers}, method, + body: method !== "GET" ? JSON.stringify(body) : undefined + }); +} + // Run a plain request with all default values export const request = async (path, method = "GET", body = {}, headers = {}) => { - return await fetch(API_ROOT + path, { + return await fetch(getApiRoot() + path, { headers: {...getHeaders(), ...headers}, method, body: method !== "GET" ? JSON.stringify(body) : undefined }); From 6b4724d337c50998f0442ccd402bfd4aa7a6df94 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:26:15 +0200 Subject: [PATCH 16/68] Created the NodeContext.jsx --- .../src/common/contexts/Node/NodeContext.jsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 client/src/common/contexts/Node/NodeContext.jsx diff --git a/client/src/common/contexts/Node/NodeContext.jsx b/client/src/common/contexts/Node/NodeContext.jsx new file mode 100644 index 00000000..d2b27eec --- /dev/null +++ b/client/src/common/contexts/Node/NodeContext.jsx @@ -0,0 +1,29 @@ +import React, {useState, createContext, useEffect} from "react"; +import {baseRequest} from "@/common/utils/RequestUtil"; + +export const NodeContext = createContext({}); + +export const NodeProvider = (props) => { + + const [nodes, setNodes] = useState(null); + const [currentNode, setCurrentNode] = useState(parseInt(localStorage.getItem("currentNode")) || 0); + + const updateNodes = () => baseRequest("/nodes").then(nodes => nodes.json()).then(nodes => setNodes(nodes)); + + useEffect(() => { + updateNodes(); + const interval = setInterval(() => updateNodes(), 60000); + return () => clearInterval(interval); + }, []); + + const updateCurrentNode = (node) => { + localStorage.setItem("currentNode", node); + setCurrentNode(parseInt(node)); + } + + return ( + + {props.children} + + ) +} \ No newline at end of file From 1e29d5b00014cea3c338c26eb29a2f80aa161547 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:26:19 +0200 Subject: [PATCH 17/68] Created the NodeContext index --- client/src/common/contexts/Node/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/src/common/contexts/Node/index.js diff --git a/client/src/common/contexts/Node/index.js b/client/src/common/contexts/Node/index.js new file mode 100644 index 00000000..9e36400d --- /dev/null +++ b/client/src/common/contexts/Node/index.js @@ -0,0 +1 @@ +export * from "./NodeContext"; \ No newline at end of file From 436372da2125833dabca42386207367ad91090c6 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:27:24 +0200 Subject: [PATCH 18/68] Created the NodeHeader.jsx component --- .../pages/Nodes/components/NodeHeader/NodeHeader.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 client/src/pages/Nodes/components/NodeHeader/NodeHeader.jsx diff --git a/client/src/pages/Nodes/components/NodeHeader/NodeHeader.jsx b/client/src/pages/Nodes/components/NodeHeader/NodeHeader.jsx new file mode 100644 index 00000000..a277ec50 --- /dev/null +++ b/client/src/pages/Nodes/components/NodeHeader/NodeHeader.jsx @@ -0,0 +1,10 @@ +import "./styles.sass"; + +export const NodeHeader = () => { + return ( +
+ Logo +

MySpeed

+
+ ) +} \ No newline at end of file From af106e44b16a43059441111c133557f58ad9a246 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:27:31 +0200 Subject: [PATCH 19/68] Created the NodeHeader component style --- .../pages/Nodes/components/NodeHeader/styles.sass | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 client/src/pages/Nodes/components/NodeHeader/styles.sass diff --git a/client/src/pages/Nodes/components/NodeHeader/styles.sass b/client/src/pages/Nodes/components/NodeHeader/styles.sass new file mode 100644 index 00000000..59225a5f --- /dev/null +++ b/client/src/pages/Nodes/components/NodeHeader/styles.sass @@ -0,0 +1,13 @@ +.node-header + display: flex + align-items: center + gap: 1rem + +.node-header img + width: 5rem + height: 5rem + border-radius: 50% + +.node-header h1 + font-size: 34pt + font-weight: 700 \ No newline at end of file From 3f8ff68dedda50352aacc94a165ebe6c46ca0b7d Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:27:36 +0200 Subject: [PATCH 20/68] Created the NodeHeader component index --- client/src/pages/Nodes/components/NodeHeader/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/src/pages/Nodes/components/NodeHeader/index.js diff --git a/client/src/pages/Nodes/components/NodeHeader/index.js b/client/src/pages/Nodes/components/NodeHeader/index.js new file mode 100644 index 00000000..ec75da73 --- /dev/null +++ b/client/src/pages/Nodes/components/NodeHeader/index.js @@ -0,0 +1 @@ +export {NodeHeader as default} from "./NodeHeader"; \ No newline at end of file From b39cfbc6a4e6a126df960da0302ecf3d5b9c3cca Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 18:47:22 +0200 Subject: [PATCH 21/68] Removed additional debug information from the nodes route --- server/routes/nodes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index 24dbaafe..d7561ad8 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -50,8 +50,8 @@ app.get("/:nodeId/*", async (req, res) => { body: req.method === "GET" ? undefined : req.body }).then(async api => { res.status(api.status).json(await api.json()); - }).catch(err => { - res.status(500).json({message: "Internal server error", err}); + }).catch(() => { + res.status(500).json({message: "Internal server error"}); }); }); From 59fadcf05e56ea5a1f3cf207b53b8f8d74fc1ca6 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 23:53:34 +0200 Subject: [PATCH 22/68] Updated the password field in the Node model --- server/models/Node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models/Node.js b/server/models/Node.js index a85dc423..fb543b11 100644 --- a/server/models/Node.js +++ b/server/models/Node.js @@ -12,6 +12,6 @@ module.exports = db.define("nodes", { }, password: { type: Sequelize.STRING, - allowNull: false + allowNull: true } }, {createdAt: false, updatedAt: false}); \ No newline at end of file From 2111ffabfa18809c2573d592dcbedb3cac251995 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 23:54:45 +0200 Subject: [PATCH 23/68] Fixed the proxy route in the node.js routes --- server/routes/nodes.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index d7561ad8..86d0c251 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -8,20 +8,23 @@ app.get("/", async (req, res) => { // Create a node app.put("/", async (req, res) => { - if (!req.body.name || !req.body.url || !req.body.password) return res.status(400).json({message: "Missing parameters"}); + if (!req.body.name || !req.body.url) return res.status(400).json({message: "Missing parameters"}); const url = req.body.url.replace(/\/+$/, ""); - fetch(url + "/api/config", {headers: {password: req.body.password}}).then(async api => { + const headers = req.body.password ? {password: req.body.password} : {}; + + fetch(url + "/api/config", {headers}).then(async api => { if (api.status !== 200) - return res.status(400).json({message: "Invalid URL or password"}); + return res.status(400).json({message: "Invalid URL", type: "INVALID_URL"}); if ((await api.json()).viewMode) - return res.status(400).json({message: "Invalid URL or password"}); + 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}); - }).catch(async () => { - res.status(400).json({message: "Invalid URL or password"}); + res.json({id: (await nodes.create(req.body.name, url, req.body.password)).id, type: "NODE_CREATED"}); + }).catch(async (error) => { + console.log(error) + res.status(400).json({message: "Invalid URL", type: "INVALID_URL"}); }); }); @@ -35,7 +38,7 @@ app.delete("/:nodeId", async (req, res) => { }); // Get information from the node -app.get("/:nodeId/*", async (req, res) => { +app.all("/:nodeId/*", async (req, res) => { const node = await nodes.get(req.params.nodeId); if (node === null) return res.status(404).json({message: "Node not found"}); @@ -47,7 +50,8 @@ app.get("/:nodeId/*", async (req, res) => { fetch(url, { method: req.method, headers: req.headers, - body: req.method === "GET" ? undefined : req.body + body: req.method === "GET" ? undefined : req.body, + signal: req.signal }).then(async api => { res.status(api.status).json(await api.json()); }).catch(() => { From eeeda0a5c98a95eea8ef7f8a278a4ecdf6868725 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 3 Apr 2023 23:56:51 +0200 Subject: [PATCH 24/68] Fixed a bug in the Statistics.jsx --- client/src/pages/Statistics/Statistics.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Statistics/Statistics.jsx b/client/src/pages/Statistics/Statistics.jsx index 18c9f7f6..ca51f638 100644 --- a/client/src/pages/Statistics/Statistics.jsx +++ b/client/src/pages/Statistics/Statistics.jsx @@ -67,7 +67,7 @@ export const Statistics = () => { - + From 7cfc6fc6184b2caa0a41421b5400177ae626530b Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 00:03:51 +0200 Subject: [PATCH 25/68] Fixed a bug in the nodes.js route --- server/routes/nodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index 86d0c251..073e6863 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -50,7 +50,7 @@ app.all("/:nodeId/*", async (req, res) => { fetch(url, { method: req.method, headers: req.headers, - body: req.method === "GET" ? undefined : req.body, + body: req.method === "GET" ? undefined : JSON.stringify(req.body), signal: req.signal }).then(async api => { res.status(api.status).json(await api.json()); From f14158818d8262db1a94883fdf6c71f19b534c4a Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 00:29:29 +0200 Subject: [PATCH 26/68] Removed a debug statement from the nodes.js route --- server/routes/nodes.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index 073e6863..35d5f57b 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -8,7 +8,7 @@ app.get("/", async (req, res) => { // Create a node app.put("/", async (req, res) => { - if (!req.body.name || !req.body.url) return res.status(400).json({message: "Missing parameters"}); + if (!req.body.name || !req.body.url) return res.status(400).json({message: "Missing parameters", type: "MISSING_PARAMETERS"}); const url = req.body.url.replace(/\/+$/, ""); @@ -22,8 +22,7 @@ app.put("/", async (req, res) => { 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 (error) => { - console.log(error) + }).catch(async () => { res.status(400).json({message: "Invalid URL", type: "INVALID_URL"}); }); }); From b1693384474f6c6d00595e38dd1bc4ee02a0d492 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:05:05 +0200 Subject: [PATCH 27/68] Fixed a bug in the ConfigContext.jsx --- client/src/common/contexts/Config/ConfigContext.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/common/contexts/Config/ConfigContext.jsx b/client/src/common/contexts/Config/ConfigContext.jsx index 345cb8ca..53d54703 100644 --- a/client/src/common/contexts/Config/ConfigContext.jsx +++ b/client/src/common/contexts/Config/ConfigContext.jsx @@ -2,11 +2,13 @@ import React, {createContext, useContext, useEffect, useState} from "react"; import {InputDialogContext} from "../InputDialog"; import {request} from "@/common/utils/RequestUtil"; import {acceptDialog, apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog"; +import {NodeContext} from "@/common/contexts/Node"; export const ConfigContext = createContext({}); export const ConfigProvider = (props) => { + const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); const [config, setConfig] = useState({}); const [setDialog] = useContext(InputDialogContext); const [dialogShown, setDialogShown] = useState(false); @@ -23,7 +25,14 @@ export const ConfigProvider = (props) => { } }) .then(result => config !== result ? setConfig(result) : null) - .catch((code) => setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog())); + .catch((code) => { + if (currentNode === 0) { + setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog()); + } else { + updateCurrentNode(0); + props.showNodePage(true); + } + }); } const checkConfig = async () => (await request("/config")).json(); From 28c262095e3ac0666dfece84c776dd46a1795f79 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:05:45 +0200 Subject: [PATCH 28/68] Added the mainRed property to the InputDialog.jsx --- client/src/common/contexts/InputDialog/InputDialog.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/common/contexts/InputDialog/InputDialog.jsx b/client/src/common/contexts/InputDialog/InputDialog.jsx index 411b422a..80436909 100644 --- a/client/src/common/contexts/InputDialog/InputDialog.jsx +++ b/client/src/common/contexts/InputDialog/InputDialog.jsx @@ -72,7 +72,7 @@ const DialogArea = ({dialog}) => {
{dialog.unsetButton ? : ""} - +
) From d5fd3c49ad25ce44db4b0db3ae51a6d2fbf7972c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:07:57 +0200 Subject: [PATCH 29/68] Removed the beta span from the Header style --- .../src/common/components/Header/styles.sass | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/client/src/common/components/Header/styles.sass b/client/src/common/components/Header/styles.sass index 913f5e5a..12b27ce5 100644 --- a/client/src/common/components/Header/styles.sass +++ b/client/src/common/components/Header/styles.sass @@ -19,18 +19,16 @@ .header-main h2 margin-left: 10% display: flex - gap: 0.3rem - -.beta-span - display: flex - cursor: pointer + gap: 1rem align-items: center - justify-content: center - font-size: 14pt + cursor: pointer + padding: 0.5rem 1rem + border-radius: 1rem + user-select: none + +.header-main h2:hover + color: $green-hover background-color: $dark-gray - color: $green - padding: 0.1rem 0.4rem - border-radius: 0.7rem .header-main div margin-right: 10% @@ -75,14 +73,17 @@ margin-right: 0 .header-main h2 margin-left: 0 - font-size: 18pt + font-size: 24pt .header-icon - width: 20px - height: 20px + width: 25px + height: 25px @media (max-width: 360px) .header-main h2 font-size: 16pt text-overflow: ellipsis - overflow: hidden \ No newline at end of file + overflow: hidden + .header-icon + width: 20px + height: 20px \ No newline at end of file From 81a065e5accc3a423981a0910330cad3d05b855d Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:08:55 +0200 Subject: [PATCH 30/68] Integrated the server chooser into the HeaderComponent.jsx --- .../components/Header/HeaderComponent.jsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/src/common/components/Header/HeaderComponent.jsx b/client/src/common/components/Header/HeaderComponent.jsx index f80ab56f..5c3a246b 100644 --- a/client/src/common/components/Header/HeaderComponent.jsx +++ b/client/src/common/components/Header/HeaderComponent.jsx @@ -1,6 +1,12 @@ import "./styles.sass"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {faCircleArrowUp, faGaugeHigh, faGear, faLock} from "@fortawesome/free-solid-svg-icons"; +import { + faCircleArrowUp, + faGaugeHigh, + faGear, + faLock, + faServer +} from "@fortawesome/free-solid-svg-icons"; import {useContext, useEffect, useState} from "react"; import DropdownComponent, {toggleDropdown} from "../Dropdown/DropdownComponent"; import {InputDialogContext} from "@/common/contexts/InputDialog"; @@ -12,10 +18,10 @@ import {t} from "i18next"; import {ConfigContext} from "@/common/contexts/Config"; import {SpeedtestDialog} from "@/common/components/SpeedtestDialog"; import {ViewContext} from "@/common/contexts/View"; -import {Trans} from "react-i18next"; -import {PROJECT_URL} from "@/index"; +import {NodeContext} from "@/common/contexts/Node"; -function HeaderComponent() { +function HeaderComponent(props) { + const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); const [setDialog] = useContext(InputDialogContext); const [icon, setIcon] = useState(faGear); const [status, updateStatus] = useContext(StatusContext); @@ -76,15 +82,14 @@ function HeaderComponent() { if (!config.viewMode) updateVersion(); }, [config]); + const getNodeName = () => + currentNode === "0" ? t("header.title") : nodes?.find(node => node.id === currentNode)?.name || t("header.title"); + return (
-

{t("header.title")} {view === 1 && setDialog({ - title: t("header.beta.title"), - description: }}>header.beta.description, - buttonText: t("dialog.okay") - })}>BETA}

+

props.showNodePage(true)}> {getNodeName()}

{updateAvailable ?
Date: Tue, 4 Apr 2023 01:09:12 +0200 Subject: [PATCH 31/68] Updated the NodeHeader style --- client/src/pages/Nodes/components/NodeHeader/styles.sass | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Nodes/components/NodeHeader/styles.sass b/client/src/pages/Nodes/components/NodeHeader/styles.sass index 59225a5f..e5cd99ce 100644 --- a/client/src/pages/Nodes/components/NodeHeader/styles.sass +++ b/client/src/pages/Nodes/components/NodeHeader/styles.sass @@ -4,10 +4,11 @@ gap: 1rem .node-header img - width: 5rem - height: 5rem + width: 5.3rem + height: 5.3rem border-radius: 50% .node-header h1 - font-size: 34pt - font-weight: 700 \ No newline at end of file + font-size: 38pt + font-weight: 700 + margin: 0 \ No newline at end of file From af500fa387a80bb00edf9533756d001311e4b6a2 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:09:46 +0200 Subject: [PATCH 32/68] Created the NodeContainer.jsx --- .../NodeContainer/NodeContainer.jsx | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx diff --git a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx new file mode 100644 index 00000000..9d9a888b --- /dev/null +++ b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx @@ -0,0 +1,131 @@ +import "./styles.sass"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import { + faArrowDown, + faArrowUp, + faCircleNotch, faClock, + faExclamationTriangle, + faServer, + faTableTennisPaddleBall +} from "@fortawesome/free-solid-svg-icons"; +import React, {useContext, useEffect, useState} from "react"; +import {NodeContext} from "@/common/contexts/Node"; +import {InputDialogContext} from "@/common/contexts/InputDialog"; +import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; +import {baseRequest} from "@/common/utils/RequestUtil"; +import {t} from "i18next"; +import {Trans} from "react-i18next"; +import {getIconBySpeed} from "@/common/utils/TestUtil"; + +export const NodeContainer = (node) => { + const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); + const updateToast = useContext(ToastNotificationContext); + const [setDialog] = useContext(InputDialogContext); + const [nodeData, setNodeData] = useState(null); + const [nodeError, setNodeError] = useState(false); + + const prefix = node.currentNode ? "" : "/nodes/" + node.id; + + const updateData = async () => { + if (nodeData) return; + const testRequest = await baseRequest(prefix + "/speedtests?limit=1"); + + if (!testRequest.ok) return setNodeError(true); + const tests = await testRequest.json(); + + if (tests.length < 0) return setNodeError(true); + + const configRequest = await baseRequest(prefix + "/config"); + + if (!configRequest.ok) return setNodeError(true); + const config = await configRequest.json(); + + if (config.viewMode) return setNodeError(true); + + if (tests[0] === undefined) return setNodeData({failed: true}); + + setNodeData({ + ping: tests[0]?.ping, + download: Math.round(tests[0]?.download), + upload: Math.round(tests[0]?.upload), + pingIcon: getIconBySpeed(tests[0]?.ping, config.ping, false), + downloadIcon: getIconBySpeed(tests[0]?.download, config.download, true), + uploadIcon: getIconBySpeed(tests[0]?.upload, config.upload, true) + }); + } + + useEffect(() => { + updateData(); + }, []); + + const switchNode = () => { + if (nodeError || !nodeData) return; + + node.setShowNodePage(false); + updateCurrentNode(node.id); + } + + const onContext = (event) => { + event.preventDefault(); + + if (node.currentNode) return; + setDialog({ + title: t("nodes.delete.title"), + description: }} + values={node}>nodes.delete.description, + buttonText: t("nodes.delete.yes"), + mainRed: true, + onSuccess: () => baseRequest("/nodes/" + node.id, "DELETE").then(() => { + updateToast(t("nodes.delete.success"), "green", faServer); + updateNodes(); + }) + }); + } + + return ( +
+
+ +
+

{node.name}

+

{node.url.replace(/(^\w+:|^)\/\//, '')}

+
+
+
+ + {nodeError && (<>)} + + {!nodeError && !nodeData && ( + )} + + {nodeData && nodeData.failed && ( + )} + + {nodeData && !nodeData.failed && ( + <> +
+ +

{nodeData.ping} {t("latest.ping_unit")}

+
+ +
+ +

{nodeData.download} {t("latest.speed_unit")}

+
+ +
+ +

{nodeData.upload} {t("latest.speed_unit")}

+
+ + )} +
+ +
+ ); +} \ No newline at end of file From e56e38f79ace829d0231eb22339dbf336f20595b Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:09:50 +0200 Subject: [PATCH 33/68] Created the NodeContainer index --- client/src/pages/Nodes/components/NodeContainer/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/src/pages/Nodes/components/NodeContainer/index.js diff --git a/client/src/pages/Nodes/components/NodeContainer/index.js b/client/src/pages/Nodes/components/NodeContainer/index.js new file mode 100644 index 00000000..a4231161 --- /dev/null +++ b/client/src/pages/Nodes/components/NodeContainer/index.js @@ -0,0 +1 @@ +export {NodeContainer as default} from "./NodeContainer"; \ No newline at end of file From 39cf490344a52615cf78447b9e4d0554028c5371 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:10:14 +0200 Subject: [PATCH 34/68] Created the NodeContainer style --- .../components/NodeContainer/styles.sass | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 client/src/pages/Nodes/components/NodeContainer/styles.sass diff --git a/client/src/pages/Nodes/components/NodeContainer/styles.sass b/client/src/pages/Nodes/components/NodeContainer/styles.sass new file mode 100644 index 00000000..37c82330 --- /dev/null +++ b/client/src/pages/Nodes/components/NodeContainer/styles.sass @@ -0,0 +1,78 @@ +@import "@/common/styles/colors" + +.node-item + border: 2px solid $gray + border-radius: 15px + display: flex + width: 50rem + cursor: pointer + padding: 1rem 1.5rem + justify-content: space-between + align-items: center + user-select: none + +.hover-green:hover + border: 2px solid $green-hover + +.hover-orange:hover + border: 2px solid $orange-hover + +.hover-red:hover + border: 2px solid $red-hover + +.node-info-area + display: flex + align-items: center + gap: 1rem + +.node-info-area svg + font-size: 30pt + +.node-info-area h1 + margin: 0 + font-size: 16pt + +.node-info-area p + margin: 0 + font-size: 11pt + color: $darker-white + +.node-info + display: flex + flex-direction: column + align-items: flex-start + +.node-info * + max-width: 11rem + text-overflow: ellipsis + overflow: hidden + white-space: nowrap + +.speed-area + display: flex + gap: 1rem + +.speed-item + display: flex + gap: 0.5rem + +.speed-item svg + font-size: 24pt + +.speed-item h1 + margin: 0 + font-size: 20pt + color: $darker-white + +.speed-icon + font-size: 28pt + +@media screen and (max-width: 862px) + .node-item + width: calc(20vw + 8rem) + flex-direction: column + gap: 1rem + + .speed-area + flex-direction: column + gap: 0.5rem \ No newline at end of file From c74e0f0517bd85e82f578cf44ab3aeb9aa1e4c6f Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:11:15 +0200 Subject: [PATCH 35/68] Created the CreateNodeDialog.jsx --- .../CreateNodeDialog/CreateNodeDialog.jsx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx new file mode 100644 index 00000000..3e8d8752 --- /dev/null +++ b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx @@ -0,0 +1,108 @@ +import {DialogContext, DialogProvider} from "@/common/contexts/Dialog"; +import React, {useContext, useState} from "react"; +import "./styles.sass"; +import {t} from "i18next"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faCircleInfo, faClose, faServer} from "@fortawesome/free-solid-svg-icons"; +import {baseRequest} from "@/common/utils/RequestUtil"; +import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; +import {InputDialogContext} from "@/common/contexts/InputDialog"; +import {NodeContext} from "@/common/contexts/Node"; + +export const Dialog = () => { + const close = useContext(DialogContext); + + const [invalidUrl, setInvalidUrl] = useState(false); + + const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); + const [setDialog] = useContext(InputDialogContext); + const updateToast = useContext(ToastNotificationContext); + + const [serverName, setServerName] = useState(""); + const [serverUrl, setServerUrl] = useState(""); + + const runPasswordProcess = (wrong = false) => { + setDialog({ + title: t("dialog.password.title"), + type: "password", + description: wrong ? {t("dialog.password.wrong")} : t("nodes.password_required"), + placeholder: t("dialog.password.placeholder"), + buttonText: t("nodes.create"), + onSuccess: async (password) => { + const res = await (await baseRequest("/nodes", "PUT", { + name: serverName, url: serverUrl, + password: password + })).json(); + + if (res.type === "PASSWORD_REQUIRED") { + runPasswordProcess(true); + } else if (res.type === "NODE_CREATED") { + updateNodes(); + updateToast(t("nodes.created"), "green", faServer); + } + } + }); + } + + const validateNode = async () => { + const response = await (await baseRequest("/nodes", "PUT", { + name: serverName, + url: serverUrl + })).json(); + + const type = await response.type; + + if (type === "INVALID_URL") { + setInvalidUrl(true); + } else if (type === "PASSWORD_REQUIRED") { + close(); + runPasswordProcess(); + } else if (type === "NODE_CREATED") { + updateNodes(); + close(); + updateToast(t("nodes.created"), "green", faServer); + } + } + + const createNode = () => { + validateNode(); + console.log(serverName); + console.log(serverUrl); + } + + return ( + <> +
+

{t("nodes.add")}

+ close()}/> +
+
+
+

Servername

+ setServerName(event.target.value)}/> +
+
+

Serveradresse

+ { + setServerUrl(event.target.value); + setInvalidUrl(false); + }}/> +
+
+
+ +
+ + ); + +} + +export const CreateNodeDialog = (props) => { + return ( + + + + ); +} \ No newline at end of file From 9af73f19f5e0bf1289929e09f81b5efea33b2091 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:11:19 +0200 Subject: [PATCH 36/68] Created the CreateNodeDialog index --- client/src/pages/Nodes/components/CreateNodeDialog/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/src/pages/Nodes/components/CreateNodeDialog/index.js diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/index.js b/client/src/pages/Nodes/components/CreateNodeDialog/index.js new file mode 100644 index 00000000..88ea8ca0 --- /dev/null +++ b/client/src/pages/Nodes/components/CreateNodeDialog/index.js @@ -0,0 +1 @@ +export {CreateNodeDialog as default} from './CreateNodeDialog'; \ No newline at end of file From 5749c5bce1c3d81cb1ddc45e058597eb067d781b Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:11:47 +0200 Subject: [PATCH 37/68] Created the CreateNodeDialog style --- .../components/CreateNodeDialog/styles.sass | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 client/src/pages/Nodes/components/CreateNodeDialog/styles.sass diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass b/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass new file mode 100644 index 00000000..5de15d83 --- /dev/null +++ b/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass @@ -0,0 +1,37 @@ +@import "@/common/styles/colors" + +.server-input + font-size: 18pt + padding: 15px + font-weight: 700 + margin-top: 15px + background-color: $darker-gray + color: $darker-white + border: none + border-radius: 15px + +.server-dialog + padding-left: 1rem + padding-right: 1rem + padding-top: 1rem + display: flex + flex-direction: column + +.server-group + display: flex + flex-direction: column + align-items: flex-start + margin: 0 0 15px 0 + +.server-group h2 + margin: 0 + color: $green + font-weight: 700 + display: flex + align-items: center + gap: 0.5rem + +.server-error + .server-input + border: 1px solid $red + color: $red \ No newline at end of file From 52418e51c13d433244acf8564d01dd74829f99ac Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:11:58 +0200 Subject: [PATCH 38/68] Created the Nodes page --- client/src/pages/Nodes/Nodes.jsx | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Nodes/Nodes.jsx b/client/src/pages/Nodes/Nodes.jsx index e4f28804..128d62fb 100644 --- a/client/src/pages/Nodes/Nodes.jsx +++ b/client/src/pages/Nodes/Nodes.jsx @@ -1,3 +1,29 @@ -export const Nodes = () => { +import "./styles.sass"; +import NodeHeader from "@/pages/Nodes/components/NodeHeader"; +import NodeContainer from "@/pages/Nodes/components/NodeContainer"; +import {useContext, useState} from "react"; +import {NodeContext} from "@/common/contexts/Node"; +import {t} from "i18next"; +import CreateNodeDialog from "@/pages/Nodes/components/CreateNodeDialog"; +export const Nodes = (props) => { + const [nodes] = useContext(NodeContext); + const [createDialogOpen, setCreateDialogOpen] = useState(false); + + return ( +
+ {createDialogOpen && setCreateDialogOpen(false)}/>} + +
+ + + {nodes.map(node => )} + +
setCreateDialogOpen(true)}> +

{t("nodes.add")}

+
+
+
+ ) } \ No newline at end of file From 006a984ce372710a686786f4fad8835c4d02fa3d Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:12:04 +0200 Subject: [PATCH 39/68] Created the Nodes style --- client/src/pages/Nodes/styles.sass | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 client/src/pages/Nodes/styles.sass diff --git a/client/src/pages/Nodes/styles.sass b/client/src/pages/Nodes/styles.sass new file mode 100644 index 00000000..5481532d --- /dev/null +++ b/client/src/pages/Nodes/styles.sass @@ -0,0 +1,55 @@ +@import "@/common/styles/colors" + +.node-page + min-height: 100vh + display: flex + flex-direction: column + justify-content: center + align-items: center + color: $white + text-align: center + margin: 0 + gap: 0.7rem + +.node-page hr + margin: 0 + +.node-area + display: flex + flex-direction: column + gap: 1.5rem + margin-top: 1rem + +.node-add + width: 53rem + border: 2px dashed #696C73 + border-radius: 15px + cursor: pointer + +.node-add h1 + font-size: 24pt + font-weight: 700 + margin: 1rem + color: $darker-white + +.node-add:hover + border: 2px dashed $green-hover + + h1 + color: $white + +@media screen and (max-width: 862px) + .node-add + width: calc(20vw + 11rem) + border: 2px dashed #696C73 + border-radius: 15px + cursor: pointer + + .node-add h1 + font-size: 22pt + + .node-add:hover + border: 2px dashed $green-hover + + h1 + color: $white \ No newline at end of file From 9fd39b97b27b3b181b4564e9092c7d7b8fa2c32a Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:12:50 +0200 Subject: [PATCH 40/68] Integrated the node system into the App.jsx --- client/src/App.jsx | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index 85fb9b3e..b9d25db9 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -15,6 +15,7 @@ import Statistics from "@/pages/Statistics"; import {t} from "i18next"; import {ToastNotificationProvider} from "@/common/contexts/ToastNotification"; import Nodes from "@/pages/Nodes"; +import {NodeProvider} from "@/common/contexts/Node"; const MainContent = () => { const [view] = useContext(ViewContext); @@ -31,27 +32,33 @@ const App = () => { const [translationsLoaded, setTranslationsLoaded] = useState(false); const [translationError, setTranslationError] = useState(false); + const [showNodePage, setShowNodePage] = useState(false); + i18n.on("initialized", () => setTranslationsLoaded(true)); i18n.on("failedLoading", () => setTranslationError(true)); return ( <> - {!translationsLoaded && !translationError && } - {translationError && } - {translationsLoaded && !translationError && + - - - - - - - - - - + + {!translationsLoaded && !translationError && } + {translationError && } + {translationsLoaded && !translationError && showNodePage && + } + {translationsLoaded && !translationError && !showNodePage && + + + + + + + + + } + - } + ); } From c5de017c40cfba57560c342607c7df9895cb8392 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:13:28 +0200 Subject: [PATCH 41/68] Added the node translations to the de.json --- client/public/locales/de.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/public/locales/de.json b/client/public/locales/de.json index 62b33c2c..aaa30344 100644 --- a/client/public/locales/de.json +++ b/client/public/locales/de.json @@ -104,7 +104,7 @@ "view_title": "Ansicht wechseln" }, "header": { - "title": "Netzwerkanalyse", + "title": "MySpeed", "running_tooltip": "Speedtest läuft", "start_tooltip": "Speedtest starten", "new_update": "Update verfügbar", @@ -237,5 +237,17 @@ "down": "Download-Werte", "up": "Upload-Werte" } + }, + "nodes": { + "add": "Server hinzufügen", + "create": "Hinzufügen", + "created": "Der Server wurde erfolgreich hinzugefügt", + "password_required": "Für diese Node ist ein Passwort erforderlich", + "delete": { + "title": "Server löschen", + "description": "Der Server {{name}} (#{{id}}) wird gelöscht. Diese Aktion kann nicht rückgängig gemacht werden. Möchtest du fortfahren?", + "yes": "Ja, löschen", + "success": "Der Server wurde erfolgreich gelöscht" + } } } From 3a88862728f7a1d283efd2985ce9952b57ae5ca7 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:18:43 +0200 Subject: [PATCH 42/68] Translated the new node system into english --- client/public/locales/en.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/public/locales/en.json b/client/public/locales/en.json index e342de07..8db0a619 100644 --- a/client/public/locales/en.json +++ b/client/public/locales/en.json @@ -104,7 +104,7 @@ "view_title": "Switch view" }, "header": { - "title": "Network Analysis", + "title": "MySpeed", "running_tooltip": "Speedtest running", "start_tooltip": "Start speedtest", "new_update": "Update available", @@ -237,5 +237,17 @@ "down": "Download values", "up": "Upload values" } + }, + "nodes": { + "add": "Add server", + "create": "Add", + "created": "The server has been successfully added", + "password_required": "This node requires a password", + "delete": { + "title": "Delete server", + "description": "The server {{name}} (#{{id}}) will be deleted. This action cannot be undone. Do you want to continue?", + "yes": "Yes, delete", + "success": "The server has been deleted successfully" + } } } From a5e1e6f825b42bc6ab79c83bf5562252df4c8882 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:26:36 +0200 Subject: [PATCH 43/68] Added german translations --- client/public/locales/de.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/public/locales/de.json b/client/public/locales/de.json index aaa30344..9d92af80 100644 --- a/client/public/locales/de.json +++ b/client/public/locales/de.json @@ -243,6 +243,14 @@ "create": "Hinzufügen", "created": "Der Server wurde erfolgreich hinzugefügt", "password_required": "Für diese Node ist ein Passwort erforderlich", + "placeholder": { + "name": "MySpeed Instanz", + "url": "https://dein-server.de" + }, + "group": { + "name": "Servername", + "url": "Serveradresse" + }, "delete": { "title": "Server löschen", "description": "Der Server {{name}} (#{{id}}) wird gelöscht. Diese Aktion kann nicht rückgängig gemacht werden. Möchtest du fortfahren?", From c1820904a89a475139b906e2e14d7e2251cd609f Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:28:06 +0200 Subject: [PATCH 44/68] Integrated the translations into the CreateNodeDialog.jsx --- .../CreateNodeDialog/CreateNodeDialog.jsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx index 3e8d8752..76df5a83 100644 --- a/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx +++ b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx @@ -44,7 +44,7 @@ export const Dialog = () => { }); } - const validateNode = async () => { + const createNode = async () => { const response = await (await baseRequest("/nodes", "PUT", { name: serverName, url: serverUrl @@ -64,12 +64,6 @@ export const Dialog = () => { } } - const createNode = () => { - validateNode(); - console.log(serverName); - console.log(serverUrl); - } - return ( <>
@@ -78,13 +72,13 @@ export const Dialog = () => {
-

Servername

- {t("nodes.group.name")} + setServerName(event.target.value)}/>
-

Serveradresse

- {t("nodes.group.url")} + { setServerUrl(event.target.value); setInvalidUrl(false); From 325318ecd400bdc581ed41af7e5e7d82cc85a378 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:31:20 +0200 Subject: [PATCH 45/68] Updated the english translations --- client/public/locales/en.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/public/locales/en.json b/client/public/locales/en.json index 8db0a619..4ceaadb4 100644 --- a/client/public/locales/en.json +++ b/client/public/locales/en.json @@ -243,6 +243,14 @@ "create": "Add", "created": "The server has been successfully added", "password_required": "This node requires a password", + "placeholder": { + "name": "MySpeed instance", + "url": "https://your-server.com" + }, + "group": { + "name": "Server name", + "url": "Server address" + }, "delete": { "title": "Delete server", "description": "The server {{name}} (#{{id}}) will be deleted. This action cannot be undone. Do you want to continue?", From cad6dba8dae8098dea2bdf9a8bf8a93e02aa3bf1 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 01:45:51 +0200 Subject: [PATCH 46/68] Added auto interval in the NodeContainer.jsx --- .../src/pages/Nodes/components/NodeContainer/NodeContainer.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx index 9d9a888b..a02c0705 100644 --- a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx +++ b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx @@ -56,6 +56,8 @@ export const NodeContainer = (node) => { useEffect(() => { updateData(); + const interval = setInterval(() => updateData(), 10000); + return () => clearInterval(interval); }, []); const switchNode = () => { From 006b9272e28b6e22550585aaab17476ef9017752 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:24:47 +0200 Subject: [PATCH 47/68] Added password protection to the nodes.js route --- server/routes/nodes.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index 35d5f57b..a748a53b 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -1,13 +1,14 @@ const app = require('express').Router(); const nodes = require('../controller/node'); +const password = require("../middlewares/password"); // List all nodes -app.get("/", async (req, res) => { +app.get("/", password(false), async (req, res) => { return res.json(await nodes.list(true)); }); // Create a node -app.put("/", async (req, res) => { +app.put("/", password(false), async (req, res) => { if (!req.body.name || !req.body.url) return res.status(400).json({message: "Missing parameters", type: "MISSING_PARAMETERS"}); const url = req.body.url.replace(/\/+$/, ""); @@ -28,7 +29,7 @@ app.put("/", async (req, res) => { }); // Delete a node -app.delete("/:nodeId", async (req, res) => { +app.delete("/:nodeId", 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"}); @@ -37,7 +38,7 @@ app.delete("/:nodeId", async (req, res) => { }); // Get information from the node -app.all("/:nodeId/*", async (req, res) => { +app.all("/:nodeId/*", 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"}); From 6f3d9834d20dfdff39cf24a37ea8e9d3107fa7e4 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:25:40 +0200 Subject: [PATCH 48/68] Removed the NodeContext from the ConfigContext --- client/src/common/contexts/Config/ConfigContext.jsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/client/src/common/contexts/Config/ConfigContext.jsx b/client/src/common/contexts/Config/ConfigContext.jsx index 53d54703..944eb471 100644 --- a/client/src/common/contexts/Config/ConfigContext.jsx +++ b/client/src/common/contexts/Config/ConfigContext.jsx @@ -2,13 +2,10 @@ import React, {createContext, useContext, useEffect, useState} from "react"; import {InputDialogContext} from "../InputDialog"; import {request} from "@/common/utils/RequestUtil"; import {acceptDialog, apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog"; -import {NodeContext} from "@/common/contexts/Node"; export const ConfigContext = createContext({}); export const ConfigProvider = (props) => { - - const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); const [config, setConfig] = useState({}); const [setDialog] = useContext(InputDialogContext); const [dialogShown, setDialogShown] = useState(false); @@ -26,11 +23,10 @@ export const ConfigProvider = (props) => { }) .then(result => config !== result ? setConfig(result) : null) .catch((code) => { - if (currentNode === 0) { - setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog()); - } else { - updateCurrentNode(0); + if (localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0") { props.showNodePage(true); + } else { + setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog()); } }); } From 2f934cdc80f51b49e0f76db4f34da1f535263ba6 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:26:10 +0200 Subject: [PATCH 49/68] The NodeContext.jsx now only updates the nodes when the config is loaded --- client/src/common/contexts/Node/NodeContext.jsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/client/src/common/contexts/Node/NodeContext.jsx b/client/src/common/contexts/Node/NodeContext.jsx index d2b27eec..4fe9c483 100644 --- a/client/src/common/contexts/Node/NodeContext.jsx +++ b/client/src/common/contexts/Node/NodeContext.jsx @@ -1,20 +1,23 @@ -import React, {useState, createContext, useEffect} from "react"; +import React, {useState, createContext, useEffect, useContext} from "react"; import {baseRequest} from "@/common/utils/RequestUtil"; +import {ConfigContext} from "@/common/contexts/Config"; export const NodeContext = createContext({}); export const NodeProvider = (props) => { - const [nodes, setNodes] = useState(null); + const [config] = useContext(ConfigContext); + const [nodes, setNodes] = useState([]); const [currentNode, setCurrentNode] = useState(parseInt(localStorage.getItem("currentNode")) || 0); - const updateNodes = () => baseRequest("/nodes").then(nodes => nodes.json()).then(nodes => setNodes(nodes)); + const updateNodes = async () => baseRequest("/nodes").then(async nodes => { + if (nodes.ok) setNodes(await nodes.json()); + }); useEffect(() => { - updateNodes(); - const interval = setInterval(() => updateNodes(), 60000); - return () => clearInterval(interval); - }, []); + if (Object.keys(config).length === 0) return; + if (!config.viewMode) updateNodes(); + }, [config]); const updateCurrentNode = (node) => { localStorage.setItem("currentNode", node); From 895360928f13ffa69c6b7811b881072be66fce98 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:26:40 +0200 Subject: [PATCH 50/68] The HeaderComponent.jsx now disables the nodes feature in view mode --- .../src/common/components/Header/HeaderComponent.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/common/components/Header/HeaderComponent.jsx b/client/src/common/components/Header/HeaderComponent.jsx index 5c3a246b..d99d168b 100644 --- a/client/src/common/components/Header/HeaderComponent.jsx +++ b/client/src/common/components/Header/HeaderComponent.jsx @@ -17,11 +17,12 @@ import {updateInfo} from "@/common/components/Header/utils/infos"; import {t} from "i18next"; import {ConfigContext} from "@/common/contexts/Config"; import {SpeedtestDialog} from "@/common/components/SpeedtestDialog"; -import {ViewContext} from "@/common/contexts/View"; import {NodeContext} from "@/common/contexts/Node"; function HeaderComponent(props) { - const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); + const nodes = useContext(NodeContext)[0]; + const currentNode = useContext(NodeContext)[2]; + const [setDialog] = useContext(InputDialogContext); const [icon, setIcon] = useState(faGear); const [status, updateStatus] = useContext(StatusContext); @@ -29,7 +30,6 @@ function HeaderComponent(props) { const updateTests = useContext(SpeedtestContext)[1]; const [config, reloadConfig, checkConfig] = useContext(ConfigContext); const [updateAvailable, setUpdateAvailable] = useState(""); - const [view] = useContext(ViewContext); function switchDropdown() { toggleDropdown(setIcon); @@ -85,11 +85,14 @@ function HeaderComponent(props) { const getNodeName = () => currentNode === "0" ? t("header.title") : nodes?.find(node => node.id === currentNode)?.name || t("header.title"); + if (Object.keys(config).length === 0) return <>; + return (
-

props.showNodePage(true)}> {getNodeName()}

+ {config.viewMode ?

{t("header.title")}

:

props.showNodePage(true)} className="h2-click"> {getNodeName()}

} +
{updateAvailable ?
Date: Tue, 4 Apr 2023 16:26:51 +0200 Subject: [PATCH 51/68] Updated the HeaderComponent style --- client/src/common/components/Header/styles.sass | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/common/components/Header/styles.sass b/client/src/common/components/Header/styles.sass index 12b27ce5..218e963f 100644 --- a/client/src/common/components/Header/styles.sass +++ b/client/src/common/components/Header/styles.sass @@ -21,12 +21,14 @@ display: flex gap: 1rem align-items: center - cursor: pointer padding: 0.5rem 1rem border-radius: 1rem + +.header-main .h2-click + cursor: pointer user-select: none -.header-main h2:hover +.header-main .h2-click:hover color: $green-hover background-color: $dark-gray From a264fe2ba42a59c15e4053df42daf3adeb76673e Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:27:22 +0200 Subject: [PATCH 52/68] Updated the hierarchy in the App.jsx --- client/src/App.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index b9d25db9..338e5228 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -39,24 +39,24 @@ const App = () => { return ( <> + {!translationsLoaded && !translationError && } + {translationError && } + - {!translationsLoaded && !translationError && } - {translationError && } {translationsLoaded && !translationError && showNodePage && } {translationsLoaded && !translationError && !showNodePage && - - } + From 5a7dd1676469efc3e7015284b759541a33d7fd1e Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:27:43 +0200 Subject: [PATCH 53/68] The NodeContainer.jsx now reloads the config on node switch --- .../pages/Nodes/components/NodeContainer/NodeContainer.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx index a02c0705..6f0a9ec9 100644 --- a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx +++ b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx @@ -16,9 +16,12 @@ import {baseRequest} from "@/common/utils/RequestUtil"; import {t} from "i18next"; import {Trans} from "react-i18next"; import {getIconBySpeed} from "@/common/utils/TestUtil"; +import {ConfigContext} from "@/common/contexts/Config"; export const NodeContainer = (node) => { - const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); + const updateNodes = useContext(NodeContext)[1]; + const updateCurrentNode = useContext(NodeContext)[3]; + const reloadConfig = useContext(ConfigContext)[1]; const updateToast = useContext(ToastNotificationContext); const [setDialog] = useContext(InputDialogContext); const [nodeData, setNodeData] = useState(null); @@ -65,6 +68,7 @@ export const NodeContainer = (node) => { node.setShowNodePage(false); updateCurrentNode(node.id); + reloadConfig(); } const onContext = (event) => { From 80b5ed967893926a515cf360ad526ed6c6db7227 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 16:49:35 +0200 Subject: [PATCH 54/68] Fixed a bug in the InputDialog.jsx --- .../contexts/InputDialog/InputDialog.jsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client/src/common/contexts/InputDialog/InputDialog.jsx b/client/src/common/contexts/InputDialog/InputDialog.jsx index 80436909..c14f9be8 100644 --- a/client/src/common/contexts/InputDialog/InputDialog.jsx +++ b/client/src/common/contexts/InputDialog/InputDialog.jsx @@ -16,16 +16,22 @@ const DialogArea = ({dialog}) => { if (dialog.value) setValue(dialog.value); }, [dialog.value]); - document.onkeyup = e => { - if (e.key === "Enter") { - e.preventDefault(); - submit(); + useEffect(() => { + document.onkeyup = e => { + if (e.key === "Enter") { + e.preventDefault(); + submit(); + } + if (e.key === "Escape" && !dialog.disableCloseButton) { + e.preventDefault(); + closeDialog(); + } } - if (e.key === "Escape" && !dialog.disableCloseButton) { - e.preventDefault(); - closeDialog(); + + return () => { + document.onkeyup = null; } - } + }); function updateValue(e) { if (dialog.updateDescription) dialog.description = dialog.updateDescription(e.target.value); From 7bf0f99723f44d068ff6cd0f7b38be46562f7e73 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 17:40:59 +0200 Subject: [PATCH 55/68] Optimized the CreateNodeDialog.jsx --- .../Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx index 76df5a83..6e466024 100644 --- a/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx +++ b/client/src/pages/Nodes/components/CreateNodeDialog/CreateNodeDialog.jsx @@ -14,7 +14,7 @@ export const Dialog = () => { const [invalidUrl, setInvalidUrl] = useState(false); - const [nodes, updateNodes, currentNode, updateCurrentNode] = useContext(NodeContext); + const updateNodes = useContext(NodeContext)[1]; const [setDialog] = useContext(InputDialogContext); const updateToast = useContext(ToastNotificationContext); From ff7de0fac835dcfd6fc4acbb650818cb5c8aab63 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 18:10:16 +0200 Subject: [PATCH 56/68] Added mobile optimization to the CreateNodeDialog style --- .../Nodes/components/CreateNodeDialog/styles.sass | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass b/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass index 5de15d83..fdfb25ad 100644 --- a/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass +++ b/client/src/pages/Nodes/components/CreateNodeDialog/styles.sass @@ -11,6 +11,7 @@ border-radius: 15px .server-dialog + transition: all 0.3s ease-in-out padding-left: 1rem padding-right: 1rem padding-top: 1rem @@ -34,4 +35,13 @@ .server-error .server-input border: 1px solid $red - color: $red \ No newline at end of file + color: $red + +@media screen and (max-width: 425px) + .server-dialog + padding-left: 0.5rem + padding-right: 0.5rem + .server-group h2 + font-size: 16pt + .server-input + font-size: 16pt \ No newline at end of file From e46fe87f387272a90af4ca66852765949de6f376 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 18:10:38 +0200 Subject: [PATCH 57/68] Added the content disposition header to the node proxy route --- server/routes/nodes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index a748a53b..1156868c 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -53,6 +53,9 @@ app.all("/:nodeId/*", password(false), async (req, res) => { 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"}); From 645080eacf9af11c01436565ef746bad2cdf5393 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 19:19:57 +0200 Subject: [PATCH 58/68] Added updateName & updatePassword to the node controller --- server/controller/node.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/controller/node.js b/server/controller/node.js index cd026628..56a880fb 100644 --- a/server/controller/node.js +++ b/server/controller/node.js @@ -18,4 +18,14 @@ module.exports.delete = async (nodeId) => { // Get a specific node entry module.exports.get = async (nodeId) => { return await nodes.findOne({where: {id: nodeId}}); +} + +// Update the name of the node entry +module.exports.updateName = async (nodeId, name) => { + return await nodes.update({name: name}, {where: {id: nodeId}}); +} + +// Update the password of the node entry +module.exports.updatePassword = async (nodeId, password) => { + return await nodes.update({password: password}, {where: {id: nodeId}}); } \ No newline at end of file From 0a2159122f7abd7a6f6d1a5f2458b19047893ef6 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 19:21:31 +0200 Subject: [PATCH 59/68] Created patch routes to change the name & password of the nodes --- server/routes/nodes.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/server/routes/nodes.js b/server/routes/nodes.js index 1156868c..42487df1 100644 --- a/server/routes/nodes.js +++ b/server/routes/nodes.js @@ -37,6 +37,38 @@ app.delete("/:nodeId", password(false), async (req, res) => { res.json({message: "Node successfully deleted"}); }); +// Update the node name +app.patch("/:nodeId/name", password(false), async (req, res) => { + if (!req.body.name) return res.status(400).json({message: "Missing parameters", type: "MISSING_PARAMETERS"}); + + const node = await nodes.get(req.params.nodeId); + if (node === null) return res.status(404).json({message: "Node not found"}); + + await nodes.updateName(req.params.nodeId, req.body.name); + res.json({message: "Node name successfully updated"}); +}); + +// Update the node password +app.patch("/:nodeId/password", password(false), async (req, res) => { + if (!req.body.password) return res.status(400).json({message: "Missing parameters", type: "MISSING_PARAMETERS"}); + + 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) + return res.status(400).json({message: "Invalid URL", type: "INVALID_URL"}); + + if ((await api.json()).viewMode) + return res.status(400).json({message: "Invalid password", type: "PASSWORD_REQUIRED"}); + + await nodes.updatePassword(req.params.nodeId, 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"}); + }); +}); + // Get information from the node app.all("/:nodeId/*", password(false), async (req, res) => { const node = await nodes.get(req.params.nodeId); From 232d835fade1714379bbf337dbd0d5211f348444 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 19:52:52 +0200 Subject: [PATCH 60/68] Added the icon-text class to the NodeContainer style --- .../Nodes/components/NodeContainer/styles.sass | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Nodes/components/NodeContainer/styles.sass b/client/src/pages/Nodes/components/NodeContainer/styles.sass index 37c82330..4268fb83 100644 --- a/client/src/pages/Nodes/components/NodeContainer/styles.sass +++ b/client/src/pages/Nodes/components/NodeContainer/styles.sass @@ -52,6 +52,15 @@ display: flex gap: 1rem +.icon-text + display: flex + gap: 0.5rem + align-items: center + +.icon-text h2 + margin: 0 + color: $darker-white + .speed-item display: flex gap: 0.5rem @@ -75,4 +84,6 @@ .speed-area flex-direction: column - gap: 0.5rem \ No newline at end of file + gap: 0.5rem + .icon-text h2 + font-size: 14pt \ No newline at end of file From 502bce2ee219540b5ce33fb2f99b24851a92a457 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 19:53:27 +0200 Subject: [PATCH 61/68] The Nodes now load on render --- client/src/pages/Nodes/Nodes.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Nodes/Nodes.jsx b/client/src/pages/Nodes/Nodes.jsx index 128d62fb..dd31ee84 100644 --- a/client/src/pages/Nodes/Nodes.jsx +++ b/client/src/pages/Nodes/Nodes.jsx @@ -1,15 +1,19 @@ import "./styles.sass"; import NodeHeader from "@/pages/Nodes/components/NodeHeader"; import NodeContainer from "@/pages/Nodes/components/NodeContainer"; -import {useContext, useState} from "react"; +import {useContext, useEffect, useState} from "react"; import {NodeContext} from "@/common/contexts/Node"; import {t} from "i18next"; import CreateNodeDialog from "@/pages/Nodes/components/CreateNodeDialog"; export const Nodes = (props) => { - const [nodes] = useContext(NodeContext); + const [nodes, updateNodes] = useContext(NodeContext); const [createDialogOpen, setCreateDialogOpen] = useState(false); + useEffect(() => { + updateNodes(); + }, []); + return (
{createDialogOpen && setCreateDialogOpen(false)}/>} From 575edab52be3a2cbf87fce4def2ddde81fc4cfa3 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 20:27:39 +0200 Subject: [PATCH 62/68] Implemented custom errors into the NodeContainer.jsx --- .../NodeContainer/NodeContainer.jsx | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx index 6f0a9ec9..f34dd3e5 100644 --- a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx +++ b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx @@ -3,8 +3,9 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { faArrowDown, faArrowUp, - faCircleNotch, faClock, - faExclamationTriangle, + faCircleNotch, + faClock, + faExclamationTriangle, faKey, faServer, faTableTennisPaddleBall } from "@fortawesome/free-solid-svg-icons"; @@ -25,28 +26,28 @@ export const NodeContainer = (node) => { const updateToast = useContext(ToastNotificationContext); const [setDialog] = useContext(InputDialogContext); const [nodeData, setNodeData] = useState(null); - const [nodeError, setNodeError] = useState(false); + const [nodeError, setNodeError] = useState(undefined); const prefix = node.currentNode ? "" : "/nodes/" + node.id; const updateData = async () => { - if (nodeData) return; const testRequest = await baseRequest(prefix + "/speedtests?limit=1"); - if (!testRequest.ok) return setNodeError(true); + if (!testRequest.ok) return setNodeError("SERVER_NOT_REACHABLE"); const tests = await testRequest.json(); - if (tests.length < 0) return setNodeError(true); + if (tests.length < 0) return setNodeError("SERVER_NOT_REACHABLE"); const configRequest = await baseRequest(prefix + "/config"); - if (!configRequest.ok) return setNodeError(true); + if (!configRequest.ok) return setNodeError("SERVER_NOT_REACHABLE"); const config = await configRequest.json(); - if (config.viewMode) return setNodeError(true); + if (config.viewMode) return setNodeError("PASSWORD_CHANGED"); - if (tests[0] === undefined) return setNodeData({failed: true}); + if (tests[0] === undefined) return setNodeData({pending: true}); + setNodeError(undefined); setNodeData({ ping: tests[0]?.ping, download: Math.round(tests[0]?.download), @@ -57,14 +58,37 @@ export const NodeContainer = (node) => { }); } + const updatePassword = (wrong = false) => { + setDialog({ + title: t("nodes.password_outdated"), + type: "password", + description: wrong ? {t("dialog.password.wrong")} : t("nodes.update_password"), + placeholder: t("dialog.password.placeholder"), + buttonText: t("dialog.update"), + onSuccess: async (password) => { + const res = await (await baseRequest(`/nodes/${node.id}/password`, "PATCH", {password: password})).json(); + + if (res.type === "PASSWORD_UPDATED") { + updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")); + updateToast(t("nodes.password_updated"), "green", faKey); + } else { + updatePassword(true); + } + } + }); + } + useEffect(() => { - updateData(); - const interval = setInterval(() => updateData(), 10000); + updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")); + const interval = setInterval(() => updateData().catch(() => setNodeError("SERVER_NOT_REACHABLE")), 10000); return () => clearInterval(interval); }, []); const switchNode = () => { - if (nodeError || !nodeData) return; + if (nodeError || !nodeData) { + if (nodeError === "PASSWORD_CHANGED") updatePassword(); + return; + } node.setShowNodePage(false); updateCurrentNode(node.id); @@ -101,15 +125,26 @@ export const NodeContainer = (node) => {
- {nodeError && (<>)} + {nodeError === "SERVER_NOT_REACHABLE" && (
+

{t("nodes.messages.not_reachable")}

+ +
)} + + {nodeError === "PASSWORD_CHANGED" && (
+

{t("nodes.messages.password_changed")}

+ +
)} {!nodeError && !nodeData && ( )} - {nodeData && nodeData.failed && ( - )} + {nodeData && nodeData.pending && !nodeError && (
+

{t("nodes.messages.tests_pending")}

+ +
+ )} - {nodeData && !nodeData.failed && ( + {nodeData && !nodeData.pending && !nodeError && ( <>
Date: Tue, 4 Apr 2023 20:29:35 +0200 Subject: [PATCH 63/68] Updated the password changer in the DropdownComponent.jsx --- .../src/common/components/Dropdown/DropdownComponent.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/common/components/Dropdown/DropdownComponent.jsx b/client/src/common/components/Dropdown/DropdownComponent.jsx index 7033f5f5..8cc7621c 100644 --- a/client/src/common/components/Dropdown/DropdownComponent.jsx +++ b/client/src/common/components/Dropdown/DropdownComponent.jsx @@ -25,7 +25,7 @@ import {ConfigContext} from "@/common/contexts/Config"; import {StatusContext} from "@/common/contexts/Status"; import {InputDialogContext} from "@/common/contexts/InputDialog"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; -import {downloadRequest, jsonRequest, patchRequest, postRequest} from "@/common/utils/RequestUtil"; +import {baseRequest, downloadRequest, jsonRequest, patchRequest, postRequest} from "@/common/utils/RequestUtil"; import {creditsInfo, healthChecksInfo, recommendationsInfo} from "@/common/components/Dropdown/utils/infos"; import { exportOptions, languageOptions, levelOptions, @@ -36,6 +36,7 @@ import {changeLanguage, t} from "i18next"; import ViewDialog from "@/common/components/ViewDialog"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {ToastNotificationContext} from "@/common/contexts/ToastNotification"; +import {NodeContext} from "@/common/contexts/Node"; let icon; @@ -56,6 +57,7 @@ export const toggleDropdown = (setIcon) => { function DropdownComponent() { const [config, reloadConfig] = useContext(ConfigContext); const [status, updateStatus] = useContext(StatusContext); + const currentNode = useContext(NodeContext)[2]; const updateTests = useContext(SpeedtestContext)[1]; const updateToast = useContext(ToastNotificationContext); const [setDialog] = useContext(InputDialogContext); @@ -153,7 +155,10 @@ function DropdownComponent() { .then(() => localStorage.removeItem("password")), onSuccess: (value) => patchRequest("/config/password", {value}) .then(() => showFeedback(undefined, false)) - .then(() => localStorage.setItem("password", value)) + .then(() => { + currentNode !== 0 ? baseRequest("/nodes/" + currentNode + "/password", "PATCH", + {password: value}) : localStorage.setItem("password", value); + }) }) } From ee2524eb27cf2fba719c3a8be7cab0760de0d190 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 20:30:16 +0200 Subject: [PATCH 64/68] The ConfigContext.jsx now redirects to the server selector if the current node is invalid --- .../common/contexts/Config/ConfigContext.jsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/client/src/common/contexts/Config/ConfigContext.jsx b/client/src/common/contexts/Config/ConfigContext.jsx index 944eb471..bc6e73f4 100644 --- a/client/src/common/contexts/Config/ConfigContext.jsx +++ b/client/src/common/contexts/Config/ConfigContext.jsx @@ -14,21 +14,20 @@ export const ConfigProvider = (props) => { request("/config").then(async res => { if (res.status === 401) throw 1; if (!res.ok) throw 2; - + try { return JSON.parse(await res.text()); } catch (e) { throw 2; } - }) - .then(result => config !== result ? setConfig(result) : null) - .catch((code) => { - if (localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0") { - props.showNodePage(true); - } else { - setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog()); - } - }); + }).then(result => { + if (config !== result) + result.viewMode && localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0" + ? props.showNodePage(true) : setConfig(result); + }).catch((code) => { + localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0" + ? props.showNodePage(true) : setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog()); + }); } const checkConfig = async () => (await request("/config")).json(); From ab900fcc64bf1c9ac565c78678cdce512de565fb Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 4 Apr 2023 22:21:48 +0200 Subject: [PATCH 65/68] Optimized the NodeContainer.jsx --- .../Nodes/components/NodeContainer/NodeContainer.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx index f34dd3e5..eeae65b5 100644 --- a/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx +++ b/client/src/pages/Nodes/components/NodeContainer/NodeContainer.jsx @@ -5,7 +5,8 @@ import { faArrowUp, faCircleNotch, faClock, - faExclamationTriangle, faKey, + faExclamationTriangle, + faKey, faServer, faTableTennisPaddleBall } from "@fortawesome/free-solid-svg-icons"; @@ -62,7 +63,8 @@ export const NodeContainer = (node) => { setDialog({ title: t("nodes.password_outdated"), type: "password", - description: wrong ? {t("dialog.password.wrong")} : t("nodes.update_password"), + description: wrong ? + {t("dialog.password.wrong")} : t("nodes.update_password"), placeholder: t("dialog.password.placeholder"), buttonText: t("dialog.update"), onSuccess: async (password) => { @@ -141,8 +143,7 @@ export const NodeContainer = (node) => { {nodeData && nodeData.pending && !nodeError && (

{t("nodes.messages.tests_pending")}

-
- )} +
)} {nodeData && !nodeData.pending && !nodeError && ( <> From f4531dd871bf077855c018d086d0c2d225ca011a Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Wed, 5 Apr 2023 00:07:56 +0200 Subject: [PATCH 66/68] Added new german translations --- client/public/locales/de.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/public/locales/de.json b/client/public/locales/de.json index 9d92af80..a1c9dd2c 100644 --- a/client/public/locales/de.json +++ b/client/public/locales/de.json @@ -243,6 +243,9 @@ "create": "Hinzufügen", "created": "Der Server wurde erfolgreich hinzugefügt", "password_required": "Für diese Node ist ein Passwort erforderlich", + "update_password": "Bitte aktualisiere das Passwort dieser Node", + "password_outdated": "Passwort veraltet", + "password_updated": "Das Passwort wurde erfolgreich aktualisiert", "placeholder": { "name": "MySpeed Instanz", "url": "https://dein-server.de" @@ -256,6 +259,11 @@ "description": "Der Server {{name}} (#{{id}}) wird gelöscht. Diese Aktion kann nicht rückgängig gemacht werden. Möchtest du fortfahren?", "yes": "Ja, löschen", "success": "Der Server wurde erfolgreich gelöscht" + }, + "messages": { + "not_reachable": "Server ist nicht erreichbar", + "password_changed": "Passwort wurde geändert", + "tests_pending": "Testergebnisse ausstehend" } } } From f07476c37b3a1123c65c58dfeca36228509ceec7 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Wed, 5 Apr 2023 00:08:03 +0200 Subject: [PATCH 67/68] Added new english translations --- client/public/locales/en.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/public/locales/en.json b/client/public/locales/en.json index 4ceaadb4..c9dfc363 100644 --- a/client/public/locales/en.json +++ b/client/public/locales/en.json @@ -243,6 +243,9 @@ "create": "Add", "created": "The server has been successfully added", "password_required": "This node requires a password", + "update_password": "Please update the password of this node", + "password_outdated": "Password outdated", + "password_updated": "The password has been successfully updated", "placeholder": { "name": "MySpeed instance", "url": "https://your-server.com" @@ -256,6 +259,11 @@ "description": "The server {{name}} (#{{id}}) will be deleted. This action cannot be undone. Do you want to continue?", "yes": "Yes, delete", "success": "The server has been deleted successfully" + }, + "messages": { + "not_reachable": "Server is not reachable", + "password_changed": "Password changed", + "tests_pending": "Test results pending" } } } From c85fafe6a6c9c361687165cd088898395b6b9115 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Wed, 5 Apr 2023 00:23:48 +0200 Subject: [PATCH 68/68] Added the "this server" translation (DE and EN) --- client/public/locales/de.json | 1 + client/public/locales/en.json | 1 + client/src/pages/Nodes/Nodes.jsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/public/locales/de.json b/client/public/locales/de.json index a1c9dd2c..eb68ba0b 100644 --- a/client/public/locales/de.json +++ b/client/public/locales/de.json @@ -241,6 +241,7 @@ "nodes": { "add": "Server hinzufügen", "create": "Hinzufügen", + "this_server": "Dieser Server", "created": "Der Server wurde erfolgreich hinzugefügt", "password_required": "Für diese Node ist ein Passwort erforderlich", "update_password": "Bitte aktualisiere das Passwort dieser Node", diff --git a/client/public/locales/en.json b/client/public/locales/en.json index c9dfc363..c246b682 100644 --- a/client/public/locales/en.json +++ b/client/public/locales/en.json @@ -241,6 +241,7 @@ "nodes": { "add": "Add server", "create": "Add", + "this_server": "This server", "created": "The server has been successfully added", "password_required": "This node requires a password", "update_password": "Please update the password of this node", diff --git a/client/src/pages/Nodes/Nodes.jsx b/client/src/pages/Nodes/Nodes.jsx index dd31ee84..1c0a4ac2 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 => )}