mirror of
https://github.com/gnmyt/myspeed.git
synced 2026-02-10 23:58:38 -06:00
Merge pull request #33 from gnmyt/features/update-time
📅 Integration der "Zeitraum festlegen" Funktion
This commit is contained in:
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
@@ -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")} : {};
|
||||
@@ -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">
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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')`)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user