From 7b50ac4b327cd210e9b52bddb139d93c1229d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Thu, 29 May 2025 00:21:58 +0200 Subject: [PATCH] add statistics --- package-lock.json | 105 +++- package.json | 8 + src/api/serverService.ts | 15 +- src/components/Statistics.svelte | 471 ++++++++++++++++++ src/models/config.ts | 17 + .../dashboard/server/[id]/+page.server.ts | 17 +- src/routes/dashboard/server/[id]/+page.svelte | 83 ++- 7 files changed, 681 insertions(+), 35 deletions(-) create mode 100644 src/components/Statistics.svelte diff --git a/package-lock.json b/package-lock.json index bf0bf3a..3e68a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,13 @@ "": { "name": "acc-server-manager-web", "version": "0.0.1", + "dependencies": { + "@date-fns/tz": "^1.2.0", + "chart.js": "^4.4.9", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0" + }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", @@ -18,6 +25,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/postcss": "^4.0.4", "@tailwindcss/vite": "^4.0.0", + "@types/d3-scale": "^4.0.9", "@types/lodash-es": "^4.17.12", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", @@ -64,6 +72,12 @@ "node": ">=6.0.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -798,6 +812,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1662,6 +1682,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1693,6 +1730,18 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "22.15.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.24.tgz", + "integrity": "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2057,6 +2106,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chartjs-adapter-date-fns": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz", + "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==", + "license": "MIT", + "peerDependencies": { + "chart.js": ">=2.8.0", + "date-fns": ">=2.0.0" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2165,6 +2236,25 @@ "node": ">=4" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2602,9 +2692,9 @@ } }, "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4326,6 +4416,15 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index fa4407a..39cde32 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/postcss": "^4.0.4", "@tailwindcss/vite": "^4.0.0", + "@types/d3-scale": "^4.0.9", "@types/lodash-es": "^4.17.12", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", @@ -41,5 +42,12 @@ "typescript-eslint": "^8.20.0", "uuid": "^11.0.5", "vite": "^6.0.0" + }, + "dependencies": { + "@date-fns/tz": "^1.2.0", + "chart.js": "^4.4.9", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0" } } diff --git a/src/api/serverService.ts b/src/api/serverService.ts index 6808f80..3de6b8f 100644 --- a/src/api/serverService.ts +++ b/src/api/serverService.ts @@ -8,7 +8,8 @@ import { type Configurations, type EventConfig, type EventRules, - type ServerSettings + type ServerSettings, + type StateHistory } from '$models/config'; import type { Server } from '$models/server'; import type { RequestEvent } from '@sveltejs/kit'; @@ -36,6 +37,18 @@ export const getConfigFile = async ( return fetchAPIEvent(event, `/server/${serverId}/config/${file}`); }; +export const getStateHistory = async ( + event: RequestEvent, + serverId: string, + startDate: string, + endDate: string +): Promise> => { + return fetchAPIEvent( + event, + `/server/${serverId}/state-history?start_date=${startDate}&end_date=${endDate}` + ); +}; + export const getEventFile = async (event: RequestEvent, serverId: string): Promise => { return fetchAPIEvent(event, `/server/${serverId}/config/${configFile.event}`); }; diff --git a/src/components/Statistics.svelte b/src/components/Statistics.svelte new file mode 100644 index 0000000..8975c9e --- /dev/null +++ b/src/components/Statistics.svelte @@ -0,0 +1,471 @@ + + + +
+
+
+
+ + + +
+
+

Average Players

+

{averagePlayerCount}

+
+
+
+ +
+
+
+ + + +
+
+

Peak Players

+

{peakPlayerCount}

+
+
+
+ +
+
+
+ + + +
+
+

Total Sessions

+

{totalSessions}

+
+
+
+ +
+
+
+ + + +
+
+

Total Playtime

+

{formatDuration(totalPlaytime)}

+
+
+
+
+ + +
+ +
+

Player Count Over Time

+
+ +
+
+ + +
+

Session Types

+
+ +
+
+
+ + +
+

Daily Activity

+
+ +
+
+ + +
+

Recent Sessions

+
+ + + + + + + + + + + + {#each flatMap(dailyActivityData?.data, ({ sessions }) => sessions) as session} + + + + + + + + {/each} + +
DateTypeTrackDurationPlayers
+ {formatDate(session.dateCreated, 'MMM dd kk:mm')} + + + {session.session} + + + {session.track} + + {formatDuration(session.sessionDurationMinutes)} + + {session.playerCount} +
+
+
diff --git a/src/models/config.ts b/src/models/config.ts index 73a141c..196a216 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -13,6 +13,23 @@ export enum configFile { eventRules = 'eventRules.json', settings = 'settings.json' } +export enum serverTab { + statistics = 'statistics', + configuration = 'configuration', + assistRules = 'assistRules', + event = 'event', + eventRules = 'eventRules', + settings = 'settings' +} + +export interface StateHistory { + dateCreated: string; + sessionStart: string; + playerCount: number; + track: string; + sessionDurationMinutes: number; + session: string; +} export type Config = Configuration | AssistRules | EventConfig | EventRules | ServerSettings; export type ConfigFile = diff --git a/src/routes/dashboard/server/[id]/+page.server.ts b/src/routes/dashboard/server/[id]/+page.server.ts index 15542e6..a532f7d 100644 --- a/src/routes/dashboard/server/[id]/+page.server.ts +++ b/src/routes/dashboard/server/[id]/+page.server.ts @@ -1,4 +1,4 @@ -import { updateConfig, getConfigFiles, getServerById } from '$api/serverService'; +import { updateConfig, getConfigFiles, getServerById, getStateHistory } from '$api/serverService'; import type { Actions } from './$types'; import { checkAuth } from '$api/authService'; import { getTracks } from '$api/lookupService'; @@ -6,21 +6,30 @@ import { redirect } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit'; import { configFile, type Config, type Session } from '$models/config'; import { set } from 'lodash-es'; +import { format } from 'date-fns-tz'; +import { subDays } from 'date-fns'; export const load = async (event: RequestEvent) => { const isAuth = await checkAuth(event); if (!isAuth) return redirect(308, '/login'); if (!event.params.id) return redirect(308, '/dashboard'); - const [server, configs, tracks] = await Promise.all([ + const today = format(new Date(), 'yyyy-MM-ddTHH:mm:ssZ'); + const thirtyDaysAgo = format(subDays(new Date(), 30), 'yyyy-MM-ddTHH:mm:ssZ'); + + const endDate = today; + const startDate = thirtyDaysAgo; + const [server, configs, tracks, stateHistory] = await Promise.all([ getServerById(event, event.params.id), getConfigFiles(event, event.params.id), - getTracks(event) + getTracks(event), + getStateHistory(event, event.params.id, startDate, endDate) ]); return { id: event.params.id, configs, tracks, - server + server, + stateHistory }; }; diff --git a/src/routes/dashboard/server/[id]/+page.svelte b/src/routes/dashboard/server/[id]/+page.svelte index 677098e..cbc392f 100644 --- a/src/routes/dashboard/server/[id]/+page.svelte +++ b/src/routes/dashboard/server/[id]/+page.svelte @@ -4,15 +4,17 @@ import EditorEvent from '$components/EditorEvent.svelte'; import EditorEventRules from '$components/EditorEventRules.svelte'; import EditorSettings from '$components/EditorSettings.svelte'; + import Statistics from '$components/Statistics.svelte'; import { getStatusColor, serviceStatusToString } from '$lib/types/serviceStatus.js'; - import { configFile } from '$models/config.js'; + import { serverTab } from '$models/config.js'; let { data } = $props(); const configs = data.configs; const tracks = data.tracks; const id = data.id; const server = data.server; - let tab = $state(configFile.event); + const stateHistory = data.stateHistory; + let tab = $state(serverTab.statistics); @@ -37,27 +39,50 @@
  • +
  • +
  • +
  • @@ -65,9 +90,9 @@ {#if configs.assistRules}
  • @@ -76,9 +101,9 @@ {#if configs.eventRules}
  • @@ -91,15 +116,17 @@

    - {#if tab === configFile.event} + {#if tab === serverTab.statistics} + Statistics + {:else if tab === serverTab.event} Event - {:else if tab === configFile.configuration} + {:else if tab === serverTab.configuration} Configuration - {:else if tab === configFile.settings} + {:else if tab === serverTab.settings} Settings - {:else if tab === configFile.assistRules} + {:else if tab === serverTab.assistRules} Assist Rules - {:else if tab === configFile.eventRules} + {:else if tab === serverTab.eventRules} Event Rules {/if}

    @@ -147,15 +174,17 @@
    - {#if tab === configFile.event} + {#if tab === serverTab.statistics} + + {:else if tab === serverTab.event} - {:else if tab === configFile.configuration} + {:else if tab === serverTab.configuration} - {:else if tab === configFile.settings} + {:else if tab === serverTab.settings} - {:else if tab === configFile.assistRules} + {:else if tab === serverTab.assistRules} - {:else if tab === configFile.eventRules} + {:else if tab === serverTab.eventRules} {:else}