Skip to content

Commit

Permalink
Merge pull request #677 from gnmyt/features/multiple-providers
Browse files Browse the repository at this point in the history
🗃️ Support für LibreSpeed & Cloudflare Speed
  • Loading branch information
gnmyt authored May 19, 2024
2 parents f00f7e8 + fbb891b commit 2d13a6c
Show file tree
Hide file tree
Showing 30 changed files with 820 additions and 184 deletions.
18 changes: 9 additions & 9 deletions client/public/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
"wrong": "The password you entered is incorrect",
"unlock": "Unlock"
},
"accept": {
"title": "We need your permission",
"description": "We use services from Ookla. By clicking <Bold>Accept</Bold>, you acknowledge that you have read and agree to Ookla's <EULA>EULA</EULA>, <Privacy>Privacy Statement</Privacy> and <Terms>Terms of Use</Terms>.",
"button": "Accept"
},
"api": {
"title": "API not reachable",
"description": "MySpeed could not reach the API of this instance. Please try again later."
},
"provider": {
"server": "Server",
"server_id": "Server ID",
"choose_automatically": "Choose automatically",
"ookla_license": "I have read and accept the <Eula>EULA</Eula>, <GDPR>privacy policy</GDPR> and <TOS>terms of service</TOS> of Ookla.",
"cloudflare_note": "Cloudflare does not require any additional settings"
}
},
"dropdown": {
Expand All @@ -35,7 +37,7 @@
"upload": "Optimal up-speed",
"download": "Optimal down-speed",
"recommendations": "Recommendations",
"server": "Change Server",
"change_provider": "Change provider",
"password": "Change password",
"cron": "Set frequency",
"time": "Set period",
Expand Down Expand Up @@ -80,10 +82,8 @@
"download_placeholder": "Down speed (Mbps)",
"recommendations_title": "Optimal recommendations",
"recommendations_set": "Set automatic recommendations?",
"server_title": "Set speedtest server",
"provider_title": "Set speedtest provider",
"manually": "Set manually",
"manual_server_title": "Set speedtest server",
"manual_server_id": "Server ID",
"new_password": "Set a new password",
"password_placeholder": "New password",
"password_removed": "The password lock has been removed and the set password has been removed.",
Expand Down
21 changes: 5 additions & 16 deletions client/src/common/components/Dropdown/DropdownComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ import {
faPause,
faPingPongPaddleBall,
faPlay,
faServer,
faWandMagicSparkles,
faCheck,
faExclamationTriangle
faExclamationTriangle, faSliders
} from "@fortawesome/free-solid-svg-icons";
import {ConfigContext} from "@/common/contexts/Config";
import {StatusContext} from "@/common/contexts/Status";
Expand All @@ -36,6 +35,7 @@ import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
import {NodeContext} from "@/common/contexts/Node";
import {IntegrationDialog} from "@/common/components/IntegrationDialog";
import LanguageDialog from "@/common/components/LanguageDialog";
import ProviderDialog from "@/common/components/ProviderDialog";

let icon;

Expand Down Expand Up @@ -65,6 +65,7 @@ function DropdownComponent() {
const [showViewDialog, setShowViewDialog] = useState(false);
const [showIntegrationDialog, setShowIntegrationDialog] = useState(false);
const [showLanguageDialog, setShowLanguageDialog] = useState(false);
const [showProviderDialog, setShowProviderDialog] = useState(false);
const ref = useRef();

useEffect(() => {
Expand Down Expand Up @@ -130,19 +131,6 @@ function DropdownComponent() {
} else setDialog({title: t("update.recommendations_title"), description: t("info.recommendations_error"), buttonText: t("dialog.okay")});
}

const updateServer = () => patchDialog("serverId", async (value) => ({
title: t("update.server_title"),
select: true,
selectOptions: await jsonRequest("/info/server"),
unsetButton: t("update.manually"),
onClear: updateServerManually,
value
}));

const updateServerManually = () => patchDialog("serverId", (value) => ({
title: t("update.manual_server_title"), placeholder: t("update.manual_server_id"), type: "number", value: value,
}));

const updatePassword = async () => {
const passwordSet = currentNode !== 0 ? findNode(currentNode).password : localStorage.getItem("password") != null;

Expand Down Expand Up @@ -239,7 +227,7 @@ function DropdownComponent() {
{run: updateDownload, icon: faArrowDown, text: t("dropdown.download")},
{run: recommendedSettings, icon: faWandMagicSparkles, text: t("dropdown.recommendations")},
{hr: true, key: 1},
{run: updateServer, icon: faServer, text: t("dropdown.server")},
{run: () => setShowProviderDialog(true), icon: faSliders, text: t("dropdown.change_provider")},
{run: updatePassword, icon: faKey, text: t("dropdown.password"), previewHidden: true},
{run: updateCron, icon: faClock, text: t("dropdown.cron")},
{run: exportDialog, icon: faFileExport, text: t("dropdown.export")},
Expand All @@ -258,6 +246,7 @@ function DropdownComponent() {
{showViewDialog && <ViewDialog onClose={() => setShowViewDialog(false)}/>}
{showIntegrationDialog && <IntegrationDialog onClose={() => setShowIntegrationDialog(false)}/>}
{showLanguageDialog && <LanguageDialog onClose={() => setShowLanguageDialog(false)}/>}
{showProviderDialog && <ProviderDialog onClose={() => setShowProviderDialog(false)}/>}
<div className="dropdown dropdown-invisible" id="dropdown" ref={ref}>
<div className="dropdown-content">
<h2>{t("dropdown.settings")}</h2>
Expand Down
5 changes: 4 additions & 1 deletion client/src/common/components/LanguageDialog/styles.sass
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
border-radius: 0.5rem

&:hover
background-color: $light-gray
background-color: $darker-gray

img
width: 2rem
Expand All @@ -38,6 +38,9 @@
background-color: $light-gray
color: $white

&:hover
background-color: $light-gray

@media screen and (max-height: 425px)
.language-chooser-dialog
height: 15rem
Expand Down
138 changes: 138 additions & 0 deletions client/src/common/components/ProviderDialog/ProviderDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
import {t} from "i18next";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faClose} from "@fortawesome/free-solid-svg-icons";
import "./styles.sass";
import React, {useContext, useEffect, useState} from "react";
import OoklaImage from "./assets/img/ookla.webp";
import LibreImage from "./assets/img/libre.webp";
import CloudflareImage from "./assets/img/cloudflare.webp";
import {jsonRequest, patchRequest} from "@/common/utils/RequestUtil";
import {Trans} from "react-i18next";
import {ConfigContext} from "@/common/contexts/Config";

const providers = [
{id: "ookla", name: "Ookla", image: OoklaImage},
{id: "libre", name: "LibreSpeed", image: LibreImage},
{id: "cloudflare", name: "Cloudflare", image: CloudflareImage}
]


export const Dialog = () => {
const close = useContext(DialogContext);
const [config, reloadConfig] = useContext(ConfigContext);
const [provider, setProvider] = useState(config.provider || "ookla");

const [licenseAccepted, setLicenseAccepted] = useState(false);
const [licenseError, setLicenseError] = useState(false);

const [ooklaServers, setOoklaServers] = useState({});
const [libreServers, setLibreServers] = useState({});

const [serverId, setServerId] = useState("none");

useEffect(() => {
jsonRequest("/info/server/ookla").then((response) => {
setOoklaServers(response);
});
jsonRequest("/info/server/libre").then((response) => {
setLibreServers(response);
});
}, []);

useEffect(() => {
if (config[provider + "Id"]) setServerId(config[provider + "Id"]);
}, [provider]);

useEffect(() => {
if (serverId === "") setServerId("none");
}, [serverId]);

const update = async () => {
if (provider === "ookla" && !licenseAccepted) {
setLicenseError(true);
return;
}

await patchRequest("/config/provider", {value: provider});

if (serverId !== config[provider + "Id"] && provider !== "cloudflare") {
await patchRequest("/config/" + provider + "Id", {value: serverId});
}

reloadConfig();

close();
}

return (
<>
<div className="dialog-header">
<h4 className="dialog-text">{t("update.provider_title")}</h4>
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={() => close()}/>
</div>
<div className="provider-dialog-content">
<div className="provider-header">
{providers.map((current, index) => (
<div className={`provider-item ${current.id === provider ? "provider-item-active" : ""}`}
key={index} onClick={() => setProvider(current.id)}>
<img src={current.image} alt={current.name}/>
<h3>{current.name}</h3>
</div>
))}
</div>
{provider !== "cloudflare" && <div className="provider-content">
<div className="provider-setting">
<h3>{t("dialog.provider.server")}</h3>
<select className="dialog-input provider-input" value={serverId}
onChange={(e) => setServerId(e.target.value)}>
<option value="none">{t("dialog.provider.choose_automatically")}</option>
{provider === "ookla" && Object.keys(ooklaServers).map((current, index) => (
<option key={index} value={current}>{ooklaServers[current]}</option>
))}
{provider === "libre" && Object.keys(libreServers).map((current, index) => (
<option key={index} value={current}>{libreServers[current]}</option>
))}
</select>
</div>
<div className="provider-setting">
<h3>{t("dialog.provider.server_id")}</h3>
<input type="text" className="dialog-input provider-input" value={serverId === "none" ? "" : serverId}
onChange={(e) => setServerId(e.target.value)}/>
</div>
</div>}
{provider === "cloudflare" && <div className="provider-content">
<p className="cloudflare-provider-info">{t("dialog.provider.cloudflare_note")}</p>
</div>}
</div>
<div className="provider-dialog-footer">
<div className="provider-license-box">
{provider === "ookla" && <>
<input type="checkbox" className={licenseError ? "cb-error" : ""} id="license" name="license"
onChange={(e) => setLicenseAccepted(e.target.checked)}/>
<label htmlFor="license"
><Trans components={{
Eula: <a href="https://www.speedtest.net/about/eula" target="_blank"
rel="noreferrer" />,
GDPR: <a href="https://www.speedtest.net/about/privacy" target="_blank"
rel="noreferrer" />,
TOS: <a href="https://www.speedtest.net/about/terms" target="_blank"
rel="noreferrer" />}}>dialog.provider.ookla_license</Trans></label>
</>}
</div>

<button className="dialog-btn" onClick={update}>{t("dialog.update")}</button>
</div>
</>
)
}

export const ProviderDialog = (props) => {
return (
<>
<DialogProvider close={props.onClose}>
<Dialog/>
</DialogProvider>
</>
)
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions client/src/common/components/ProviderDialog/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ProviderDialog as default} from "./ProviderDialog";
95 changes: 95 additions & 0 deletions client/src/common/components/ProviderDialog/styles.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@import "@/common/styles/colors"

.provider-dialog-content
display: flex
margin: 1rem 0.5rem
user-select: none
flex-direction: column

.provider-header
display: flex
gap: 1rem

.provider-item
display: flex
align-items: center
padding: 0.3rem 0.5rem
gap: 0.5rem
border-radius: 0.8rem
border: 2px solid $light-gray
color: $darker-white
cursor: pointer

img
width: 2.5rem
height: 2.5rem

h3
margin: 0

&:hover
background-color: $darker-gray

.provider-item-active
background-color: $light-gray

&:hover
background-color: $light-gray

.provider-content
display: flex
flex-direction: column
margin-top: 1rem


.provider-setting
display: flex
gap: 1rem
align-items: center
justify-content: space-between

.provider-input
width: 20rem
box-sizing: border-box
margin-top: 0.5rem
margin-bottom: 0.5rem
font-size: 1.3rem

h3
color: $darker-white

.cloudflare-provider-info
color: $subtext
text-align: center

.provider-dialog-footer
display: flex
align-items: center
justify-content: space-between

.provider-license-box
display: flex
align-items: center
gap: 0.5rem

input
border: 2px solid $light-gray

.cb-error
border-color: $red

label
color: $subtext
max-width: 16rem
flex: 1


@media screen and (max-width: 610px)
.provider-dialog-content
.provider-header
flex-direction: column

@media screen and (max-width: 520px)
.provider-dialog-content
.provider-setting .provider-input
width: 60%
10 changes: 1 addition & 9 deletions client/src/common/contexts/Config/ConfigContext.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React, {createContext, useContext, useEffect, useState} from "react";
import {InputDialogContext} from "../InputDialog";
import {request} from "@/common/utils/RequestUtil";
import {acceptDialog, apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog";
import {apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog";

export const ConfigContext = createContext({});

export const ConfigProvider = (props) => {
const [config, setConfig] = useState({});
const [setDialog] = useContext(InputDialogContext);
const [dialogShown, setDialogShown] = useState(false);

const reloadConfig = () => {
request("/config").then(async res => {
Expand All @@ -32,13 +31,6 @@ export const ConfigProvider = (props) => {

const checkConfig = async () => (await request("/config")).json();

useEffect(() => {
if (config.acceptOoklaLicense !== undefined && config.acceptOoklaLicense === "false" && !dialogShown) {
setDialogShown(true);
setDialog(acceptDialog());
}
}, [config]);

useEffect(reloadConfig, []);

return (
Expand Down
Loading

0 comments on commit 2d13a6c

Please sign in to comment.