Merge pull request #301 from gnmyt/optimizations/1.0.8

🆕 Version 1.0.8 - Update
This commit is contained in:
Mathias Wagner
2023-05-28 22:19:49 +02:00
committed by GitHub
39 changed files with 175 additions and 61 deletions

View File

@@ -1,18 +1,15 @@
<!DOCTYPE html>
<html lang="de">
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/assets/img/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#232835" />
<meta property="og:image" content="/logo.png" />
<meta property="og:image" content="/assets/img/logo.png" />
<meta name="description"
content="A speed test analysis software that shows your internet speed for up to 30 days"
/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="apple-touch-icon" href="/assets/img/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>MySpeed</title>
</head>

View File

@@ -1,12 +1,12 @@
{
"name": "client",
"version": "1.0.7",
"version": "1.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "client",
"version": "1.0.7",
"version": "1.0.8",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "client",
"version": "1.0.7",
"version": "1.0.8",
"scripts": {
"dev": "vite",
"build": "vite build",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -281,7 +281,20 @@
"display_name": "Anzeigename",
"display_name_placeholder": "MySpeed Benachrichtigung",
"error_message": "Fehlernachricht",
"error_message_placeholder": "Fehler: %upload%",
"error_message_placeholder": "Fehler: %error%",
"send_failed": "Fehlernachrichten senden",
"send_finished": "Abgeschlossene senden",
"finished_message": "Abgeschlossen-Nachricht",
"finished_message_placeholder": "%ping% ms, %download% Mbit/s, %upload% Mbit/s"
}
},
"gotify": {
"title": "Gotify",
"fields": {
"url": "Server-URL",
"key": "Anwendungs-Token",
"error_message": "Fehlernachricht",
"error_message_placeholder": "Fehler: %error%",
"send_failed": "Fehlernachrichten senden",
"send_finished": "Abgeschlossene senden",
"finished_message": "Abgeschlossen-Nachricht",
@@ -301,7 +314,7 @@
"token": "Bot Token",
"chat_id": "Chat ID",
"error_message": "Fehlernachricht",
"error_message_placeholder": "Fehler: %upload%",
"error_message_placeholder": "Fehler: %error%",
"send_failed": "Fehlernachrichten senden",
"send_finished": "Abgeschlossene senden",
"finished_message": "Abgeschlossen-Nachricht",

View File

@@ -281,7 +281,20 @@
"display_name": "Display name",
"display_name_placeholder": "MySpeed Notification",
"error_message": "Error message",
"error_message_placeholder": "Error: %upload%",
"error_message_placeholder": "Error: %error%",
"send_failed": "Send error messages",
"send_finished": "Send finished messages",
"finished_message": "Finished message",
"finished_message_placeholder": "%ping% ms, %download% Mbps, %upload% Mbps"
}
},
"gotify": {
"title": "Gotify",
"fields": {
"url": "Server-URL",
"key": "App token",
"error_message": "Error message",
"error_message_placeholder": "Error: %error%",
"send_failed": "Send error messages",
"send_finished": "Send finished messages",
"finished_message": "Finished message",
@@ -301,7 +314,7 @@
"token": "Bot Token",
"chat_id": "Chat ID",
"error_message": "Error message",
"error_message_placeholder": "Error: %upload%",
"error_message_placeholder": "Error: %error%",
"send_failed": "Send error messages",
"send_finished": "Send finished messages",
"finished_message": "Finished message",

View File

@@ -4,18 +4,18 @@
"description": "A speed test analysis software that shows your internet speed for up to 30 days",
"icons": [
{
"src": "favicon.ico",
"src": "/assets/img/favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"src": "/assets/img/logo192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "logo.png",
"src": "/assets/img/logo.png",
"type": "image/png",
"sizes": "512x512"
}

View File

@@ -1,4 +1,6 @@
import './App.sass';
import "@/common/styles/default.sass";
import "@/common/styles/fonts.sass";
import "@/common/styles/spinner.sass";
import Home from "./pages/Home";
import HeaderComponent from "./common/components/Header";
import {SpeedtestProvider} from "./common/contexts/Speedtests";
@@ -8,7 +10,6 @@ import {InputDialogProvider} from "@/common/contexts/InputDialog/InputDialog";
import {useContext, useState} from "react";
import i18n from './i18n';
import Loading from "@/pages/Loading";
import "@/common/styles/spinner.sass";
import Error from "@/pages/Error";
import {ViewContext, ViewProvider} from "@/common/contexts/View";
import Statistics from "@/pages/Statistics";

View File

@@ -53,18 +53,16 @@ export const IntegrationDialog = (props) => {
useEffect(() => {
jsonRequest("/integrations")
.then(data => setIntegrationData(data))
.catch(err => console.log(err));
.then(data => setIntegrationData(data));
jsonRequest("/integrations/active")
.then(data => setActiveData(data.map(item => ({...item, uuid: uuid()}))))
.catch(err => console.log(err));
.then(data => setActiveData(data.map(item => ({...item, uuid: uuid()}))));
}, []);
return (
<>
<DialogProvider close={props.onClose}>
{!integrationData || !activeData && <div className="lds-ellipsis"><div/><div/><div/><div/></div>}
<DialogProvider close={props.onClose} customClass={(!integrationData || !activeData) ? "dialog-loading" : ""}>
{(!integrationData || !activeData) && <div className="lds-ellipsis"><div/><div/><div/><div/></div>}
{integrationData && activeData && <Dialog integrations={integrationData} active={activeData} setActive={setActiveData}/>}
</DialogProvider>
</>

View File

@@ -5,6 +5,8 @@
flex-direction: column
gap: 0.5rem
user-select: none
overflow-x: hidden
overflow-y: scroll
.integration-tab
display: flex

View File

@@ -10,9 +10,21 @@ import {
faTrashArrowUp
} from "@fortawesome/free-solid-svg-icons";
import "./styles.sass";
import {useEffect, useState} from "react";
export const IntegrationItemHeader = ({integration, displayName, unsavedChanges, changesConfirmed, saveIntegration,
deleteConfirmed, deleteIntegration, open, setOpen, data}) => (
deleteConfirmed, deleteIntegration, open, setOpen, data}) => {
const [lastActivity, setLastActivity] = useState(generateRelativeTime(data.lastActivity));
useEffect(() => {
const interval = setInterval(() => {
if (data.lastActivity !== null) setLastActivity(generateRelativeTime(data.lastActivity));
}, 1000);
return () => clearInterval(interval);
});
return (
<div className="integration-item-header">
<div className="integration-item-left">
<FontAwesomeIcon icon={integration.icon} className="integration-item-icon"/>
@@ -23,7 +35,7 @@ export const IntegrationItemHeader = ({integration, displayName, unsavedChanges,
: (data.lastActivity === null || !data.lastActivity ? "inactive" : "active"))}/>
<p>{data.activityFailed ? t("failed") : (data.lastActivity === null || !data.lastActivity
? t("integrations.activity.never_executed") : t("integrations.activity.last_run") + generateRelativeTime(data.lastActivity))}</p>
? t("integrations.activity.never_executed") : t("integrations.activity.last_run") + lastActivity)}</p>
</div>
</div>
</div>
@@ -41,4 +53,4 @@ export const IntegrationItemHeader = ({integration, displayName, unsavedChanges,
className="integration-green"/>
</div>
</div>
)
)}

View File

@@ -15,7 +15,7 @@
gap: 0.5rem
overflow-y: scroll
overflow-x: clip
width: 100%
width: 70%
@media (max-width: 781px)
.integration-dialog
@@ -27,4 +27,5 @@
flex-direction: column
.integrations-tab
max-height: 15rem
max-height: 15rem
width: 100%

View File

@@ -5,12 +5,10 @@ export const LoadingDialog = (props) => {
return (
<>
{props.isOpen && <DialogProvider disableClosing={true} customClass="dialog-speedtest">
<div className="dialog-speedtest">
{props.isOpen && <DialogProvider disableClosing={true} customClass="dialog-loading">
<div className="lds-ellipsis">
<div/><div/><div/><div/>
</div>
</div>
</DialogProvider>}
</>
)

View File

@@ -1,4 +1,4 @@
@import "./common/styles/colors"
@import "colors"
body, html
margin: 0

View File

@@ -0,0 +1,34 @@
@font-face
font-display: swap
font-family: 'Inter'
font-style: normal
font-weight: 300
src: url('/assets/fonts/inter-v12-latin-300.woff2') format("woff2"), url('/assets/fonts/inter-v12-latin-300.ttf') format("truetype")
@font-face
font-display: swap
font-family: 'Inter'
font-style: normal
font-weight: 400
src: url('/assets/fonts/inter-v12-latin-regular.woff2') format("woff2"), url('/assets/fonts/inter-v12-latin-regular.ttf') format("truetype")
@font-face
font-display: swap
font-family: 'Inter'
font-style: normal
font-weight: 500
src: url('/assets/fonts/inter-v12-latin-500.woff2') format("woff2"), url('/assets/fonts/inter-v12-latin-500.ttf') format("truetype")
@font-face
font-display: swap
font-family: 'Inter'
font-style: normal
font-weight: 700
src: url('/assets/fonts/inter-v12-latin-700.woff2') format("woff2"), url('/assets/fonts/inter-v12-latin-700.ttf') format("truetype")
@font-face
font-display: swap
font-family: 'Inter'
font-style: normal
font-weight: 900
src: url('/assets/fonts/inter-v12-latin-900.woff2') format("woff2"), url('/assets/fonts/inter-v12-latin-900.ttf') format("truetype")

View File

@@ -10,7 +10,7 @@ i18n.use(initReactI18next).use(LanguageDetector).use(HttpApi).init({
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
backend: {
loadPath: '/locales/{{lng}}.json'
loadPath: '/assets/locales/{{lng}}.json'
},
detection: {
order: ['localStorage'],

View File

@@ -17,7 +17,9 @@ function TestArea() {
return (
<div className="speedtest-area">
{speedtests.map ? speedtests.map(test => {
let date = new Date(Date.parse(test.created));
const dateArray = test.created.split("-").map((value) => parseInt(value));
const date = test.type === "average" ? new Date(dateArray[0], dateArray[1] - 1, dateArray[2])
: new Date(Date.parse(test.created));
let item = localStorage.getItem("testTime");
if ((item === "3" || item === "4") && test.type !== "average") return;

View File

@@ -3,7 +3,7 @@ import "./styles.sass";
export const NodeHeader = () => {
return (
<div className="node-header">
<img src="/logo192.png" alt="Logo"/>
<img src="/assets/img/logo192.png" alt="Logo"/>
<h1>MySpeed</h1>
</div>
)

View File

@@ -1,18 +1,27 @@
import react from '@vitejs/plugin-react'
import path from "path";
import react from "@vitejs/plugin-react";
import {VitePWA} from "vite-plugin-pwa";
import path from "path";
export default {
plugins: [
VitePWA({ injectRegister: 'auto' }),
VitePWA({injectRegister: "auto", manifest: false}),
react()
],
build: {
outDir: 'build'
outDir: "build",
chunkSizeWarningLimit: 1600,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules'))
return id.includes('@fortawesome') ? 'icons' : 'vendor';
}
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
"@": path.resolve(__dirname, "./src"),
},
},
server: {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "myspeed",
"version": "1.0.7",
"version": "1.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "myspeed",
"version": "1.0.7",
"version": "1.0.8",
"dependencies": {
"axios": "^1.4.0",
"bcrypt": "^5.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "myspeed",
"version": "1.0.7",
"version": "1.0.8",
"scripts": {
"client": "cd client && npm run dev",
"server": "nodemon server",
@@ -17,8 +17,8 @@
"express": "^4.18.2",
"node-schedule": "^2.1.1",
"sequelize": "^6.31.1",
"tmp": "^0.2.1",
"sqlite3": "^5.1.6"
"sqlite3": "^5.1.6",
"tmp": "^0.2.1"
},
"devDependencies": {
"concurrently": "^8.0.1",

View File

@@ -9,8 +9,7 @@ const configDefaults = {
"serverId": "none",
"password": "none",
"passwordLevel": "none",
"acceptOoklaLicense": "false",
"healthChecksUrl": "https://hc-ping.com/<uuid>"
"acceptOoklaLicense": "false"
}
module.exports.insertDefaults = async () => {

View File

@@ -0,0 +1,39 @@
const axios = require("axios");
const {replaceVariables} = require("../util/variables");
const defaults = {
finished: "A speedtest is finished:\nPing: %ping% ms\nUpload: %upload% Mbps\nDownload: %download% Mbps",
failed: "A speedtest has failed. Reason: %error%"
}
const postWebhook = async (url, key, triggerActivity, message, priority) => {
axios.post(`${url}/message`, {message, priority}, {headers: {"Authorization": "Bearer " + key}})
.then(() => triggerActivity())
.catch(() => triggerActivity(true));
}
module.exports = (registerEvent) => {
registerEvent('testFinished', async (integration, data, activity) => {
if (integration.data.send_finished)
await postWebhook(integration.data.url, integration.data.key, activity,
replaceVariables(integration.data.finished_message || defaults.finished, data), 3);
});
registerEvent('testFailed', async (integration, error, activity) => {
if (integration.data.send_failed)
await postWebhook(integration.data.url, integration.data.key, activity,
replaceVariables(integration.data.failed_message || defaults.failed, {error}), 8);
});
return {
icon: "fa-solid fa-bell",
fields: [
{name: "url", type: "text", required: true, regex: /https?:\/\/.+/},
{name: "key", type: "text", required: true, regex: /^.{15}$/},
{name: "send_finished", type: "boolean", required: false},
{name: "finished_message", type: "textarea", required: false},
{name: "send_failed", type: "boolean", required: false},
{name: "error_message", type: "textarea", required: false}
]
};
}

View File

@@ -34,7 +34,7 @@ module.exports = (registerEvent) => {
return {
icon: "fa-solid fa-globe",
fields: [
{name: "url", type: "text", required: true, regex: /https:\/\/.+/},
{name: "url", type: "text", required: true, regex: /https?:\/\/.+/},
{name: "send_started", type: "boolean", required: false},
{name: "send_finished", type: "boolean", required: false},
{name: "send_alive", type: "boolean", required: false},

View File

@@ -8,7 +8,7 @@ const password = require('../middlewares/password');
app.get("/", password(true), async (req, res) => {
let configValues = {};
(await config.list()).forEach(row => {
if (row.key !== "password" && !(req.viewMode && ["healthChecksUrl", "serverId", "cron", "passwordLevel"].includes(row.key)))
if (row.key !== "password" && !(req.viewMode && ["serverId", "cron", "passwordLevel"].includes(row.key)))
configValues[row.key] = row.value;
});
configValues['viewMode'] = req.viewMode;

View File

@@ -3,14 +3,9 @@ const integrations = require('../controller/integrations');
const password = require('../middlewares/password');
const {validateInput} = require("../controller/integrations");
app.get("/", password(false), (req, res) => {
return res.json(integrations.getIntegrations());
});
app.get("/", password(false), (req, res) => res.json(integrations.getIntegrations()));
app.get("/active", password(false), async (req, res) => {
const active = await integrations.getActive();
return res.json(active);
});
app.get("/active", password(false), async (req, res) => res.json(await integrations.getActive()));
app.put("/:integrationName", password(false), async (req, res) => {
const integration = integrations.getIntegration(req.params.integrationName);

View File

@@ -9,7 +9,7 @@ module.exports.setState = (state = "ping") => {
currentState = state;
}
// Send a ping to the provided healthcheck server
// Send a ping to the event system
module.exports.sendPing = async (type, message) => {
await triggerEvent("minutePassed", {type, message});
}
@@ -19,16 +19,17 @@ module.exports.sendCurrent = async () => {
if (currentState === "ping") await this.sendPing();
}
// Sends an error to the healthcheck server
// Sends an 'error' ping to the event system
module.exports.sendError = async (error = "Unknown error") => {
await triggerEvent("testFailed", error);
}
// Sends a 'running' ping to the healthcheck server
// Sends a 'running' ping to the event system
module.exports.sendRunning = async () => {
await triggerEvent("testStarted");
}
// Sends a 'finished' ping to the event system
module.exports.sendFinished = async (data) => {
await triggerEvent("testFinished", data);
}