Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📅 Integration der "Zeitraum festlegen" Funktion #33

Merged
merged 17 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion client/src/common/components/Dropdown/DropdownComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "./styles.sass";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faArrowDown,
faArrowUp, faClock, faClose, faFileExport,
faArrowUp, faCalendarDays, faClock, faClose, faFileExport,
faGear, faInfo,
faKey,
faPause,
Expand All @@ -14,6 +14,7 @@ import {
import {ConfigContext} from "@/common/contexts/Config";
import {StatusContext} from "@/common/contexts/Status";
import {DialogContext} from "@/common/contexts/Dialog";
import {SpeedtestContext} from "@/common/contexts/Speedtests";

let icon;

Expand All @@ -33,6 +34,7 @@ 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")} : {};
Expand Down Expand Up @@ -236,6 +238,26 @@ function DropdownComponent() {
});
}

const updateTime = async () => {
toggleDropdown();
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)"
},
value: localStorage.getItem("testTime") || 1,
onSuccess: value => {
localStorage.setItem("testTime", value);
updateTests();
showFeedback(undefined, false);
}
});
}

return (
<div className="dropdown dropdown-invisible" id="dropdown">
<div className="dropdown-content">
Expand Down Expand Up @@ -272,6 +294,10 @@ function DropdownComponent() {
<FontAwesomeIcon icon={faClock}/>
<h3>Häufigkeit einstellen</h3>
</div>
<div className="dropdown-item" onClick={updateTime}>
<FontAwesomeIcon icon={faCalendarDays} />
<h3>Zeitraum festlegen</h3>
</div>
<div className="dropdown-item" onClick={exportDialog}>
<FontAwesomeIcon icon={faFileExport}/>
<h3>Tests exportieren</h3>
Expand Down
17 changes: 16 additions & 1 deletion client/src/common/contexts/Speedtests/SpeedtestContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,24 @@ export const SpeedtestProvider = (props) => {

const [speedtests, setSpeedtests] = useState({});

const generatePath = (level) => {
switch (level) {
case 1:
return "?hours=24";
case 2:
return "?hours=48";
case 3:
return "/averages?days=7";
case 4:
return "/averages?days=30";
}
}

const updateTests = () => {
let passwordHeaders = localStorage.getItem("password") ? {password: localStorage.getItem("password")} : {}
fetch("/api/speedtests", {headers: passwordHeaders})
let testTime = localStorage.getItem("testTime") || 1;

fetch("/api/speedtests" + generatePath(parseInt(testTime)), {headers: passwordHeaders})
.then(res => res.json())
.then(tests => setSpeedtests(tests))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function LatestTestComponent() {

useEffect(() => {
if (latest) setLatestTestTime(generateRelativeTime(latest.created));
const interval = setInterval(() => setLatestTestTime(generateRelativeTime(latest.created || 0)), 1000);
const interval = setInterval(() => setLatestTestTime(generateRelativeTime(latest ? latest.created : 0)), 1000);
return () => clearInterval(interval);
}, [latest]);

Expand Down
79 changes: 57 additions & 22 deletions client/src/pages/Home/components/Speedtest/SpeedtestComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ const errors = {
"Could not retrieve or read configuration": "Die Konfigurationsdatei konnte nicht geladen werden",
}

const tooltips = {
custom: "Benutzerdefiniert",
average: "Durchschnitt",
auto: "Automatisiert",
}

function SpeedtestComponent(props) {

const [setDialog] = useContext(DialogContext);
Expand All @@ -31,40 +37,69 @@ function SpeedtestComponent(props) {

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');
}

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 showInfoDialog = () => {
if (props.type === "average") {
setDialog({
title: "Durchschnittsgeschwindigkeit",
buttonText: "Okay",
description: <><span className="dialog-value">{props.amount}</span> Tests haben ergeben, dass am <span
className="dialog-value">{timeString}</span> eine durchschnittliche Downloadgeschwindigkeit von <span
className="dialog-value">{props.down} Mbit/s</span> und eine Upload-geschwindigkeit von <span
className="dialog-value">{props.up} Mbit/s</span> bestand. Die Tests dauerten im Durchschnitt <span
className="dialog-value">{props.duration} Sekunden</span>.</>
});
} else {
setDialog({
title: "Testergebnis",
description: <>Dieser Test erreichte eine maximale Downloadgeschwindigkeit von <span
className="dialog-value">{props.down} Mbit/s </span>
und eine maximale Uploadgeschwindigkeit von <span className="dialog-value">{props.up} Mbit/s</span>.
Er wurde <span className="dialog-value">{props.type === "custom"
? "von dir" : "automatisch"}</span> angelegt und hat <span
className="dialog-value">{props.duration} Sekunden</span> gedauert.</>,
buttonText: "Okay",
unsetButton: true,
unsetButtonText: "Test löschen",
onClear: () => fetch("/api/speedtests/" + props.id, {headers: passwordHeaders, method: "DELETE"})
.then(updateTests)
});
}
}

return (
<div className="speedtest">
<div className="date">
<div className="tooltip-element">
<FontAwesomeIcon icon={props.error ? faInfo : faClockRotateLeft}
className={"container-icon help-icon icon-" + (props.error ? "error" : "blue")}
onClick={props.error ? () => 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)
}) : () => setDialog({
title: "Testergebnis",
description: <>Dieser Test erreichte eine maximale Downloadgeschwindigkeit von <span className="dialog-value">{props.down} Mbit/s </span>
und eine maximale Uploadgeschwindigkeit von <span className="dialog-value">{props.up} Mbit/s</span>. Er wurde <span className="dialog-value">{props.type === "custom"
? "von dir" : "automatisch"}</span> angelegt und hat <span className="dialog-value">{props.duration} Sekunden</span> gedauert.</>,
buttonText: "Okay",
unsetButton: true,
unsetButtonText: "Test löschen",
onClear: () => fetch("/api/speedtests/"+props.id, {headers: passwordHeaders, method: "DELETE"})
.then(updateTests)
})} />
<span className="tooltip">{props.type === "custom" ? "Benutzerdefiniert" :"Automatisiert"}</span>
onClick={props.error ? showErrorDialog : showInfoDialog}/>
<span className="tooltip">{tooltips[props.type]}</span>
</div>

<h2 className="date-text">Um {props.time}</h2>
<h2 className="date-text">{(props.type === "average" ? "Am " : "Um ") + timeString}</h2>
</div>
<div className="speedtest-row">
<FontAwesomeIcon icon={props.error ? faClose : faPingPongPaddleBall}
Expand Down
16 changes: 11 additions & 5 deletions client/src/pages/Home/components/TestArea/TestAreaComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,29 @@ function TestArea() {
const config = useContext(ConfigContext)[0];
const [speedtests] = useContext(SpeedtestContext);

if (Object.entries(config).length === 0) return (<></>)
if (Object.entries(config).length === 0) return (<></>);

return (
<div className="speedtest-area">
{speedtests.map ? speedtests.map(test => {
let date = new Date(Date.parse(test.created));
let timeString = String(date.getHours()).padStart(2, '0') + ":" + String(date.getMinutes()).padStart(2, '0');
return <Speedtest time={timeString}

let item = localStorage.getItem("testTime");
if ((item === "3" || item === "4") && test.type !== "average") return;

let id = (test.type === "average") ? date.getDate() + "-" + date.getMonth() : test.id;

return <Speedtest time={date}
ping={test.ping} pingLevel={getIconBySpeed(test.ping, config.ping, false)}
down={test.download} downLevel={getIconBySpeed(test.download, config.download, true)}
up={test.upload} upLevel={getIconBySpeed(test.upload, config.upload, true)}
error={test.error}
key={test.id}
key={id}
url={test.url}
type={test.type}
duration={test.time}
id={test.id}
amount={test.amount}
id={id}
/>
}) : ""}
</div>
Expand Down
63 changes: 55 additions & 8 deletions server/controller/speedtests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,63 @@ module.exports.get = async (id) => {
}

// Lists all speedtests from the database
module.exports.list = async () => {
let dbEntries = await tests.findAll({order: [["created", "DESC"]]});
let all = [];
module.exports.list = async (hours = 24) => {
let dbEntries = (await tests.findAll({order: [["created", "DESC"]]}))
.filter((entry) => new Date(entry.created) > new Date().getTime() - hours * 3600000);

for (let dbEntry of dbEntries)
if (dbEntry.error === null) delete dbEntry.error

return dbEntries;
}

// Lists all speedtests from the database grouped by days
module.exports.listByDays = async (days) => {
let dbEntries = (await tests.findAll({order: [["created", "DESC"]]})).filter((entry) => entry.error === null)
.filter((entry) => new Date(entry.created) > new Date().getTime() - days * 24 * 3600000);

let averages = {};
dbEntries.forEach((entry) => {
if (entry.error === null) delete entry.error
all.push(entry);
const day = new Date(entry.created).toLocaleDateString();
if (!averages[day]) averages[day] = [];
averages[day].push(entry);
});

return all;
return averages;
}

// Calculates the average speedtests and lists them
module.exports.listAverage = async (days) => {
const averages = await this.listByDays(days);
let result = [];

if (Object.keys(averages).length !== 0)
result.push(averages[Object.keys(averages)[0]][0]);

for (let day in averages) {
let avgNumbers = {ping: 0, down: 0, up: 0, time: 0};
let currentDay = averages[day];

currentDay.forEach((current) => {
avgNumbers.ping += current.ping;
avgNumbers.down += current.download;
avgNumbers.up += current.upload;
avgNumbers.time += current.time;
});

const created = new Date(currentDay[0].created);
result.push({
ping: Math.round(avgNumbers["ping"] / currentDay.length),
download: parseFloat((avgNumbers["down"] / currentDay.length).toFixed(2)),
upload: parseFloat((avgNumbers["up"] / currentDay.length).toFixed(2)),
time: Math.round(avgNumbers["time"] / currentDay.length),
type: "average",
amount: currentDay.length,
created: created.getFullYear() + "-" + (created.getMonth() + 1) + "-" + created.getDate()
});
}

return result;
}

// Gets the latest speedtest from the database
Expand All @@ -41,12 +88,12 @@ module.exports.delete = async (id) => {
return true;
}

// Removes speedtests older than 24 hours
// Removes speedtests older than 30 days
module.exports.removeOld = async () => {
await tests.destroy({
where: {
created: {
[Op.lte]: Sequelize.literal(`datetime('now', '-1 day')`)
[Op.lte]: Sequelize.literal(`datetime('now', '-30 days')`)
}
}
});
Expand Down
7 changes: 6 additions & 1 deletion server/routes/speedtests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ const pauseController = require('../controller/pause');

// List all speedtests
app.get("/", async (req, res) => {
res.json(await tests.list());
res.json(await tests.list(req.query.hours || 24));
});

// List all speedtests by average
app.get("/averages", async (req, res) => {
res.json(await tests.listAverage(req.query.days || 7));
});

// Runs a speedtest
Expand Down
2 changes: 1 addition & 1 deletion server/tasks/speedtest.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports.create = async (type = "auto", retried = false) => {
let upload = roundSpeed(test.upload.bytes, test.upload.elapsed);
let time = Math.round((test.download.elapsed + test.upload.elapsed) / 1000);
let testResult = await tests.create(ping, download, upload, time, type);
console.log(`Test #${testResult} was executed successfully. 🏓 ${ping} ⬇ ${download}️ ⬆ ${upload}️`);
console.log(`Test #${testResult} was executed successfully in ${time}s. 🏓 ${ping} ⬇ ${download}️ ⬆ ${upload}️`);
createRecommendations().then(() => "");
} catch (e) {
if (!retried) return this.create(type, true);
Expand Down