Skip to content

Commit 45772f8

Browse files
authored
Merge pull request #681 from gnmyt/updates/storage-management
💾 Storage Management hinzugefügt
2 parents 851d4bf + d4bd2de commit 45772f8

File tree

24 files changed

+638
-93
lines changed

24 files changed

+638
-93
lines changed

client/public/assets/locales/en.json

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
"dropdown": {
4545
"settings": "Settings",
4646
"changes_applied": "Your changes have been saved.",
47+
"language_changed": "The language has been changed.",
48+
"provider_changed": "The provider has been changed.",
4749
"view_changed": "The view has been changed.",
4850
"changes_unsaved": "Your changes were not applied. Check your input.",
4951
"invalid": "Input invalid",
@@ -55,7 +57,7 @@
5557
"password": "Change password",
5658
"cron": "Set frequency",
5759
"time": "Set period",
58-
"export": "Export tests",
60+
"storage": "Manage storage",
5961
"pause_tests": "Pause tests",
6062
"resume_tests": "Resume tests",
6163
"language": "Change language",
@@ -78,10 +80,6 @@
7880
"rare": "Rarely (every 3 hours)",
7981
"really_rare": "Very rarely (every 6 hours)"
8082
},
81-
"export": {
82-
"json": "JSON file",
83-
"csv": "CSV file"
84-
},
8583
"level": {
8684
"no_access": "No Access",
8785
"read_access": "Read-only Access"
@@ -107,7 +105,6 @@
107105
"cron_rules": "Cron rule",
108106
"cron_next_test": "Next Test:",
109107
"time_title": "Show tests of the last ...",
110-
"export_title": "Export speedtests",
111108
"download": "Download",
112109
"pause_title": "Pause speedtests for...",
113110
"hours": "Hours",
@@ -130,6 +127,33 @@
130127
"description": "This feature is still in beta. If you find any bugs, please report them <Link>here</Link>."
131128
}
132129
},
130+
"storage": {
131+
"speedtests": "Speedtests",
132+
"configuration": "Configuration",
133+
"stored_tests": "Stored tests",
134+
"tests": "Tests",
135+
"export_tests": "Export tests",
136+
"tests_exported": "The tests have been exported",
137+
"csv": "CSV",
138+
"json": "JSON",
139+
"import_tests": "Import tests",
140+
"tests_imported": "The tests have been imported",
141+
"import_error": "An error occurred while importing the tests",
142+
"export": "Export",
143+
"import": "Import",
144+
"clear_history": "Clear history",
145+
"history_cleared": "The history has been cleared",
146+
"delete": "Delete",
147+
"confirm_delete": "Yes, delete",
148+
"export_settings": "Export settings",
149+
"import_settings": "Import settings",
150+
"factory_reset": "Factory reset",
151+
"factory_reset_completed": "The factory reset has been completed",
152+
"reset": "Reset",
153+
"confirm_reset": "Yes, reset",
154+
"settings_exported": "The settings have been exported",
155+
"settings_imported": "The settings have been imported"
156+
},
133157
"latest": {
134158
"ping": "Ping",
135159
"ping_unit": "ms",
@@ -140,7 +164,7 @@
140164
"before": "before"
141165
},
142166
"info": {
143-
"credits": "<Link>MySpeed</Link> is provided by GNMYT and uses the <CLILink>Speedtest CLI</CLILink> from Ookla.",
167+
"credits": "<MSpeed>MySpeed</MSpeed> is a open source project provided by GNMYT. Leave a star on <Github>GitHub</Github> or <Donate>donate</Donate> to support the project.",
144168
"recommendations_error": "You have to do at least 10 tests to get an average. It doesn't matter if the tests were done manually or automatically.",
145169
"recommendations_info": "Based on the last 10 tests, it was found that the optimal ping was <Bold>{{ping}} ms</Bold>, the download at <Bold>{{down}} Mbps</Bold> and the upload at <Bold>{{up}} Mbps</Bold>. It is best to orientate yourself on your internet contract and only adopt it if it matches that.",
146170
"update": "An update to version {{version}} is available. See <Changes>the changes</Changes> and <DLLink>download the update</DLLink>.",

client/src/common/components/Dropdown/DropdownComponent.jsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
faCircleNodes,
88
faClock,
99
faClose,
10-
faFileExport,
1110
faChartSimple,
1211
faGear,
1312
faGlobeEurope,
@@ -18,15 +17,15 @@ import {
1817
faPlay,
1918
faWandMagicSparkles,
2019
faCheck,
21-
faExclamationTriangle, faSliders
20+
faExclamationTriangle, faSliders, faHardDrive
2221
} from "@fortawesome/free-solid-svg-icons";
2322
import {ConfigContext} from "@/common/contexts/Config";
2423
import {StatusContext} from "@/common/contexts/Status";
2524
import {InputDialogContext} from "@/common/contexts/InputDialog";
2625
import {SpeedtestContext} from "@/common/contexts/Speedtests";
27-
import {baseRequest, downloadRequest, jsonRequest, patchRequest, postRequest} from "@/common/utils/RequestUtil";
26+
import {baseRequest, jsonRequest, patchRequest, postRequest} from "@/common/utils/RequestUtil";
2827
import {creditsInfo, recommendationsInfo} from "@/common/components/Dropdown/utils/infos";
29-
import {exportOptions, levelOptions, selectOptions, timeOptions} from "@/common/components/Dropdown/utils/options";
28+
import {levelOptions, selectOptions, timeOptions} from "@/common/components/Dropdown/utils/options";
3029
import {parseCron, stringifyCron} from "@/common/components/Dropdown/utils/utils";
3130
import {t} from "i18next";
3231
import ViewDialog from "@/common/components/ViewDialog";
@@ -36,6 +35,7 @@ import {NodeContext} from "@/common/contexts/Node";
3635
import {IntegrationDialog} from "@/common/components/IntegrationDialog";
3736
import LanguageDialog from "@/common/components/LanguageDialog";
3837
import ProviderDialog from "@/common/components/ProviderDialog";
38+
import StorageDialog from "@/common/components/StorageDialog";
3939

4040
let icon;
4141

@@ -66,6 +66,7 @@ function DropdownComponent() {
6666
const [showIntegrationDialog, setShowIntegrationDialog] = useState(false);
6767
const [showLanguageDialog, setShowLanguageDialog] = useState(false);
6868
const [showProviderDialog, setShowProviderDialog] = useState(false);
69+
const [showStorageDialog, setShowStorageDialog] = useState(false);
6970
const ref = useRef();
7071

7172
useEffect(() => {
@@ -192,17 +193,6 @@ function DropdownComponent() {
192193
});
193194
}
194195

195-
function exportDialog() {
196-
setDialog({
197-
select: true,
198-
title: t("update.export_title"),
199-
buttonText: t("update.download"),
200-
value: "json",
201-
selectOptions: exportOptions(),
202-
onSuccess: value => downloadRequest("/export/" + value)
203-
});
204-
}
205-
206196
const togglePause = () => {
207197
if (!status.paused) {
208198
setDialog({
@@ -228,9 +218,9 @@ function DropdownComponent() {
228218
{run: recommendedSettings, icon: faWandMagicSparkles, text: t("dropdown.recommendations")},
229219
{hr: true, key: 1},
230220
{run: () => setShowProviderDialog(true), icon: faSliders, text: t("dropdown.change_provider")},
221+
{run: () => setShowStorageDialog(true), icon: faHardDrive, text: t("dropdown.storage")},
231222
{run: updatePassword, icon: faKey, text: t("dropdown.password"), previewHidden: true},
232223
{run: updateCron, icon: faClock, text: t("dropdown.cron")},
233-
{run: exportDialog, icon: faFileExport, text: t("dropdown.export")},
234224
{run: togglePause, icon: status.paused ? faPlay : faPause, text: t("dropdown." + (status.paused ? "resume_tests" : "pause_tests"))},
235225
{run: () => setShowIntegrationDialog(true), icon: faCircleNodes, text: t("dropdown.integrations")},
236226
{hr: true, key: 2},
@@ -247,6 +237,7 @@ function DropdownComponent() {
247237
{showIntegrationDialog && <IntegrationDialog onClose={() => setShowIntegrationDialog(false)}/>}
248238
{showLanguageDialog && <LanguageDialog onClose={() => setShowLanguageDialog(false)}/>}
249239
{showProviderDialog && <ProviderDialog onClose={() => setShowProviderDialog(false)}/>}
240+
{showStorageDialog && <StorageDialog onClose={() => setShowStorageDialog(false)}/>}
250241
<div className="dropdown dropdown-invisible" id="dropdown" ref={ref}>
251242
<div className="dropdown-content">
252243
<h2>{t("dropdown.settings")}</h2>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {WEB_URL} from "@/index";
1+
import {DONATION_URL, PROJECT_URL, WEB_URL} from "@/index";
22
import {Trans} from "react-i18next";
3-
const CLI_URL = "https://www.speedtest.net/apps/cli";
43

5-
export const creditsInfo = () => <Trans components={{Link: <a href={WEB_URL} target="_blank" />,
6-
CLILink: <a href={CLI_URL} target="_blank"/>}}>info.credits</Trans>
4+
5+
export const creditsInfo = () => <Trans components={{MSpeed: <a href={WEB_URL} target="_blank" />,
6+
Github: <a href={PROJECT_URL} target="_blank" />, Donate: <a href={DONATION_URL} target="_blank" />}}>info.credits</Trans>
77

88
export const recommendationsInfo = (ping, down, up) => <Trans components={{Bold: <span className="dialog-value" />}}
99
values={{ping, down, up}}>info.recommendations_info</Trans>

client/src/common/components/Dropdown/utils/options.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,4 @@ export const selectOptions = () => ({
1818
"0 * * * *": t("options.cron.default"),
1919
"0 0,3,6,9,12,15,18,21 * * *": t("options.cron.rare"),
2020
"0 0,6,12,18 * * *": t("options.cron.really_rare")
21-
});
22-
23-
export const exportOptions = () => ({
24-
json: t("options.export.json"),
25-
csv: t("options.export.csv")
2621
});

client/src/common/components/LanguageDialog/LanguageDialog.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
22
import {t, changeLanguage} from "i18next";
33
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4-
import {faClose} from "@fortawesome/free-solid-svg-icons";
4+
import {faClose, faGlobe} from "@fortawesome/free-solid-svg-icons";
55
import "./styles.sass";
66
import {languages} from "@/i18n";
77
import {useContext, useState} from "react";
8+
import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
89

910
export const Dialog = () => {
1011
const [selectedLanguage, setSelectedLanguage] = useState(localStorage.getItem("language") || "en");
12+
const updateToast = useContext(ToastNotificationContext);
1113
const close = useContext(DialogContext);
1214

1315
const updateLanguage = () => {
1416
changeLanguage(selectedLanguage);
17+
updateToast(t('dropdown.language_changed'), "green", faGlobe);
1518
close();
1619
}
1720

client/src/common/components/LanguageDialog/styles.sass

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
cursor: pointer
2121
transition: background-color 0.3s
2222
border-radius: 0.5rem
23+
border: 2px solid $darker-gray
2324

2425
&:hover
2526
background-color: $darker-gray
@@ -37,6 +38,7 @@
3738
.language-selected
3839
background-color: $light-gray
3940
color: $white
41+
border: 2px solid $light-gray
4042

4143
&:hover
4244
background-color: $light-gray

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
22
import {t} from "i18next";
33
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4-
import {faClose} from "@fortawesome/free-solid-svg-icons";
4+
import {faCheck, faClose} from "@fortawesome/free-solid-svg-icons";
55
import "./styles.sass";
66
import React, {useContext, useEffect, useState} from "react";
77
import OoklaImage from "./assets/img/ookla.webp";
@@ -10,6 +10,7 @@ import CloudflareImage from "./assets/img/cloudflare.webp";
1010
import {jsonRequest, patchRequest} from "@/common/utils/RequestUtil";
1111
import {Trans} from "react-i18next";
1212
import {ConfigContext} from "@/common/contexts/Config";
13+
import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
1314

1415
export const providers = [
1516
{id: "ookla", name: "Ookla", image: OoklaImage},
@@ -21,6 +22,7 @@ export const providers = [
2122
export const Dialog = () => {
2223
const close = useContext(DialogContext);
2324
const [config, reloadConfig] = useContext(ConfigContext);
25+
const updateToast = useContext(ToastNotificationContext);
2426
const [provider, setProvider] = useState(config.provider || "ookla");
2527

2628
const [licenseAccepted, setLicenseAccepted] = useState(false);
@@ -61,6 +63,7 @@ export const Dialog = () => {
6163
}
6264

6365
reloadConfig();
66+
updateToast(t('dropdown.provider_changed'), "green", faCheck);
6467

6568
close();
6669
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import "./styles.sass";
2+
import React, {useContext, useEffect, useState} from "react";
3+
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
4+
import {t} from "i18next";
5+
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
6+
import {faClose, faDatabase, faGauge, faScrewdriverWrench} from "@fortawesome/free-solid-svg-icons";
7+
import Speedtests from "./tabs/Speedtests";
8+
import Configuration from "./tabs/Configuration";
9+
import {jsonRequest} from "@/common/utils/RequestUtil";
10+
11+
const Dialog = () => {
12+
const close = useContext(DialogContext);
13+
const [storageSize, setStorageSize] = useState({size: 0, testCount: 0});
14+
15+
const [currentTab, setCurrentTab] = useState(1);
16+
17+
useEffect(() => {
18+
jsonRequest("/storage").then((res) => {
19+
setStorageSize(res);
20+
});
21+
}, []);
22+
23+
return (
24+
<>
25+
<div className="dialog-header">
26+
<h4 className="dialog-text">{t("dropdown.storage")}</h4>
27+
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={() => close()}/>
28+
</div>
29+
<div className="storage-dialog">
30+
<div className="storage-options">
31+
<div className="storage-top">
32+
<div className={"storage-tab" + (1 === currentTab ? " storage-item-active" : "")}
33+
onClick={() => setCurrentTab(1)}>
34+
<FontAwesomeIcon icon={faGauge}/>
35+
<p>{t("storage.speedtests")}</p>
36+
</div>
37+
<div className={"storage-tab" + (2 === currentTab ? " storage-item-active" : "")}
38+
onClick={() => setCurrentTab(2)}>
39+
<FontAwesomeIcon icon={faScrewdriverWrench}/>
40+
<p>{t("storage.configuration")}</p>
41+
</div>
42+
43+
</div>
44+
<div className="storage-bottom">
45+
<div className="storage-tab reset-cursor">
46+
<FontAwesomeIcon icon={faDatabase}/>
47+
<p>{Math.round(storageSize.size / 1024)} KB</p>
48+
</div>
49+
</div>
50+
</div>
51+
52+
<div className="storage-manager">
53+
{currentTab === 1 && <Speedtests tests={storageSize.testCount}/>}
54+
{currentTab === 2 && <Configuration/>}
55+
</div>
56+
57+
58+
</div>
59+
</>
60+
);
61+
}
62+
63+
export const StorageDialog = ({onClose}) => {
64+
return (
65+
<DialogProvider close={onClose}>
66+
<Dialog/>
67+
</DialogProvider>
68+
)
69+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {StorageDialog as default} from "./StorageDialog";

0 commit comments

Comments
 (0)