mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 06:46:00 +00:00
Merge pull request #3110 from cciollaro/feat/stats-metadata-coverage-regions
Per-platform metadata coverage and region breakdown to server stats
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class MetadataCoverageItem(TypedDict):
|
||||
source: str
|
||||
matched: int
|
||||
|
||||
|
||||
class RegionBreakdownItem(TypedDict):
|
||||
region: str
|
||||
count: int
|
||||
|
||||
|
||||
class StatsReturn(TypedDict):
|
||||
PLATFORMS: int
|
||||
ROMS: int
|
||||
@@ -8,3 +18,5 @@ class StatsReturn(TypedDict):
|
||||
STATES: int
|
||||
SCREENSHOTS: int
|
||||
TOTAL_FILESIZE_BYTES: int
|
||||
METADATA_COVERAGE: dict[int, list[MetadataCoverageItem]]
|
||||
REGION_BREAKDOWN: dict[int, list[RegionBreakdownItem]]
|
||||
|
||||
@@ -23,4 +23,6 @@ def stats() -> StatsReturn:
|
||||
"STATES": db_stats_handler.get_states_count(),
|
||||
"SCREENSHOTS": db_stats_handler.get_screenshots_count(),
|
||||
"TOTAL_FILESIZE_BYTES": db_stats_handler.get_total_filesize(),
|
||||
"METADATA_COVERAGE": db_stats_handler.get_metadata_coverage_by_platform(),
|
||||
"REGION_BREAKDOWN": db_stats_handler.get_region_breakdown_by_platform(),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import distinct, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
||||
|
||||
from decorators.database import begin_session
|
||||
from models.assets import Save, Screenshot, State
|
||||
@@ -7,6 +10,20 @@ from models.rom import Rom, RomFile
|
||||
|
||||
from .base_handler import DBBaseHandler
|
||||
|
||||
# Metadata source columns on the Rom model, keyed by source identifier.
|
||||
_METADATA_SOURCE_COLUMNS: dict[str, InstrumentedAttribute] = {
|
||||
"igdb": Rom.igdb_id,
|
||||
"ss": Rom.ss_id,
|
||||
"moby": Rom.moby_id,
|
||||
"launchbox": Rom.launchbox_id,
|
||||
"ra": Rom.ra_id,
|
||||
"hasheous": Rom.hasheous_id,
|
||||
"tgdb": Rom.tgdb_id,
|
||||
"flashpoint": Rom.flashpoint_id,
|
||||
"hltb": Rom.hltb_id,
|
||||
"gamelist": Rom.gamelist_id,
|
||||
}
|
||||
|
||||
|
||||
class DBStatsHandler(DBBaseHandler):
|
||||
@begin_session
|
||||
@@ -79,3 +96,58 @@ class DBStatsHandler(DBBaseHandler):
|
||||
)
|
||||
or 0
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def get_metadata_coverage_by_platform(
|
||||
self,
|
||||
session: Session = None, # type: ignore
|
||||
) -> dict[int, list[dict]]:
|
||||
"""Get the count of ROMs matched per metadata source, grouped by platform."""
|
||||
rows = session.execute(
|
||||
select(
|
||||
Rom.platform_id,
|
||||
*(
|
||||
func.count(col).label(key)
|
||||
for key, col in _METADATA_SOURCE_COLUMNS.items()
|
||||
),
|
||||
)
|
||||
.select_from(Rom)
|
||||
.group_by(Rom.platform_id)
|
||||
).all()
|
||||
|
||||
result: dict[int, list[dict]] = {}
|
||||
for row in rows:
|
||||
result[row.platform_id] = [
|
||||
{"source": key, "matched": getattr(row, key)}
|
||||
for key in _METADATA_SOURCE_COLUMNS
|
||||
if getattr(row, key) > 0
|
||||
]
|
||||
return result
|
||||
|
||||
@begin_session
|
||||
def get_region_breakdown_by_platform(
|
||||
self,
|
||||
session: Session = None, # type: ignore
|
||||
) -> dict[int, list[dict]]:
|
||||
"""Get the count of ROMs per region, grouped by platform."""
|
||||
rows = session.execute(
|
||||
select(Rom.platform_id, Rom.regions).where(Rom.regions.is_not(None))
|
||||
).all()
|
||||
|
||||
counter: dict[int, dict[str, int]] = {}
|
||||
for platform_id, regions_list in rows:
|
||||
if regions_list:
|
||||
if platform_id not in counter:
|
||||
counter[platform_id] = {}
|
||||
for region in regions_list:
|
||||
counter[platform_id][region] = (
|
||||
counter[platform_id].get(region, 0) + 1
|
||||
)
|
||||
|
||||
return {
|
||||
pid: [
|
||||
{"region": r, "count": c}
|
||||
for r, c in sorted(regions.items(), key=lambda x: -x[1])
|
||||
]
|
||||
for pid, regions in counter.items()
|
||||
}
|
||||
|
||||
2
frontend/src/__generated__/index.ts
generated
2
frontend/src/__generated__/index.ts
generated
@@ -66,6 +66,7 @@ export type { InviteLinkSchema } from './models/InviteLinkSchema';
|
||||
export type { JobStatus } from './models/JobStatus';
|
||||
export type { LaunchboxImage } from './models/LaunchboxImage';
|
||||
export type { ManualMetadata } from './models/ManualMetadata';
|
||||
export type { MetadataCoverageItem } from './models/MetadataCoverageItem';
|
||||
export type { MetadataSourcesDict } from './models/MetadataSourcesDict';
|
||||
export type { MobyMetadataPlatform } from './models/MobyMetadataPlatform';
|
||||
export type { NetplayICEServer } from './models/NetplayICEServer';
|
||||
@@ -76,6 +77,7 @@ export type { PlatformSchema } from './models/PlatformSchema';
|
||||
export type { RAGameRomAchievement } from './models/RAGameRomAchievement';
|
||||
export type { RAProgression } from './models/RAProgression';
|
||||
export type { RAUserGameProgression } from './models/RAUserGameProgression';
|
||||
export type { RegionBreakdownItem } from './models/RegionBreakdownItem';
|
||||
export type { Role } from './models/Role';
|
||||
export type { RomFileCategory } from './models/RomFileCategory';
|
||||
export type { RomFileSchema } from './models/RomFileSchema';
|
||||
|
||||
9
frontend/src/__generated__/models/MetadataCoverageItem.ts
generated
Normal file
9
frontend/src/__generated__/models/MetadataCoverageItem.ts
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type MetadataCoverageItem = {
|
||||
source: string;
|
||||
matched: number;
|
||||
};
|
||||
|
||||
9
frontend/src/__generated__/models/RegionBreakdownItem.ts
generated
Normal file
9
frontend/src/__generated__/models/RegionBreakdownItem.ts
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type RegionBreakdownItem = {
|
||||
region: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
4
frontend/src/__generated__/models/StatsReturn.ts
generated
4
frontend/src/__generated__/models/StatsReturn.ts
generated
@@ -2,6 +2,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { MetadataCoverageItem } from './MetadataCoverageItem';
|
||||
import type { RegionBreakdownItem } from './RegionBreakdownItem';
|
||||
export type StatsReturn = {
|
||||
PLATFORMS: number;
|
||||
ROMS: number;
|
||||
@@ -9,5 +11,7 @@ export type StatsReturn = {
|
||||
STATES: number;
|
||||
SCREENSHOTS: number;
|
||||
TOTAL_FILESIZE_BYTES: number;
|
||||
METADATA_COVERAGE: Record<string, Array<MetadataCoverageItem>>;
|
||||
REGION_BREAKDOWN: Record<string, Array<RegionBreakdownItem>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,35 +2,64 @@
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import PlatformListItem from "@/components/common/Platform/ListItem.vue";
|
||||
import type { MetadataCoverageItem } from "@/__generated__/models/MetadataCoverageItem";
|
||||
import type { RegionBreakdownItem } from "@/__generated__/models/RegionBreakdownItem";
|
||||
import PlatformIcon from "@/components/common/Platform/PlatformIcon.vue";
|
||||
import RSection from "@/components/common/RSection.vue";
|
||||
import storeHeartbeat from "@/stores/heartbeat";
|
||||
import storePlatforms from "@/stores/platforms";
|
||||
import { formatBytes } from "@/utils";
|
||||
import { formatBytes, platformCategoryToIcon, regionToEmoji } from "@/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
totalFilesize: number;
|
||||
metadataCoverage: Record<string, MetadataCoverageItem[]>;
|
||||
regionBreakdown: Record<string, RegionBreakdownItem[]>;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
const platformsStore = storePlatforms();
|
||||
const { allPlatforms } = storeToRefs(platformsStore);
|
||||
const heartbeat = storeHeartbeat();
|
||||
const orderBy = ref<"name" | "size" | "count">("name");
|
||||
|
||||
const sortedPlatforms = computed(() => {
|
||||
if (orderBy.value === "size") {
|
||||
return allPlatforms.value.sort(
|
||||
return [...allPlatforms.value].sort(
|
||||
(a, b) => Number(b.fs_size_bytes) - Number(a.fs_size_bytes),
|
||||
);
|
||||
}
|
||||
if (orderBy.value === "count") {
|
||||
return allPlatforms.value.sort((a, b) => b.rom_count - a.rom_count);
|
||||
return [...allPlatforms.value].sort((a, b) => b.rom_count - a.rom_count);
|
||||
}
|
||||
return allPlatforms.value.sort((a, b) =>
|
||||
return [...allPlatforms.value].sort((a, b) =>
|
||||
a.display_name.localeCompare(b.display_name, undefined, {
|
||||
sensitivity: "base",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const metadataOptions = computed(() =>
|
||||
heartbeat.getMetadataOptionsByPriority(),
|
||||
);
|
||||
|
||||
const sourceInfo = computed(() => {
|
||||
const map: Record<string, { name: string; logo_path: string }> = {};
|
||||
for (const opt of metadataOptions.value) {
|
||||
map[opt.value] = { name: opt.name, logo_path: opt.logo_path };
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
const orderedCoverageByPlatform = computed(() => {
|
||||
const priority = metadataOptions.value.map((o) => o.value);
|
||||
const result: Record<string, MetadataCoverageItem[]> = {};
|
||||
for (const [id, items] of Object.entries(props.metadataCoverage)) {
|
||||
result[id] = [...items].sort(
|
||||
(a, b) => priority.indexOf(a.source) - priority.indexOf(b.source),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function getPlatformPercentage(
|
||||
filesize: number | string,
|
||||
total: number,
|
||||
@@ -39,12 +68,41 @@ function getPlatformPercentage(
|
||||
if (!total || isNaN(size)) return 0;
|
||||
return (size / total) * 100;
|
||||
}
|
||||
|
||||
const MAX_VISIBLE_REGIONS = 5;
|
||||
const expandedRegions = ref(new Set<number>());
|
||||
|
||||
function getVisibleRegions(platformId: number): RegionBreakdownItem[] {
|
||||
const items = props.regionBreakdown[String(platformId)];
|
||||
if (!items) return [];
|
||||
if (expandedRegions.value.has(platformId)) return items;
|
||||
return items.slice(0, MAX_VISIBLE_REGIONS);
|
||||
}
|
||||
|
||||
function getHiddenRegionCount(platformId: number): number {
|
||||
if (expandedRegions.value.has(platformId)) return 0;
|
||||
const items = props.regionBreakdown[String(platformId)];
|
||||
if (!items) return 0;
|
||||
return Math.max(0, items.length - MAX_VISIBLE_REGIONS);
|
||||
}
|
||||
|
||||
function toggleRegions(platformId: number): void {
|
||||
const s = new Set(expandedRegions.value);
|
||||
if (s.has(platformId)) s.delete(platformId);
|
||||
else s.add(platformId);
|
||||
expandedRegions.value = s;
|
||||
}
|
||||
|
||||
function getCoveragePercent(matched: number, total: number): string {
|
||||
if (!total) return "0";
|
||||
return ((matched / total) * 100).toFixed(0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RSection
|
||||
icon="mdi-harddisk"
|
||||
:title="t('common.platforms-size')"
|
||||
icon="mdi-controller"
|
||||
:title="t('common.platforms')"
|
||||
elevation="0"
|
||||
title-divider
|
||||
bg-color="bg-background"
|
||||
@@ -63,51 +121,212 @@ function getPlatformPercentage(
|
||||
label="Order by"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
density="compact"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col
|
||||
<div class="px-4 pb-3">
|
||||
<v-sheet
|
||||
v-for="platform in sortedPlatforms"
|
||||
:key="platform.slug"
|
||||
cols="12"
|
||||
class="pa-4"
|
||||
class="overflow-hidden mb-3"
|
||||
rounded
|
||||
>
|
||||
<v-row no-gutters class="d-flex justify-space-between align-center">
|
||||
<v-col cols="6">
|
||||
<PlatformListItem
|
||||
:key="platform.slug"
|
||||
:platform="platform"
|
||||
:show-rom-count="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)) }}
|
||||
({{
|
||||
getPlatformPercentage(
|
||||
platform.fs_size_bytes,
|
||||
props.totalFilesize,
|
||||
).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="
|
||||
getPlatformPercentage(platform.fs_size_bytes, props.totalFilesize)
|
||||
"
|
||||
rounded
|
||||
color="primary"
|
||||
height="8"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div class="pa-3">
|
||||
<div class="platform-layout grid gap-3">
|
||||
<div class="flex items-start pt-1">
|
||||
<PlatformIcon
|
||||
:slug="platform.slug"
|
||||
:name="platform.name"
|
||||
:fs-slug="platform.fs_slug"
|
||||
:size="36"
|
||||
/>
|
||||
</div>
|
||||
<div class="platform-content">
|
||||
<!-- Header: Name + size -->
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<div class="text-subtitle-1 font-weight-medium">
|
||||
{{ platform.display_name }}
|
||||
</div>
|
||||
<div class="d-flex align-center ga-1">
|
||||
<v-chip size="x-small" label class="text-grey">
|
||||
{{ platform.fs_slug }}
|
||||
</v-chip>
|
||||
<v-icon
|
||||
:icon="platformCategoryToIcon(platform.category || '')"
|
||||
size="10"
|
||||
class="text-medium-emphasis"
|
||||
:title="platform.category"
|
||||
/>
|
||||
<span
|
||||
v-if="platform.family_name"
|
||||
class="text-medium-emphasis"
|
||||
style="font-size: 0.65rem"
|
||||
>
|
||||
{{ platform.family_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right shrink-0 ml-4">
|
||||
<div class="text-subtitle-2 font-weight-bold text-primary">
|
||||
{{ formatBytes(Number(platform.fs_size_bytes)) }}
|
||||
</div>
|
||||
<div class="text-medium-emphasis" style="font-size: 0.7rem">
|
||||
{{
|
||||
getPlatformPercentage(
|
||||
platform.fs_size_bytes,
|
||||
props.totalFilesize,
|
||||
).toFixed(1)
|
||||
}}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail table: label | value -->
|
||||
<div class="detail-table mt-2 flex flex-col gap-2">
|
||||
<div class="detail-row grid items-baseline gap-2">
|
||||
<span
|
||||
class="detail-label whitespace-nowrap font-semibold uppercase opacity-50"
|
||||
>
|
||||
{{ t("setup.games") }}
|
||||
</span>
|
||||
<div>
|
||||
<v-chip size="x-small" label>
|
||||
{{ platform.rom_count }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row grid items-baseline gap-2">
|
||||
<span
|
||||
class="detail-label whitespace-nowrap font-semibold uppercase opacity-50"
|
||||
>
|
||||
{{ t("rom.metadata") }}
|
||||
</span>
|
||||
<div
|
||||
v-if="
|
||||
orderedCoverageByPlatform[String(platform.id)]?.length >
|
||||
0
|
||||
"
|
||||
class="d-flex flex-wrap ga-1"
|
||||
>
|
||||
<v-chip
|
||||
v-for="item in orderedCoverageByPlatform[
|
||||
String(platform.id)
|
||||
]"
|
||||
:key="item.source"
|
||||
:title="`${sourceInfo[item.source]?.name ?? item.source}: ${item.matched} / ${platform.rom_count}`"
|
||||
class="min-w-13 justify-center"
|
||||
size="x-small"
|
||||
label
|
||||
variant="tonal"
|
||||
>
|
||||
<v-avatar
|
||||
v-if="sourceInfo[item.source]?.logo_path"
|
||||
start
|
||||
size="12"
|
||||
rounded
|
||||
>
|
||||
<v-img :src="sourceInfo[item.source]?.logo_path" />
|
||||
</v-avatar>
|
||||
{{
|
||||
getCoveragePercent(item.matched, platform.rom_count)
|
||||
}}%
|
||||
</v-chip>
|
||||
</div>
|
||||
<span v-else class="text-xs opacity-25">—</span>
|
||||
</div>
|
||||
<div class="detail-row grid items-baseline gap-2">
|
||||
<span
|
||||
class="detail-label whitespace-nowrap font-semibold uppercase opacity-50"
|
||||
>
|
||||
{{ t("platform.region") }}
|
||||
</span>
|
||||
<div
|
||||
v-if="getVisibleRegions(platform.id).length > 0"
|
||||
class="d-flex flex-wrap ga-1"
|
||||
>
|
||||
<v-chip
|
||||
v-for="item in getVisibleRegions(platform.id)"
|
||||
:key="item.region"
|
||||
:title="`${item.region}: ${item.count}`"
|
||||
class="min-w-13 justify-center"
|
||||
size="x-small"
|
||||
label
|
||||
variant="tonal"
|
||||
>
|
||||
<span class="mr-1">{{
|
||||
regionToEmoji(item.region)
|
||||
}}</span>
|
||||
{{ item.count }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="
|
||||
getHiddenRegionCount(platform.id) > 0 ||
|
||||
expandedRegions.has(platform.id)
|
||||
"
|
||||
size="x-small"
|
||||
label
|
||||
variant="text"
|
||||
class="text-medium-emphasis cursor-pointer"
|
||||
@click="toggleRegions(platform.id)"
|
||||
>
|
||||
{{
|
||||
expandedRegions.has(platform.id)
|
||||
? "−"
|
||||
: "+" + getHiddenRegionCount(platform.id)
|
||||
}}
|
||||
</v-chip>
|
||||
</div>
|
||||
<span v-else class="text-xs opacity-25">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-bar-track h-0.75">
|
||||
<div
|
||||
class="size-bar-fill h-full min-w-0 rounded-r-xs duration-300 ease-in-out"
|
||||
:style="{
|
||||
width:
|
||||
getPlatformPercentage(
|
||||
platform.fs_size_bytes,
|
||||
props.totalFilesize,
|
||||
) + '%',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</div>
|
||||
</template>
|
||||
</RSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.platform-layout {
|
||||
grid-template-columns: 36px 1fr;
|
||||
}
|
||||
|
||||
.detail-table {
|
||||
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
grid-template-columns: 120px 1fr;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 0.625rem;
|
||||
line-height: 1.8;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.size-bar-track {
|
||||
background: rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
}
|
||||
|
||||
.size-bar-fill {
|
||||
background: rgb(var(--v-theme-primary));
|
||||
transition-property: width;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platforma",
|
||||
"platforms": "Platformy",
|
||||
"platforms-n": "{n} platforem | {n} platforma | {n} platformy | {n} platforem",
|
||||
"platforms-size": "Velikost podle platforem",
|
||||
"profile": "Profil",
|
||||
"random": "Náhodně",
|
||||
"removing-from-filesystem": "Odstraňování ze systému",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Plattform",
|
||||
"platforms": "Plattformen",
|
||||
"platforms-n": "{n} Plattform | {n} Plattformen",
|
||||
"platforms-size": "Größe pro Plattform",
|
||||
"profile": "Profil",
|
||||
"random": "Zufällig",
|
||||
"removing-from-filesystem": "Entfernen vom System",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platform",
|
||||
"platforms": "Platforms",
|
||||
"platforms-n": "{n} Platform | {n} Platforms",
|
||||
"platforms-size": "Size per platform",
|
||||
"profile": "Profile",
|
||||
"random": "Random",
|
||||
"removing-from-filesystem": "Removing from filesystem",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platform",
|
||||
"platforms": "Platforms",
|
||||
"platforms-n": "{n} Platform | {n} Platforms",
|
||||
"platforms-size": "Size per platform",
|
||||
"profile": "Profile",
|
||||
"random": "Random",
|
||||
"removing-from-filesystem": "Removing from filesystem",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Plataforma",
|
||||
"platforms": "Plataformas",
|
||||
"platforms-n": "{n} Plataforma | {n} Plataformas",
|
||||
"platforms-size": "Tamaño por plataforma",
|
||||
"profile": "Perfil",
|
||||
"random": "Aleatorio",
|
||||
"removing-from-filesystem": "Eliminando del sistema",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Plateforme",
|
||||
"platforms": "Plateformes",
|
||||
"platforms-n": "{n} Plateforme | {n} Plateformes",
|
||||
"platforms-size": "Taille par plateforme",
|
||||
"profile": "Profil",
|
||||
"random": "Aléatoire",
|
||||
"removing-from-filesystem": "Suppression du système",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platform",
|
||||
"platforms": "Platformok",
|
||||
"platforms-n": "{n} Platform | {n} Platformok",
|
||||
"platforms-size": "Méret platformonként",
|
||||
"profile": "Profil",
|
||||
"random": "Véletlenszerű",
|
||||
"removing-from-filesystem": "Eltávolítás a fájlrendszerből",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Piattaforma",
|
||||
"platforms": "Piattaforme",
|
||||
"platforms-n": "{n} Piattaforma | {n} Piattaforme",
|
||||
"platforms-size": "Dimensione per piattaforma",
|
||||
"profile": "Profilo",
|
||||
"random": "Casuale",
|
||||
"removing-from-filesystem": "Rimozione dal sistema",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "プラットフォーム",
|
||||
"platforms": "プラットフォーム",
|
||||
"platforms-n": "{n} プラットフォーム | {n} プラットフォームs",
|
||||
"platforms-size": "プラットフォームごとのサイズ",
|
||||
"profile": "プロファイル",
|
||||
"random": "ランダム",
|
||||
"removing-from-filesystem": "システムから削除中",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "플랫폼",
|
||||
"platforms": "플랫폼",
|
||||
"platforms-n": "{n}가지 플랫폼 | {n}가지 플랫폼",
|
||||
"platforms-size": "플랫폼별 크기",
|
||||
"profile": "프로필",
|
||||
"random": "무작위",
|
||||
"removing-from-filesystem": "시스템에서 제거 중",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platforma",
|
||||
"platforms": "Platformy",
|
||||
"platforms-n": "{n} Platforma | {n} Platformy | {n} Platform",
|
||||
"platforms-size": "Rozmiar na platformę",
|
||||
"profile": "Profil",
|
||||
"random": "Losowo",
|
||||
"removing-from-filesystem": "Usuwanie z systemu",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Plataforma",
|
||||
"platforms": "Plataformas",
|
||||
"platforms-n": "{n} Plataforma | {n} Plataformas",
|
||||
"platforms-size": "Tamanho por plataforma",
|
||||
"profile": "Perfil",
|
||||
"random": "Aleatório",
|
||||
"removing-from-filesystem": "Removendo do sistema",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Platformă",
|
||||
"platforms": "Platforme",
|
||||
"platforms-n": "{n} Platformă | {n} Platforme",
|
||||
"platforms-size": "Dimensiune per platformă",
|
||||
"profile": "Profil",
|
||||
"random": "Aleatoriu",
|
||||
"removing-from-filesystem": "Ștergere din sistem",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "Платформа",
|
||||
"platforms": "Платформы",
|
||||
"platforms-n": "{n} Платформа | {n} Платформы",
|
||||
"platforms-size": "Размер по платформе",
|
||||
"profile": "Профиль",
|
||||
"random": "Случайный",
|
||||
"removing-from-filesystem": "Удаление из системы",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "平台",
|
||||
"platforms": "平台",
|
||||
"platforms-n": "{n} 平台 | {n} 平台",
|
||||
"platforms-size": "每个平台的大小",
|
||||
"profile": "简介",
|
||||
"random": "随机",
|
||||
"removing-from-filesystem": "正在从系统删除",
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"platform": "平台",
|
||||
"platforms": "平台",
|
||||
"platforms-n": "{n} 平台 | {n} 平台",
|
||||
"platforms-size": "每個平台的大小",
|
||||
"profile": "用戶資料",
|
||||
"random": "隨機",
|
||||
"removing-from-filesystem": "正在從系統移除",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref } from "vue";
|
||||
import type { MetadataCoverageItem } from "@/__generated__/models/MetadataCoverageItem";
|
||||
import type { RegionBreakdownItem } from "@/__generated__/models/RegionBreakdownItem";
|
||||
import PlatformsStats from "@/components/Settings/ServerStats/PlatformsStats.vue";
|
||||
import SummaryStats from "@/components/Settings/ServerStats/SummaryStats.vue";
|
||||
import api from "@/services/api";
|
||||
@@ -11,6 +13,8 @@ const stats = ref({
|
||||
STATES: 0,
|
||||
SCREENSHOTS: 0,
|
||||
TOTAL_FILESIZE_BYTES: 0,
|
||||
METADATA_COVERAGE: {} as Record<string, MetadataCoverageItem[]>,
|
||||
REGION_BREAKDOWN: {} as Record<string, RegionBreakdownItem[]>,
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
@@ -21,5 +25,10 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
<template>
|
||||
<SummaryStats class="ma-2" :stats="stats" />
|
||||
<PlatformsStats class="ma-2" :total-filesize="stats.TOTAL_FILESIZE_BYTES" />
|
||||
<PlatformsStats
|
||||
class="ma-2"
|
||||
:total-filesize="stats.TOTAL_FILESIZE_BYTES"
|
||||
:metadata-coverage="stats.METADATA_COVERAGE"
|
||||
:region-breakdown="stats.REGION_BREAKDOWN"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user