mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-25 21:29:37 +00:00
Update:No longer creating initial root user and initial library, add init root user page, web app works with no libraries
This commit is contained in:
parent
63a8e2433e
commit
c962090c3a
20 changed files with 287 additions and 149 deletions
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<ui-libraries-dropdown />
|
||||
|
||||
<controls-global-search class="hidden md:block" />
|
||||
<controls-global-search v-if="currentLibrary" class="hidden md:block" />
|
||||
<div class="flex-grow" />
|
||||
|
||||
<span v-if="showExperimentalFeatures" class="material-icons text-4xl text-warning pr-0 sm:pr-2 md:pr-4">logo_dev</span>
|
||||
|
|
@ -24,11 +24,11 @@
|
|||
<google-cast-launcher></google-cast-launcher>
|
||||
</div>
|
||||
|
||||
<nuxt-link to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||
<nuxt-link v-if="currentLibrary" to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||
<span class="material-icons" aria-label="User Stats" role="button">equalizer</span>
|
||||
</nuxt-link>
|
||||
|
||||
<nuxt-link v-if="userCanUpload" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||
<span class="material-icons" aria-label="Upload Media" role="button">upload</span>
|
||||
</nuxt-link>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ export default {
|
|||
return {}
|
||||
},
|
||||
computed: {
|
||||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
userIsAdminOrUp() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
|
|
@ -38,7 +41,7 @@ export default {
|
|||
}
|
||||
]
|
||||
}
|
||||
return [
|
||||
const configRoutes = [
|
||||
{
|
||||
id: 'config',
|
||||
title: 'Settings',
|
||||
|
|
@ -63,18 +66,23 @@ export default {
|
|||
id: 'config-log',
|
||||
title: 'Log',
|
||||
path: '/config/log'
|
||||
},
|
||||
{
|
||||
}
|
||||
]
|
||||
|
||||
if (this.currentLibraryId) {
|
||||
configRoutes.push({
|
||||
id: 'config-library-stats',
|
||||
title: 'Library Stats',
|
||||
path: '/config/library-stats'
|
||||
},
|
||||
{
|
||||
})
|
||||
configRoutes.push({
|
||||
id: 'config-stats',
|
||||
title: 'Your Stats',
|
||||
path: '/config/stats'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return configRoutes
|
||||
},
|
||||
wrapperClass() {
|
||||
var classes = []
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default {
|
|||
settings: {
|
||||
disableWatcher: false,
|
||||
skipMatchingMediaWithAsin: false,
|
||||
skipMatchingMediaWithIsbn: false,
|
||||
skipMatchingMediaWithIsbn: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -193,6 +193,11 @@ export default {
|
|||
this.processing = false
|
||||
this.show = false
|
||||
this.$toast.success(`Library "${res.name}" created successfully`)
|
||||
if (!this.$store.state.libraries.currentLibraryId) {
|
||||
console.log('Setting initially library id', res.id)
|
||||
// First library added
|
||||
this.$store.dispatch('libraries/fetch', res.id)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
|
|
|
|||
|
|
@ -6,18 +6,20 @@
|
|||
<span class="material-icons" style="font-size: 1.4rem">add</span>
|
||||
</div>
|
||||
</div>
|
||||
<draggable :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
|
||||
<draggable v-if="libraryCopies.length" :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
|
||||
<template v-for="library in libraryCopies">
|
||||
<div :key="library.id" class="item">
|
||||
<tables-library-item :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @click="setLibrary" />
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<modals-libraries-edit-modal v-model="showLibraryModal" :library="selectedLibrary" />
|
||||
<div v-if="!libraries.length" class="pb-4">
|
||||
<ui-btn @click="clickAddLibrary">Add your first library</ui-btn>
|
||||
</div>
|
||||
|
||||
<p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
|
||||
<p v-if="libraries.length" class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
|
||||
|
||||
<p class="text-xs mt-4 text-gray-200">**<strong>Match Books</strong> will attempt to match books in library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.</p>
|
||||
<p v-if="libraries.length && libraries.some((li) => li.mediaType === 'book')" class="text-xs mt-4 text-gray-200">**<strong>Match Books</strong> will attempt to match books in library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -32,8 +34,6 @@ export default {
|
|||
return {
|
||||
libraryCopies: [],
|
||||
currentOrder: [],
|
||||
showLibraryModal: false,
|
||||
selectedLibrary: null,
|
||||
drag: false,
|
||||
dragOptions: {
|
||||
animation: 200,
|
||||
|
|
@ -97,12 +97,10 @@ export default {
|
|||
this.$router.push(`/library/${library.id}`)
|
||||
},
|
||||
clickAddLibrary() {
|
||||
this.selectedLibrary = null
|
||||
this.showLibraryModal = true
|
||||
this.$emit('showLibraryModal', null)
|
||||
},
|
||||
editLibrary(library) {
|
||||
this.selectedLibrary = library
|
||||
this.showLibraryModal = true
|
||||
this.$emit('showLibraryModal', library)
|
||||
},
|
||||
init() {
|
||||
this.libraryCopies = this.libraries.map((lib) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<div class="relative">
|
||||
<input ref="input" v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
||||
<div ref="wrapper" class="relative">
|
||||
<input ref="input" v-model="inputValue" :type="actualType" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
||||
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
||||
<span class="material-icons text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
|
||||
</div>
|
||||
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
|
||||
<span class="material-icons-outlined text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -31,7 +34,10 @@ export default {
|
|||
clearable: Boolean
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
showPassword: false,
|
||||
isHovering: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inputValue: {
|
||||
|
|
@ -49,6 +55,10 @@ export default {
|
|||
if (this.noSpinner) _list.push('no-spinner')
|
||||
if (this.textCenter) _list.push('text-center')
|
||||
return _list.join(' ')
|
||||
},
|
||||
actualType() {
|
||||
if (this.type === 'password' && this.showPassword) return 'text'
|
||||
return this.type
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -69,9 +79,20 @@ export default {
|
|||
},
|
||||
blur() {
|
||||
if (this.$refs.input) this.$refs.input.blur()
|
||||
},
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
},
|
||||
mouseleave() {
|
||||
this.isHovering = false
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
mounted() {
|
||||
if (this.type === 'password' && this.$refs.wrapper) {
|
||||
this.$refs.wrapper.addEventListener('mouseover', this.mouseover)
|
||||
this.$refs.wrapper.addEventListener('mouseleave', this.mouseleave)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">
|
||||
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }">
|
||||
{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
||||
</p>
|
||||
<ui-text-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" @blur="inputBlurred" />
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
},
|
||||
isShowingSideRail() {
|
||||
if (!this.$route.name) return false
|
||||
return !this.$route.name.startsWith('config')
|
||||
return !this.$route.name.startsWith('config') && this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
appContentMarginLeft() {
|
||||
return this.isShowingSideRail ? 80 : 0
|
||||
|
|
@ -173,6 +173,7 @@ export default {
|
|||
this.$store.commit('libraries/addUpdate', library)
|
||||
},
|
||||
async libraryRemoved(library) {
|
||||
console.log('Library removed', library)
|
||||
this.$store.commit('libraries/remove', library)
|
||||
|
||||
// When removed currently selected library then set next accessible library
|
||||
|
|
@ -191,7 +192,8 @@ export default {
|
|||
this.$router.push(`/library/${nextLibrary.id}`)
|
||||
}
|
||||
} else {
|
||||
console.error('User has no accessible libraries')
|
||||
console.error('User has no more accessible libraries')
|
||||
this.$store.commit('libraries/setCurrentLibrary', null)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
<template>
|
||||
<div>
|
||||
<tables-library-libraries-table />
|
||||
<tables-library-libraries-table @showLibraryModal="setShowLibraryModal" />
|
||||
|
||||
<modals-libraries-edit-modal v-model="showLibraryModal" :library="selectedLibrary" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
showLibraryModal: false,
|
||||
selectedLibrary: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
methods: {
|
||||
setShowLibraryModal(selectedLibrary) {
|
||||
this.selectedLibrary = selectedLibrary
|
||||
this.showLibraryModal = true
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -67,6 +67,12 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
asyncData({ redirect, store }) {
|
||||
if (!store.state.libraries.currentLibraryId) {
|
||||
return redirect('/config')
|
||||
}
|
||||
return {}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
libraryStats: null
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
<script>
|
||||
export default {
|
||||
asyncData({ redirect, store }) {
|
||||
if (!store.state.libraries.currentLibraryId) {
|
||||
return redirect('/oops?message=No libraries')
|
||||
}
|
||||
redirect(`/library/${store.state.libraries.currentLibraryId}`)
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,29 @@
|
|||
<template>
|
||||
<div class="w-full h-screen bg-bg">
|
||||
<div class="w-full flex h-1/2 items-center justify-center">
|
||||
<div class="w-full max-w-md border border-opacity-0 rounded-xl px-8 pb-8 pt-4">
|
||||
<div class="w-full flex h-full items-center justify-center">
|
||||
<div v-if="criticalError" class="w-full max-w-md rounded border border-error border-opacity-25 bg-error bg-opacity-10 p-4">
|
||||
<p class="text-center text-lg font-semibold">Server could not be reached</p>
|
||||
</div>
|
||||
<div v-else-if="showInitScreen" class="w-full max-w-lg px-4 md:px-8 pb-8 pt-4">
|
||||
<p class="text-3xl text-white text-center mb-4">Initial Server Setup</p>
|
||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||
|
||||
<form @submit.prevent="submitServerSetup">
|
||||
<p class="text-lg font-semibold mb-2 pl-1 text-center">Create Root User</p>
|
||||
<ui-text-input-with-label v-model="newRoot.username" label="Username" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model="newRoot.password" label="Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model="confirmPassword" label="Confirm Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
|
||||
<p class="text-lg font-semibold mt-6 mb-2 pl-1 text-center">Directory Paths</p>
|
||||
<ui-text-input-with-label v-model="ConfigPath" label="Config Path" disabled class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model="MetadataPath" label="Metadata Path" disabled class="w-full mb-3 text-sm" />
|
||||
|
||||
<div class="w-full flex justify-end py-3">
|
||||
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Initializing...' : 'Submit' }}</ui-btn>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
||||
<p class="text-3xl text-white text-center mb-4">Login</p>
|
||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
||||
|
|
@ -11,8 +33,8 @@
|
|||
|
||||
<label class="text-xs text-gray-300 uppercase">Password</label>
|
||||
<ui-text-input v-model="password" type="password" :disabled="processing" class="w-full mb-3" />
|
||||
<div class="w-full flex justify-end">
|
||||
<button type="submit" :disabled="processing" class="bg-blue-600 hover:bg-blue-800 px-8 py-1 mt-3 rounded-md text-white text-center transition duration-300 ease-in-out focus:outline-none">{{ processing ? 'Checking...' : 'Submit' }}</button>
|
||||
<div class="w-full flex justify-end py-3">
|
||||
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : 'Submit' }}</ui-btn>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -26,15 +48,33 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
error: null,
|
||||
criticalError: null,
|
||||
processing: false,
|
||||
username: '',
|
||||
password: null
|
||||
password: null,
|
||||
showInitScreen: false,
|
||||
isInit: false,
|
||||
newRoot: {
|
||||
username: 'root',
|
||||
password: ''
|
||||
},
|
||||
confirmPassword: '',
|
||||
ConfigPath: '',
|
||||
MetadataPath: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
user(newVal) {
|
||||
if (newVal) {
|
||||
if (this.$route.query.redirect) {
|
||||
if (!this.$store.state.libraries.currentLibraryId) {
|
||||
// No libraries available to this user
|
||||
if (this.$store.getters['user/getIsRoot']) {
|
||||
// If root user go to config/libraries
|
||||
this.$router.replace('/config/libraries')
|
||||
} else {
|
||||
this.$router.replace('/oops?message=No libraries available')
|
||||
}
|
||||
} else if (this.$route.query.redirect) {
|
||||
this.$router.replace(this.$route.query.redirect)
|
||||
} else {
|
||||
this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`)
|
||||
|
|
@ -48,6 +88,42 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
async submitServerSetup() {
|
||||
if (!this.newRoot.username || !this.newRoot.username.trim()) {
|
||||
this.$toast.error('Must enter a root username')
|
||||
return
|
||||
}
|
||||
if (this.newRoot.password !== this.confirmPassword) {
|
||||
this.$toast.error('Password mismatch')
|
||||
return
|
||||
}
|
||||
if (!this.newRoot.password) {
|
||||
if (!confirm('Are you sure you want to create the root user with no password?')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.processing = true
|
||||
|
||||
const payload = {
|
||||
newRoot: { ...this.newRoot }
|
||||
}
|
||||
var success = await this.$axios
|
||||
.$post('/init', payload)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('Failed', error.response)
|
||||
const errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.$toast.error(errorMsg)
|
||||
return false
|
||||
})
|
||||
|
||||
if (!success) {
|
||||
this.processing = false
|
||||
return
|
||||
}
|
||||
|
||||
location.reload()
|
||||
},
|
||||
setUser({ user, userDefaultLibraryId, serverSettings }) {
|
||||
this.$store.commit('setServerSettings', serverSettings)
|
||||
|
||||
|
|
@ -81,32 +157,54 @@ export default {
|
|||
this.processing = false
|
||||
},
|
||||
checkAuth() {
|
||||
if (localStorage.getItem('token')) {
|
||||
var token = localStorage.getItem('token')
|
||||
var token = localStorage.getItem('token')
|
||||
if (!token) return false
|
||||
|
||||
if (token) {
|
||||
this.processing = true
|
||||
this.processing = true
|
||||
|
||||
this.$axios
|
||||
.$post('/api/authorize', null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
this.setUser(res)
|
||||
this.processing = false
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Authorize error', error)
|
||||
this.processing = false
|
||||
})
|
||||
}
|
||||
}
|
||||
return this.$axios
|
||||
.$post('/api/authorize', null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
this.setUser(res)
|
||||
this.processing = false
|
||||
return true
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Authorize error', error)
|
||||
this.processing = false
|
||||
return false
|
||||
})
|
||||
},
|
||||
checkStatus() {
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$get('/status')
|
||||
.then((res) => {
|
||||
this.processing = false
|
||||
this.isInit = res.isInit
|
||||
this.showInitScreen = !res.isInit
|
||||
if (this.showInitScreen) {
|
||||
this.ConfigPath = res.ConfigPath || ''
|
||||
this.MetadataPath = res.MetadataPath || ''
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Status check failed', error)
|
||||
this.processing = false
|
||||
this.criticalError = 'Status check failed'
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkAuth()
|
||||
async mounted() {
|
||||
if (localStorage.getItem('token')) {
|
||||
var userfound = await this.checkAuth()
|
||||
if (userfound) return // if valid user no need to check status
|
||||
}
|
||||
this.checkStatus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -2,7 +2,7 @@ export const state = () => ({
|
|||
libraries: [],
|
||||
lastLoad: 0,
|
||||
listeners: [],
|
||||
currentLibraryId: 'main',
|
||||
currentLibraryId: null,
|
||||
folders: [],
|
||||
issues: 0,
|
||||
folderLastUpdate: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue