mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update:Move library stats page to SideRail #3134
This commit is contained in:
parent
43b7ccd61a
commit
eabfa90121
@ -114,9 +114,9 @@ export default {
|
|||||||
|
|
||||||
if (this.currentLibraryId) {
|
if (this.currentLibraryId) {
|
||||||
configRoutes.push({
|
configRoutes.push({
|
||||||
id: 'config-library-stats',
|
id: 'library-stats',
|
||||||
title: this.$strings.HeaderLibraryStats,
|
title: this.$strings.HeaderLibraryStats,
|
||||||
path: '/config/library-stats'
|
path: `/library/${this.currentLibraryId}/stats`
|
||||||
})
|
})
|
||||||
configRoutes.push({
|
configRoutes.push({
|
||||||
id: 'config-stats',
|
id: 'config-stats',
|
||||||
@ -182,4 +182,4 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -79,6 +79,14 @@
|
|||||||
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
|
<nuxt-link v-if="isBookLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/stats`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isStatsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<span class="material-symbols text-2xl">monitoring</span>
|
||||||
|
|
||||||
|
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonStats }}</p>
|
||||||
|
|
||||||
|
<div v-show="isStatsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
<span class="abs-icons icon-podcast text-xl"></span>
|
<span class="abs-icons icon-podcast text-xl"></span>
|
||||||
|
|
||||||
@ -103,7 +111,7 @@
|
|||||||
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'">
|
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : 'bg-error bg-opacity-20'">
|
||||||
<span class="material-symbols text-2xl">warning</span>
|
<span class="material-symbols text-2xl">warning</span>
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
|
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
|
||||||
@ -194,6 +202,9 @@ export default {
|
|||||||
isPlaylistsPage() {
|
isPlaylistsPage() {
|
||||||
return this.paramId === 'playlists'
|
return this.paramId === 'playlists'
|
||||||
},
|
},
|
||||||
|
isStatsPage() {
|
||||||
|
return this.$route.name === 'library-library-stats'
|
||||||
|
},
|
||||||
libraryBookshelfPage() {
|
libraryBookshelfPage() {
|
||||||
return this.$route.name === 'library-library-bookshelf-id'
|
return this.$route.name === 'library-library-bookshelf-id'
|
||||||
},
|
},
|
||||||
|
@ -52,7 +52,6 @@ export default {
|
|||||||
else if (pageName === 'notifications') return this.$strings.HeaderNotifications
|
else if (pageName === 'notifications') return this.$strings.HeaderNotifications
|
||||||
else if (pageName === 'sessions') return this.$strings.HeaderListeningSessions
|
else if (pageName === 'sessions') return this.$strings.HeaderListeningSessions
|
||||||
else if (pageName === 'stats') return this.$strings.HeaderYourStats
|
else if (pageName === 'stats') return this.$strings.HeaderYourStats
|
||||||
else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats
|
|
||||||
else if (pageName === 'users') return this.$strings.HeaderUsers
|
else if (pageName === 'users') return this.$strings.HeaderUsers
|
||||||
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
||||||
else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds
|
else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds
|
||||||
@ -94,4 +93,4 @@ export default {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,175 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<app-settings-content :header-text="$strings.HeaderLibraryStats + ': ' + currentLibraryName">
|
|
||||||
<stats-preview-icons v-if="totalItems" :library-stats="libraryStats" />
|
|
||||||
|
|
||||||
<div class="flex lg:flex-row flex-wrap justify-between flex-col mt-8">
|
|
||||||
<div class="w-80 my-6 mx-auto">
|
|
||||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop5Genres }}</h1>
|
|
||||||
<p v-if="!top5Genres.length">{{ $strings.MessageNoGenres }}</p>
|
|
||||||
<template v-for="genre in top5Genres">
|
|
||||||
<div :key="genre.genre" class="w-full py-2">
|
|
||||||
<div class="flex items-end mb-1">
|
|
||||||
<p class="text-2xl font-bold">{{ Math.round((100 * genre.count) / totalItems) }} %</p>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(genre.genre)}`" class="text-base text-white text-opacity-70 hover:underline">
|
|
||||||
{{ genre.genre }}
|
|
||||||
</nuxt-link>
|
|
||||||
</div>
|
|
||||||
<div class="w-full rounded-full h-3 bg-primary bg-opacity-50 overflow-hidden">
|
|
||||||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * genre.count) / totalItems) + '%' }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-if="isBookLibrary" class="w-80 my-6 mx-auto">
|
|
||||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
|
||||||
<p v-if="!top10Authors.length">{{ $strings.MessageNoAuthors }}</p>
|
|
||||||
<template v-for="(author, index) in top10Authors">
|
|
||||||
<div :key="author.id" class="w-full py-2">
|
|
||||||
<div class="flex items-center mb-1">
|
|
||||||
<p class="text-sm text-white text-opacity-70 w-36 pr-2 truncate">
|
|
||||||
{{ index + 1 }}. <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">{{ author.name }}</nuxt-link>
|
|
||||||
</p>
|
|
||||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
|
||||||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * author.count) / mostUsedAuthorCount) + '%' }" />
|
|
||||||
</div>
|
|
||||||
<div class="w-4 ml-3">
|
|
||||||
<p class="text-sm font-bold">{{ author.count }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="w-80 my-6 mx-auto">
|
|
||||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsLongestItems }}</h1>
|
|
||||||
<p v-if="!top10LongestItems.length">{{ $strings.MessageNoItems }}</p>
|
|
||||||
<template v-for="(ab, index) in top10LongestItems">
|
|
||||||
<div :key="index" class="w-full py-2">
|
|
||||||
<div class="flex items-center mb-1">
|
|
||||||
<p class="text-sm text-white text-opacity-70 w-44 pr-2 truncate">
|
|
||||||
{{ index + 1 }}. <nuxt-link :to="`/item/${ab.id}`" class="hover:underline">{{ ab.title }}</nuxt-link>
|
|
||||||
</p>
|
|
||||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
|
||||||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * ab.duration) / longestItemDuration) + '%' }" />
|
|
||||||
</div>
|
|
||||||
<div class="w-4 ml-3">
|
|
||||||
<p class="text-sm font-bold">{{ (ab.duration / 3600).toFixed(1) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="w-80 my-6 mx-auto">
|
|
||||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsLargestItems }}</h1>
|
|
||||||
<p v-if="!top10LargestItems.length">{{ $strings.MessageNoItems }}</p>
|
|
||||||
<template v-for="(ab, index) in top10LargestItems">
|
|
||||||
<div :key="index" class="w-full py-2">
|
|
||||||
<div class="flex items-center mb-1">
|
|
||||||
<p class="text-sm text-white text-opacity-70 w-44 pr-2 truncate">
|
|
||||||
{{ index + 1 }}. <nuxt-link :to="`/item/${ab.id}`" class="hover:underline">{{ ab.title }}</nuxt-link>
|
|
||||||
</p>
|
|
||||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
|
||||||
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * ab.size) / largestItemSize) + '%' }" />
|
|
||||||
</div>
|
|
||||||
<div class="w-4 ml-3">
|
|
||||||
<p class="text-sm font-bold">{{ $bytesPretty(ab.size) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</app-settings-content>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
asyncData({ redirect, store }) {
|
|
||||||
if (!store.getters['user/getIsAdminOrUp']) {
|
|
||||||
redirect('/')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!store.state.libraries.currentLibraryId) {
|
|
||||||
return redirect('/config')
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
libraryStats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
currentLibraryId(newVal, oldVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
user() {
|
|
||||||
return this.$store.state.user.user
|
|
||||||
},
|
|
||||||
totalItems() {
|
|
||||||
return this.libraryStats?.totalItems || 0
|
|
||||||
},
|
|
||||||
genresWithCount() {
|
|
||||||
return this.libraryStats?.genresWithCount || []
|
|
||||||
},
|
|
||||||
top5Genres() {
|
|
||||||
return this.genresWithCount?.slice(0, 5) || []
|
|
||||||
},
|
|
||||||
top10LongestItems() {
|
|
||||||
return this.libraryStats?.longestItems || []
|
|
||||||
},
|
|
||||||
longestItemDuration() {
|
|
||||||
if (!this.top10LongestItems.length) return 0
|
|
||||||
return this.top10LongestItems[0].duration
|
|
||||||
},
|
|
||||||
top10LargestItems() {
|
|
||||||
return this.libraryStats?.largestItems || []
|
|
||||||
},
|
|
||||||
largestItemSize() {
|
|
||||||
if (!this.top10LargestItems.length) return 0
|
|
||||||
return this.top10LargestItems[0].size
|
|
||||||
},
|
|
||||||
authorsWithCount() {
|
|
||||||
return this.libraryStats?.authorsWithCount || []
|
|
||||||
},
|
|
||||||
mostUsedAuthorCount() {
|
|
||||||
if (!this.authorsWithCount.length) return 0
|
|
||||||
return this.authorsWithCount[0].count
|
|
||||||
},
|
|
||||||
top10Authors() {
|
|
||||||
return this.authorsWithCount?.slice(0, 10) || []
|
|
||||||
},
|
|
||||||
currentLibraryId() {
|
|
||||||
return this.$store.state.libraries.currentLibraryId
|
|
||||||
},
|
|
||||||
currentLibraryName() {
|
|
||||||
return this.$store.getters['libraries/getCurrentLibraryName']
|
|
||||||
},
|
|
||||||
currentLibraryMediaType() {
|
|
||||||
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
|
||||||
},
|
|
||||||
isBookLibrary() {
|
|
||||||
return this.currentLibraryMediaType === 'book'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async init() {
|
|
||||||
this.libraryStats = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/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}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
181
client/pages/library/_library/stats.vue
Normal file
181
client/pages/library/_library/stats.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page relative" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
|
<app-book-shelf-toolbar page="library-stats" is-home />
|
||||||
|
<div id="bookshelf" class="w-full h-full px-1 py-4 md:p-8 relative overflow-y-auto">
|
||||||
|
<div class="w-full max-w-4xl mx-auto">
|
||||||
|
<stats-preview-icons v-if="totalItems" :library-stats="libraryStats" />
|
||||||
|
|
||||||
|
<div class="flex lg:flex-row flex-wrap justify-between flex-col mt-8">
|
||||||
|
<div class="w-80 my-6 mx-auto">
|
||||||
|
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop5Genres }}</h1>
|
||||||
|
<p v-if="!top5Genres.length">{{ $strings.MessageNoGenres }}</p>
|
||||||
|
<template v-for="genre in top5Genres">
|
||||||
|
<div :key="genre.genre" class="w-full py-2">
|
||||||
|
<div class="flex items-end mb-1">
|
||||||
|
<p class="text-2xl font-bold">{{ Math.round((100 * genre.count) / totalItems) }} %</p>
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(genre.genre)}`" class="text-base text-white text-opacity-70 hover:underline">
|
||||||
|
{{ genre.genre }}
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
<div class="w-full rounded-full h-3 bg-primary bg-opacity-50 overflow-hidden">
|
||||||
|
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * genre.count) / totalItems) + '%' }" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="w-80 my-6 mx-auto">
|
||||||
|
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
||||||
|
<p v-if="!top10Authors.length">{{ $strings.MessageNoAuthors }}</p>
|
||||||
|
<template v-for="(author, index) in top10Authors">
|
||||||
|
<div :key="author.id" class="w-full py-2">
|
||||||
|
<div class="flex items-center mb-1">
|
||||||
|
<p class="text-sm text-white text-opacity-70 w-36 pr-2 truncate">
|
||||||
|
{{ index + 1 }}. <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">{{ author.name }}</nuxt-link>
|
||||||
|
</p>
|
||||||
|
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||||
|
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * author.count) / mostUsedAuthorCount) + '%' }" />
|
||||||
|
</div>
|
||||||
|
<div class="w-4 ml-3">
|
||||||
|
<p class="text-sm font-bold">{{ author.count }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="w-80 my-6 mx-auto">
|
||||||
|
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsLongestItems }}</h1>
|
||||||
|
<p v-if="!top10LongestItems.length">{{ $strings.MessageNoItems }}</p>
|
||||||
|
<template v-for="(ab, index) in top10LongestItems">
|
||||||
|
<div :key="index" class="w-full py-2">
|
||||||
|
<div class="flex items-center mb-1">
|
||||||
|
<p class="text-sm text-white text-opacity-70 w-44 pr-2 truncate">
|
||||||
|
{{ index + 1 }}. <nuxt-link :to="`/item/${ab.id}`" class="hover:underline">{{ ab.title }}</nuxt-link>
|
||||||
|
</p>
|
||||||
|
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||||
|
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * ab.duration) / longestItemDuration) + '%' }" />
|
||||||
|
</div>
|
||||||
|
<div class="w-4 ml-3">
|
||||||
|
<p class="text-sm font-bold">{{ (ab.duration / 3600).toFixed(1) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="w-80 my-6 mx-auto">
|
||||||
|
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsLargestItems }}</h1>
|
||||||
|
<p v-if="!top10LargestItems.length">{{ $strings.MessageNoItems }}</p>
|
||||||
|
<template v-for="(ab, index) in top10LargestItems">
|
||||||
|
<div :key="index" class="w-full py-2">
|
||||||
|
<div class="flex items-center mb-1">
|
||||||
|
<p class="text-sm text-white text-opacity-70 w-44 pr-2 truncate">
|
||||||
|
{{ index + 1 }}. <nuxt-link :to="`/item/${ab.id}`" class="hover:underline">{{ ab.title }}</nuxt-link>
|
||||||
|
</p>
|
||||||
|
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||||
|
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * ab.size) / largestItemSize) + '%' }" />
|
||||||
|
</div>
|
||||||
|
<div class="w-4 ml-3">
|
||||||
|
<p class="text-sm font-bold">{{ $bytesPretty(ab.size) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
asyncData({ redirect, store }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store.state.libraries.currentLibraryId) {
|
||||||
|
return redirect('/config')
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
libraryStats: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentLibraryId(newVal, oldVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
streamLibraryItem() {
|
||||||
|
return this.$store.state.streamLibraryItem
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
|
totalItems() {
|
||||||
|
return this.libraryStats?.totalItems || 0
|
||||||
|
},
|
||||||
|
genresWithCount() {
|
||||||
|
return this.libraryStats?.genresWithCount || []
|
||||||
|
},
|
||||||
|
top5Genres() {
|
||||||
|
return this.genresWithCount?.slice(0, 5) || []
|
||||||
|
},
|
||||||
|
top10LongestItems() {
|
||||||
|
return this.libraryStats?.longestItems || []
|
||||||
|
},
|
||||||
|
longestItemDuration() {
|
||||||
|
if (!this.top10LongestItems.length) return 0
|
||||||
|
return this.top10LongestItems[0].duration
|
||||||
|
},
|
||||||
|
top10LargestItems() {
|
||||||
|
return this.libraryStats?.largestItems || []
|
||||||
|
},
|
||||||
|
largestItemSize() {
|
||||||
|
if (!this.top10LargestItems.length) return 0
|
||||||
|
return this.top10LargestItems[0].size
|
||||||
|
},
|
||||||
|
authorsWithCount() {
|
||||||
|
return this.libraryStats?.authorsWithCount || []
|
||||||
|
},
|
||||||
|
mostUsedAuthorCount() {
|
||||||
|
if (!this.authorsWithCount.length) return 0
|
||||||
|
return this.authorsWithCount[0].count
|
||||||
|
},
|
||||||
|
top10Authors() {
|
||||||
|
return this.authorsWithCount?.slice(0, 10) || []
|
||||||
|
},
|
||||||
|
currentLibraryId() {
|
||||||
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
},
|
||||||
|
currentLibraryName() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||||
|
},
|
||||||
|
currentLibraryMediaType() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||||
|
},
|
||||||
|
isBookLibrary() {
|
||||||
|
return this.currentLibraryMediaType === 'book'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async init() {
|
||||||
|
this.libraryStats = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/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}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -89,6 +89,7 @@
|
|||||||
"ButtonShow": "Show",
|
"ButtonShow": "Show",
|
||||||
"ButtonStartM4BEncode": "Start M4B Encode",
|
"ButtonStartM4BEncode": "Start M4B Encode",
|
||||||
"ButtonStartMetadataEmbed": "Start Metadata Embed",
|
"ButtonStartMetadataEmbed": "Start Metadata Embed",
|
||||||
|
"ButtonStats": "Stats",
|
||||||
"ButtonSubmit": "Submit",
|
"ButtonSubmit": "Submit",
|
||||||
"ButtonTest": "Test",
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
|
@ -285,6 +285,7 @@ class Server {
|
|||||||
'/library/:library/bookshelf/:id?',
|
'/library/:library/bookshelf/:id?',
|
||||||
'/library/:library/authors',
|
'/library/:library/authors',
|
||||||
'/library/:library/narrators',
|
'/library/:library/narrators',
|
||||||
|
'/library/:library/stats',
|
||||||
'/library/:library/series/:id?',
|
'/library/:library/series/:id?',
|
||||||
'/library/:library/podcast/search',
|
'/library/:library/podcast/search',
|
||||||
'/library/:library/podcast/latest',
|
'/library/:library/podcast/latest',
|
||||||
|
Loading…
Reference in New Issue
Block a user