mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-08-01 06:01:49 +00:00
Compare commits
No commits in common. "e9ea511c90fbd16e4e0a382af39c385e404cfbe5" and "4c71a076e7d859eaa1550b4cdc6fd7cb0c82d7ab" have entirely different histories.
e9ea511c90
...
4c71a076e7
5 changed files with 2 additions and 131 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="page-wrapper" class="page p-6 overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
|
<div id="page-wrapper" class="page p-6 overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<div class="w-full max-w-2xl mx-auto">
|
<div class="w-full max-w-xl mx-auto">
|
||||||
<h1 class="text-2xl">{{ $strings.HeaderAccount }}</h1>
|
<h1 class="text-2xl">{{ $strings.HeaderAccount }}</h1>
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
|
|
@ -69,41 +69,9 @@
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!isGuest">
|
|
||||||
<div class="w-full h-px bg-white/10 my-4" />
|
|
||||||
|
|
||||||
<app-settings-content :header-text="$strings.HeaderSessions">
|
|
||||||
<table v-if="authSessions.length" class="tracksTable mt-4 table-fixed w-full">
|
|
||||||
<tr>
|
|
||||||
<th class="text-left">{{ $strings.LabelDeviceInfo }}</th>
|
|
||||||
<th class="text-left hidden sm:table-cell w-36">{{ $strings.LabelIpAddress }}</th>
|
|
||||||
<th class="text-left w-32 sm:w-40">{{ $strings.LabelLastUpdate }}</th>
|
|
||||||
</tr>
|
|
||||||
<tr v-for="session in authSessions" :key="session.id">
|
|
||||||
<td class="max-w-0">
|
|
||||||
<div class="flex items-center gap-0.5 min-w-0">
|
|
||||||
<span class="text-sm text-gray-100 truncate min-w-0" :title="session.userAgent">{{ getSessionDeviceLabel(session) }}</span>
|
|
||||||
<ui-tooltip v-if="session.current" direction="top" :text="$strings.LabelCurrent" class="inline-flex shrink-0">
|
|
||||||
<span class="material-symbols text-success text-sm leading-none">check</span>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="hidden sm:table-cell w-36">
|
|
||||||
<p class="text-sm text-gray-100 truncate" :title="session.ipAddress">{{ session.ipAddress || '-' }}</p>
|
|
||||||
</td>
|
|
||||||
<td class="w-32 sm:w-40">
|
|
||||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
|
||||||
<p class="text-xs sm:text-sm text-gray-100">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
|
||||||
</ui-tooltip>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</app-settings-content>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="py-4 mt-8 flex flex-wrap gap-2">
|
<div class="py-4 mt-8 flex flex-wrap gap-2">
|
||||||
<ui-btn v-if="!isGuest" color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(true)"> <span class="material-symbols mr-4 icon-text">devices</span>{{ $strings.ButtonLogoutAllDevices }} </ui-btn>
|
<ui-btn v-if="!isGuest" color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(true)"> <span class="material-symbols mr-4 icon-text">devices</span>{{ $strings.ButtonLogoutAllDevices }} </ui-btn>
|
||||||
<ui-btn color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(false)"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
<ui-btn color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
||||||
|
|
@ -121,7 +89,6 @@ export default {
|
||||||
confirmPassword: null,
|
confirmPassword: null,
|
||||||
changingPassword: false,
|
changingPassword: false,
|
||||||
loggingOut: false,
|
loggingOut: false,
|
||||||
authSessions: [],
|
|
||||||
selectedLanguage: '',
|
selectedLanguage: '',
|
||||||
newEReaderDevice: {
|
newEReaderDevice: {
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -164,12 +131,6 @@ export default {
|
||||||
},
|
},
|
||||||
revisedEreaderDevices() {
|
revisedEreaderDevices() {
|
||||||
return this.ereaderDevices.filter((device) => device.users?.length === 1)
|
return this.ereaderDevices.filter((device) => device.users?.length === 1)
|
||||||
},
|
|
||||||
dateFormat() {
|
|
||||||
return this.$store.getters['getServerSetting']('dateFormat')
|
|
||||||
},
|
|
||||||
timeFormat() {
|
|
||||||
return this.$store.getters['getServerSetting']('timeFormat')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -284,34 +245,11 @@ export default {
|
||||||
},
|
},
|
||||||
ereaderDevicesUpdated(ereaderDevices) {
|
ereaderDevicesUpdated(ereaderDevices) {
|
||||||
this.ereaderDevices = ereaderDevices
|
this.ereaderDevices = ereaderDevices
|
||||||
},
|
|
||||||
loadAuthSessions() {
|
|
||||||
this.$axios
|
|
||||||
.$get('/api/me/sessions')
|
|
||||||
.then((data) => {
|
|
||||||
this.authSessions = data.sessions || []
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to load sessions', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getSessionDeviceLabel(session) {
|
|
||||||
const deviceInfo = session.deviceInfo
|
|
||||||
if (!deviceInfo) return session.userAgent || '-'
|
|
||||||
|
|
||||||
const parts = []
|
|
||||||
if (deviceInfo.model) parts.push(deviceInfo.model)
|
|
||||||
if (deviceInfo.osName) parts.push(`${deviceInfo.osName}${deviceInfo.osVersion ? ` ${deviceInfo.osVersion}` : ''}`)
|
|
||||||
if (deviceInfo.browserName) parts.push(deviceInfo.browserName)
|
|
||||||
return parts.join(' / ') || session.userAgent || '-'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.selectedLanguage = this.$languageCodes.current
|
this.selectedLanguage = this.$languageCodes.current
|
||||||
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
|
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
|
||||||
if (!this.isGuest) {
|
|
||||||
this.loadAuthSessions()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,6 @@
|
||||||
"HeaderScheduleEpisodeDownloads": "Schedule Automatic Episode Downloads",
|
"HeaderScheduleEpisodeDownloads": "Schedule Automatic Episode Downloads",
|
||||||
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
||||||
"HeaderSession": "Session",
|
"HeaderSession": "Session",
|
||||||
"HeaderSessions": "Sessions",
|
|
||||||
"HeaderSetBackupSchedule": "Set Backup Schedule",
|
"HeaderSetBackupSchedule": "Set Backup Schedule",
|
||||||
"HeaderSettings": "Settings",
|
"HeaderSettings": "Settings",
|
||||||
"HeaderSettingsDisplay": "Display",
|
"HeaderSettingsDisplay": "Display",
|
||||||
|
|
@ -417,7 +416,6 @@
|
||||||
"LabelIntervalEveryHour": "Every hour",
|
"LabelIntervalEveryHour": "Every hour",
|
||||||
"LabelIntervalEveryMinute": "Every minute",
|
"LabelIntervalEveryMinute": "Every minute",
|
||||||
"LabelInvert": "Invert",
|
"LabelInvert": "Invert",
|
||||||
"LabelIpAddress": "IP Address",
|
|
||||||
"LabelItem": "Item",
|
"LabelItem": "Item",
|
||||||
"LabelJumpBackwardAmount": "Jump backward amount",
|
"LabelJumpBackwardAmount": "Jump backward amount",
|
||||||
"LabelJumpForwardAmount": "Jump forward amount",
|
"LabelJumpForwardAmount": "Jump forward amount",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
const { Request, Response } = require('express')
|
const { Request, Response } = require('express')
|
||||||
const { Op } = require('sequelize')
|
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const { sort } = require('../libs/fastSort')
|
const { sort } = require('../libs/fastSort')
|
||||||
const { toNumber, isNullOrNaN } = require('../utils/index')
|
const { toNumber, isNullOrNaN } = require('../utils/index')
|
||||||
const userStats = require('../utils/queries/userStats')
|
const userStats = require('../utils/queries/userStats')
|
||||||
const parseUserAgent = require('../utils/parsers/parseUserAgent')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RequestUserObject
|
* @typedef RequestUserObject
|
||||||
|
|
@ -28,40 +26,6 @@ class MeController {
|
||||||
res.json(req.user.toOldJSONForBrowser())
|
res.json(req.user.toOldJSONForBrowser())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GET: /api/me/sessions
|
|
||||||
*
|
|
||||||
* @param {RequestWithUser} req
|
|
||||||
* @param {Response} res
|
|
||||||
*/
|
|
||||||
async getSessions(req, res) {
|
|
||||||
if (req.user.isGuest) {
|
|
||||||
return res.json({ sessions: [] })
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshToken = req.cookies.refresh_token || req.headers['x-refresh-token']
|
|
||||||
const sessions = await Database.sessionModel.findAll({
|
|
||||||
where: {
|
|
||||||
userId: req.user.id,
|
|
||||||
expiresAt: { [Op.gt]: new Date() }
|
|
||||||
},
|
|
||||||
order: [['updatedAt', 'DESC']]
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
sessions: sessions.map((session) => ({
|
|
||||||
id: session.id,
|
|
||||||
ipAddress: session.ipAddress,
|
|
||||||
userAgent: session.userAgent,
|
|
||||||
// For display convenience
|
|
||||||
deviceInfo: parseUserAgent(session.userAgent),
|
|
||||||
createdAt: session.createdAt?.valueOf() ?? null,
|
|
||||||
updatedAt: session.updatedAt?.valueOf() ?? null,
|
|
||||||
current: !!refreshToken && (session.refreshToken === refreshToken || session.lastRefreshToken === refreshToken)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET: /api/me/progress
|
* GET: /api/me/progress
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,6 @@ class ApiRouter {
|
||||||
// Current User Routes (Me)
|
// Current User Routes (Me)
|
||||||
//
|
//
|
||||||
this.router.get('/me', MeController.getCurrentUser.bind(this))
|
this.router.get('/me', MeController.getCurrentUser.bind(this))
|
||||||
this.router.get('/me/sessions', MeController.getSessions.bind(this))
|
|
||||||
this.router.get('/me/progress', MeController.getAllMediaProgress.bind(this))
|
this.router.get('/me/progress', MeController.getAllMediaProgress.bind(this))
|
||||||
this.router.get('/me/bookmarks', MeController.getAllBookmarks.bind(this))
|
this.router.get('/me/bookmarks', MeController.getAllBookmarks.bind(this))
|
||||||
this.router.get('/me/bookmarks/:libraryItemId', MeController.getBookmarksForLibraryItem.bind(this))
|
this.router.get('/me/bookmarks/:libraryItemId', MeController.getBookmarksForLibraryItem.bind(this))
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
const uaParserJs = require('../../libs/uaParser')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string|null|undefined} userAgent
|
|
||||||
* @returns {{ browserName?:string, browserVersion?:string, osName?:string, osVersion?:string, deviceType?:string, model?:string, vendor?:string }|null}
|
|
||||||
*/
|
|
||||||
function parseUserAgent(userAgent) {
|
|
||||||
if (!userAgent) return null
|
|
||||||
|
|
||||||
const ua = uaParserJs(userAgent)
|
|
||||||
const deviceInfo = {
|
|
||||||
browserName: ua?.browser?.name || undefined,
|
|
||||||
browserVersion: ua?.browser?.version || undefined,
|
|
||||||
osName: ua?.os?.name || undefined,
|
|
||||||
osVersion: ua?.os?.version || undefined,
|
|
||||||
deviceType: ua?.device?.type || undefined,
|
|
||||||
model: ua?.device?.model || undefined,
|
|
||||||
vendor: ua?.device?.vendor || undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in deviceInfo) {
|
|
||||||
if (deviceInfo[key] === undefined) delete deviceInfo[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(deviceInfo).length ? deviceInfo : null
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = parseUserAgent
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue