mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-18 13:52:02 +02:00
updated view
This commit is contained in:
parent
e102cd6df0
commit
9dab97a4f7
@ -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
|
||||||
},
|
},
|
||||||
|
159
client/pages/config/server-stats.vue
Normal file
159
client/pages/config/server-stats.vue
Normal 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>
|
@ -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,24 +67,36 @@ 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
|
||||||
|
const categories = ['all'];
|
||||||
|
if (library.mediaType === 'book') categories.push('books');
|
||||||
|
if (library.mediaType === 'podcast') categories.push('podcasts');
|
||||||
|
|
||||||
|
// Process each relevant category
|
||||||
|
categories.forEach(category => {
|
||||||
for (const [key, value] of Object.entries(libraryStats)) {
|
for (const [key, value] of Object.entries(libraryStats)) {
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
combinedStats[key] = (combinedStats[key] || 0) + value;
|
combinedStats[category][key] = (combinedStats[category][key] || 0) + value;
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
if (!combinedStats[key]) combinedStats[key] = [];
|
if (!combinedStats[category][key]) combinedStats[category][key] = [];
|
||||||
|
combinedStats[category][key].push(...Object.values(value));
|
||||||
combinedStats[key].push(...Object.values(value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process arrays to keep top 10 entries based on 'size', 'count', or 'duration'
|
// Process arrays to keep top 10 entries for all categories
|
||||||
for (const key in combinedStats) {
|
Object.keys(combinedStats).forEach(category => {
|
||||||
if (Array.isArray(combinedStats[key])) {
|
for (const key in combinedStats[category]) {
|
||||||
combinedStats[key] = combinedStats[key]
|
if (Array.isArray(combinedStats[category][key])) {
|
||||||
|
combinedStats[category][key] = combinedStats[category][key]
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const props = ['size', 'count', 'duration'];
|
const props = ['size', 'count', 'duration'];
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
@ -93,11 +109,12 @@ class LibraryController {
|
|||||||
.slice(0, 10);
|
.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 });
|
||||||
|
Loading…
Reference in New Issue
Block a user