Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New View: Server Stats #3335

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
89f6473
added libraries/stats endpoint
Vito0912 Aug 24, 2024
858ea50
fixed wrong ide completions
Vito0912 Aug 24, 2024
3d90634
added library to be returned instead of libraryId and fixed stupid re…
Vito0912 Aug 24, 2024
0e205b5
copy pasted and edited some parts in all-stats
Vito0912 Aug 24, 2024
cbd042d
refactored libraries-stats and changed path
Vito0912 Aug 25, 2024
27b5d0c
removed redirect
Vito0912 Aug 25, 2024
df15c22
Merge branch 'advplyr:master' into feat/all-stats-page
Vito0912 Dec 29, 2024
39d40d5
first draft controller
Vito0912 Jan 1, 2025
793ef0e
updated controller
Vito0912 Jan 1, 2025
b127f0c
delete old stats view and added all library stats view
Vito0912 Jan 1, 2025
00b179e
fixed controller to include id of library without changing the schema
Vito0912 Jan 1, 2025
7c0a746
optimized all stats
Vito0912 Jan 1, 2025
8c7d911
added client code
Vito0912 Jan 1, 2025
2c8e362
Merge branch 'advplyr:master' into feat/all-stats-page
Vito0912 Jan 1, 2025
48ca61b
Merge branch 'feat/all-stats-page' of https://github.com/Vito0912/aud…
Vito0912 Jan 1, 2025
e102cd6
removed old artifacts
Vito0912 Jan 1, 2025
9dab97a
updated view
Vito0912 Jan 2, 2025
d515e67
fixed side bar, language and layout
Vito0912 Jan 2, 2025
47272d6
added stats to podcast view
Vito0912 Jan 2, 2025
bd41f23
reverted old changes and fixed library switching
Vito0912 Jan 3, 2025
48ba8bc
reverted old artifact agains
Vito0912 Jan 3, 2025
fd18452
Merge branch 'advplyr:master' into feat/all-stats-page
Vito0912 Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion client/components/app/BookShelfToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export default {
totalEntities: 0,
processingSeries: false,
processingIssues: false,
processingAuthors: false
processingAuthors: false,
showAllLibraryStats: false
}
},
computed: {
Expand Down Expand Up @@ -235,6 +236,9 @@ export default {
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
currentLibraryName() {
return this.$store.getters['libraries/getCurrentLibraryName']
},
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
},
Expand Down
6 changes: 3 additions & 3 deletions client/components/app/ConfigSideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ export default {

if (this.currentLibraryId) {
configRoutes.push({
id: 'library-stats',
title: this.$strings.HeaderLibraryStats,
path: `/library/${this.currentLibraryId}/stats`
id: 'config-server-stats',
title: this.$strings.HeaderServerStats,
path: `/config/server-stats`
})
configRoutes.push({
id: 'config-stats',
Expand Down
16 changes: 8 additions & 8 deletions client/components/app/SideRail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,6 @@
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</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">&#xf190;</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'">
<span class="abs-icons icon-podcast text-xl"></span>

Expand All @@ -103,6 +95,14 @@
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>

<nuxt-link v-if="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">&#xf190;</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="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>

Expand Down
18 changes: 15 additions & 3 deletions client/components/stats/PreviewIcons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
</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">{{ $strings.LabelEpisodes }}</p>
</div>
</div>

<div class="flex p-2">
<span class="material-symbols text-5xl py-1">show_chart</span>
<div class="px-1">
Expand All @@ -36,7 +44,7 @@
</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>
<div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p>
Expand All @@ -52,18 +60,22 @@ export default {
libraryStats: {
type: Object,
default: () => {}
}
},
mediaType: null
},
data() {
return {}
},
computed: {
currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
return this.mediaType || this.$store.getters['libraries/getCurrentLibraryMediaType']
},
isBookLibrary() {
return this.currentLibraryMediaType === 'book'
},
isOverView(){
return this.mediaType === 'overview'
},
user() {
return this.$store.state.user.user
},
Expand Down
168 changes: 168 additions & 0 deletions client/pages/config/server-stats.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<template>

<div>
<app-settings-content v-if="serverStats != null" :header-text="$strings.HeaderAllStats">
<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="$strings.HeaderBookLibraries">
<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">{{ $strings.LabelName }}</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">
<a :href="'/library/' + library.id + '/stats'" class="hover:underline" @click.prevent="switchLibrary(library.id)">
{{ library.name }}
</a>
</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="$strings.HeaderPodcastLibraries">
<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">{{ $strings.LabelName }}</th>
<th class="text-left">{{ $strings.LabelStatsItemsInLibrary }}</th>
<th class="text-left">{{ $strings.LabelEpisodes }}</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">
<a :href="'/library/' + library.id + '/stats'" class="hover:underline" @click.prevent="switchLibrary(library.id)">
{{ library.name }}
</a>
</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
})
},
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]
},
async switchLibrary(libraryId) {
await this.$store.dispatch('libraries/fetch', libraryId);

await this.$router.push(`/library/${libraryId}/stats`)
}
},
mounted() {
this.init()
}
}
</script>
3 changes: 0 additions & 3 deletions client/pages/library/_library/stats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,6 @@ export default {
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
currentLibraryName() {
return this.$store.getters['libraries/getCurrentLibraryName']
},
currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
Expand Down
4 changes: 4 additions & 0 deletions client/strings/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,13 @@
"HeaderAccount": "Account",
"HeaderAddCustomMetadataProvider": "Add Custom Metadata Provider",
"HeaderAdvanced": "Advanced",
"HeaderAllStats": "All Stats",
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
"HeaderAudioTracks": "Audio Tracks",
"HeaderAudiobookTools": "Audiobook File Management Tools",
"HeaderAuthentication": "Authentication",
"HeaderBackups": "Backups",
"HeaderBookLibraries": "Book Libraries",
"HeaderChangePassword": "Change Password",
"HeaderChapters": "Chapters",
"HeaderChooseAFolder": "Choose a Folder",
Expand Down Expand Up @@ -174,6 +176,7 @@
"HeaderPlayerSettings": "Player Settings",
"HeaderPlaylist": "Playlist",
"HeaderPlaylistItems": "Playlist Items",
"HeaderPodcastLibraries": "Podcast Libraries",
"HeaderPodcastsToAdd": "Podcasts to Add",
"HeaderPreviewCover": "Preview Cover",
"HeaderRSSFeedGeneral": "RSS Details",
Expand All @@ -185,6 +188,7 @@
"HeaderSchedule": "Schedule",
"HeaderScheduleEpisodeDownloads": "Schedule Automatic Episode Downloads",
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
"HeaderServerStats": "Server Stats",
"HeaderSession": "Session",
"HeaderSetBackupSchedule": "Set Backup Schedule",
"HeaderSettings": "Settings",
Expand Down
Loading
Loading