Skip to content

Commit 95ec9d2

Browse files
authored
Merge pull request #255 from gnmyt/optimizations/1.0.7
🆕 Version 1.0.7 - Update
2 parents 74774b8 + 8b76f60 commit 95ec9d2

File tree

18 files changed

+97
-54
lines changed

18 files changed

+97
-54
lines changed

client/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "client",
3-
"version": "1.0.6",
3+
"version": "1.0.7",
44
"scripts": {
55
"dev": "vite",
66
"build": "vite build",

client/public/locales/de.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"password": {
1313
"title": "Passwort erforderlich",
1414
"placeholder": "Dein Passwort",
15-
"wrong": "Das von dir eingegebene Passwort ist falsch"
15+
"wrong": "Das von dir eingegebene Passwort ist falsch",
16+
"unlock": "Sperre aufheben"
1617
},
1718
"accept": {
1819
"title": "Wir brauchen deine Genehmigung",
@@ -160,6 +161,7 @@
160161
},
161162
"test": {
162163
"not_available": "Es liegen aktuell keine Tests vor",
164+
"no_latest": "Es liegt kein aktueller Test vor. Bitte führe einen Test durch oder warte, bis der nächste Test durchgeführt wurde.",
163165
"unknown_error": "Unbekannter Fehler:",
164166
"failed": "Test fehlgeschlagen",
165167
"recheck": "Bitte überprüfe weitestgehend, ob das öfter passiert.",

client/public/locales/en.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"password": {
1313
"title": "Password required",
1414
"placeholder": "Your password",
15-
"wrong": "The password you entered is incorrect"
15+
"wrong": "The password you entered is incorrect",
16+
"unlock": "Unlock"
1617
},
1718
"accept": {
1819
"title": "We need your permission",
@@ -129,7 +130,7 @@
129130
"healthchecks": "MySpeed uses <HCLink>HealthChecks</HCLink> to notify you when your internet is down. To enable this, put your ping URL in the text box. Read more <WIKILink>here</WIKILink>",
130131
"credits": "<Link>MySpeed</Link> is provided by GNMYT and uses the <CLILink>Speedtest CLI</CLILink> from Ookla.",
131132
"recommendations_error": "You have to do at least 10 tests to get an average. It doesn't matter if the tests were done manually or automatically.",
132-
"recommendations_info": "Based on the last 10 tests, it was found that the optimal ping was <Bold>{{ping}} ms</Bold>, the download at <Bold>{{down}} Mbit/s</Bold> and the upload at <Bold>{{up}} Mbit/s</Bold>. It is best to orientate yourself on your internet contract and only adopt it if it matches that.",
133+
"recommendations_info": "Based on the last 10 tests, it was found that the optimal ping was <Bold>{{ping}} ms</Bold>, the download at <Bold>{{down}} Mbps</Bold> and the upload at <Bold>{{up}} Mbps</Bold>. It is best to orientate yourself on your internet contract and only adopt it if it matches that.",
133134
"update": "An update to version {{version}} is available. See <Changes>the changes</Changes> and <DLLink>download the update</DLLink>.",
134135
"down": {
135136
"title": "Download speed",
@@ -160,6 +161,7 @@
160161
},
161162
"test": {
162163
"not_available": "There are currently no tests available",
164+
"no_latest": "There is no current test available. Please perform a test or wait until the next test is performed.",
163165
"unknown_error": "Unknown error:",
164166
"failed": "Test failed",
165167
"recheck": "Please check as far as possible if this happens often.",

client/src/common/components/Dropdown/DropdownComponent.jsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export const toggleDropdown = (setIcon) => {
5757
function DropdownComponent() {
5858
const [config, reloadConfig] = useContext(ConfigContext);
5959
const [status, updateStatus] = useContext(StatusContext);
60+
const findNode = useContext(NodeContext)[4];
61+
const updateNodes = useContext(NodeContext)[1];
6062
const currentNode = useContext(NodeContext)[2];
6163
const updateTests = useContext(SpeedtestContext)[1];
6264
const updateToast = useContext(ToastNotificationContext);
@@ -145,19 +147,24 @@ function DropdownComponent() {
145147

146148
const updatePassword = async () => {
147149
toggleDropdown();
150+
const passwordSet = currentNode !== 0 ? findNode(currentNode).password : localStorage.getItem("password") != null;
151+
148152
setDialog({
149153
title: <>{t("update.new_password")} » <a onClick={updatePasswordLevel}>{t("update.level")}</a></>,
150154
placeholder: t("update.password_placeholder"),
151155
type: "password",
152-
unsetButton: localStorage.getItem("password") != null ? "Sperre aufheben" : undefined,
156+
unsetButton: passwordSet ? t("dialog.password.unlock") : undefined,
153157
onClear: () => patchRequest("/config/password", {value: "none"})
154158
.then(() => showFeedback("update.password_removed", false))
155-
.then(() => localStorage.removeItem("password")),
159+
.then(() => {
160+
currentNode !== 0 ? baseRequest("/nodes/" + currentNode + "/password", "PATCH",
161+
{password: "none"}).then(() => updateNodes()) : localStorage.removeItem("password");
162+
}),
156163
onSuccess: (value) => patchRequest("/config/password", {value})
157164
.then(() => showFeedback(undefined, false))
158165
.then(() => {
159166
currentNode !== 0 ? baseRequest("/nodes/" + currentNode + "/password", "PATCH",
160-
{password: value}) : localStorage.setItem("password", value);
167+
{password: value}).then(() => updateNodes()) : localStorage.setItem("password", value);
161168
})
162169
})
163170
}

client/src/common/components/Header/HeaderComponent.jsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {SpeedtestDialog} from "@/common/components/SpeedtestDialog";
2020
import {NodeContext} from "@/common/contexts/Node";
2121

2222
function HeaderComponent(props) {
23-
const nodes = useContext(NodeContext)[0];
23+
const findNode = useContext(NodeContext)[4];
2424
const currentNode = useContext(NodeContext)[2];
2525

2626
const [setDialog] = useContext(InputDialogContext);
@@ -82,8 +82,7 @@ function HeaderComponent(props) {
8282
if (!config.viewMode) updateVersion();
8383
}, [config]);
8484

85-
const getNodeName = () =>
86-
currentNode === "0" ? t("header.title") : nodes?.find(node => node.id === currentNode)?.name || t("header.title");
85+
const getNodeName = () => currentNode === "0" ? t("header.title") : findNode(currentNode)?.name || t("header.title");
8786

8887
if (Object.keys(config).length === 0) return <></>;
8988

client/src/common/contexts/Node/NodeContext.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ export const NodeProvider = (props) => {
2424
setCurrentNode(parseInt(node));
2525
}
2626

27+
const findNode = (nodeId) => nodes?.find(node => node.id === nodeId);
28+
2729
return (
28-
<NodeContext.Provider value={[nodes, updateNodes, currentNode, updateCurrentNode]}>
30+
<NodeContext.Provider value={[nodes, updateNodes, currentNode, updateCurrentNode, findNode]}>
2931
{props.children}
3032
</NodeContext.Provider>
3133
)

client/src/common/utils/RequestUtil.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ const getHeaders = () => {
1414

1515
// Run a plain request with all default values using the base path
1616
export const baseRequest = async (path, method = "GET", body = {}, headers = {}) => {
17+
const controller = new AbortController();
18+
setTimeout(() => controller.abort(), 10000);
1719
return await fetch("/api" + path, {
1820
headers: {...getHeaders(), ...headers}, method,
19-
body: method !== "GET" ? JSON.stringify(body) : undefined
21+
body: method !== "GET" ? JSON.stringify(body) : undefined,
22+
signal: controller.signal
2023
});
2124
}
2225

client/src/pages/Home/components/LatestTest/LatestTestComponent.jsx

+10-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {t} from "i18next";
1313

1414
function LatestTestComponent() {
1515
const status = useContext(StatusContext)[0];
16-
const [latest, setLatest] = useState({});
16+
const [latest, setLatest] = useState(null);
1717
const [latestTestTime, setLatestTestTime] = useState("N/A");
1818
const [setDialog] = useContext(InputDialogContext);
1919
const [speedtests] = useContext(SpeedtestContext);
@@ -30,6 +30,7 @@ function LatestTestComponent() {
3030
}, [latest]);
3131

3232
if (Object.entries(config).length === 0) return (<></>);
33+
if (latest === null) return (<></>);
3334

3435
return (
3536
<div className={"analyse-area " + (status.paused ? "tests-paused" : "pulse")}>
@@ -38,7 +39,8 @@ function LatestTestComponent() {
3839
<div className="container-header">
3940
<FontAwesomeIcon onClick={() => setDialog(pingInfo())} icon={faPingPongPaddleBall}
4041
className={"container-icon help-icon icon-" + getIconBySpeed(latest.ping, config.ping, false)}/>
41-
<h2 className="container-text">{t("latest.ping")}<span className="container-subtext">{t("latest.ping_unit")}</span></h2>
42+
<h2 className="container-text">{t("latest.ping")}<span
43+
className="container-subtext">{t("latest.ping_unit")}</span></h2>
4244
</div>
4345
<div className="container-main">
4446
<h2>{latest.ping === -1 ? "N/A" : latest.ping}</h2>
@@ -50,7 +52,8 @@ function LatestTestComponent() {
5052
<div className="container-header">
5153
<FontAwesomeIcon onClick={() => setDialog(downloadInfo())} icon={faArrowDown}
5254
className={"container-icon help-icon icon-" + getIconBySpeed(latest.download, config.download, true)}/>
53-
<h2 className="container-text">{t("latest.down")}<span className="container-subtext">{t("latest.speed_unit")}</span></h2>
55+
<h2 className="container-text">{t("latest.down")}<span
56+
className="container-subtext">{t("latest.speed_unit")}</span></h2>
5457
</div>
5558
<div className="container-main">
5659
<h2>{latest.download === -1 ? "N/A" : latest.download}</h2>
@@ -64,7 +67,8 @@ function LatestTestComponent() {
6467
<div className="container-header">
6568
<FontAwesomeIcon onClick={() => setDialog(uploadInfo())} icon={faArrowUp}
6669
className={"container-icon help-icon icon-" + getIconBySpeed(latest.upload, config.upload, true)}/>
67-
<h2 className="container-text">{t("latest.up")}<span className="container-subtext">{t("latest.speed_unit")}</span></h2>
70+
<h2 className="container-text">{t("latest.up")}<span
71+
className="container-subtext">{t("latest.speed_unit")}</span></h2>
6872
</div>
6973
<div className="container-main">
7074
<h2>{latest.upload === -1 ? "N/A" : latest.upload}</h2>
@@ -76,7 +80,8 @@ function LatestTestComponent() {
7680
<div className="container-header">
7781
<FontAwesomeIcon onClick={() => setDialog(latestTestInfo(latest))} icon={faClockRotateLeft}
7882
className="container-icon icon-blue help-icon"/>
79-
<h2 className="container-text">{t("latest.latest")}<span className="container-subtext">{t("latest.before")}</span></h2>
83+
<h2 className="container-text">{t("latest.latest")}<span
84+
className="container-subtext">{t("latest.before")}</span></h2>
8085
</div>
8186
<div className="container-main">
8287
<h2>{latestTestTime}</h2>

client/src/pages/Home/components/LatestTest/utils/dialogs.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export const uploadInfo = () => ({title: t("info.up.title"), description: t("inf
99

1010
export const latestTestInfo = (latest) => ({
1111
title: t("info.latest.title"),
12-
description: <Trans components={{Bold: <span className="dialog-value"/>}} values={{date: new Date(latest.created).toLocaleDateString(),
12+
description: latest.created ? <Trans components={{Bold: <span className="dialog-value"/>}} values={{date: new Date(latest.created).toLocaleDateString(),
1313
time: new Date(latest.created).toLocaleTimeString(undefined, {hour: "2-digit", minute: "2-digit"})}}>
14-
info.latest.description</Trans>,
14+
info.latest.description</Trans> : t("test.no_latest"),
1515
buttonText: t("dialog.okay")
1616
});

client/src/pages/Nodes/Nodes.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const Nodes = (props) => {
1919
{createDialogOpen && <CreateNodeDialog onClose={() => setCreateDialogOpen(false)}/>}
2020
<NodeHeader/>
2121
<div className="node-area">
22-
<NodeContainer name={t("nodes.this_server")} url={location.href} currentNode={true}
22+
<NodeContainer name={t("nodes.this_server")} url={location.host} currentNode={true}
2323
setShowNodePage={props.setShowNodePage} id={0}/>
2424

2525
{nodes.map(node => <NodeContainer {...node} key={node.id} setShowNodePage={props.setShowNodePage} />)}

client/src/pages/Nodes/styles.sass

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
border: 2px dashed #696C73
2626
border-radius: 15px
2727
cursor: pointer
28+
user-select: none
2829

2930
.node-add h1
3031
font-size: 24pt

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "myspeed",
3-
"version": "1.0.6",
3+
"version": "1.0.7",
44
"scripts": {
55
"client": "cd client && npm run dev",
66
"server": "nodemon server",

server/controller/node.js

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
const axios = require('axios');
12
const nodes = require('../models/Node');
23

34
// Gets all node entries
4-
module.exports.list = async (excludePassword) => {
5-
return await nodes.findAll({attributes: {exclude: excludePassword ? ['password'] : []}});
5+
module.exports.list = async () => {
6+
return await nodes.findAll().then((result) => result.map((node) => ({...node, password: node.password !== null})));
67
}
78

89
// Create a new node entry
@@ -28,4 +29,37 @@ module.exports.updateName = async (nodeId, name) => {
2829
// Update the password of the node entry
2930
module.exports.updatePassword = async (nodeId, password) => {
3031
return await nodes.update({password: password}, {where: {id: nodeId}});
32+
}
33+
34+
module.exports.checkNode = async (url, password) => {
35+
if (password === "none") password = undefined;
36+
const api = await axios.get(url + "/api/config", {headers: {password: password}}).catch(() => {
37+
return "INVALID_URL";
38+
});
39+
40+
if (api === "INVALID_URL" || api.status !== 200) return "INVALID_URL";
41+
42+
if (!api.data.ping) return "INVALID_URL";
43+
44+
if (api.data.viewMode) return "PASSWORD_REQUIRED";
45+
46+
return "NODE_VALID";
47+
}
48+
49+
module.exports.proxyRequest = async (url, req, res) => {
50+
const response = await axios(url, {
51+
method: req.method,
52+
headers: req.headers,
53+
data: req.method === "GET" ? undefined : JSON.stringify(req.body),
54+
signal: req.signal,
55+
validateStatus: (status) => status >= 200 && status < 400
56+
}).catch(() => "INVALID_URL");
57+
58+
if (response === "INVALID_URL")
59+
return res.status(500).json({message: "Internal server error"});
60+
61+
if (response.headers["content-disposition"])
62+
res.setHeader("content-disposition", response.headers["content-disposition"]);
63+
64+
res.status(response.status).json(response.data);
3165
}

server/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const timerTask = require('./tasks/timer');
44
const healthCheckTask = require('./tasks/healthchecks');
55

66
const app = express();
7+
8+
app.disable('x-powered-by');
9+
710
const port = process.env.port || 5216;
811

912
// Create the data folder and the servers file

server/routes/export.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ app.get("/json", password(false), async (req, res) => {
1010
app.get("/csv", password(false), async (req, res) => {
1111
res.set({"Content-Disposition": "attachment; filename=\"speedtests.csv\""});
1212
let list = await tests.list();
13+
14+
if (list.length === 0) return res.send("");
1315
let fields = Object.keys(list[0]);
1416

1517
let replacer = (key, value) => value === null ? '' : value;

0 commit comments

Comments
 (0)