Skip to content

Commit dac6036

Browse files
authored
Merge pull request #685 from gnmyt/features/interface-chooser
👨‍💻 Interfaceauswahl hinzugefügt
2 parents 21a25c1 + 2ac2dbe commit dac6036

File tree

16 files changed

+230
-73
lines changed

16 files changed

+230
-73
lines changed

client/public/assets/locales/en.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
"description": "MySpeed could not reach the API of this instance. Please try again later."
3535
},
3636
"provider": {
37+
"interface": "Interface",
3738
"server": "Server",
3839
"server_id": "Server ID",
3940
"choose_automatically": "Choose automatically",
40-
"ookla_license": "I have read and accept the <Eula>EULA</Eula>, <GDPR>privacy policy</GDPR> and <TOS>terms of service</TOS> of Ookla.",
41-
"cloudflare_note": "Cloudflare does not require any additional settings"
41+
"ookla_license": "I have read and accept the <Eula>EULA</Eula>, <GDPR>privacy policy</GDPR> and <TOS>terms of service</TOS> of Ookla."
4242
}
4343
},
4444
"dropdown": {
@@ -210,7 +210,7 @@
210210
},
211211
"result": {
212212
"title": "Test result",
213-
"description": "This test achieved a maximum download speed of <Bold>{{down}} Mbps</Bold> and a maximum upload speed of <Bold>{{up}} Mbps</Bold>. It was created <Bold>{{type}}</Bold> and took <Bold>{{duration}} Seconds</Bold>.",
213+
"description": "<Link>This test</Link> achieved a maximum download speed of <Bold>{{down}} Mbps</Bold> and a maximum upload speed of <Bold>{{up}} Mbps</Bold>. It was created <Bold>{{type}}</Bold> and took <Bold>{{duration}} Seconds</Bold>.",
214214
"from_you": "by you",
215215
"automatic": "automatically"
216216
},

client/src/common/components/ProviderDialog/ProviderDialog.jsx

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export const Dialog = () => {
2828
const [licenseAccepted, setLicenseAccepted] = useState(false);
2929
const [licenseError, setLicenseError] = useState(false);
3030

31+
const [interfaces, setInterfaces] = useState({});
32+
const [currentInterface, setCurrentInterface] = useState(config.interface || "none");
3133
const [ooklaServers, setOoklaServers] = useState({});
3234
const [libreServers, setLibreServers] = useState({});
3335

@@ -40,6 +42,9 @@ export const Dialog = () => {
4042
jsonRequest("/info/server/libre").then((response) => {
4143
setLibreServers(response);
4244
});
45+
jsonRequest("/info/interfaces").then((response) => {
46+
setInterfaces(response);
47+
});
4348
}, []);
4449

4550
useEffect(() => {
@@ -62,6 +67,10 @@ export const Dialog = () => {
6267
await patchRequest("/config/" + provider + "Id", {value: serverId});
6368
}
6469

70+
if (currentInterface !== config.interface) {
71+
await patchRequest("/config/interface", {value: currentInterface});
72+
}
73+
6574
reloadConfig();
6675
updateToast(t('dropdown.provider_changed'), "green", faCheck);
6776

@@ -84,43 +93,53 @@ export const Dialog = () => {
8493
</div>
8594
))}
8695
</div>
87-
{provider !== "cloudflare" && <div className="provider-content">
96+
<div className="provider-content">
8897
<div className="provider-setting">
89-
<h3>{t("dialog.provider.server")}</h3>
90-
<select className="dialog-input provider-input" value={serverId}
91-
onChange={(e) => setServerId(e.target.value)}>
92-
<option value="none">{t("dialog.provider.choose_automatically")}</option>
93-
{provider === "ookla" && Object.keys(ooklaServers).map((current, index) => (
94-
<option key={index} value={current}>{ooklaServers[current]}</option>
95-
))}
96-
{provider === "libre" && Object.keys(libreServers).map((current, index) => (
97-
<option key={index} value={current}>{libreServers[current]}</option>
98+
<h3>{t("dialog.provider.interface")}</h3>
99+
<select className="dialog-input provider-input" value={currentInterface}
100+
onChange={(e) => setCurrentInterface(e.target.value)}>
101+
{interfaces && Object.keys(interfaces).map((current, index) => (
102+
<option key={index} value={current}>{current} ({interfaces[current]})</option>
98103
))}
99104
</select>
100105
</div>
101-
<div className="provider-setting">
102-
<h3>{t("dialog.provider.server_id")}</h3>
103-
<input type="text" className="dialog-input provider-input" value={serverId === "none" ? "" : serverId}
104-
onChange={(e) => setServerId(e.target.value)}/>
105-
</div>
106-
</div>}
107-
{provider === "cloudflare" && <div className="provider-content">
108-
<p className="cloudflare-provider-info">{t("dialog.provider.cloudflare_note")}</p>
109-
</div>}
106+
107+
{provider !== "cloudflare" && <>
108+
<div className="provider-setting">
109+
<h3>{t("dialog.provider.server")}</h3>
110+
<select className="dialog-input provider-input" value={serverId}
111+
onChange={(e) => setServerId(e.target.value)}>
112+
<option value="none">{t("dialog.provider.choose_automatically")}</option>
113+
{provider === "ookla" && Object.keys(ooklaServers).map((current, index) => (
114+
<option key={index} value={current}>{ooklaServers[current]}</option>
115+
))}
116+
{provider === "libre" && Object.keys(libreServers).map((current, index) => (
117+
<option key={index} value={current}>{libreServers[current]}</option>
118+
))}
119+
</select>
120+
</div>
121+
<div className="provider-setting">
122+
<h3>{t("dialog.provider.server_id")}</h3>
123+
<input type="text" className="dialog-input provider-input"
124+
value={serverId === "none" ? "" : serverId}
125+
onChange={(e) => setServerId(e.target.value)}/>
126+
</div>
127+
</>}
128+
</div>
110129
</div>
111130
<div className="provider-dialog-footer">
112131
<div className="provider-license-box">
113132
{provider === "ookla" && <>
114133
<input type="checkbox" className={licenseError ? "cb-error" : ""} id="license" name="license"
115-
onChange={(e) => setLicenseAccepted(e.target.checked)}/>
134+
onChange={(e) => setLicenseAccepted(e.target.checked)}/>
116135
<label htmlFor="license"
117136
><Trans components={{
118137
Eula: <a href="https://www.speedtest.net/about/eula" target="_blank"
119-
rel="noreferrer" />,
138+
rel="noreferrer"/>,
120139
GDPR: <a href="https://www.speedtest.net/about/privacy" target="_blank"
121-
rel="noreferrer" />,
140+
rel="noreferrer"/>,
122141
TOS: <a href="https://www.speedtest.net/about/terms" target="_blank"
123-
rel="noreferrer" />}}>dialog.provider.ookla_license</Trans></label>
142+
rel="noreferrer"/>}}>dialog.provider.ookla_license</Trans></label>
124143
</>}
125144
</div>
126145

client/src/common/contexts/Dialog/styles.sass

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666

6767
.dialog-description a
6868
color: $green
69+
text-decoration: underline
6970

7071
.dialog-value
7172
color: $green

client/src/pages/Home/components/Speedtest/utils/infos.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import React from "react";
22
import {Trans} from "react-i18next";
33
import {t} from "i18next";
44

5+
const RESULT_URL = "https://www.speedtest.net/result/c/";
6+
57
export const averageResultDialog = (timeString, props) => <Trans components={{Bold: <span className="dialog-value"/> }}
68
values={{amount: props.amount, date: timeString, down: props.down,
79
up: props.up, duration: props.duration}}>test.average.description</Trans>
810

9-
export const resultDialog = (props) => <Trans components={{Bold: <span className="dialog-value"/> }}
11+
export const resultDialog = (props) => <Trans components={{Bold: <span className="dialog-value"/>,
12+
Link: (props.resultId ? <a href={RESULT_URL + props.resultId} target="_blank" rel="noreferrer"/> : <span/>) }}
1013
values={{down: props.down, up: props.up,
1114
type: t("test.result." + (props.type === "custom" ? "from_you" : "automatic")),
1215
duration: props.duration}}>test.result.description</Trans>

client/src/pages/Home/components/TestArea/TestAreaComponent.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function TestArea() {
3636
type={test.type}
3737
duration={test.time}
3838
amount={test.amount}
39+
resultId={test.resultId}
3940
id={id}
4041
/>
4142
}) : ""}

server/controller/config.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const cron = require('cron-validator');
1010
const db = require("../config/database");
1111
const fs = require('fs');
1212
const path = require('path');
13+
const interfaces = require('../util/loadInterfaces');
1314

1415
const configDefaults = {
1516
ping: "25",
@@ -20,14 +21,23 @@ const configDefaults = {
2021
ooklaId: "none",
2122
libreId: "none",
2223
password: "none",
23-
passwordLevel: "none"
24+
passwordLevel: "none",
25+
interface: "none"
2426
}
2527

2628
module.exports.insertDefaults = async () => {
2729
let insert = [];
2830
for (let key in configDefaults) {
29-
if (!(await config.findOne({where: {key: key}})))
31+
if (key !== "interface" && !(await config.findOne({where: {key: key}})))
3032
insert.push({key: key, value: configDefaults[key]});
33+
34+
if (key === "interface") {
35+
const ips = Object.keys(interfaces.interfaces);
36+
let ip = ips.length > 0 ? ips[0] : "none";
37+
38+
if (!(await config.findOne({where: {key: key}})))
39+
insert.push({key: key, value: ip});
40+
}
3141
}
3242

3343
await config.bulkCreate(insert, {validate: true});
@@ -94,6 +104,9 @@ module.exports.validateInput = async (key, value) => {
94104
if (key === "cron" && !cron.isValidCron(value.toString()))
95105
return "Not a valid cron expression";
96106

107+
if (key === "interface" && !Object.keys(interfaces.interfaces).includes(value))
108+
return "The provided interface does not exist";
109+
97110
if (configDefaults[key] === undefined)
98111
return "The provided key does not exist";
99112

@@ -174,5 +187,7 @@ module.exports.factoryReset = async () => {
174187
timer.stopTimer();
175188
timer.startTimer(configDefaults.cron);
176189

190+
interfaces.requestInterfaces();
191+
177192
return true;
178193
}

server/controller/speedtests.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ const tests = require('../models/Speedtests');
22
const {Op, Sequelize} = require("sequelize");
33
const {mapFixed, mapRounded, calculateTestAverages} = require("../util/helpers");
44

5-
module.exports.create = async (ping, download, upload, time, serverId, type = "auto", error = null) => {
6-
return (await tests.create({ping, download, upload, error, serverId, type, time})).id;
5+
module.exports.create = async (ping, download, upload, time, serverId, type = "auto", resultId = null, error = null) => {
6+
return (await tests.create({ping, download, upload, error, serverId, type, resultId, time, created: new Date().toISOString()})).id;
77
}
88

99
module.exports.getOne = async (id) => {
@@ -15,8 +15,10 @@ module.exports.getOne = async (id) => {
1515

1616
module.exports.listAll = async () => {
1717
let dbEntries = await tests.findAll({order: [["created", "DESC"]]});
18-
for (let dbEntry of dbEntries)
18+
for (let dbEntry of dbEntries) {
1919
if (dbEntry.error === null) delete dbEntry.error;
20+
if (dbEntry.resultId === null) delete dbEntry.resultId;
21+
}
2022

2123
return dbEntries;
2224
}
@@ -28,8 +30,10 @@ module.exports.listTests = async (hours = 24, start, limit) => {
2830
let dbEntries = (await tests.findAll({where: whereClause, order: [["created", "DESC"]], limit}))
2931
.filter((entry) => new Date(entry.created) > new Date().getTime() - hours * 3600000);
3032

31-
for (let dbEntry of dbEntries)
33+
for (let dbEntry of dbEntries) {
3234
if (dbEntry.error === null) delete dbEntry.error;
35+
if (dbEntry.resultId === null) delete dbEntry.resultId;
36+
}
3337

3438
return dbEntries;
3539
}
@@ -84,6 +88,7 @@ module.exports.importTests = async (data) => {
8488

8589
for (let entry of data) {
8690
if (entry.error === null) delete entry.error;
91+
if (entry.resultId === null) delete entry.resultId;
8792
try {
8893
await tests.create(entry);
8994
} catch (e) {

server/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const run = async () => {
4343

4444
await require('./controller/integrations').initialize();
4545

46+
await require('./util/loadInterfaces').requestInterfaces();
47+
setInterval(() => require('./util/loadInterfaces').requestInterfaces(), 3600000);
48+
4649
if (process.env.PREVIEW_MODE !== "true") await require('./util/loadCli').load();
4750

4851
await config.insertDefaults();
@@ -60,7 +63,7 @@ const run = async () => {
6063

6164
db.authenticate().then(() => {
6265
console.log("Successfully connected to the database " + (process.env.DB_TYPE === "mysql" ? "server" : "file"));
63-
run().then(undefined);
66+
run().then(undefined);
6467
}).catch(err => {
6568
console.error("Could not open the database file. Maybe it is damaged?: " + err.message);
6669
process.exit(111);

server/models/Speedtests.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ module.exports = db.define("speedtests", {
3131
type: Sequelize.STRING,
3232
defaultValue: "auto"
3333
},
34+
resultId: {
35+
type: Sequelize.STRING,
36+
allowNull: true
37+
},
3438
time: {
3539
type: Sequelize.INTEGER,
3640
defaultValue: 0
41+
},
42+
created: {
43+
type: Sequelize.TIME,
44+
defaultValue: Sequelize.NOW
3745
}
38-
}, {createdAt: "created", updatedAt: false});
46+
}, {createdAt: false, updatedAt: false});

server/routes/system.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const remote_url = "https://api.github.com/repos/gnmyt/myspeed/releases/latest";
44
const axios = require('axios');
55
const password = require('../middlewares/password');
66
const serverController = require('../controller/servers');
7+
const interfaces = require('../util/loadInterfaces');
78

89
app.get("/version", password(false), async (req, res) => {
910
if (process.env.PREVIEW_MODE === "true") return res.json({local: version, remote: "0"});
@@ -22,4 +23,8 @@ app.get("/server/:provider", password(false), (req, res) => {
2223
res.json(serverController.getByMode(req.params.provider));
2324
});
2425

26+
app.get("/interfaces", password(false), async (req, res) => {
27+
res.json(interfaces.interfaces);
28+
});
29+
2530
module.exports = app;

0 commit comments

Comments
 (0)