From 3e866614c6da70eb75eaec2d15df1e063d71eaa3 Mon Sep 17 00:00:00 2001 From: tabarra <1808295+tabarra@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:19:51 -0300 Subject: [PATCH] chore: version bump + added player crashes page --- .../StatsManager/playerDrop/index.ts | 15 ++++ core/components/WebServer/router.ts | 1 + core/webroutes/index.js | 1 + core/webroutes/playerCrashes.ts | 25 ++++++ fxmanifest.lua | 2 +- panel/src/layout/MainRouter.tsx | 6 ++ panel/src/pages/PlayerCrashesPage.tsx | 85 +++++++++++++++++++ shared/otherTypes.ts | 1 + 8 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 core/webroutes/playerCrashes.ts create mode 100644 panel/src/pages/PlayerCrashesPage.tsx diff --git a/core/components/StatsManager/playerDrop/index.ts b/core/components/StatsManager/playerDrop/index.ts index 8d6192e78..a07b79cbd 100644 --- a/core/components/StatsManager/playerDrop/index.ts +++ b/core/components/StatsManager/playerDrop/index.ts @@ -60,6 +60,21 @@ export default class PlayerDropStatsManager { } + /** + * Get the recent category count for player drops in the last X hours + */ + public getRecentCrashes(windowHours: number) { + const logCutoff = (new Date).setUTCMinutes(0, 0, 0) - (windowHours * 60 * 60 * 1000) - 1; + const flatCounts = this.eventLog + .filter((entry) => entry.hour.dateHourTs >= logCutoff) + .map((entry) => entry.crashTypes.toSortedValuesArray()) + .flat(); + const cumulativeCounter = new MultipleCounter(); + cumulativeCounter.merge(flatCounts); + return cumulativeCounter.toSortedValuesArray(); + } + + /** * Returns the object of the current hour object in log. * Creates one if doesn't exist one for the current hour. diff --git a/core/components/WebServer/router.ts b/core/components/WebServer/router.ts index 7e047ad13..093b08a28 100644 --- a/core/components/WebServer/router.ts +++ b/core/components/WebServer/router.ts @@ -91,6 +91,7 @@ export default (config: WebServerConfigType) => { router.get('/serverLog/partial', apiAuthMw, webRoutes.serverLogPartial); router.get('/systemLog/:scope', apiAuthMw, webRoutes.systemLogs); router.get('/perfChartData/:thread', apiAuthMw, webRoutes.perfChart); + router.get('/playerCrashesData', apiAuthMw, webRoutes.playerCrashes); /* FIXME: reorganizar TODAS rotas de logs, incluindo listagem e download diff --git a/core/webroutes/index.js b/core/webroutes/index.js index dbb8acd39..6bdbd2e2d 100644 --- a/core/webroutes/index.js +++ b/core/webroutes/index.js @@ -4,6 +4,7 @@ export { default as diagnostics_sendReport } from './diagnostics/sendReport'; export { default as intercom } from './intercom.js'; export { default as resources } from './resources.js'; export { default as perfChart } from './perfChart'; +export { default as playerCrashes } from './playerCrashes.js'; export { default as systemLogs } from './systemLogs'; export { default as auth_addMasterPin } from './authentication/addMasterPin.js'; diff --git a/core/webroutes/playerCrashes.ts b/core/webroutes/playerCrashes.ts new file mode 100644 index 000000000..1c9605713 --- /dev/null +++ b/core/webroutes/playerCrashes.ts @@ -0,0 +1,25 @@ +const modulename = 'WebServer:PerfChart'; +import { AuthedCtx } from '@core/components/WebServer/ctxTypes'; +import consoleFactory from '@extras/console'; +import { DeepReadonly } from 'utility-types'; +const console = consoleFactory(modulename); + + +//Types +export type PlayerCrashesApiErrorResp = { + fail_reason: string; +}; +export type PlayerCrashesApiSuccessResp = [reason: string, count: number][]; +export type PlayerCrashesApiResp = DeepReadonly; + + +/** + * Returns the data required to build the dashboard performance chart of a specific thread + */ +export default async function perfChart(ctx: AuthedCtx) { + const sendTypedResp = (data: PlayerCrashesApiResp) => ctx.send(data); + + const crashSummary = ctx.txAdmin.statsManager.playerDrop.getRecentCrashes(24); + + return sendTypedResp(crashSummary); +}; diff --git a/fxmanifest.lua b/fxmanifest.lua index cab471739..550286efc 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -5,7 +5,7 @@ author 'Tabarra' description 'The official FiveM/RedM server web/in-game management platform.' repository 'https://github.com/tabarra/txAdmin' -version '7.2.0' +version '7.2.2' ui_label 'txAdmin' rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.' diff --git a/panel/src/layout/MainRouter.tsx b/panel/src/layout/MainRouter.tsx index d0a9c3b37..8014d3a73 100644 --- a/panel/src/layout/MainRouter.tsx +++ b/panel/src/layout/MainRouter.tsx @@ -15,6 +15,7 @@ import BanTemplatesPage from "@/pages/BanTemplates/BanTemplatesPage"; import SystemLogPage from "@/pages/SystemLogPage"; import AddLegacyBanPage from "@/pages/AddLegacyBanPage"; import DashboardPage from "@/pages/Dashboard/DashboardPage"; +import PlayerCrashesPage from "@/pages/PlayerCrashesPage"; type RouteType = { @@ -124,6 +125,11 @@ const allRoutes: RouteType[] = [ title: 'Ban Identifiers', children: }, + { + path: '/player-crashes', + title: 'Player Crashes', + children: + }, ]; diff --git a/panel/src/pages/PlayerCrashesPage.tsx b/panel/src/pages/PlayerCrashesPage.tsx new file mode 100644 index 000000000..063aa1b16 --- /dev/null +++ b/panel/src/pages/PlayerCrashesPage.tsx @@ -0,0 +1,85 @@ +import InlineCode from "@/components/InlineCode"; +import { DndSortableGroup, DndSortableItem } from "@/components/dndSortable"; +import { useBackendApi } from "@/hooks/fetch"; +import { Loader2Icon } from "lucide-react"; +import { PlayerCrashesApiResp } from "@shared/otherTypes"; +import useSWR from "swr"; +import { useState } from "react"; + +function CrashReasonCard({ reason, count, totalCrashes }: { reason: string, count: number, totalCrashes: number }) { + const percentage = ((count / totalCrashes) * 100).toFixed(2); + + return ( +
+
+ {count} + ({percentage}%) +
+

+ {reason} lipsu +

+
+ ) +} + + +export default function PlayerCrashesPage() { + const playerCrashesApi = useBackendApi({ + method: 'GET', + path: `/playerCrashesData`, + }); + + const swrDataApiResp = useSWR('/playerCrashesData', async () => { + const data = await playerCrashesApi({}); + if (!data) throw new Error('empty_response'); + if ('fail_reason' in data) { + throw new Error(data.fail_reason); + } + return data as [reason: string, count: number][]; + }, {}); + + let totalCrashes = 0; + if (swrDataApiResp.data) { + console.log('asdfsfdgfsd'); + totalCrashes = swrDataApiResp.data?.reduce((acc, [, count]) => acc + count, 0) ?? 0 + } + return <> +
+
+

Crash Reasons - last 24h

+

+ Here you can see all the player crash reasons in the last 24 hours.
+ This is useful for troubleshooting and solving bugs.
+ + NOTE: Some crash reasons might be missing due to players using translated game clients.
+
+

+
+
+
+ Reasons: {swrDataApiResp.data?.length ?? 0} + {swrDataApiResp.error ? ( +
+ Error loading: {swrDataApiResp.error.message} +
+ ) : swrDataApiResp.isLoading || swrDataApiResp.isValidating ? ( + + Loading... + + ) : null} +
+ +
+ {swrDataApiResp.data?.map(([reason, count]) => ( + + ))} +
+
+
+ ; +} diff --git a/shared/otherTypes.ts b/shared/otherTypes.ts index a75ffd935..b66908af4 100644 --- a/shared/otherTypes.ts +++ b/shared/otherTypes.ts @@ -6,6 +6,7 @@ export type { ApiAddLegacyBanReqSchema, ApiRevokeActionReqSchema } from "@core/w export type { SvRtLogFilteredType, SvRtPerfCountsThreadType } from "@core/components/StatsManager/svRuntime/perfSchemas"; export type { SvRtPerfThreadNamesType } from "@core/components/StatsManager/svRuntime/config"; export type { PerfChartApiResp, PerfChartApiSuccessResp } from "@core/webroutes/perfChart"; +export type { PlayerCrashesApiResp, PlayerCrashesApiSuccessResp } from "@core/webroutes/playerCrashes"; export type UpdateDataType = {