From 01bbb541d168d2f59dee92eff3aa5479b23ca0b9 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 29 Aug 2022 19:54:32 +0200 Subject: [PATCH 01/57] Updated the return value of the generateRelativeTime method to "N/A" --- client/src/pages/Home/components/LatestTest/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Home/components/LatestTest/utils.js b/client/src/pages/Home/components/LatestTest/utils.js index fb40ccb0..7662ce78 100644 --- a/client/src/pages/Home/components/LatestTest/utils.js +++ b/client/src/pages/Home/components/LatestTest/utils.js @@ -14,5 +14,5 @@ export function generateRelativeTime(created) { return Math.floor(diff / 3600) === 1 ? "1 Stunde" : `${Math.floor(diff / 3600)} Stunden` } - return "Einer langen Zeit" + return "N/A" } \ No newline at end of file From aa02acd44565d01d6a975d95e207db1735db5929 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 29 Aug 2022 19:57:54 +0200 Subject: [PATCH 02/57] MySpeed now displays N/A at the latest test if no test has been made yet --- .../Home/components/LatestTest/LatestTestComponent.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Home/components/LatestTest/LatestTestComponent.jsx b/client/src/pages/Home/components/LatestTest/LatestTestComponent.jsx index 963766a3..f56e53aa 100644 --- a/client/src/pages/Home/components/LatestTest/LatestTestComponent.jsx +++ b/client/src/pages/Home/components/LatestTest/LatestTestComponent.jsx @@ -11,13 +11,15 @@ import {getIconBySpeed} from "@/common/utils/TestUtil"; function LatestTestComponent() { const status = useContext(StatusContext)[0]; - const [latest, setLatest] = useState({ping: "-", download: "-", upload: "-"}); + const [latest, setLatest] = useState({}); const [latestTestTime, setLatestTestTime] = useState("-"); const [setDialog] = useContext(DialogContext); const [speedtests] = useContext(SpeedtestContext); const config = useContext(ConfigContext)[0]; - useEffect(() => setLatest(speedtests[0]), [speedtests]); + useEffect(() => { + setLatest(Object.keys(speedtests).length !== 0 ? speedtests[0] : {ping: "N/A", download: "N/A", upload: "N/A"}); + }, [speedtests]); useEffect(() => { if (latest) setLatestTestTime(generateRelativeTime(latest.created)); @@ -25,8 +27,6 @@ function LatestTestComponent() { return () => clearInterval(interval); }, [latest]); - if (!latest) return <>; - if (Object.entries(config).length === 0) return (<>); return ( From 5629e5ffe13623d4fc08bb6f12fdfa2a0a680fee Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 29 Aug 2022 20:17:41 +0200 Subject: [PATCH 03/57] Added the styling for the TestAreaComponent.jsx (error text) --- .../pages/Home/components/TestArea/styles.sass | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 client/src/pages/Home/components/TestArea/styles.sass diff --git a/client/src/pages/Home/components/TestArea/styles.sass b/client/src/pages/Home/components/TestArea/styles.sass new file mode 100644 index 00000000..36426112 --- /dev/null +++ b/client/src/pages/Home/components/TestArea/styles.sass @@ -0,0 +1,16 @@ +@import "@/common/styles/colors" + +.error-text + margin: 0 + font-size: 26pt + font-weight: 700 + color: $darker-white + +@media (max-width: 605px) + .error-text + text-align: center + width: 28rem + +@media (max-width: 475px) + .error-text + width: 20rem \ No newline at end of file From 0cbe7611a07162cd95973a4cdeeaaa304ebd3667 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Mon, 29 Aug 2022 20:23:35 +0200 Subject: [PATCH 04/57] Added a message in thr TestAreaComponent.jsx if no tests exists --- .../src/pages/Home/components/TestArea/TestAreaComponent.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx b/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx index 0f823765..10c5fe6c 100644 --- a/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx +++ b/client/src/pages/Home/components/TestArea/TestAreaComponent.jsx @@ -3,6 +3,7 @@ import {ConfigContext} from "@/common/contexts/Config"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; import Speedtest from "../Speedtest"; import {getIconBySpeed} from "@/common/utils/TestUtil"; +import "./styles.sass"; function TestArea() { const config = useContext(ConfigContext)[0]; @@ -10,6 +11,8 @@ function TestArea() { if (Object.entries(config).length === 0) return (<>); + if (speedtests.length === 0) return

Es liegen aktuell keine Tests vor

+ return (
{speedtests.map ? speedtests.map(test => { From ac62cdcc18f79384652b9275cb6a19ffa976dba4 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 15:26:23 +0200 Subject: [PATCH 05/57] Added the unit to the placeholder in the update methods --- client/src/common/components/Dropdown/DropdownComponent.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/common/components/Dropdown/DropdownComponent.jsx b/client/src/common/components/Dropdown/DropdownComponent.jsx index b5e8aa45..64bf13f6 100644 --- a/client/src/common/components/Dropdown/DropdownComponent.jsx +++ b/client/src/common/components/Dropdown/DropdownComponent.jsx @@ -67,15 +67,15 @@ function DropdownComponent() { } const updatePing = async () => patchDialog("ping", (value) => ({ - title: "Optimalen Ping setzen (ms)", placeholder: "Ping", value + title: "Optimalen Ping setzen (ms)", placeholder: "Ping (ms)", value })); const updateDownload = async () => patchDialog("download", (value) => ({ - title: "Optimalen Down-Speed setzen (Mbit/s)", placeholder: "Down-Speed", value + title: "Optimalen Down-Speed setzen (Mbit/s)", placeholder: "Down-Speed (Mbit/s)", value })); const updateUpload = async () => patchDialog("upload", (value) => ({ - title: "Optimalen Up-Speed setzen (Mbit/s)", placeholder: "Up-Speed", value + title: "Optimalen Up-Speed setzen (Mbit/s)", placeholder: "Up-Speed (Mbit/s)", value })); From 53afd85d49bdc68219d23d64c9b8fb5294d4d013 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 15:45:22 +0200 Subject: [PATCH 06/57] Fixed a bug in the speedtest util --- server/util/speedtest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/util/speedtest.js b/server/util/speedtest.js index 95480354..8e51550b 100644 --- a/server/util/speedtest.js +++ b/server/util/speedtest.js @@ -21,7 +21,7 @@ module.exports = async (serverId, binary_path = './bin/speedtest' + (process.pla const line = buffer.toString().replace("\n", ""); if (!line.startsWith("{")) return; - let data; + let data = {}; try { data = JSON.parse(line); } catch (e) { From 521bfc1319fb608ec916e9c734dd7b21d558beba Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:13:59 +0200 Subject: [PATCH 07/57] Created an easier utility to send requests. This cleans some code up --- client/src/common/utils/RequestUtil.js | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 client/src/common/utils/RequestUtil.js diff --git a/client/src/common/utils/RequestUtil.js b/client/src/common/utils/RequestUtil.js new file mode 100644 index 00000000..0632f1fd --- /dev/null +++ b/client/src/common/utils/RequestUtil.js @@ -0,0 +1,51 @@ +const API_ROOT = "/api"; + +// Get the default headers of the request +const getHeaders = () => { + let headers = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {}; + headers['content-type'] = 'application/json'; + + return headers; +} + +// Run a plain request with all default values +export const request = async (path, method = "GET", body = {}, headers = {}) => { + return await fetch(API_ROOT + path, { + headers: {...getHeaders(), ...headers}, method, + body: method !== "GET" ? JSON.stringify(body) : undefined + }); +} + +// Run a GET request and get the json of the response +export const jsonRequest = async (path, headers = {}) => { + return (await request(path, "GET", null, headers)).json(); +} + +// Run a POST request and post some values +export const postRequest = async (path, body, headers = {}) => { + return await request(path, "POST", body, headers); +} + +// Run a PATCH request update a resource +export const patchRequest = async (path, body, headers = {}) => { + return await request(path, "PATCH", body, headers); +} + +// Run a DELETE request and delete a resource +export const deleteRequest = async (path, body, headers = {}) => { + return await request(path, "DELETE", body, headers); +} + +// Download a specific file from the response output +export const downloadRequest = async (path, body, headers = {}) => { + const file = await request(path, "GET", body, headers); + let element = document.createElement('a'); + let url = file.headers.get('Content-Disposition').split('filename=')[1]; + element.setAttribute("download", url.replaceAll("\"", "")); + + const blob = await file.blob(); + element.href = window.URL.createObjectURL(blob); + document.body.appendChild(element); + element.click(); + element.remove(); +} \ No newline at end of file From 7b282df466ccb290ffeec8bf34478a55d288a0f9 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:14:26 +0200 Subject: [PATCH 08/57] Removed the deprecation warning in the client --- client/package-lock.json | 34 ---------------------------------- client/package.json | 1 - 2 files changed, 35 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index f637ee10..cafae276 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,7 +8,6 @@ "name": "client", "version": "1.0.3", "dependencies": { - "@fortawesome/fontawesome-free-solid": "^5.0.13", "@fortawesome/fontawesome-svg-core": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/react-fontawesome": "^0.2.0", @@ -436,26 +435,6 @@ "node": ">=12" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.7.tgz", - "integrity": "sha512-ego8jRVSHfq/iq4KRZJKQeUAdi3ZjGNrqw4oPN3fNdvTBnLCSntwVCnc37bsAJP9UB8MhrTfPnZYxkv2vpS4pg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-free-solid": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz", - "integrity": "sha512-b+krVnqkdDt52Yfev0x0ZZgtxBQsLw00Zfa3uaVWIDzpNZVtrEXuxldUSUaN/ihgGhSNi8VpvDAdNPVgCKOSxw==", - "deprecated": "This package is deprecated. See https://git.io/fNCzJ for information about upgrading.", - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.1.7" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@fortawesome/fontawesome-svg-core": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.2.tgz", @@ -2000,19 +1979,6 @@ "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", "optional": true }, - "@fortawesome/fontawesome-common-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.7.tgz", - "integrity": "sha512-ego8jRVSHfq/iq4KRZJKQeUAdi3ZjGNrqw4oPN3fNdvTBnLCSntwVCnc37bsAJP9UB8MhrTfPnZYxkv2vpS4pg==" - }, - "@fortawesome/fontawesome-free-solid": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz", - "integrity": "sha512-b+krVnqkdDt52Yfev0x0ZZgtxBQsLw00Zfa3uaVWIDzpNZVtrEXuxldUSUaN/ihgGhSNi8VpvDAdNPVgCKOSxw==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.1.7" - } - }, "@fortawesome/fontawesome-svg-core": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.2.tgz", diff --git a/client/package.json b/client/package.json index 23a4e67e..93ebe3ff 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,6 @@ "preview": "vite preview" }, "dependencies": { - "@fortawesome/fontawesome-free-solid": "^5.0.13", "@fortawesome/fontawesome-svg-core": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/react-fontawesome": "^0.2.0", From 1ee901e40671356accc4da706aa6454c5d82fbdd Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:28:45 +0200 Subject: [PATCH 09/57] Moved the options of the DropdownComponent.jsx into a separate file --- .../components/Dropdown/utils/options.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 client/src/common/components/Dropdown/utils/options.js diff --git a/client/src/common/components/Dropdown/utils/options.js b/client/src/common/components/Dropdown/utils/options.js new file mode 100644 index 00000000..9159a3ea --- /dev/null +++ b/client/src/common/components/Dropdown/utils/options.js @@ -0,0 +1,19 @@ +export const timeOptions = { + 1: "24 Stunden (Standard)", + 2: "2 Tage (Insgesamt)", + 3: "7 Tage (Durchschnitt)", + 4: "30 Tage (Durchschnitt)" +}; + +export const selectOptions = { + "* * * * *": "Durchgehend (jede Minute)", + "0,30 * * * *": "Sehr häufig (alle 30 Minuten)", + "0 * * * *": "Standard (jede Stunde)", + "0 0,3,6,9,12,15,18,21 * * *": "Selten (alle 3 Stunden)", + "0 0,6,12,18 * * *": "Sehr selten (alle 6 Stunden)" +} + +export const exportOptions = { + json: "JSON-Datei", + csv: "CSV-Datei" +} \ No newline at end of file From b95a6934f653a66a815521ea7a833f891d0250b9 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:28:56 +0200 Subject: [PATCH 10/57] Moved the dialog infos of the DropdownComponent.jsx into a separate file --- .../common/components/Dropdown/utils/infos.jsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 client/src/common/components/Dropdown/utils/infos.jsx diff --git a/client/src/common/components/Dropdown/utils/infos.jsx b/client/src/common/components/Dropdown/utils/infos.jsx new file mode 100644 index 00000000..6b78fac0 --- /dev/null +++ b/client/src/common/components/Dropdown/utils/infos.jsx @@ -0,0 +1,16 @@ +export const healthChecksInfo = <>MySpeed verwendet Healthchecks, + um dich zu benachrichtigen, wenn dein Internet ausfällt. Um dies zu aktivieren, setze deine Ping URL in das Textfeld + ein. Mehr dazu hier; + +export const creditsInfo = <>MySpeed wird von GNMYT bereitgestellt und verwendet die Speedtest-CLI von Ookla.; + +export const recommendationsError = <>Du musst mindestens 10 Tests machen, damit ein Durchschnitt ermittelt werden kann. + Ob die Tests manuell oder automatisch durchgeführt wurden ist egal.; + +export const recommendationsInfo = (ping, download, upload) => <>Anhand der letzten 10 Testergebnisse wurde + festgestellt, dass der optimale Ping bei {ping} ms, der Download bei {download} Mbit/s und der Upload bei {upload} Mbit/s liegt.
Orientiere dich am besten an deinem Internetvertrag + und übernehme es nur, wenn es mit dem übereinstimmt.; \ No newline at end of file From 1c21adb556b4d42ae1adbb3c13ecdcc7be13be7d Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:29:32 +0200 Subject: [PATCH 11/57] Cleaned up the code of the DropdownComponent.jsx --- .../components/Dropdown/DropdownComponent.jsx | 268 +++++------------- 1 file changed, 78 insertions(+), 190 deletions(-) diff --git a/client/src/common/components/Dropdown/DropdownComponent.jsx b/client/src/common/components/Dropdown/DropdownComponent.jsx index 64bf13f6..2b700a42 100644 --- a/client/src/common/components/Dropdown/DropdownComponent.jsx +++ b/client/src/common/components/Dropdown/DropdownComponent.jsx @@ -2,19 +2,16 @@ import React, {useContext, useEffect} from "react"; import "./styles.sass"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { - faArrowDown, - faArrowUp, faCalendarDays, faCircleNodes, faClock, faClose, faFileExport, - faGear, faInfo, - faKey, - faPause, - faPingPongPaddleBall, - faPlay, - faServer, faWandMagicSparkles + faArrowDown, faArrowUp, faCalendarDays, faCircleNodes, faClock, faClose, faFileExport, + faGear, faInfo, faKey, faPause, faPingPongPaddleBall, faPlay, faServer, faWandMagicSparkles } from "@fortawesome/free-solid-svg-icons"; import {ConfigContext} from "@/common/contexts/Config"; import {StatusContext} from "@/common/contexts/Status"; import {DialogContext} from "@/common/contexts/Dialog"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; +import {downloadRequest, jsonRequest, patchRequest, postRequest} from "@/common/utils/RequestUtil"; +import {creditsInfo, healthChecksInfo, recommendationsError, recommendationsInfo} from "@/common/components/Dropdown/utils/infos"; +import {exportOptions, selectOptions, timeOptions} from "@/common/components/Dropdown/utils/options"; let icon; @@ -31,15 +28,11 @@ export const toggleDropdown = (setIcon) => { } function DropdownComponent() { - const [config, reloadConfig] = useContext(ConfigContext); const [status, updateStatus] = useContext(StatusContext); const updateTests = useContext(SpeedtestContext)[1]; const [setDialog] = useContext(DialogContext); - let headers = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {}; - headers['content-type'] = 'application/json'; - useEffect(() => { const onPress = event => { if (event.code === "Escape" && !document.getElementById("dropdown").classList.contains("dropdown-invisible")) @@ -54,15 +47,13 @@ function DropdownComponent() { onSuccess: () => reload ? reloadConfig() : "", onClose: () => reloadConfig() }); - const patchDialog = (key, dialog, toggle = true) => { + const patchDialog = async (key, dialog, toggle = true) => { if (toggle) toggleDropdown(); setDialog({ - ...dialog(config[key]), - onSuccess: value => { - fetch("/api/config/" + key, {headers, method: "PATCH", body: JSON.stringify({value})}) - .then(res => showFeedback(!res.ok ? "Deine Änderungen wurden nicht übernommen. Überprüfe deine Eingabe." : undefined)); - } + ...(await dialog(config[key])), + onSuccess: value => patchRequest(`/config/${key}`, {value}) + .then(res => showFeedback(!res.ok ? "Deine Änderungen wurden nicht übernommen. Überprüfe deine Eingabe." : undefined)) }) } @@ -78,57 +69,37 @@ function DropdownComponent() { title: "Optimalen Up-Speed setzen (Mbit/s)", placeholder: "Up-Speed (Mbit/s)", value })); - const updatePassword = async () => { toggleDropdown(); setDialog({ title: "Neues Passwort festlegen", placeholder: "Neues Passwort", type: "password", - unsetButton: localStorage.getItem("password") != null, - unsetButtonText: "Sperre aufheben", - onClear: () => { - fetch("/api/config/password", {headers: headers, method: "PATCH", body: JSON.stringify({value: "none"})}) - .then(() => showFeedback(<>Die Passwortsperre wurde aufgehoben und das festgelegte Passwort wurde entfernt., false)); - localStorage.removeItem("password"); - }, - onSuccess: value => { - fetch("/api/config/password", {headers: headers, method: "PATCH", body: JSON.stringify({value: value})}) - .then(() => showFeedback(undefined, false)); - localStorage.setItem("password", value); - } + unsetButton: localStorage.getItem("password") != null ? "Sperre aufheben" : undefined, + onClear: () => patchRequest("/config/password", {value: "none"}) + .then(() => showFeedback(<>Die Passwortsperre wurde aufgehoben und das festgelegte Passwort wurde + entfernt., false)) + .then(() => localStorage.removeItem("password")), + onSuccess: (value) => patchRequest("/config/password", {value}) + .then(() => showFeedback(undefined, false)) + .then(() => localStorage.setItem("password", value)) }) } - const updateServer = () => { - toggleDropdown(); - - let servers = {}; - fetch("/api/info/server", {headers: headers}) - .then(res => res.json()) - .then(json => servers = json) - .then(() => fetch("/api/config/serverId", {headers: headers}).then(res => res.json()) - .then(async server => setDialog({ - title: "Speedtest-Server setzen", - select: true, - selectOptions: servers, - value: server.value, - unsetButton: true, - unsetButtonText: "Manuell festlegen", - onClear: () => updateServerManually(), - onSuccess: value => { - fetch("/api/config/serverId", {headers: headers, method: "PATCH", body: JSON.stringify({value: value})}) - .then(() => showFeedback(undefined, false)); - } - }))); - } + const updateServer = () => patchDialog("serverId", async (value) => ({ + title: "Speedtest-Server setzen", + select: true, + selectOptions: await jsonRequest("/info/server"), + unsetButton: "Manuell festlegen", + onClear: updateServerManually, + value + })); const updateServerManually = () => patchDialog("serverId", (value) => ({ title: "Speedtest-Server setzen", placeholder: "Server-ID", type: "number", value: value, }), false); - - function togglePause() { + const togglePause = () => { toggleDropdown(); if (!status.paused) { setDialog({ @@ -136,58 +107,35 @@ function DropdownComponent() { placeholder: "Stunden", type: "number", buttonText: "Pausieren", - unsetButton: true, - unsetButtonText: "Manuell freigeben", - onClear: async () => { - fetch("/api/speedtests/pause", {headers: headers, method: "POST", - body: JSON.stringify({resumeIn: -1})}).then(() => updateStatus()); - }, - onSuccess: async hours => { - fetch("/api/speedtests/pause", {headers: headers, method: "POST", - body: JSON.stringify({resumeIn: hours})}).then(() => updateStatus()); - } + unsetButton: "Manuell freigeben", + onClear: () => postRequest("/speedtests/pause", {resumeIn: -1}).then(updateStatus), + onSuccess: (hours) => postRequest("/speedtests/pause", {resumeIn: hours}).then(updateStatus) }); - } else fetch("/api/speedtests/continue", {headers: headers, method: "POST"}) - .then(() => updateStatus()); + } else postRequest("/speedtests/continue").then(updateStatus); } const showCredits = () => { toggleDropdown(); - setDialog({ - title: "MySpeed", - description: <>MySpeed wird - von GNMYT bereitgestellt - und verwendet die Speedtest-CLI von Ookla., - buttonText: "Schließen" - }); + setDialog({title: "MySpeed", description: creditsInfo, buttonText: "Schließen"}); } const recommendedSettings = async () => { toggleDropdown(); - fetch("/api/recommendations", {headers: headers}).then(res => res.json()) - .then(values => values.message !== undefined ? setDialog({ - title: "Automatische Empfehlungen", - description: <>Du musst mindestens 10 Tests machen, damit ein Durchschnitt ermittelt werden kann. Ob die - Tests manuell oder automatisch durchgeführt wurden ist egal. - , - buttonText: "Okay" - }) : setDialog({ + const result = await jsonRequest("/recommendations"); + + if (!result.message) { + setDialog({ title: "Automatische Empfehlungen setzen?", - description: <>Anhand der letzten 10 Testergebnisse wurde festgestellt, dass der optimale Ping bei - {values.ping} ms, der Download bei {values.download} Mbit/s - und der Upload bei {values.upload} Mbit/s liegt.
- Orientiere dich am besten an deinem Internetvertrag und übernehme es nur, wenn es mit dem - übereinstimmt., + description: recommendationsInfo(result.ping, result.download, result.upload), buttonText: "Ja, übernehmen", onSuccess: async () => { - await fetch("/api/config/ping", {headers: headers, method: "PATCH", body: JSON.stringify({value: values.ping})}); - await fetch("/api/config/download", {headers: headers, method: "PATCH", body: JSON.stringify({value: values.download})}); - await fetch("/api/config/upload", {headers: headers, method: "PATCH", body: JSON.stringify({value: values.upload})}); + await patchRequest("/config/ping", {value: result.ping}); + await patchRequest("/config/download", {value: result.download}); + await patchRequest("/config/upload", {value: result.upload}); showFeedback(); } - })); + }); + } else setDialog({title: "Automatische Empfehlungen", description: recommendationsError, buttonText: "Okay"}); } function exportDialog() { @@ -197,29 +145,15 @@ function DropdownComponent() { title: "Speedtests exportieren", buttonText: "Herunterladen", value: "json", - selectOptions: { - json: "JSON-Datei", - csv: "CSV-Datei" - }, - onSuccess: value => { - fetch("/api/export/" + value, {headers: headers}) - .then(async res => { - let element = document.createElement('a'); - let url = res.headers.get('Content-Disposition').split('filename=')[1]; - element.setAttribute("download", url.replaceAll("\"", "")); - res.blob().then(async blob => { - element.href = window.URL.createObjectURL(blob); - document.body.appendChild(element); - element.click(); - element.remove(); - }); - }); - } + selectOptions: exportOptions, + onSuccess: value => downloadRequest("/export/" + value) }); } const updateCronManually = () => patchDialog("cron", (value) => ({ - title: <>Test-Häufigkeit einstellen ?, placeholder: "Cron-Regel", value: value, + title: <>Test-Häufigkeit einstellen ?, + placeholder: "Cron-Regel", + value: value, }), false); const updateCron = async () => { @@ -227,21 +161,11 @@ function DropdownComponent() { setDialog({ title: "Test-Häufigkeit einstellen", select: true, - selectOptions: { - "* * * * *": "Durchgehend (jede Minute)", - "0,30 * * * *": "Sehr häufig (alle 30 Minuten)", - "0 * * * *": "Standard (jede Stunde)", - "0 0,3,6,9,12,15,18,21 * * *": "Selten (alle 3 Stunden)", - "0 0,6,12,18 * * *": "Sehr selten (alle 6 Stunden)" - }, + selectOptions: selectOptions, value: config.cron, - unsetButton: true, - unsetButtonText: "Manuell festlegen", + unsetButton: "Manuell festlegen", onClear: updateCronManually, - onSuccess: value => { - fetch("/api/config/cron", {headers: headers, method: "PATCH", body: JSON.stringify({value: value})}) - .then(() => showFeedback()); - } + onSuccess: value => patchRequest("/config/cron", {value: value}).then(showFeedback) }); } @@ -250,12 +174,7 @@ function DropdownComponent() { setDialog({ title: "Zeige Tests der letzten ...", select: true, - selectOptions: { - 1: "24 Stunden (Standard)", - 2: "2 Tage (Insgesamt)", - 3: "7 Tage (Durchschnitt)", - 4: "30 Tage (Durchschnitt)" - }, + selectOptions: timeOptions, value: localStorage.getItem("testTime") || 1, onSuccess: value => { localStorage.setItem("testTime", value); @@ -267,9 +186,7 @@ function DropdownComponent() { const showIntegrationInfo = () => setDialog({ title: "HealthChecks Integration", - description: <>MySpeed verwendet Healthchecks, um - dich zu benachrichtigen, wenn dein Internet ausfällt. Um dies zu aktivieren, setze deine Ping URL in das - Textfeld ein. Mehr dazu hier, + description: healthChecksInfo, buttonText: "Okay" }); @@ -277,73 +194,44 @@ function DropdownComponent() { title: <>HealthChecks Integration ?, placeholder: "HealthChecks Server URL", value, buttonText: "Aktualisieren", - unsetButton: !value.includes(""), - unsetButtonText: "Deaktivieren", - onClear: () => fetch("/api/config/healthChecksUrl", {headers: headers, method: "PATCH", - body: JSON.stringify({value: "https://hc-ping.com/"}) - }).then(() => showFeedback(<>Die Healthchecks wurden deaktiviert)) + unsetButton: !value.includes("") ? "Deaktivieren" : undefined, + onClear: () => patchDialog("/config/healthChecksUrl", {value: "https://hc-ping.com/"}) + .then(() => showFeedback(<>Die Healthchecks wurden deaktiviert)) })); + const options = [ + {run: updatePing, icon: faPingPongPaddleBall, text: "Optimaler Ping"}, + {run: updateUpload, icon: faArrowUp, text: "Optimaler Up-Speed"}, + {run: updateDownload, icon: faArrowDown, text: "Optimaler Down-Speed"}, + {run: recommendedSettings, icon: faWandMagicSparkles, text: "Optimale Werte"}, + {hr: true, key: 1}, + {run: updateServer, icon: faServer, text: "Server wechseln"}, + {run: updatePassword, icon: faKey, text: "Passwort ändern"}, + {run: updateCron, icon: faClock, text: "Häufigkeit einstellen"}, + {run: updateTime, icon: faCalendarDays, text: "Zeitraum festlegen"}, + {run: exportDialog, icon: faFileExport, text: "Tests exportieren"}, + {run: togglePause, icon: status.paused ? faPlay : faPause, text: "Tests " + (status.paused ? "fortsetzen" : "pausieren")}, + {run: updateIntegration, icon: faCircleNodes, text: "Healthchecks"}, + {run: showCredits, icon: faInfo, text: "Info"} + ]; + return ( ) } -export default DropdownComponent; +export default DropdownComponent; \ No newline at end of file From ceff77829fbf24d9e1ea09c9be41c374a3ce0d2c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:30:16 +0200 Subject: [PATCH 12/57] Moved unsetButtonText -> unsetButton in DialogContext.jsx --- client/src/common/contexts/Dialog/DialogContext.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/common/contexts/Dialog/DialogContext.jsx b/client/src/common/contexts/Dialog/DialogContext.jsx index 9cd0f72a..e2c0c88d 100644 --- a/client/src/common/contexts/Dialog/DialogContext.jsx +++ b/client/src/common/contexts/Dialog/DialogContext.jsx @@ -82,7 +82,7 @@ const Dialog = ({dialog, setDialog}) => {
{dialog.unsetButton ? : ""} + onClick={clear}>{dialog.unsetButton || "Entfernen"} : ""}
From 63ebf171cac97b98712b212bc62cd1a14c56a567 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 17:44:18 +0200 Subject: [PATCH 13/57] Changed the order of the functions in the DropdownComponent.jsx --- .../components/Dropdown/DropdownComponent.jsx | 142 +++++++++--------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/client/src/common/components/Dropdown/DropdownComponent.jsx b/client/src/common/components/Dropdown/DropdownComponent.jsx index 2b700a42..088d133f 100644 --- a/client/src/common/components/Dropdown/DropdownComponent.jsx +++ b/client/src/common/components/Dropdown/DropdownComponent.jsx @@ -61,64 +61,14 @@ function DropdownComponent() { title: "Optimalen Ping setzen (ms)", placeholder: "Ping (ms)", value })); - const updateDownload = async () => patchDialog("download", (value) => ({ - title: "Optimalen Down-Speed setzen (Mbit/s)", placeholder: "Down-Speed (Mbit/s)", value - })); - const updateUpload = async () => patchDialog("upload", (value) => ({ title: "Optimalen Up-Speed setzen (Mbit/s)", placeholder: "Up-Speed (Mbit/s)", value })); - const updatePassword = async () => { - toggleDropdown(); - setDialog({ - title: "Neues Passwort festlegen", - placeholder: "Neues Passwort", - type: "password", - unsetButton: localStorage.getItem("password") != null ? "Sperre aufheben" : undefined, - onClear: () => patchRequest("/config/password", {value: "none"}) - .then(() => showFeedback(<>Die Passwortsperre wurde aufgehoben und das festgelegte Passwort wurde - entfernt., false)) - .then(() => localStorage.removeItem("password")), - onSuccess: (value) => patchRequest("/config/password", {value}) - .then(() => showFeedback(undefined, false)) - .then(() => localStorage.setItem("password", value)) - }) - } - - const updateServer = () => patchDialog("serverId", async (value) => ({ - title: "Speedtest-Server setzen", - select: true, - selectOptions: await jsonRequest("/info/server"), - unsetButton: "Manuell festlegen", - onClear: updateServerManually, - value + const updateDownload = async () => patchDialog("download", (value) => ({ + title: "Optimalen Down-Speed setzen (Mbit/s)", placeholder: "Down-Speed (Mbit/s)", value })); - const updateServerManually = () => patchDialog("serverId", (value) => ({ - title: "Speedtest-Server setzen", placeholder: "Server-ID", type: "number", value: value, - }), false); - - const togglePause = () => { - toggleDropdown(); - if (!status.paused) { - setDialog({ - title: "Speedtests pausieren für...", - placeholder: "Stunden", - type: "number", - buttonText: "Pausieren", - unsetButton: "Manuell freigeben", - onClear: () => postRequest("/speedtests/pause", {resumeIn: -1}).then(updateStatus), - onSuccess: (hours) => postRequest("/speedtests/pause", {resumeIn: hours}).then(updateStatus) - }); - } else postRequest("/speedtests/continue").then(updateStatus); - } - - const showCredits = () => { - toggleDropdown(); - setDialog({title: "MySpeed", description: creditsInfo, buttonText: "Schließen"}); - } - const recommendedSettings = async () => { toggleDropdown(); const result = await jsonRequest("/recommendations"); @@ -138,24 +88,36 @@ function DropdownComponent() { } else setDialog({title: "Automatische Empfehlungen", description: recommendationsError, buttonText: "Okay"}); } - function exportDialog() { + const updateServer = () => patchDialog("serverId", async (value) => ({ + title: "Speedtest-Server setzen", + select: true, + selectOptions: await jsonRequest("/info/server"), + unsetButton: "Manuell festlegen", + onClear: updateServerManually, + value + })); + + const updateServerManually = () => patchDialog("serverId", (value) => ({ + title: "Speedtest-Server setzen", placeholder: "Server-ID", type: "number", value: value, + }), false); + + const updatePassword = async () => { toggleDropdown(); setDialog({ - select: true, - title: "Speedtests exportieren", - buttonText: "Herunterladen", - value: "json", - selectOptions: exportOptions, - onSuccess: value => downloadRequest("/export/" + value) - }); + title: "Neues Passwort festlegen", + placeholder: "Neues Passwort", + type: "password", + unsetButton: localStorage.getItem("password") != null ? "Sperre aufheben" : undefined, + onClear: () => patchRequest("/config/password", {value: "none"}) + .then(() => showFeedback(<>Die Passwortsperre wurde aufgehoben und das festgelegte Passwort wurde + entfernt., false)) + .then(() => localStorage.removeItem("password")), + onSuccess: (value) => patchRequest("/config/password", {value}) + .then(() => showFeedback(undefined, false)) + .then(() => localStorage.setItem("password", value)) + }) } - const updateCronManually = () => patchDialog("cron", (value) => ({ - title: <>Test-Häufigkeit einstellen ?, - placeholder: "Cron-Regel", - value: value, - }), false); - const updateCron = async () => { toggleDropdown(); setDialog({ @@ -169,6 +131,12 @@ function DropdownComponent() { }); } + const updateCronManually = () => patchDialog("cron", (value) => ({ + title: <>Test-Häufigkeit einstellen ?, + placeholder: "Cron-Regel", + value: value, + }), false); + const updateTime = async () => { toggleDropdown(); setDialog({ @@ -184,11 +152,32 @@ function DropdownComponent() { }); } - const showIntegrationInfo = () => setDialog({ - title: "HealthChecks Integration", - description: healthChecksInfo, - buttonText: "Okay" - }); + function exportDialog() { + toggleDropdown(); + setDialog({ + select: true, + title: "Speedtests exportieren", + buttonText: "Herunterladen", + value: "json", + selectOptions: exportOptions, + onSuccess: value => downloadRequest("/export/" + value) + }); + } + + const togglePause = () => { + toggleDropdown(); + if (!status.paused) { + setDialog({ + title: "Speedtests pausieren für...", + placeholder: "Stunden", + type: "number", + buttonText: "Pausieren", + unsetButton: "Manuell freigeben", + onClear: () => postRequest("/speedtests/pause", {resumeIn: -1}).then(updateStatus), + onSuccess: (hours) => postRequest("/speedtests/pause", {resumeIn: hours}).then(updateStatus) + }); + } else postRequest("/speedtests/continue").then(updateStatus); + } const updateIntegration = async () => patchDialog("healthChecksUrl", (value) => ({ title: <>HealthChecks Integration ?, @@ -199,6 +188,17 @@ function DropdownComponent() { .then(() => showFeedback(<>Die Healthchecks wurden deaktiviert)) })); + const showIntegrationInfo = () => setDialog({ + title: "HealthChecks Integration", + description: healthChecksInfo, + buttonText: "Okay" + }); + + const showCredits = () => { + toggleDropdown(); + setDialog({title: "MySpeed", description: creditsInfo, buttonText: "Schließen"}); + } + const options = [ {run: updatePing, icon: faPingPongPaddleBall, text: "Optimaler Ping"}, {run: updateUpload, icon: faArrowUp, text: "Optimaler Up-Speed"}, From 39a2366e49861a0836572dfff441be7930983d23 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:05:09 +0200 Subject: [PATCH 14/57] Moved SpeedtestComponent.jsx tooltips -> tooltips.js --- client/src/pages/Home/components/Speedtest/utils/tooltips.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 client/src/pages/Home/components/Speedtest/utils/tooltips.js diff --git a/client/src/pages/Home/components/Speedtest/utils/tooltips.js b/client/src/pages/Home/components/Speedtest/utils/tooltips.js new file mode 100644 index 00000000..45046857 --- /dev/null +++ b/client/src/pages/Home/components/Speedtest/utils/tooltips.js @@ -0,0 +1,5 @@ +export const tooltips = { + custom: "Benutzerdefiniert", + average: "Durchschnitt", + auto: "Automatisiert", +} \ No newline at end of file From 5c7334c234a4b7634ef09097d68152685915a93b Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:05:20 +0200 Subject: [PATCH 15/57] Moved SpeedtestComponent.jsx errors -> errors.js --- .../pages/Home/components/Speedtest/utils/errors.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 client/src/pages/Home/components/Speedtest/utils/errors.js diff --git a/client/src/pages/Home/components/Speedtest/utils/errors.js b/client/src/pages/Home/components/Speedtest/utils/errors.js new file mode 100644 index 00000000..c97294a4 --- /dev/null +++ b/client/src/pages/Home/components/Speedtest/utils/errors.js @@ -0,0 +1,10 @@ +export const errors = { + "Network unreachable": "Die Internetverbindung scheint unterbrochen gewesen zu sein", + "Timeout occurred in connect": "Der Test hat zu lange gedauert und wurde abgebrochen", + "permission denied": "MySpeed hat keine Berechtigung, diesen Test zu starten", + "Resource temporarily unavailable": "Der Test konnte nicht durchgeführt werden, da die Ressource vorübergehend nicht verfügbar ist", + "No route to host": "Der Test konnte nicht durchgeführt werden, da keine Route zum Host existiert", + "Connection refused": "Der Test konnte nicht durchgeführt werden, da die Verbindung abgelehnt wurde", + "timed out": "Der Test konnte nicht durchgeführt werden, da die Verbindung zu lange gedauert hat", + "Could not retrieve or read configuration": "Die Konfigurationsdatei konnte nicht geladen werden", +} \ No newline at end of file From cd64a0b30bf22adb6b9980c856ca718959209ba0 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:05:34 +0200 Subject: [PATCH 16/57] Moved SpeedtestComponent.jsx infos -> infos.js --- .../pages/Home/components/Speedtest/utils/infos.jsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 client/src/pages/Home/components/Speedtest/utils/infos.jsx diff --git a/client/src/pages/Home/components/Speedtest/utils/infos.jsx b/client/src/pages/Home/components/Speedtest/utils/infos.jsx new file mode 100644 index 00000000..0d28a597 --- /dev/null +++ b/client/src/pages/Home/components/Speedtest/utils/infos.jsx @@ -0,0 +1,13 @@ +import React from "react"; + +export const averageResultDialog = (timeString, props) => <>{props.amount} Tests haben + ergeben, dass am {timeString} eine durchschnittliche Downloadgeschwindigkeit + von {props.down} Mbit/s und eine Upload-geschwindigkeit von {props.up} Mbit/s bestand. Die Tests dauerten im Durchschnitt {props.duration} Sekunden.; + +export const resultDialog = (props) => <>Dieser Test erreichte eine maximale Downloadgeschwindigkeit von {props.down} Mbit/s und eine maximale Uploadgeschwindigkeit von {props.up} Mbit/s. Er wurde {props.type === "custom" ? "von dir" : "automatisch"} angelegt und hat {props.duration} Sekunden gedauert. \ No newline at end of file From 2384d8c2853e2c147c63bb887658b97819184d20 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:06:54 +0200 Subject: [PATCH 17/57] Cleaned up the code in the SpeedtestComponent.jsx --- .../Speedtest/SpeedtestComponent.jsx | 79 +++++-------------- 1 file changed, 21 insertions(+), 58 deletions(-) diff --git a/client/src/pages/Home/components/Speedtest/SpeedtestComponent.jsx b/client/src/pages/Home/components/Speedtest/SpeedtestComponent.jsx index ad408a6e..95ca487e 100644 --- a/client/src/pages/Home/components/Speedtest/SpeedtestComponent.jsx +++ b/client/src/pages/Home/components/Speedtest/SpeedtestComponent.jsx @@ -1,91 +1,54 @@ import React, {useContext} from "react"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { - faArrowDown, - faArrowUp, - faClockRotateLeft, - faClose, faInfo, - faPingPongPaddleBall + faArrowDown, faArrowUp, faClockRotateLeft, faClose, + faInfo, faPingPongPaddleBall } from "@fortawesome/free-solid-svg-icons"; import {DialogContext} from "@/common/contexts/Dialog"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; +import {deleteRequest} from "@/common/utils/RequestUtil"; import "./styles.sass"; - -const errors = { - "Network unreachable": "Die Internetverbindung scheint unterbrochen gewesen zu sein", - "Timeout occurred in connect": "Der Test hat zu lange gedauert und wurde abgebrochen", - "permission denied": "MySpeed hat keine Berechtigung, diesen Test zu starten", - "Resource temporarily unavailable": "Der Test konnte nicht durchgeführt werden, da die Ressource vorübergehend nicht verfügbar ist", - "No route to host": "Der Test konnte nicht durchgeführt werden, da keine Route zum Host existiert", - "Connection refused": "Der Test konnte nicht durchgeführt werden, da die Verbindung abgelehnt wurde", - "timed out": "Der Test konnte nicht durchgeführt werden, da die Verbindung zu lange gedauert hat", - "Could not retrieve or read configuration": "Die Konfigurationsdatei konnte nicht geladen werden", -} - -const tooltips = { - custom: "Benutzerdefiniert", - average: "Durchschnitt", - auto: "Automatisiert", -} +import {averageResultDialog, resultDialog} from "@/pages/Home/components/Speedtest/utils/infos"; +import {errors} from "@/pages/Home/components/Speedtest/utils/errors"; +import {tooltips} from "@/pages/Home/components/Speedtest/utils/tooltips"; function SpeedtestComponent(props) { - const [setDialog] = useContext(DialogContext); const updateTests = useContext(SpeedtestContext)[1]; - let passwordHeaders = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {} - let errorMessage = "Unbekannter Fehler: " + props.error; - let timeString; - if (props.type === "average") { - timeString = String(props.time.getDate()).padStart(2, '0') + "." + String(props.time.getMonth() + 1).padStart(2, '0'); - } else { - timeString = String(props.time.getHours()).padStart(2, '0') + ":" + String(props.time.getMinutes()).padStart(2, '0'); - } + let isAverage = props.type === "average"; + let timeString = (String(isAverage ? props.time.getDate() : props.time.getHours()).padStart(2, '0')) + (isAverage ? "." : ":") + + (String(isAverage ? (props.time.getMonth() + 1) : props.time.getMinutes()).padStart(2, '0')); if (props.error) { for (let errorsKey in errors) if (props.error.includes(errorsKey)) errorMessage = errors[errorsKey]; } - const showErrorDialog = () => { - setDialog({ - title: "Test fehlgeschlagen", - description: errorMessage + ". Bitte überprüfe weitestgehend, ob das öfters passiert.", - buttonText: "Okay", - unsetButton: true, - unsetButtonText: "Test löschen", - onClear: () => fetch("/api/speedtests/" + props.id, {headers: passwordHeaders, method: "DELETE"}) - .then(updateTests) - }); - } + const showErrorDialog = () => setDialog({ + title: "Test fehlgeschlagen", + description: errorMessage + ". Bitte überprüfe weitestgehend, ob das öfters passiert.", + buttonText: "Okay", + unsetButton: "Test löschen", + onClear: () => deleteRequest(`/speedtests/${props.id}`).then(updateTests) + }); const showInfoDialog = () => { if (props.type === "average") { setDialog({ title: "Durchschnittsgeschwindigkeit", buttonText: "Okay", - description: <>{props.amount} Tests haben ergeben, dass am {timeString} eine durchschnittliche Downloadgeschwindigkeit von {props.down} Mbit/s und eine Upload-geschwindigkeit von {props.up} Mbit/s bestand. Die Tests dauerten im Durchschnitt {props.duration} Sekunden. + description: averageResultDialog(timeString, props) }); } else { setDialog({ title: "Testergebnis", - description: <>Dieser Test erreichte eine maximale Downloadgeschwindigkeit von {props.down} Mbit/s - und eine maximale Uploadgeschwindigkeit von {props.up} Mbit/s. - Er wurde {props.type === "custom" - ? "von dir" : "automatisch"} angelegt und hat {props.duration} Sekunden gedauert., + description: resultDialog(props), buttonText: "Okay", - unsetButton: true, - unsetButtonText: "Test löschen", - onClear: () => fetch("/api/speedtests/" + props.id, {headers: passwordHeaders, method: "DELETE"}) - .then(updateTests) + unsetButton: "Test löschen", + onClear: () => deleteRequest(`/speedtests/${props.id}`).then(updateTests) }); } } @@ -99,7 +62,7 @@ function SpeedtestComponent(props) { onClick={props.error ? showErrorDialog : showInfoDialog}/> {tooltips[props.type]} -

{(props.type === "average" ? "Am " : "Um ") + timeString}

+

{(isAverage ? "Am " : "Um ") + timeString}

Date: Tue, 30 Aug 2022 18:26:57 +0200 Subject: [PATCH 18/57] Moved HeaderComponent.jsx infos -> infos.js --- client/src/common/components/Header/utils/infos.jsx | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 client/src/common/components/Header/utils/infos.jsx diff --git a/client/src/common/components/Header/utils/infos.jsx b/client/src/common/components/Header/utils/infos.jsx new file mode 100644 index 00000000..38ef4d0c --- /dev/null +++ b/client/src/common/components/Header/utils/infos.jsx @@ -0,0 +1,4 @@ +export const updateInfo = (update) => <>Ein Update auf die Version {update} ist verfügbar. Sieh dir + die Änderungen + an und lade dir das Update + herunter.; \ No newline at end of file From 83a81104ea2620cbc2612bab28569bcfe5a1b01c Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:29:24 +0200 Subject: [PATCH 19/57] Cleaned up the code in the HeaderComponent.jsx --- .../components/Header/HeaderComponent.jsx | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/client/src/common/components/Header/HeaderComponent.jsx b/client/src/common/components/Header/HeaderComponent.jsx index 52c7cceb..3ed35e9d 100644 --- a/client/src/common/components/Header/HeaderComponent.jsx +++ b/client/src/common/components/Header/HeaderComponent.jsx @@ -6,19 +6,16 @@ import DropdownComponent, {toggleDropdown} from "../Dropdown/DropdownComponent"; import {DialogContext} from "@/common/contexts/Dialog"; import {StatusContext} from "@/common/contexts/Status"; import {SpeedtestContext} from "@/common/contexts/Speedtests"; - +import {jsonRequest, postRequest} from "@/common/utils/RequestUtil"; +import {updateInfo} from "@/common/components/Header/utils/infos"; function HeaderComponent() { - const [setDialog] = useContext(DialogContext); const [icon, setIcon] = useState(faGear); const [status, updateStatus] = useContext(StatusContext); const updateTests = useContext(SpeedtestContext)[1]; const [updateAvailable, setUpdateAvailable] = useState(""); - const headers = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {} - headers['content-type'] = 'application/json' - function switchDropdown() { toggleDropdown(setIcon); } @@ -39,23 +36,18 @@ function HeaderComponent() { setDialog({speedtest: true}); - fetch("/api/speedtests/run", {headers, method: "POST"}).then(() => { - updateTests(); - updateStatus(); - setDialog(); - }); + postRequest("/speedtests/run").then(updateTests).then(updateStatus).then(setDialog); } useEffect(() => { - fetch("/api/info/version", {headers}) - .then(res => res.json()) - .then(version => { - if (version.remote.localeCompare(version.local, undefined, - {numeric: true, sensitivity: 'base'}) === 1) - setUpdateAvailable(version.remote); - }); + async function updateVersion() { + const version = await jsonRequest("/info/version"); - // eslint-disable-next-line react-hooks/exhaustive-deps + if (version.remote.localeCompare(version.local, undefined, {numeric: true, sensitivity: 'base'}) === 1) + setUpdateAvailable(version.remote); + } + + updateVersion(); }, []); return ( @@ -68,16 +60,9 @@ function HeaderComponent() { onClick={() => setDialog({ title: "Update verfügbar", buttonText: "Okay", - description: <>Ein Update auf die Version {updateAvailable} ist - verfügbar. Sieh dir die Änderungen - an und lade dir das Update - herunter. - })}/>
- : <>} + description: updateInfo(updateAvailable) + })}/> : <>} +
Einstellungen
- ) - } export default HeaderComponent; \ No newline at end of file From 4f871046e5904c5102f4ecb3e5d2c36c83565d0e Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:47:19 +0200 Subject: [PATCH 20/57] Moved ConfigContext.jsx dialog -> dialog.jsx --- client/src/common/contexts/Config/dialog.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 client/src/common/contexts/Config/dialog.jsx diff --git a/client/src/common/contexts/Config/dialog.jsx b/client/src/common/contexts/Config/dialog.jsx new file mode 100644 index 00000000..c4a0493f --- /dev/null +++ b/client/src/common/contexts/Config/dialog.jsx @@ -0,0 +1,12 @@ +export const passwordRequiredDialog = { + title: "Passwort erforderlich", + placeholder: "Dein Passwort", + description: localStorage.getItem("password") ? Das von dir eingegebene Passwort ist falsch : "", + type: "password", + buttonText: "Fertig", + onClose: () => window.location.reload(), + onSuccess: (value) => { + localStorage.setItem("password", value); + window.location.reload(); + } +} \ No newline at end of file From 3369f6cbacab5fbde03e6ee9f9cf6426976842db Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:47:29 +0200 Subject: [PATCH 21/57] Cleaned up the code in the ConfigContext.jsx --- .../common/contexts/Config/ConfigContext.jsx | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/client/src/common/contexts/Config/ConfigContext.jsx b/client/src/common/contexts/Config/ConfigContext.jsx index b32587e3..5ffc5a1f 100644 --- a/client/src/common/contexts/Config/ConfigContext.jsx +++ b/client/src/common/contexts/Config/ConfigContext.jsx @@ -1,7 +1,9 @@ import React, {useState, createContext, useEffect, useContext} from "react"; import {DialogContext} from "../Dialog"; +import {request} from "@/common/utils/RequestUtil"; +import {passwordRequiredDialog} from "@/common/contexts/Config/dialog"; -export const ConfigContext = createContext(); +export const ConfigContext = createContext({}); export const ConfigProvider = (props) => { @@ -9,32 +11,15 @@ export const ConfigProvider = (props) => { const [setDialog] = useContext(DialogContext); const reloadConfig = () => { - let passwordHeaders = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {} - fetch("/api/config", {headers: passwordHeaders}) - .then(res => { - if (!res.ok) throw new Error(); - return res; + request("/config").then(res => { + if (!res.ok) throw "No connection to server"; + return res.json(); }) - .then(res => res.json()) .then(result => setConfig(result)) - .catch(() => setDialog({ - title: "Passwort erforderlich", - placeholder: "Dein Passwort", - description: localStorage.getItem("password") ? Das von dir eingegebene Passwort ist falsch : "", - type: "password", - buttonText: "Fertig", - onClose: () => window.location.reload(), - onSuccess: (value) => { - localStorage.setItem("password", value); - window.location.reload(); - } - })); + .catch(() => setDialog(passwordRequiredDialog)); } - useEffect(() => { - reloadConfig(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(reloadConfig, []); return ( From b5b76989160e12abde29665d5bbf5b9dfba78426 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 18:50:22 +0200 Subject: [PATCH 22/57] Cleaned up the code in the DialogContext.jsx & fixed the tooltip bug --- .../common/contexts/Dialog/DialogContext.jsx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/client/src/common/contexts/Dialog/DialogContext.jsx b/client/src/common/contexts/Dialog/DialogContext.jsx index e2c0c88d..60468586 100644 --- a/client/src/common/contexts/Dialog/DialogContext.jsx +++ b/client/src/common/contexts/Dialog/DialogContext.jsx @@ -1,9 +1,9 @@ -import React, {useState, createContext} from "react"; +import React, {useState, createContext, useEffect} from "react"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faClose} from "@fortawesome/free-solid-svg-icons"; import "./styles.sass"; -export const DialogContext = createContext(); +export const DialogContext = createContext({}); const Dialog = ({dialog, setDialog}) => { const [value, setValue] = useState(dialog.value || ""); @@ -23,26 +23,14 @@ const Dialog = ({dialog, setDialog}) => { setValue(e.target.value); } - function hideTooltips(state) { - Array.from(document.getElementsByClassName("tooltip")).forEach(element => { - if (state && !element.classList.contains("tooltip-invisible")) { - element.classList.add("tooltip-invisible"); - } else if (!state && element.classList.contains("tooltip-invisible")) { - element.classList.remove("tooltip-invisible"); - } - }); - } - function closeDialog() { setDialog(); - hideTooltips(false); if (dialog.onClose) dialog.onClose(); } function submit() { if (!dialog.description && !value) return; setDialog(); - hideTooltips(false); if (dialog.onSuccess) dialog.onSuccess(value); } @@ -51,8 +39,6 @@ const Dialog = ({dialog, setDialog}) => { if (dialog.onClear) dialog.onClear(); } - hideTooltips(true); - if (dialog.speedtest) return (
@@ -93,6 +79,17 @@ const Dialog = ({dialog, setDialog}) => { export const DialogProvider = (props) => { const [dialog, setDialog] = useState(); + const hideTooltips = (state) => Array.from(document.getElementsByClassName("tooltip")).forEach(element => { + if (state && !element.classList.contains("tooltip-invisible")) + element.classList.add("tooltip-invisible"); + if (!state && element.classList.contains("tooltip-invisible")) + element.classList.remove("tooltip-invisible"); + }); + + useEffect(() => { + hideTooltips(dialog); + }, [dialog]); + return ( {dialog ? : <>} From c10d4de32223241e8e096c97393790e6431879aa Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 20:02:30 +0200 Subject: [PATCH 23/57] Fixed a bug in the DialogContext.jsx --- client/src/common/contexts/Dialog/DialogContext.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/common/contexts/Dialog/DialogContext.jsx b/client/src/common/contexts/Dialog/DialogContext.jsx index 60468586..a9b6935b 100644 --- a/client/src/common/contexts/Dialog/DialogContext.jsx +++ b/client/src/common/contexts/Dialog/DialogContext.jsx @@ -1,4 +1,4 @@ -import React, {useState, createContext, useEffect} from "react"; +import React, {useState, createContext, useEffect, useRef} from "react"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faClose} from "@fortawesome/free-solid-svg-icons"; import "./styles.sass"; @@ -7,8 +7,11 @@ export const DialogContext = createContext({}); const Dialog = ({dialog, setDialog}) => { const [value, setValue] = useState(dialog.value || ""); + const ref = useRef(); document.onkeyup = e => { + if (ref.current == null) return; + console.log(ref) if (e.key === "Enter") { e.preventDefault(); submit(); @@ -50,7 +53,7 @@ const Dialog = ({dialog, setDialog}) => { ) return ( -
+

{dialog.title}

From 858feff916cdd1c26bea652c88cd54f536f278d6 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 20:03:58 +0200 Subject: [PATCH 24/57] Added an id to the open dropdown entry in the HeaderComponent.jsx --- client/src/common/components/Header/HeaderComponent.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/common/components/Header/HeaderComponent.jsx b/client/src/common/components/Header/HeaderComponent.jsx index 3ed35e9d..625f0369 100644 --- a/client/src/common/components/Header/HeaderComponent.jsx +++ b/client/src/common/components/Header/HeaderComponent.jsx @@ -70,7 +70,7 @@ function HeaderComponent() { Speedtest starten
-
+
Einstellungen
From 4564e1289d458ce4f4938ee815769328eac560b9 Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Tue, 30 Aug 2022 20:04:32 +0200 Subject: [PATCH 25/57] The DropdownComponent.jsx now closes if clicked outside the component --- .../components/Dropdown/DropdownComponent.jsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/client/src/common/components/Dropdown/DropdownComponent.jsx b/client/src/common/components/Dropdown/DropdownComponent.jsx index 088d133f..8dab0ea8 100644 --- a/client/src/common/components/Dropdown/DropdownComponent.jsx +++ b/client/src/common/components/Dropdown/DropdownComponent.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from "react"; +import React, {useContext, useEffect, useRef} from "react"; import "./styles.sass"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { @@ -15,6 +15,8 @@ import {exportOptions, selectOptions, timeOptions} from "@/common/components/Dro let icon; +export const isOpen = () => !document.getElementById("dropdown").classList.contains("dropdown-invisible"); + export const toggleDropdown = (setIcon) => { if (setIcon) icon = setIcon; let classList = document.getElementById("dropdown").classList; @@ -32,16 +34,27 @@ function DropdownComponent() { const [status, updateStatus] = useContext(StatusContext); const updateTests = useContext(SpeedtestContext)[1]; const [setDialog] = useContext(DialogContext); + const ref = useRef(); useEffect(() => { const onPress = event => { - if (event.code === "Escape" && !document.getElementById("dropdown").classList.contains("dropdown-invisible")) + if (event.code === "Escape" && isOpen()) toggleDropdown(icon); } document.addEventListener("keyup", onPress); return () => document.removeEventListener("keyup", onPress); }, []); + useEffect(() => { + const onClick = event => { + let headerIcon = event.composedPath()[1].id || event.composedPath()[2].id; + if (isOpen() && !ref.current.contains(event.target) && headerIcon !== "open-header") + toggleDropdown(icon); + } + document.addEventListener("mousedown", onClick); + return () => document.removeEventListener("mousedown", onClick); + }, []); + const showFeedback = (customText, reload = true) => setDialog({ title: "MySpeed", description: customText || <>Deine Änderungen wurden übernommen., buttonText: "Okay", onSuccess: () => reload ? reloadConfig() : "", onClose: () => reloadConfig() @@ -216,7 +229,7 @@ function DropdownComponent() { ]; return ( -