updated view

This commit is contained in:
Vito0912 2025-01-02 20:16:43 +01:00
parent e102cd6df0
commit 9dab97a4f7
No known key found for this signature in database
GPG Key ID: 29A3D509FE70B237
3 changed files with 220 additions and 32 deletions

View File

@ -10,6 +10,14 @@
</div> </div>
</div> </div>
<div v-if="!isBookLibrary && !isOverView" class="flex p-2">
<span class="material-symbols text-5xl pt-1">podcasts</span>
<div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p>
<p class="text-xs md:text-sm text-white text-opacity-80">Episodes</p>
</div>
</div>
<div class="flex p-2"> <div class="flex p-2">
<span class="material-symbols text-5xl py-1">show_chart</span> <span class="material-symbols text-5xl py-1">show_chart</span>
<div class="px-1"> <div class="px-1">
@ -36,7 +44,7 @@
</div> </div>
</div> </div>
<div class="flex p-2"> <div v-if="isBookLibrary || isOverView" class="flex p-2">
<span class="material-symbols text-5xl pt-1">audio_file</span> <span class="material-symbols text-5xl pt-1">audio_file</span>
<div class="px-1"> <div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p> <p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p>
@ -52,18 +60,22 @@ export default {
libraryStats: { libraryStats: {
type: Object, type: Object,
default: () => {} default: () => {}
} },
mediaType: null
}, },
data() { data() {
return {} return {}
}, },
computed: { computed: {
currentLibraryMediaType() { currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType'] return this.mediaType || this.$store.getters['libraries/getCurrentLibraryMediaType']
}, },
isBookLibrary() { isBookLibrary() {
return this.currentLibraryMediaType === 'book' return this.currentLibraryMediaType === 'book'
}, },
isOverView(){
return this.mediaType === 'overview'
},
user() { user() {
return this.$store.state.user.user return this.$store.state.user.user
}, },

View File

@ -0,0 +1,159 @@
<template>
<div>
<app-settings-content v-if="serverStats != null" header-text="All Stats">
<stats-preview-icons :library-stats="serverStats['combined']['all']" media-type="overview"/>
</app-settings-content>
<app-settings-content v-if="serverStats != null && bookLibraryListStats.length >= 1" header-text="Book Libraries">
<stats-preview-icons :library-stats="serverStats['combined']['books']" media-type="book"/>
<table class="tracksTable max-w-3xl mx-auto mt-8">
<tr>
<th class="text-left">Name</th>
<th class="text-left">{{ $strings.LabelStatsItemsInLibrary }}</th>
<th class="text-left">{{ $strings.LabelStatsOverallHours }}</th>
<th class="text-left">{{ $strings.LabelStatsAuthors }}</th>
<th class="text-left">{{ $strings.LabelSize }}</th>
<th class="text-left">{{ $strings.LabelStatsAudioTracks }}</th>
</tr>
<tr v-for="library in bookLibraryListStats">
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.name }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.stats.totalItems }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ $formatNumber(totalHours(library.stats.totalDuration)) }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.stats.totalAuthors }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ $formatNumber(totalSizeNum(library.stats.totalSize)) }} {{totalSizeMod(library.stats.totalSize)}}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.stats.numAudioTracks }}</p>
</td>
</tr>
</table>
</app-settings-content>
<app-settings-content v-if="serverStats != null && podcastLibraryListStats.length >= 1" header-text="Podcast Libraries">
<stats-preview-icons :library-stats="serverStats['combined']['podcasts']" media-type="podcast"/>
<table class="tracksTable max-w-3xl mx-auto mt-8">
<tr>
<th class="text-left">Name</th>
<th class="text-left">{{ $strings.LabelStatsItemsInLibrary }}</th>
<th class="text-left">Episodes</th>
<th class="text-left">{{ $strings.LabelStatsOverallHours }}</th>
<th class="text-left">{{ $strings.LabelSize }}</th>
</tr>
<tr v-for="library in podcastLibraryListStats">
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.name }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.stats.totalItems }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ library.stats.numAudioTracks }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ $formatNumber(totalHours(library.stats.totalDuration)) }}</p>
</td>
<td>
<p class="text-sm md:text-base text-gray-100">{{ $formatNumber(totalSizeNum(library.stats.totalSize)) }} {{totalSizeMod(library.stats.totalSize)}}</p>
</td>
</tr>
</table>
</app-settings-content>
</div>
</template>
<script>
export default {
asyncData({ redirect, store }) {
if (!store.getters['user/getIsAdminOrUp']) {
redirect('/')
return
}
return {}
},
data() {
return {
serverStats: null
}
},
computed: {
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
user() {
return this.$store.state.user.user
},
totalItems() {
return this.serverStats?.totalItems || 0
},
bookLibraryIndex() {
return this.serverStats?.libraries.findIndex((lib) => lib['type'] === 'book')
},
podcastLibraryIndex() {
return this.serverStats?.libraries.findIndex((lib) => lib['type'] === 'podcast')
},
bookLibraryListStats() {
if (this.bookLibraryIndex === -1) return []
if (this.podcastLibraryIndex !== -1) {
return this.serverStats['libraries'].slice(0, this.podcastLibraryIndex)
}
return this.serverStats['libraries']
},
podcastLibraryListStats() {
if (this.podcastLibraryIndex === -1) return []
return this.serverStats['libraries'].slice(this.podcastLibraryIndex)
}
},
methods: {
async init() {
this.serverStats = (await this.$axios.$get(`/api/libraries/stats`).catch((err) => {
console.error('Failed to get library stats', err)
var errorMsg = err.response ? err.response.data || 'Unknown Error' : 'Unknown Error'
this.$toast.error(`Failed to get library stats: ${errorMsg}`)
}))
// Sort the libraries by type
this.serverStats['libraries'].sort((a, b) => {
if (a['type'] < b['type']) return -1
if (a['type'] > b['type']) return 1
return 0
})
console.log(this.bookLibraryIndex)
console.log(this.podcastLibraryIndex)
},
totalHours(duration) {
return Math.round(duration / (60 * 60))
},
totalSizePretty(size) {
let totalSize = size || 0
return this.$bytesPretty(totalSize, 1)
},
totalSizeNum(size) {
return this.totalSizePretty(size).split(' ')[0]
},
totalSizeMod(size) {
return this.totalSizePretty(size).split(' ')[1]
},
},
mounted() {
this.init()
}
}
</script>

View File

@ -48,7 +48,11 @@ class LibraryController {
async allStats(req, res) { async allStats(req, res) {
try { try {
const allStats = []; const allStats = [];
const combinedStats = {}; const combinedStats = {
all: {},
books: {},
podcasts: {}
};
let libraries = await Database.libraryModel.getAllWithFolders(); let libraries = await Database.libraryModel.getAllWithFolders();
const librariesAccessible = req.user.permissions?.librariesAccessible || []; const librariesAccessible = req.user.permissions?.librariesAccessible || [];
@ -63,41 +67,54 @@ class LibraryController {
const libraryStats = await libraryHelpers.getLibraryStats(req); const libraryStats = await libraryHelpers.getLibraryStats(req);
// Add this library's stats to the array of individual stats // Add this library's stats to the array of individual stats
allStats.push({ [library.id]: libraryStats }); allStats.push({
'id': library.id,
'name': library.name,
'type': library.mediaType,
'stats': libraryStats
});
// Combine all stats // Combine stats for all categories
for (const [key, value] of Object.entries(libraryStats)) { const categories = ['all'];
if (typeof value === "number") { if (library.mediaType === 'book') categories.push('books');
combinedStats[key] = (combinedStats[key] || 0) + value; if (library.mediaType === 'podcast') categories.push('podcasts');
} else if (typeof value === "object") {
if (!combinedStats[key]) combinedStats[key] = [];
combinedStats[key].push(...Object.values(value)); // Process each relevant category
categories.forEach(category => {
for (const [key, value] of Object.entries(libraryStats)) {
if (typeof value === "number") {
combinedStats[category][key] = (combinedStats[category][key] || 0) + value;
} else if (typeof value === "object") {
if (!combinedStats[category][key]) combinedStats[category][key] = [];
combinedStats[category][key].push(...Object.values(value));
}
}
});
}
// Process arrays to keep top 10 entries for all categories
Object.keys(combinedStats).forEach(category => {
for (const key in combinedStats[category]) {
if (Array.isArray(combinedStats[category][key])) {
combinedStats[category][key] = combinedStats[category][key]
.sort((a, b) => {
const props = ['size', 'count', 'duration'];
for (const prop of props) {
if (a[prop] !== undefined && b[prop] !== undefined) {
return b[prop] - a[prop];
}
}
return 0;
})
.slice(0, 10);
} }
} }
} });
// Process arrays to keep top 10 entries based on 'size', 'count', or 'duration'
for (const key in combinedStats) {
if (Array.isArray(combinedStats[key])) {
combinedStats[key] = combinedStats[key]
.sort((a, b) => {
const props = ['size', 'count', 'duration'];
for (const prop of props) {
if (a[prop] !== undefined && b[prop] !== undefined) {
return b[prop] - a[prop];
}
}
return 0;
})
.slice(0, 10);
}
}
// Respond with the aggregated stats // Respond with the aggregated stats
res.json({ res.json({
libraries: allStats, // Individual library stats libraries: allStats,
combined: combinedStats // Combined aggregated stats combined: combinedStats
}); });
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });