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 000000000..386bc28be Binary files /dev/null and b/react/dashboard/dashboard/public/logo192.png differ diff --git a/react/dashboard/dashboard/public/logo512.png b/react/dashboard/dashboard/public/logo512.png new file mode 100644 index 000000000..e10345289 Binary files /dev/null and b/react/dashboard/dashboard/public/logo512.png differ 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"