mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 23:42:07 +01:00
refactor: update StatsReturn structure and related components; remove unused code and enhance platform stats display
This commit is contained in:
@@ -2,10 +2,9 @@ from typing import TypedDict
|
||||
|
||||
|
||||
class StatsReturn(TypedDict):
|
||||
PLATFORMS_COUNT: int
|
||||
PLATFORMS: list
|
||||
PLATFORMS: int
|
||||
ROMS: int
|
||||
SAVES: int
|
||||
STATES: int
|
||||
SCREENSHOTS: int
|
||||
TOTAL_FILESIZE: int
|
||||
TOTAL_FILESIZE_BYTES: int
|
||||
|
||||
@@ -17,11 +17,10 @@ def stats() -> StatsReturn:
|
||||
"""
|
||||
|
||||
return {
|
||||
"PLATFORMS_COUNT": db_stats_handler.get_platforms_count(),
|
||||
"PLATFORMS": db_stats_handler.get_platforms_filesize(),
|
||||
"PLATFORMS": db_stats_handler.get_platforms_count(),
|
||||
"ROMS": db_stats_handler.get_roms_count(),
|
||||
"SAVES": db_stats_handler.get_saves_count(),
|
||||
"STATES": db_stats_handler.get_states_count(),
|
||||
"SCREENSHOTS": db_stats_handler.get_screenshots_count(),
|
||||
"TOTAL_FILESIZE": db_stats_handler.get_total_filesize(),
|
||||
"TOTAL_FILESIZE_BYTES": db_stats_handler.get_total_filesize(),
|
||||
}
|
||||
|
||||
@@ -57,24 +57,3 @@ class DBStatsHandler(DBBaseHandler):
|
||||
)
|
||||
or 0
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def get_platforms_filesize(self, session: Session = None) -> list[dict]:
|
||||
"""
|
||||
Returns a list of dicts: each with 'id', 'name', and 'filesize' for each platform.
|
||||
"""
|
||||
platform_rows = (
|
||||
session.execute(
|
||||
select(Platform.id, Platform.name)
|
||||
.join(Rom, Rom.platform_id == Platform.id)
|
||||
.group_by(Platform.id, Platform.name)
|
||||
)
|
||||
).all()
|
||||
return [
|
||||
{
|
||||
"id": platform_id,
|
||||
"name": platform_name,
|
||||
"filesize": self.get_platform_filesize(platform_id, session=session),
|
||||
}
|
||||
for platform_id, platform_name in platform_rows
|
||||
]
|
||||
|
||||
5
frontend/src/__generated__/models/StatsReturn.ts
generated
5
frontend/src/__generated__/models/StatsReturn.ts
generated
@@ -3,12 +3,11 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type StatsReturn = {
|
||||
PLATFORMS_COUNT: number;
|
||||
PLATFORMS: Array<any>;
|
||||
PLATFORMS: number;
|
||||
ROMS: number;
|
||||
SAVES: number;
|
||||
STATES: number;
|
||||
SCREENSHOTS: number;
|
||||
TOTAL_FILESIZE: number;
|
||||
TOTAL_FILESIZE_BYTES: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "@/utils";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
// Props
|
||||
defineProps<{
|
||||
stats: {
|
||||
PLATFORMS_COUNT: number;
|
||||
ROMS: number;
|
||||
SAVES: number;
|
||||
STATES: number;
|
||||
SCREENSHOTS: number;
|
||||
TOTAL_FILESIZE: number;
|
||||
};
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-text class="pa-1">
|
||||
<v-row no-gutters class="flex-nowrap overflow-x-auto text-center">
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-controller"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.platforms-n", stats.PLATFORMS_COUNT) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-disc"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.games-n", stats.ROMS) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-content-save"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.saves-n", stats.SAVES) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-file"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.states-n", stats.STATES) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-image-area"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.screenshots-n", stats.SCREENSHOTS) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-chip
|
||||
class="text-overline"
|
||||
prepend-icon="mdi-harddisk"
|
||||
variant="text"
|
||||
label
|
||||
>
|
||||
{{ t("common.size-on-disk") }}:
|
||||
{{ formatBytes(stats.TOTAL_FILESIZE, 1) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -1,46 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "@/utils";
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
platforms: Array<{ id: number; name: string; filesize: number | string }>;
|
||||
total: number;
|
||||
}>();
|
||||
|
||||
// Functions
|
||||
function getPercentage(filesize: number | string, total: number): number {
|
||||
const size = typeof filesize === "string" ? parseInt(filesize, 10) : filesize;
|
||||
if (!total || isNaN(size)) return 0;
|
||||
return (size / total) * 100;
|
||||
}
|
||||
|
||||
function idToHexColor(id: number): string {
|
||||
const knuthHash = 2654435761;
|
||||
const hex = ((id * knuthHash) >>> 0).toString(16).padStart(6, "0");
|
||||
return `#${hex.slice(0, 6)}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<div v-for="platform in props.platforms" :key="platform.id" class="mb-4">
|
||||
<div class="d-flex justify-space-between align-center mb-1">
|
||||
<span class="ml-2">
|
||||
<strong>{{ platform.name }}</strong>
|
||||
</span>
|
||||
<span class="mr-2">
|
||||
{{ formatBytes(Number(platform.filesize)) }}
|
||||
({{ getPercentage(platform.filesize, props.total).toFixed(1) }}%)
|
||||
</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="getPercentage(platform.filesize, props.total)"
|
||||
rounded
|
||||
:color="idToHexColor(platform.id)"
|
||||
height="15"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "@/utils";
|
||||
import PlatformListItem from "@/components/common/Platform/ListItem.vue";
|
||||
import storePlatforms from "@/stores/platforms";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
total_filesize: number;
|
||||
}>();
|
||||
|
||||
const platformsStore = storePlatforms();
|
||||
const { filteredPlatforms } = storeToRefs(platformsStore);
|
||||
|
||||
// Functions
|
||||
function getPercentage(filesize: number | string, total: number): number {
|
||||
const size = typeof filesize === "string" ? parseInt(filesize, 10) : filesize;
|
||||
if (!total || isNaN(size)) return 0;
|
||||
return (size / total) * 100;
|
||||
}
|
||||
|
||||
function idToHexColor(id: number): string {
|
||||
const knuthHash = 2654435761;
|
||||
const hex = ((id * knuthHash) >>> 0).toString(16).padStart(6, "0");
|
||||
return `#${hex.slice(0, 6)}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<div
|
||||
v-for="(platform, idx) in filteredPlatforms"
|
||||
:class="{ 'mb-8': idx !== filteredPlatforms.length - 1 }"
|
||||
>
|
||||
<v-row no-gutters class="d-flex justify-space-between align-center">
|
||||
<v-col cols="6">
|
||||
<platform-list-item
|
||||
:platform="platform"
|
||||
:key="platform.slug"
|
||||
:showRomCount="false"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6" class="text-right">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-body-2">
|
||||
{{ formatBytes(Number(platform.fs_size_bytes)) }}
|
||||
({{
|
||||
getPercentage(
|
||||
platform.fs_size_bytes,
|
||||
props.total_filesize,
|
||||
).toFixed(1)
|
||||
}}%)
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-right mt-1">
|
||||
{{ platform.rom_count }} roms
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-progress-linear
|
||||
:model-value="
|
||||
getPercentage(platform.fs_size_bytes, props.total_filesize)
|
||||
"
|
||||
rounded
|
||||
:color="idToHexColor(platform.id)"
|
||||
height="10"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "@/utils";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
defineProps<{
|
||||
stats: {
|
||||
PLATFORMS: number;
|
||||
ROMS: number;
|
||||
SAVES: number;
|
||||
STATES: number;
|
||||
SCREENSHOTS: number;
|
||||
TOTAL_FILESIZE_BYTES: number;
|
||||
};
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="pa-4">
|
||||
<v-card-title class="text-h6 text-truncate mb-2">
|
||||
<v-icon class="mr-2">mdi-server</v-icon>
|
||||
{{ t("rom.summary") }}
|
||||
</v-card-title>
|
||||
<v-divider class="mb-3"></v-divider>
|
||||
<v-row dense>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-controller</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ t("common.platforms-n", stats.PLATFORMS) }}
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-disc</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ t("common.games-n", stats.ROMS) }}
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-content-save</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ t("common.saves-n", stats.SAVES) }}
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-file</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ t("common.states-n", stats.STATES) }}
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-image-area</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ t("common.screenshots-n", stats.SCREENSHOTS) }}
|
||||
</div>
|
||||
<div class="text-caption"></div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="6" md="4" lg="2">
|
||||
<v-sheet class="pa-3 text-center" color="toplayer" rounded>
|
||||
<v-icon size="32">mdi-harddisk</v-icon>
|
||||
<div class="text-h6 text-truncate font-weight-bold">
|
||||
{{ formatBytes(stats.TOTAL_FILESIZE_BYTES, 1) }}
|
||||
{{ t("common.size-on-disk") }}
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -104,6 +104,7 @@ function clear() {
|
||||
v-for="platform in platforms"
|
||||
:key="platform.slug"
|
||||
:platform="platform"
|
||||
withLink
|
||||
/>
|
||||
</v-list>
|
||||
</v-expansion-panel-text>
|
||||
@@ -116,6 +117,7 @@ function clear() {
|
||||
v-for="platform in filteredPlatforms"
|
||||
:key="platform.slug"
|
||||
:platform="platform"
|
||||
withLink
|
||||
/>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
@@ -4,14 +4,28 @@ import { ROUTES } from "@/plugins/router";
|
||||
import type { Platform } from "@/stores/platforms";
|
||||
|
||||
// Props
|
||||
withDefaults(defineProps<{ platform: Platform; rail?: boolean }>(), {
|
||||
rail: false,
|
||||
});
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
platform: Platform;
|
||||
withLink?: boolean;
|
||||
showRomCount?: boolean;
|
||||
}>(),
|
||||
{
|
||||
withLink: false,
|
||||
showRomCount: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list-item
|
||||
:to="{ name: ROUTES.PLATFORM, params: { platform: platform.id } }"
|
||||
v-bind="{
|
||||
...(withLink
|
||||
? {
|
||||
to: { name: ROUTES.PLATFORM, params: { platform: platform.id } },
|
||||
}
|
||||
: {}),
|
||||
}"
|
||||
:value="platform.slug"
|
||||
rounded
|
||||
density="compact"
|
||||
@@ -52,7 +66,7 @@ withDefaults(defineProps<{ platform: Platform; rail?: boolean }>(), {
|
||||
<span class="text-caption text-grey">{{ platform.fs_slug }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template #append>
|
||||
<template v-if="showRomCount" #append>
|
||||
<v-chip class="ml-2" size="x-small" label>
|
||||
{{ platform.rom_count }}
|
||||
</v-chip>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import RFooter from "@/components/Settings/Footer.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- # Bottom padding needed to avoid footer overlap -->
|
||||
<div class="mb-16">
|
||||
<router-view />
|
||||
</div>
|
||||
<r-footer />
|
||||
</template>
|
||||
@@ -1,4 +1,3 @@
|
||||
// Composables
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
@@ -144,58 +143,28 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: "user/:user",
|
||||
component: () => import("@/layouts/Settings.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: ROUTES.USER_PROFILE,
|
||||
component: () => import("@/views/Settings/UserProfile.vue"),
|
||||
},
|
||||
],
|
||||
name: ROUTES.USER_PROFILE,
|
||||
component: () => import("@/views/Settings/UserProfile.vue"),
|
||||
},
|
||||
{
|
||||
path: "user-interface",
|
||||
component: () => import("@/layouts/Settings.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: ROUTES.USER_INTERFACE,
|
||||
component: () => import("@/views/Settings/UserInterface.vue"),
|
||||
},
|
||||
],
|
||||
name: ROUTES.USER_INTERFACE,
|
||||
component: () => import("@/views/Settings/UserInterface.vue"),
|
||||
},
|
||||
{
|
||||
path: "library-management",
|
||||
component: () => import("@/layouts/Settings.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: ROUTES.LIBRARY_MANAGEMENT,
|
||||
component: () => import("@/views/Settings/LibraryManagement.vue"),
|
||||
},
|
||||
],
|
||||
name: ROUTES.LIBRARY_MANAGEMENT,
|
||||
component: () => import("@/views/Settings/LibraryManagement.vue"),
|
||||
},
|
||||
{
|
||||
path: "administration",
|
||||
component: () => import("@/layouts/Settings.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: ROUTES.ADMINISTRATION,
|
||||
component: () => import("@/views/Settings/Administration.vue"),
|
||||
},
|
||||
],
|
||||
name: ROUTES.ADMINISTRATION,
|
||||
component: () => import("@/views/Settings/Administration.vue"),
|
||||
},
|
||||
{
|
||||
path: "server-stats",
|
||||
component: () => import("@/layouts/Settings.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: ROUTES.SERVER_STATS,
|
||||
component: () => import("@/views/Settings/ServerStats.vue"),
|
||||
},
|
||||
],
|
||||
name: ROUTES.SERVER_STATS,
|
||||
component: () => import("@/views/Settings/ServerStats.vue"),
|
||||
},
|
||||
{
|
||||
path: ":pathMatch(.*)*",
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import Stats from "@/components/Home/Stats.vue";
|
||||
import PlatformsSize from "@/components/Settings/ServerStats/PlatformsSize.vue";
|
||||
import SummaryStats from "@/components/Settings/ServerStats/SummaryStats.vue";
|
||||
import PlatformsStats from "@/components/Settings/ServerStats/PlatformsStats.vue";
|
||||
import api from "@/services/api/index";
|
||||
import { onBeforeMount, ref } from "vue";
|
||||
|
||||
// Props
|
||||
const stats = ref({
|
||||
PLATFORMS_COUNT: 0,
|
||||
PLATFORMS: [],
|
||||
PLATFORMS: 0,
|
||||
ROMS: 0,
|
||||
SAVES: 0,
|
||||
STATES: 0,
|
||||
SCREENSHOTS: 0,
|
||||
TOTAL_FILESIZE: 0,
|
||||
TOTAL_FILESIZE_BYTES: 0,
|
||||
});
|
||||
|
||||
// Functions
|
||||
@@ -23,10 +22,6 @@ onBeforeMount(() => {
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<stats class="ma-2" :stats="stats" />
|
||||
<platforms-size
|
||||
class="ma-2"
|
||||
:platforms="stats.PLATFORMS"
|
||||
:total="stats.TOTAL_FILESIZE"
|
||||
/>
|
||||
<summary-stats class="ma-2" :stats="stats" />
|
||||
<platforms-stats class="ma-2" :total_filesize="stats.TOTAL_FILESIZE_BYTES" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user