From 8008131ad7ed0fa77c3afa7e1747728f61215515 Mon Sep 17 00:00:00 2001 From: AuroraLS3 Date: Fri, 2 Dec 2022 18:08:10 +0000 Subject: [PATCH] 5.5.2100 --- react/dashboard/dashboard/package.json | 6 +- react/dashboard/dashboard/public/logo192.png | Bin 0 -> 6826 bytes react/dashboard/dashboard/public/logo512.png | Bin 0 -> 21060 bytes react/dashboard/dashboard/src/App.js | 25 ++- .../modal/FinalizeRegistrationModal.js | 35 +++ .../src/components/navigation/Header.js | 6 +- .../src/components/navigation/Sidebar.js | 2 +- .../dashboard/src/hooks/dataFetchHook.js | 3 +- .../src/service/authenticationService.js | 15 +- .../src/service/backendConfiguration.js | 8 +- .../dashboard/src/service/localeService.js | 5 +- .../dashboard/src/service/metadataService.js | 16 +- .../dashboard/src/service/networkService.js | 22 +- .../dashboard/src/service/playerService.js | 9 +- .../dashboard/src/service/serverService.js | 208 +++++++++++++++--- .../dashboard/src/views/layout/ErrorPage.js | 2 +- .../dashboard/src/views/layout/LoginPage.js | 9 +- .../dashboard/src/views/layout/NetworkPage.js | 3 +- .../src/views/layout/RegisterPage.js | 200 +++++++++++++++++ react/dashboard/dashboard/yarn.lock | 28 +-- 20 files changed, 513 insertions(+), 89 deletions(-) create mode 100644 react/dashboard/dashboard/public/logo192.png create mode 100644 react/dashboard/dashboard/public/logo512.png create mode 100644 react/dashboard/dashboard/src/components/modal/FinalizeRegistrationModal.js create mode 100644 react/dashboard/dashboard/src/views/layout/RegisterPage.js diff --git a/react/dashboard/dashboard/package.json b/react/dashboard/dashboard/package.json index f305acca9..714491e14 100644 --- a/react/dashboard/dashboard/package.json +++ b/react/dashboard/dashboard/package.json @@ -13,13 +13,13 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@fullcalendar/bootstrap": "^5.11.3", "@fullcalendar/daygrid": "^5.11.3", - "@fullcalendar/react": "^5.11.2", + "@fullcalendar/react": "^5.11.3", "@highcharts/map-collection": "^2.0.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^14.4.3", - "axios": "^1.1.3", - "bootstrap": "^5.2.2", + "axios": "^1.2.0", + "bootstrap": "^5.2.3", "datatables.net": "^1.13.1", "datatables.net-bs5": "^1.12.1", "datatables.net-responsive-bs5": "^2.4.0", diff --git a/react/dashboard/dashboard/public/logo192.png b/react/dashboard/dashboard/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..386bc28bec0802e039dc88e85edfab4177fcd19f GIT binary patch literal 6826 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

Mhu*U zCjrgD)+Bi~z^!5r3B&Z&xwkwyy1h0(_9xGQocVRga_+A!R0bF#^1V>$cG%A65-h8! z%m%#RU_({)yLnBA11Nf*9>yp=-9PDKJd13$Zv$72-*)QZDm7$`qvnZ_z7Wb@opel3 z_tvrDBQIJ{Z15Q_SJ9AQ@$Y>S)DG#r{jegH0&C%U=M9Aq*Jyc5Ye5>dCnsoP&2FMW zgu^iouh(hyc1@o!_^1lF1Nmu5Lpq@~ZFem3 zC#eXVb7QY$RAPr-#H*7xjE%b>mRuGlN-`Di7Ky***pFjX%~4MAc1Jlz#mt!-F4pU%&BTKm$qsEG+!DE311J1*s^s-pjyuFTFm))|ng8vNs zxpxu$hckmNi1qiQ&m)&ZRK$+iy!v{AtpWn2-$|~#i@vIR{CEAVeE-O%b*Dib+t;BLhDGUx3MSVkHF>d zwk}hm?WJkrqA^t=W4Ii2r+1(#VUjeWa)HNOjt5U zRDgep2g;j!Ue> z{`Ut{(GJ6WvfHzwXexaMmRTm}@95LfX-Grk*WW^Qf)P3{Caj(#Ccyiy2fNrU30u>a zONtEbBn^^N;^Zl+9W{t1@F9S1*Tb7f1LG=cYB@c6o7=lGe;ROU6h8R_0~w%VU*R2caeafoXqP~+oTh|ax|EB4 zM0!!Eot+W%0sJU8biy91Z%T%>-YKk^UhPCqL(@qs|LNr*Q-oHnIOpu%0L0XxEgueD zd%shQs)P-@oZy?d zB)E$%x3*`l=*W^57#kma(xm%gK9xFU#Amq_%N+P2ME8hIQxco*Pyqbx-)sN(6Kl7G F{{tbbF^>QM literal 0 HcmV?d00001 diff --git a/react/dashboard/dashboard/src/App.js b/react/dashboard/dashboard/src/App.js index 2e25d97b0..a9c9fdcb2 100644 --- a/react/dashboard/dashboard/src/App.js +++ b/react/dashboard/dashboard/src/App.js @@ -12,6 +12,7 @@ import {MetadataContextProvider} from "./hooks/metadataHook"; import {AuthenticationContextProvider} from "./hooks/authenticationHook"; import {NavigationContextProvider} from "./hooks/navigationHook"; import MainPageRedirect from "./components/navigation/MainPageRedirect"; +import {staticSite} from "./service/backendConfiguration"; const PlayerPage = React.lazy(() => import("./views/layout/PlayerPage")); const PlayerOverview = React.lazy(() => import("./views/player/PlayerOverview")); @@ -28,7 +29,6 @@ const ServerPvpPve = React.lazy(() => import("./views/server/ServerPvpPve")); const PlayerbaseOverview = React.lazy(() => import("./views/server/PlayerbaseOverview")); const ServerPlayers = React.lazy(() => import("./views/server/ServerPlayers")); const ServerGeolocations = React.lazy(() => import("./views/server/ServerGeolocations")); -const LoginPage = React.lazy(() => import("./views/layout/LoginPage")); const ServerPerformance = React.lazy(() => import("./views/server/ServerPerformance")); const ServerPluginData = React.lazy(() => import("./views/server/ServerPluginData")); const ServerWidePluginData = React.lazy(() => import("./views/server/ServerWidePluginData")); @@ -50,6 +50,9 @@ const QueryPage = React.lazy(() => import("./views/layout/QueryPage")); const NewQueryView = React.lazy(() => import("./views/query/NewQueryView")); const QueryResultView = React.lazy(() => import("./views/query/QueryResultView")); +const LoginPage = React.lazy(() => import("./views/layout/LoginPage")); +const RegisterPage = React.lazy(() => import("./views/layout/RegisterPage")); +const ErrorPage = React.lazy(() => import("./views/layout/ErrorPage")); const ErrorsPage = React.lazy(() => import("./views/layout/ErrorsPage")); const SwaggerView = React.lazy(() => import("./views/SwaggerView")); @@ -89,7 +92,9 @@ function App() { }/> }/> - }/> + }/> + {!staticSite && }/>} + {!staticSite && }/>} }> }/> }/> @@ -132,7 +137,8 @@ function App() { }/> }/> }/> - }/> + {!staticSite && + }/>} }/> }/> }/> @@ -145,13 +151,18 @@ function App() { icon: faMapSigns }}/>}/> - }> + {!staticSite && }> }/> }/> }/> - - }/> - }/> + } + {!staticSite && }/>} + {!staticSite && }/>} + }/> diff --git a/react/dashboard/dashboard/src/components/modal/FinalizeRegistrationModal.js b/react/dashboard/dashboard/src/components/modal/FinalizeRegistrationModal.js new file mode 100644 index 000000000..1d1ab22ec --- /dev/null +++ b/react/dashboard/dashboard/src/components/modal/FinalizeRegistrationModal.js @@ -0,0 +1,35 @@ +import React from 'react'; +import {useTranslation} from "react-i18next"; +import {useMetadata} from "../../hooks/metadataHook"; +import {Modal} from "react-bootstrap-v5"; +import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; +import {faHandPointRight} from "@fortawesome/free-regular-svg-icons"; + +const FinalizeRegistrationModal = ({show, toggle, registerCode}) => { + const {t} = useTranslation(); + const {mainCommand} = useMetadata(); + + return ( + + + + {t('html.register.completion')} + + + } {' '} {lastUpdate.formatted} diff --git a/react/dashboard/dashboard/src/components/navigation/Sidebar.js b/react/dashboard/dashboard/src/components/navigation/Sidebar.js index f06171be4..9d7341384 100644 --- a/react/dashboard/dashboard/src/components/navigation/Sidebar.js +++ b/react/dashboard/dashboard/src/components/navigation/Sidebar.js @@ -230,7 +230,7 @@ const Sidebar = ({items, showBackButton}) => { } - {items.length ? items.map((item, i) => renderItem(item, i, openCollapse, toggleCollapse, t)) : ''} + {items.length ? items.filter(item => item !== undefined).map((item, i) => renderItem(item, i, openCollapse, toggleCollapse, t)) : ''} } diff --git a/react/dashboard/dashboard/src/hooks/dataFetchHook.js b/react/dashboard/dashboard/src/hooks/dataFetchHook.js index b0211014b..f413d7084 100644 --- a/react/dashboard/dashboard/src/hooks/dataFetchHook.js +++ b/react/dashboard/dashboard/src/hooks/dataFetchHook.js @@ -2,6 +2,7 @@ import {useEffect, useState} from "react"; import {useNavigation} from "./navigationHook"; import {useDataStore} from "./datastoreHook"; import {useMetadata} from "./metadataHook"; +import {staticSite} from "../service/backendConfiguration"; export const useDataRequest = (fetchMethod, parameters) => { const [data, setData] = useState(undefined); @@ -16,7 +17,7 @@ export const useDataRequest = (fetchMethod, parameters) => { const handleResponse = (json, error, skipOldData, timeout) => { if (json) { const timestamp = json.timestamp; - if (timestamp) { + if (staticSite || timestamp) { // Data has timestamp, the data may come from cache const acceptedTimestamp = timestamp + (refreshBarrierMs ? refreshBarrierMs : 15000); if (acceptedTimestamp < updateRequested) { diff --git a/react/dashboard/dashboard/src/service/authenticationService.js b/react/dashboard/dashboard/src/service/authenticationService.js index 29dc9daf0..dca9ef148 100644 --- a/react/dashboard/dashboard/src/service/authenticationService.js +++ b/react/dashboard/dashboard/src/service/authenticationService.js @@ -1,6 +1,9 @@ -import {doGetRequest, doSomePostRequest, standard200option} from "./backendConfiguration"; +import {doGetRequest, doSomePostRequest, standard200option, staticSite} from "./backendConfiguration"; export const fetchWhoAmI = async () => { + if (staticSite) { + return {authRequired: false, loggedIn: false} + } const url = '/v1/whoami'; return doGetRequest(url); } @@ -8,4 +11,14 @@ export const fetchWhoAmI = async () => { export const fetchLogin = async (username, password) => { const url = '/auth/login'; return doSomePostRequest(url, [standard200option], `user=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`); +} + +export const postRegister = async (username, password) => { + const url = '/auth/register'; + return doSomePostRequest(url, [standard200option], `user=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`); +} + +export const fetchRegisterCheck = async (code) => { + const url = `/auth/register?code=${encodeURIComponent(code)}`; + return doGetRequest(url); } \ No newline at end of file diff --git a/react/dashboard/dashboard/src/service/backendConfiguration.js b/react/dashboard/dashboard/src/service/backendConfiguration.js index 1752df668..f8a370fcf 100644 --- a/react/dashboard/dashboard/src/service/backendConfiguration.js +++ b/react/dashboard/dashboard/src/service/backendConfiguration.js @@ -1,6 +1,9 @@ import axios from "axios"; -const toBeReplaced = "PLAN_BASE_ADDRESS"; +const javaReplaced = { + isStatic: "PLAN_EXPORTED_VERSION", + address: "PLAN_BASE_ADDRESS" +} const isCurrentAddress = (address) => { const is = window.location.href.startsWith(address); @@ -8,7 +11,8 @@ const isCurrentAddress = (address) => { return is; } -export const baseAddress = "PLAN_BASE_ADDRESS" === toBeReplaced || !isCurrentAddress(toBeReplaced) ? "" : toBeReplaced; +export const baseAddress = javaReplaced.address.startsWith('PLAN_') || !isCurrentAddress(javaReplaced.address) ? "" : javaReplaced.address; +export const staticSite = javaReplaced.isStatic === 'true'; export const doSomeGetRequest = async (url, statusOptions) => { return doSomeRequest(url, statusOptions, async () => axios.get(url)); diff --git a/react/dashboard/dashboard/src/service/localeService.js b/react/dashboard/dashboard/src/service/localeService.js index 583384ffa..9b1930364 100644 --- a/react/dashboard/dashboard/src/service/localeService.js +++ b/react/dashboard/dashboard/src/service/localeService.js @@ -4,6 +4,7 @@ import I18NextLocalStorageBackend from "i18next-localstorage-backend"; import I18NextHttpBackend from 'i18next-http-backend'; import {initReactI18next} from 'react-i18next'; import {fetchAvailableLocales} from "./metadataService"; +import {baseAddress, staticSite} from "./backendConfiguration"; /** * A locale system for localizing the website. @@ -53,6 +54,8 @@ export const localeService = { this.clientLocale = this.defaultLanguage; } + let loadPath = baseAddress + '/v1/locale/{{lng}}'; + if (staticSite) loadPath = baseAddress + '/locale/{{lng}}.json' await i18next .use(I18NextChainedBackend) .use(initReactI18next) @@ -70,7 +73,7 @@ export const localeService = { expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days versions: this.languageVersions }, { - loadPath: '/v1/locale/{{lng}}' + loadPath: loadPath }] }, }, () => {/* No need to initialize anything */ diff --git a/react/dashboard/dashboard/src/service/metadataService.js b/react/dashboard/dashboard/src/service/metadataService.js index 6604a672b..84c01f410 100644 --- a/react/dashboard/dashboard/src/service/metadataService.js +++ b/react/dashboard/dashboard/src/service/metadataService.js @@ -1,26 +1,30 @@ -import {doGetRequest} from "./backendConfiguration"; +import {doGetRequest, staticSite} from "./backendConfiguration"; export const fetchPlanMetadata = async () => { - const url = '/v1/metadata'; + let url = '/v1/metadata'; + if (staticSite) url = '/metadata/metadata.json' return doGetRequest(url); } export const fetchPlanVersion = async () => { - const url = '/v1/version'; + let url = '/v1/version'; + if (staticSite) url = '/metadata/version.json' return doGetRequest(url); } export const fetchAvailableLocales = async () => { - const url = '/v1/locale'; + let url = '/v1/locale'; + if (staticSite) url = '/locale/locale.json' return doGetRequest(url); } export const fetchErrorLogs = async () => { - const url = '/v1/errors'; + let url = '/v1/errors'; return doGetRequest(url); } export const fetchNetworkMetadata = async () => { - const url = '/v1/networkMetadata'; + let url = '/v1/networkMetadata'; + if (staticSite) url = '/metadata/networkMetadata.json' return doGetRequest(url); } \ No newline at end of file diff --git a/react/dashboard/dashboard/src/service/networkService.js b/react/dashboard/dashboard/src/service/networkService.js index b37b546ec..94ceb4907 100644 --- a/react/dashboard/dashboard/src/service/networkService.js +++ b/react/dashboard/dashboard/src/service/networkService.js @@ -1,36 +1,42 @@ -import {doGetRequest} from "./backendConfiguration"; +import {doGetRequest, staticSite} from "./backendConfiguration"; export const fetchNetworkOverview = async (updateRequested) => { - const url = `/v1/network/overview?timestamp=${updateRequested}`; + let url = `/v1/network/overview?timestamp=${updateRequested}`; + if (staticSite) url = `/data/network-overview.json`; return doGetRequest(url); } export const fetchServersOverview = async (updateRequested) => { - const url = `/v1/network/servers?timestamp=${updateRequested}`; + let url = `/v1/network/servers?timestamp=${updateRequested}`; + if (staticSite) url = `/data/network-servers.json`; return doGetRequest(url); } export const fetchServerPie = async (timestamp) => { - const url = `/v1/graph?type=serverPie×tamp=${timestamp}`; + let url = `/v1/graph?type=serverPie×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-serverPie.json`; return doGetRequest(url); } export const fetchNetworkSessionsOverview = async (timestamp) => { - const url = `/v1/network/sessionsOverview?timestamp=${timestamp}`; + let url = `/v1/network/sessionsOverview?timestamp=${timestamp}`; + if (staticSite) url = `/data/network-sessionsOverview.json`; return doGetRequest(url); } export const fetchNetworkPlayerbaseOverview = async (timestamp) => { - const url = `/v1/network/playerbaseOverview?timestamp=${timestamp}`; + let url = `/v1/network/playerbaseOverview?timestamp=${timestamp}`; + if (staticSite) url = `/data/network-playerbaseOverview.json`; return doGetRequest(url); } export const fetchNetworkPingTable = async (timestamp) => { - const url = `/v1/network/pingTable?timestamp=${timestamp}`; + let url = `/v1/network/pingTable?timestamp=${timestamp}`; + if (staticSite) url = `/data/network-pingTable.json`; return doGetRequest(url); } export const fetchNetworkPerformanceOverview = async (timestamp, serverUUIDs) => { - const url = `/v1/network/performanceOverview?servers=${encodeURIComponent(JSON.stringify(serverUUIDs))}×tamp=${timestamp}`; + let url = `/v1/network/performanceOverview?servers=${encodeURIComponent(JSON.stringify(serverUUIDs))}×tamp=${timestamp}`; return doGetRequest(url); } \ No newline at end of file diff --git a/react/dashboard/dashboard/src/service/playerService.js b/react/dashboard/dashboard/src/service/playerService.js index c6c9a97d4..1d8bb6c15 100644 --- a/react/dashboard/dashboard/src/service/playerService.js +++ b/react/dashboard/dashboard/src/service/playerService.js @@ -1,14 +1,15 @@ import {faMapSigns} from "@fortawesome/free-solid-svg-icons"; -import {doSomeGetRequest, standard200option} from "./backendConfiguration"; +import {doSomeGetRequest, standard200option, staticSite} from "./backendConfiguration"; export const fetchPlayer = async (timestamp, uuid) => { - const url = `/v1/player?player=${uuid}×tamp=${timestamp}`; + let url = `/v1/player?player=${uuid}×tamp=${timestamp}`; + if (staticSite) url = `/player/${uuid}/player-${uuid}.json` return doSomeGetRequest(url, [ standard200option, { - status: 400, + status: staticSite ? 404 : 400, get: () => ({ - message: 'Player not found: ' + uuid + ', try another player', + message: 'Player not found: ' + uuid + ', try another player.' + (staticSite ? ' You can try the export players command.' : ''), title: '404 Player not found', icon: faMapSigns }) diff --git a/react/dashboard/dashboard/src/service/serverService.js b/react/dashboard/dashboard/src/service/serverService.js index eeb4a6e39..95a841431 100644 --- a/react/dashboard/dashboard/src/service/serverService.js +++ b/react/dashboard/dashboard/src/service/serverService.js @@ -1,130 +1,270 @@ -import {doGetRequest} from "./backendConfiguration"; - +import {doGetRequest, staticSite} from "./backendConfiguration"; export const fetchServerIdentity = async (timestamp, identifier) => { - const url = `/v1/serverIdentity?server=${identifier}`; + let url = `/v1/serverIdentity?server=${identifier}`; + if (staticSite) url = `/data/serverIdentity-${identifier}.json`; return doGetRequest(url); } export const fetchServerOverview = async (timestamp, identifier) => { - const url = `/v1/serverOverview?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/serverOverview?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/serverOverview-${identifier}.json`; return doGetRequest(url); } export const fetchOnlineActivityOverview = async (timestamp, identifier) => { - const url = `/v1/onlineOverview?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/onlineOverview?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/onlineOverview-${identifier}.json`; return doGetRequest(url); } export const fetchPlayerbaseOverview = async (timestamp, identifier) => { - const url = `/v1/playerbaseOverview?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/playerbaseOverview?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/playerbaseOverview-${identifier}.json`; return doGetRequest(url); } export const fetchSessionOverview = async (timestamp, identifier) => { - const url = `/v1/sessionsOverview?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/sessionsOverview?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/sessionsOverview-${identifier}.json`; return doGetRequest(url); } export const fetchPvpPve = async (timestamp, identifier) => { - const url = `/v1/playerVersus?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/playerVersus?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/playerVersus-${identifier}.json`; return doGetRequest(url); } export const fetchPerformanceOverview = async (timestamp, identifier) => { - const url = `/v1/performanceOverview?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/performanceOverview?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/performanceOverview-${identifier}.json`; return doGetRequest(url); } export const fetchExtensionData = async (timestamp, identifier) => { - const url = `/v1/extensionData?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/extensionData?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/extensionData-${identifier}.json`; return doGetRequest(url); } export const fetchSessions = async (timestamp, identifier) => { - const url = identifier ? `/v1/sessions?server=${identifier}×tamp=${timestamp}` : - `/v1/sessions?timestamp=${timestamp}`; + if (identifier) { + return await fetchSessionsServer(timestamp, identifier); + } else { + return await fetchSessionsNetwork(timestamp); + } +} + +const fetchSessionsServer = async (timestamp, identifier) => { + let url = `/v1/sessions?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/sessions-${identifier}.json`; + return doGetRequest(url); +} + +const fetchSessionsNetwork = async (timestamp) => { + let url = `/v1/sessions?timestamp=${timestamp}`; + if (staticSite) url = `/data/sessions.json`; return doGetRequest(url); } export const fetchKills = async (timestamp, identifier) => { - const url = `/v1/kills?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/kills?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/kills-${identifier}.json`; return doGetRequest(url); } export const fetchPlayers = async (timestamp, identifier) => { - const url = identifier ? `/v1/players?server=${identifier}×tamp=${timestamp}` : `/v1/players?timestamp=${timestamp}`; + if (identifier) { + return await fetchPlayersServer(timestamp, identifier); + } else { + return await fetchPlayersNetwork(timestamp); + } +} +const fetchPlayersServer = async (timestamp, identifier) => { + let url = `/v1/players?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/players-${identifier}.json`; + return doGetRequest(url); +} + +const fetchPlayersNetwork = async (timestamp) => { + let url = `/v1/players?timestamp=${timestamp}`; + if (staticSite) url = `/data/players.json`; return doGetRequest(url); } export const fetchPingTable = async (timestamp, identifier) => { - const url = `/v1/pingTable?server=${identifier}×tamp=${timestamp}`; + let url = `/v1/pingTable?server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/pingTable-${identifier}.json`; return doGetRequest(url); } export const fetchPlayersOnlineGraph = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=playersOnline&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=playersOnline×tamp=${timestamp}`; + if (identifier) { + return await fetchPlayersOnlineGraphServer(timestamp, identifier); + } else { + return await fetchPlayersOnlineGraphNetwork(timestamp); + } +} + +const fetchPlayersOnlineGraphServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=playersOnline&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-playersOnline_${identifier}.json`; + return doGetRequest(url); +} + +const fetchPlayersOnlineGraphNetwork = async (timestamp) => { + let url = `/v1/graph?type=playersOnline×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-playersOnline.json`; return doGetRequest(url); } export const fetchPlayerbaseDevelopmentGraph = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=activity&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=activity×tamp=${timestamp}`; + if (identifier) { + return await fetchPlayerbaseDevelopmentGraphServer(timestamp, identifier); + } else { + return await fetchPlayerbaseDevelopmentGraphNetwork(timestamp); + } +} + +const fetchPlayerbaseDevelopmentGraphServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=activity&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-activity_${identifier}.json`; + return doGetRequest(url); +} + +const fetchPlayerbaseDevelopmentGraphNetwork = async (timestamp) => { + let url = `/v1/graph?type=activity×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-activity.json`; return doGetRequest(url); } export const fetchDayByDayGraph = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=uniqueAndNew&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=uniqueAndNew×tamp=${timestamp}`; + if (identifier) { + return await fetchDayByDayGraphServer(timestamp, identifier); + } else { + return await fetchDayByDayGraphNetwork(timestamp); + } +} + +const fetchDayByDayGraphServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=uniqueAndNew&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-uniqueAndNew_${identifier}.json`; + return doGetRequest(url); +} + +const fetchDayByDayGraphNetwork = async (timestamp) => { + let url = `/v1/graph?type=uniqueAndNew×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-uniqueAndNew.json`; return doGetRequest(url); } export const fetchHourByHourGraph = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=hourlyUniqueAndNew×tamp=${timestamp}`; + if (identifier) { + return await fetchHourByHourGraphServer(timestamp, identifier); + } else { + return await fetchHourByHourGraphNetwork(timestamp); + } +} + +const fetchHourByHourGraphServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-hourlyUniqueAndNew_${identifier}.json`; + return doGetRequest(url); +} + +const fetchHourByHourGraphNetwork = async (timestamp) => { + let url = `/v1/graph?type=hourlyUniqueAndNew×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-hourlyUniqueAndNew.json`; return doGetRequest(url); } export const fetchServerCalendarGraph = async (timestamp, identifier) => { - const url = `/v1/graph?type=serverCalendar&server=${identifier}×tamp=${timestamp}`; + let url = `/v1/graph?type=serverCalendar&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-serverCalendar_${identifier}.json`; return doGetRequest(url); } export const fetchPunchCardGraph = async (timestamp, identifier) => { - const url = `/v1/graph?type=punchCard&server=${identifier}×tamp=${timestamp}`; + let url = `/v1/graph?type=punchCard&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-punchCard_${identifier}.json`; return doGetRequest(url); } export const fetchWorldPie = async (timestamp, identifier) => { - const url = `/v1/graph?type=worldPie&server=${identifier}×tamp=${timestamp}`; + let url = `/v1/graph?type=worldPie&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-worldPie_${identifier}.json`; return doGetRequest(url); } export const fetchGeolocations = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=geolocation&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=geolocation×tamp=${timestamp}`; + if (identifier) { + return await fetchGeolocationsServer(timestamp, identifier); + } else { + return await fetchGeolocationsNetwork(timestamp); + } +} + +const fetchGeolocationsServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=geolocation&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-geolocation_${identifier}.json`; + return doGetRequest(url); +} + +const fetchGeolocationsNetwork = async (timestamp) => { + let url = `/v1/graph?type=geolocation×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-geolocation.json`; return doGetRequest(url); } export const fetchOptimizedPerformance = async (timestamp, identifier, after) => { - const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}&after=${after}`; + let url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}&after=${after}`; + if (staticSite) url = `/data/graph-optimizedPerformance_${identifier}.json`; return doGetRequest(url); } export const fetchPingGraph = async (timestamp, identifier) => { - const url = `/v1/graph?type=aggregatedPing&server=${identifier}×tamp=${timestamp}`; + let url = `/v1/graph?type=aggregatedPing&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-aggregatedPing_${identifier}.json`; return doGetRequest(url); } export const fetchJoinAddressPie = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=joinAddressPie&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=joinAddressPie×tamp=${timestamp}`; + if (identifier) { + return await fetchJoinAddressPieServer(timestamp, identifier); + } else { + return await fetchJoinAddressPieNetwork(timestamp); + } +} + +const fetchJoinAddressPieServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=joinAddressPie&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-joinAddressPie_${identifier}.json`; + return doGetRequest(url); +} + +const fetchJoinAddressPieNetwork = async (timestamp) => { + let url = `/v1/graph?type=joinAddressPie×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-joinAddressPie.json`; return doGetRequest(url); } export const fetchJoinAddressByDay = async (timestamp, identifier) => { - const url = identifier ? `/v1/graph?type=joinAddressByDay&server=${identifier}×tamp=${timestamp}` : - `/v1/graph?type=joinAddressByDay×tamp=${timestamp}`; + if (identifier) { + return await fetchJoinAddressByDayServer(timestamp, identifier); + } else { + return await fetchJoinAddressByDayNetwork(timestamp); + } +} + +const fetchJoinAddressByDayServer = async (timestamp, identifier) => { + let url = `/v1/graph?type=joinAddressByDay&server=${identifier}×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-joinAddressByDay_${identifier}.json`; + return doGetRequest(url); +} + +const fetchJoinAddressByDayNetwork = async (timestamp) => { + let url = `/v1/graph?type=joinAddressByDay×tamp=${timestamp}`; + if (staticSite) url = `/data/graph-joinAddressByDay.json`; return doGetRequest(url); } diff --git a/react/dashboard/dashboard/src/views/layout/ErrorPage.js b/react/dashboard/dashboard/src/views/layout/ErrorPage.js index 7bb606c26..eb9c2f77a 100644 --- a/react/dashboard/dashboard/src/views/layout/ErrorPage.js +++ b/react/dashboard/dashboard/src/views/layout/ErrorPage.js @@ -12,7 +12,7 @@ const ErrorPage = ({error}) => {

-
+
diff --git a/react/dashboard/dashboard/src/views/layout/LoginPage.js b/react/dashboard/dashboard/src/views/layout/LoginPage.js index ff10e9899..ad4de517c 100644 --- a/react/dashboard/dashboard/src/views/layout/LoginPage.js +++ b/react/dashboard/dashboard/src/views/layout/LoginPage.js @@ -125,6 +125,7 @@ const LoginPage = () => { const [forgotPasswordModalOpen, setForgotPasswordModalOpen] = useState(false); + const [successMessage, setSuccessMessage] = useState('') const [failMessage, setFailMessage] = useState(''); const [redirectTo, setRedirectTo] = useState(undefined); @@ -138,10 +139,13 @@ const LoginPage = () => { const cameFrom = urlParams.get('from'); if (cameFrom) setRedirectTo(cameFrom); + const registerSuccess = urlParams.get('registerSuccess'); + if (registerSuccess) setSuccessMessage(t('html.register.success')) + return () => { document.body.classList.remove("bg-plan", "plan-bg-gradient"); } - }, [setRedirectTo]) + }, [setRedirectTo, setSuccessMessage, t]) const login = async (username, password) => { if (!username || username.length < 1) { @@ -189,8 +193,9 @@ const LoginPage = () => { {failMessage && {failMessage}} + {successMessage && {successMessage}} -
+
diff --git a/react/dashboard/dashboard/src/views/layout/NetworkPage.js b/react/dashboard/dashboard/src/views/layout/NetworkPage.js index 8a7a85bb5..b6a10112a 100644 --- a/react/dashboard/dashboard/src/views/layout/NetworkPage.js +++ b/react/dashboard/dashboard/src/views/layout/NetworkPage.js @@ -26,6 +26,7 @@ import {SwitchTransition} from "react-transition-group"; import MainPageRedirect from "../../components/navigation/MainPageRedirect"; import {ServerExtensionContextProvider, useServerExtensionContext} from "../../hooks/serverExtensionDataContext"; import {iconTypeToFontAwesomeClass} from "../../util/icons"; +import {staticSite} from "../../service/backendConfiguration"; const NetworkSidebar = () => { const {t, i18n} = useTranslation(); @@ -49,7 +50,7 @@ const NetworkSidebar = () => { href: "serversOverview" }, {name: 'html.label.sessions', icon: faCalendarCheck, href: "sessions"}, - {name: 'html.label.performance', icon: faCogs, href: "performance"}, + staticSite ? undefined : {name: 'html.label.performance', icon: faCogs, href: "performance"}, {}, ...servers.map(server => { return { diff --git a/react/dashboard/dashboard/src/views/layout/RegisterPage.js b/react/dashboard/dashboard/src/views/layout/RegisterPage.js new file mode 100644 index 000000000..7b214f415 --- /dev/null +++ b/react/dashboard/dashboard/src/views/layout/RegisterPage.js @@ -0,0 +1,200 @@ +import React, {useCallback, useEffect, useState} from 'react'; + +import logo from '../../Flaticon_circle.png' +import {Alert, Card, Col, Row} from "react-bootstrap-v5"; +import {Link, useNavigate} from "react-router-dom"; +import {useTranslation} from "react-i18next"; +import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; +import {faPalette} from "@fortawesome/free-solid-svg-icons"; +import {useTheme} from "../../hooks/themeHook"; +import ColorSelectorModal from "../../components/modal/ColorSelectorModal"; +import {useAuth} from "../../hooks/authenticationHook"; +import FinalizeRegistrationModal from "../../components/modal/FinalizeRegistrationModal"; +import {fetchRegisterCheck, postRegister} from "../../service/authenticationService"; +import {baseAddress} from "../../service/backendConfiguration"; + +const Logo = () => { + return ( + + logo + + ) +}; + +const RegisterCard = ({children}) => { + return ( + + + + + + +
+ {children} +
+ +
+
+
+ +
+ ) +} + +const RegisterForm = ({register}) => { + const {t} = useTranslation(); + + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + + const onRegister = useCallback(event => { + event.preventDefault(); + register(username, password).then(() => setPassword('')); + }, [username, password, setPassword, register]); + + return ( +
+
+ setUsername(event.target.value)}/> +
{t('html.register.usernameTip')}
+
+
+ setPassword(event.target.value)}/> +
{t('html.register.passwordTip')}
+
+ +
+ ); +} + +const ColorChooserButton = () => { + const {t} = useTranslation(); + const {toggleColorChooser} = useTheme(); + + return ( +
+ +
+ ) +} + +const LoginLink = () => { + const {t} = useTranslation(); + + return ( +
+ {t('html.register.login')} +
+ ) +} + +const RegisterPage = () => { + const {t} = useTranslation(); + const navigate = useNavigate(); + const {authLoaded, authRequired, loggedIn} = useAuth(); + + const [finalizeRegistrationModalOpen, setFinalizeRegistrationModalOpen] = useState(false); + + const [registerCode, setRegisterCode] = useState(undefined); + const [failMessage, setFailMessage] = useState(''); + + const toggleRegistrationModal = useCallback(() => setFinalizeRegistrationModalOpen(!finalizeRegistrationModalOpen), + [setFinalizeRegistrationModalOpen, finalizeRegistrationModalOpen]) + + useEffect(() => { + document.body.classList.add("bg-plan", "plan-bg-gradient"); + + return () => { + document.body.classList.remove("bg-plan", "plan-bg-gradient"); + } + }, []); + + const checkRegistration = async (code) => { + if (!code) { + setFinalizeRegistrationModalOpen(false); + return setFailMessage("Register code was not received."); + } + if (!finalizeRegistrationModalOpen) { + setFinalizeRegistrationModalOpen(true); + } + + const {data, error} = await fetchRegisterCheck(code); + if (error) { + setFailMessage(t('html.register.error.checkFailed') + error) + } else if (data && data.success) { + navigate(baseAddress + '/login?registerSuccess=true'); + } else { + setTimeout(() => checkRegistration(code), 5000); + } + } + + const register = async (username, password) => { + if (!username || username.length < 1) { + return setFailMessage(t('html.register.error.noUsername')); + } + if (username.length > 50) { + return setFailMessage(t('html.register.error.usernameLength') + username.length); + } + if (!password || password.length < 1) { + return setFailMessage(t('html.register.error.noPassword')); + } + + const {data, error} = await postRegister(username, password); + + if (error) { + setFailMessage(t('html.register.error.failed') + (error.data && error.data.error ? error.data.error : error.message)); + } else if (data && data.code) { + setRegisterCode(data.code); + setFinalizeRegistrationModalOpen(true); + setTimeout(() => checkRegistration(data.code), 10000); + } else { + setFailMessage(t('html.register.error.failed') + data ? data.error : t('generic.noData')); + } + } + + if (!authLoaded) { + return <> + } + + if (!authRequired || loggedIn) { + navigate('../'); + } + + return ( + <> +
+ + +
+

{t('html.register.createNewUser')}

+
+ {failMessage && {failMessage}} + +
+ + +
+
+ + + ) +}; + +export default RegisterPage \ No newline at end of file diff --git a/react/dashboard/dashboard/yarn.lock b/react/dashboard/dashboard/yarn.lock index 2e4c6e71e..be691026c 100644 --- a/react/dashboard/dashboard/yarn.lock +++ b/react/dashboard/dashboard/yarn.lock @@ -1204,7 +1204,7 @@ "@fullcalendar/common" "~5.11.3" tslib "^2.1.0" -"@fullcalendar/common@~5.11.2", "@fullcalendar/common@~5.11.3": +"@fullcalendar/common@~5.11.3": version "5.11.3" resolved "https://registry.yarnpkg.com/@fullcalendar/common/-/common-5.11.3.tgz#6d555a06925b8a6d1556570c9f039960539908d5" integrity sha512-welVwyfQOXQQGfDwBMSfYEPbiO1cPfUD+C7jd3ZoweJR+dSO11ddFugxIQ7dGfABAGZ63oq/+LW9FsmAJezVNg== @@ -1219,12 +1219,12 @@ "@fullcalendar/common" "~5.11.3" tslib "^2.1.0" -"@fullcalendar/react@^5.11.2": - version "5.11.2" - resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.11.2.tgz#019e6a0573d869c2a52f63426e13593857ee2d30" - integrity sha512-OnLvfV406VEQcK4QGN8xR4ro6Manp9dKE7/n9dhs19J1kKpqS1w1sIEYg1dT11njbk0Ob+TdF3cXLDFq73jUlA== +"@fullcalendar/react@^5.11.3": + version "5.11.3" + resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.11.3.tgz#34c076785ec8ea12cfbc29b7028cb5d923c2aecd" + integrity sha512-pxk6da5V1Mn41M664phQ1cr+0QbhXqkDHUhAVp8OOBrtMW84nhs82IYWTZNA3cbFCn4aUgoG2oGnyxekSF3pYA== dependencies: - "@fullcalendar/common" "~5.11.2" + "@fullcalendar/common" "~5.11.3" tslib "^2.1.0" "@highcharts/map-collection@^2.0.1": @@ -2662,10 +2662,10 @@ axe-core@^4.3.5: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== -axios@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35" - integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA== +axios@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.0.tgz#1cb65bd75162c70e9f8d118a905126c4a201d383" + integrity sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -2890,10 +2890,10 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bootstrap@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.2.tgz#834e053eed584a65e244d8aa112a6959f56e27a0" - integrity sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ== +bootstrap@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b" + integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ== brace-expansion@^1.1.7: version "1.1.11"