mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-09 10:51:37 +00:00
added server styling settings
This commit is contained in:
parent
76ea038f5a
commit
32ab726fe9
11 changed files with 466 additions and 3 deletions
|
|
@ -7,6 +7,92 @@
|
||||||
:root {
|
:root {
|
||||||
--bookshelf-texture-img: url(~static/textures/wood_default.jpg);
|
--bookshelf-texture-img: url(~static/textures/wood_default.jpg);
|
||||||
--bookshelf-divider-bg: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
|
--bookshelf-divider-bg: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
|
||||||
|
|
||||||
|
/* Theme Colors */
|
||||||
|
--color-primary: #1e293b;
|
||||||
|
--color-primary-dark: #0f172a;
|
||||||
|
--color-success: #22c55e;
|
||||||
|
--color-warning: #f59e0b;
|
||||||
|
--color-error: #ef4444;
|
||||||
|
--color-info: #3b82f6;
|
||||||
|
--color-bg: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color Classes */
|
||||||
|
.bg-primary {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary-dark {
|
||||||
|
background-color: var(--color-primary-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-success {
|
||||||
|
background-color: var(--color-success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-warning {
|
||||||
|
background-color: var(--color-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-error {
|
||||||
|
background-color: var(--color-error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-info {
|
||||||
|
background-color: var(--color-info) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-bg {
|
||||||
|
background-color: var(--color-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary-dark {
|
||||||
|
color: var(--color-primary-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: var(--color-success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-warning {
|
||||||
|
color: var(--color-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-error {
|
||||||
|
color: var(--color-error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-info {
|
||||||
|
color: var(--color-info) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-primary {
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-primary-dark {
|
||||||
|
border-color: var(--color-primary-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-success {
|
||||||
|
border-color: var(--color-success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-warning {
|
||||||
|
border-color: var(--color-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-error {
|
||||||
|
border-color: var(--color-error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-info {
|
||||||
|
border-color: var(--color-info) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
|
|
@ -249,4 +335,4 @@ Bookshelf Label
|
||||||
|
|
||||||
.abs-btn:disabled::before {
|
.abs-btn:disabled::before {
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-3 h-12 border-b border-primary/30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary/70' : 'hover:bg-primary/30'">
|
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-3 h-12 border-b border-primary/30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary/70' : 'hover:bg-primary/30'">
|
||||||
<p class="leading-4">{{ route.title }}</p>
|
<p class="leading-4">{{ route.title }}</p>
|
||||||
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="routeName === route.id" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<modals-changelog-view-modal v-model="showChangelogModal" :versionData="versionData" />
|
<modals-changelog-view-modal v-model="showChangelogModal" :versionData="versionData" />
|
||||||
|
|
@ -109,6 +109,11 @@ export default {
|
||||||
id: 'config-authentication',
|
id: 'config-authentication',
|
||||||
title: this.$strings.HeaderAuthentication,
|
title: this.$strings.HeaderAuthentication,
|
||||||
path: '/config/authentication'
|
path: '/config/authentication'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'styling',
|
||||||
|
title: this.$strings.HeaderStyling,
|
||||||
|
path: '/config/styling'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
53
client/components/app/GlobalStyling.vue
Normal file
53
client/components/app/GlobalStyling.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GlobalStyling',
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['getServerStyling'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
applyColors() {
|
||||||
|
const styling = this.getServerStyling
|
||||||
|
if (!styling) return
|
||||||
|
|
||||||
|
// Create a style element if it doesn't exist
|
||||||
|
let styleEl = document.getElementById('custom-colors')
|
||||||
|
if (!styleEl) {
|
||||||
|
styleEl = document.createElement('style')
|
||||||
|
styleEl.id = 'custom-colors'
|
||||||
|
document.head.appendChild(styleEl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update CSS variables
|
||||||
|
const css = `
|
||||||
|
:root {
|
||||||
|
--color-primary: ${styling.primary};
|
||||||
|
--color-primary-dark: ${styling.primaryDark};
|
||||||
|
--color-success: ${styling.success};
|
||||||
|
--color-warning: ${styling.warning};
|
||||||
|
--color-error: ${styling.error};
|
||||||
|
--color-info: ${styling.info};
|
||||||
|
--color-bg: ${styling.background};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
styleEl.innerHTML = css
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.applyColors()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
getServerStyling: {
|
||||||
|
handler() {
|
||||||
|
this.applyColors()
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
68
client/components/app/ServerSettingsTab.vue
Normal file
68
client/components/app/ServerSettingsTab.vue
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<nuxt-link v-for="setting in serverSettings" :key="setting.path" :to="setting.path" class="p-4 bg-primary hover:bg-primary-600 rounded-lg transition-colors duration-200">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="material-symbols text-2xl mr-3">{{ setting.icon }}</span>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold">{{ setting.title }}</h3>
|
||||||
|
<p class="text-sm text-gray-300">{{ setting.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
serverSettings: [
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderAuthentication || 'Authentication',
|
||||||
|
description: this.$strings.DescriptionAuthentication || 'Configure authentication methods and settings',
|
||||||
|
path: '/config/authentication',
|
||||||
|
icon: 'security'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderLibraries || 'Libraries',
|
||||||
|
description: this.$strings.DescriptionLibraries || 'Manage your libraries',
|
||||||
|
path: '/config/libraries',
|
||||||
|
icon: 'library_books'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderBackups || 'Backups',
|
||||||
|
description: this.$strings.DescriptionBackups || 'Configure server backups',
|
||||||
|
path: '/config/backups',
|
||||||
|
icon: 'backup'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderEmail || 'Email',
|
||||||
|
description: this.$strings.DescriptionEmail || 'Configure email settings',
|
||||||
|
path: '/config/email',
|
||||||
|
icon: 'mail'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderNotifications || 'Notifications',
|
||||||
|
description: this.$strings.DescriptionNotifications || 'Configure notification settings',
|
||||||
|
path: '/config/notifications',
|
||||||
|
icon: 'notifications'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderServerLogs || 'Server Logs',
|
||||||
|
description: this.$strings.DescriptionServerLogs || 'View server logs',
|
||||||
|
path: '/config/log',
|
||||||
|
icon: 'article'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$strings.HeaderServerStyling || 'Server Styling',
|
||||||
|
description: this.$strings.DescriptionServerStyling || 'Customize server appearance',
|
||||||
|
path: '/config/styling',
|
||||||
|
icon: 'palette'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="text-white max-h-screen h-screen overflow-hidden bg-bg">
|
<div class="text-white max-h-screen h-screen overflow-hidden bg-bg">
|
||||||
|
<app-global-styling />
|
||||||
<app-appbar />
|
<app-appbar />
|
||||||
|
|
||||||
<app-side-rail v-if="isShowingSideRail" class="hidden md:block" />
|
<app-side-rail v-if="isShowingSideRail" class="hidden md:block" />
|
||||||
|
|
@ -27,7 +28,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import AppGlobalStyling from '~/components/app/GlobalStyling.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
AppGlobalStyling
|
||||||
|
},
|
||||||
middleware: 'authenticated',
|
middleware: 'authenticated',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
164
client/pages/config/styling.vue
Normal file
164
client/pages/config/styling.vue
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<app-settings-content :header-text="$strings.HeaderServerStyling || 'Server Styling'">
|
||||||
|
<div class="w-full max-w-3xl">
|
||||||
|
<div class="pt-4">
|
||||||
|
<h2 class="font-semibold">Theme Colors</h2>
|
||||||
|
<p class="text-sm text-gray-400 mt-1">Customize the appearance of your server by adjusting these colors.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
||||||
|
<!-- Primary Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Primary Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.primary" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('primary', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.primary" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('primary', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Primary Dark -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Primary Dark</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.primaryDark" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('primaryDark', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.primaryDark" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('primaryDark', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Success Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Success Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.success" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('success', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.success" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('success', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Warning Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Warning Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.warning" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('warning', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.warning" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('warning', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Error Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.error" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('error', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.error" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('error', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Info Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.info" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('info', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.info" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('info', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Background Color -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="block text-sm font-medium">Background Color</label>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input type="color" v-model="colors.background" class="w-10 h-10 rounded cursor-pointer border border-gray-600" @input="updateColor('background', $event.target.value)" />
|
||||||
|
<input type="text" v-model="colors.background" class="flex-1 bg-primary/20 border border-gray-600 rounded px-3 py-2" @input="updateColor('background', $event.target.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Button -->
|
||||||
|
<div class="mt-8 flex justify-end">
|
||||||
|
<ui-btn color="bg-success" :loading="saving" @click="saveSettings">Save Changes</ui-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview Section -->
|
||||||
|
<div class="mt-12 border-t border-gray-600 pt-8">
|
||||||
|
<h2 class="font-semibold mb-4">Preview</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="text-sm font-medium mb-2">Buttons</p>
|
||||||
|
<ui-btn color="bg-primary" class="mr-2">Primary</ui-btn>
|
||||||
|
<ui-btn color="bg-success" class="mr-2">Success</ui-btn>
|
||||||
|
<ui-btn color="bg-warning" class="mr-2">Warning</ui-btn>
|
||||||
|
<ui-btn color="bg-error">Error</ui-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alerts -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="text-sm font-medium mb-2">Alerts</p>
|
||||||
|
<widgets-alert type="info" class="mb-2">This is an info alert</widgets-alert>
|
||||||
|
<widgets-alert type="success" class="mb-2">This is a success alert</widgets-alert>
|
||||||
|
<widgets-alert type="warning" class="mb-2">This is a warning alert</widgets-alert>
|
||||||
|
<widgets-alert type="error">This is an error alert</widgets-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-settings-content>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'styling',
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
saving: false,
|
||||||
|
colors: {
|
||||||
|
primary: '#1e293b',
|
||||||
|
primaryDark: '#0f172a',
|
||||||
|
success: '#22c55e',
|
||||||
|
warning: '#f59e0b',
|
||||||
|
error: '#ef4444',
|
||||||
|
info: '#3b82f6',
|
||||||
|
background: '#111827'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['getServerStyling'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateColor(key, value) {
|
||||||
|
// Validate hex color
|
||||||
|
if (!/^#[0-9A-F]{6}$/i.test(value)) return
|
||||||
|
|
||||||
|
this.colors[key] = value
|
||||||
|
},
|
||||||
|
async saveSettings() {
|
||||||
|
this.saving = true
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('updateServerSettings', {
|
||||||
|
styling: this.colors
|
||||||
|
})
|
||||||
|
this.$toast.success('Server styling updated')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save styling settings:', error)
|
||||||
|
this.$toast.error('Failed to save styling settings')
|
||||||
|
}
|
||||||
|
this.saving = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// Load saved colors from server settings
|
||||||
|
const serverStyling = this.getServerStyling
|
||||||
|
if (serverStyling) {
|
||||||
|
this.colors = { ...this.colors, ...serverStyling }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -36,6 +36,10 @@ export const getters = {
|
||||||
if (!state.serverSettings) return null
|
if (!state.serverSettings) return null
|
||||||
return state.serverSettings[key]
|
return state.serverSettings[key]
|
||||||
},
|
},
|
||||||
|
getServerStyling: (state) => {
|
||||||
|
if (!state.serverSettings?.styling) return null
|
||||||
|
return state.serverSettings.styling
|
||||||
|
},
|
||||||
getLibraryItemIdStreaming: (state) => {
|
getLibraryItemIdStreaming: (state) => {
|
||||||
return state.streamLibraryItem?.id || null
|
return state.streamLibraryItem?.id || null
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1119,5 +1119,15 @@
|
||||||
"LabelRating": "Rating",
|
"LabelRating": "Rating",
|
||||||
"LabelStarRating": "{0} stars",
|
"LabelStarRating": "{0} stars",
|
||||||
"LabelNoRating": "No rating",
|
"LabelNoRating": "No rating",
|
||||||
"LabelAverageRating": "Average rating: {0}"
|
"LabelAverageRating": "Average rating: {0}",
|
||||||
|
"HeaderStyling": "Styling",
|
||||||
|
"HeaderStylingColors": "Colors",
|
||||||
|
"HeaderStylingPreview": "Preview",
|
||||||
|
"LabelBackgroundColor": "Background Color",
|
||||||
|
"LabelErrorColor": "Error Color",
|
||||||
|
"LabelInfoColor": "Info Color",
|
||||||
|
"LabelPrimaryColor": "Primary Color",
|
||||||
|
"LabelPrimaryDarkColor": "Primary Dark Color",
|
||||||
|
"LabelSuccessColor": "Success Color",
|
||||||
|
"LabelWarningColor": "Warning Color"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,25 @@
|
||||||
{
|
{
|
||||||
|
"HeaderServerStyling": "Server Styling",
|
||||||
|
"DescriptionServerStyling": "Customize server appearance",
|
||||||
|
"HeaderSettings": "Settings",
|
||||||
|
"HeaderLibraries": "Libraries",
|
||||||
|
"HeaderUsers": "Users",
|
||||||
|
"HeaderListeningSessions": "Listening Sessions",
|
||||||
|
"HeaderBackups": "Backups",
|
||||||
|
"HeaderLogs": "Logs",
|
||||||
|
"HeaderNotifications": "Notifications",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderItemMetadataUtils": "Item Metadata Utils",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
|
"HeaderAuthentication": "Authentication",
|
||||||
|
"HeaderLibraryStats": "Library Stats",
|
||||||
|
"HeaderYourStats": "Your Stats",
|
||||||
|
"DescriptionAuthentication": "Configure authentication methods and settings",
|
||||||
|
"DescriptionLibraries": "Manage your libraries",
|
||||||
|
"DescriptionBackups": "Configure server backups",
|
||||||
|
"DescriptionEmail": "Configure email settings",
|
||||||
|
"DescriptionNotifications": "Configure notification settings",
|
||||||
|
"DescriptionServerLogs": "View server logs",
|
||||||
"LabelSelectAll": "Select All",
|
"LabelSelectAll": "Select All",
|
||||||
"LabelComments": "Comments",
|
"LabelComments": "Comments",
|
||||||
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
|
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
|
||||||
|
|
|
||||||
25
readme.md
25
readme.md
|
|
@ -450,3 +450,28 @@ If you are using VSCode, this project includes a couple of pre-defined targets t
|
||||||
# How to Support
|
# How to Support
|
||||||
|
|
||||||
[See the incomplete "How to Support" page](https://www.audiobookshelf.org/support)
|
[See the incomplete "How to Support" page](https://www.audiobookshelf.org/support)
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
|
### Killing Running Processes
|
||||||
|
When developing locally, you might need to kill running instances of the client or server. Here are some helpful commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Kill process on port 3000 (client)
|
||||||
|
sudo kill $(lsof -t -i:3000)
|
||||||
|
|
||||||
|
# Kill process on port 3333 (server)
|
||||||
|
sudo kill $(lsof -t -i:3333)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also find and kill processes manually:
|
||||||
|
```bash
|
||||||
|
# List all Node.js processes
|
||||||
|
ps aux | grep node
|
||||||
|
|
||||||
|
# Kill processes by PID
|
||||||
|
kill <PID>
|
||||||
|
|
||||||
|
# Force kill if process won't exit
|
||||||
|
kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@ class ServerSettings {
|
||||||
this.id = 'server-settings'
|
this.id = 'server-settings'
|
||||||
this.tokenSecret = null
|
this.tokenSecret = null
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
this.styling = {
|
||||||
|
primary: '#1e293b',
|
||||||
|
primaryDark: '#0f172a',
|
||||||
|
success: '#22c55e',
|
||||||
|
warning: '#f59e0b',
|
||||||
|
error: '#ef4444',
|
||||||
|
info: '#3b82f6',
|
||||||
|
background: '#111827'
|
||||||
|
}
|
||||||
|
|
||||||
// Scanner
|
// Scanner
|
||||||
this.scannerParseSubtitle = false
|
this.scannerParseSubtitle = false
|
||||||
this.scannerFindCovers = false
|
this.scannerFindCovers = false
|
||||||
|
|
@ -88,6 +99,15 @@ class ServerSettings {
|
||||||
|
|
||||||
construct(settings) {
|
construct(settings) {
|
||||||
this.tokenSecret = settings.tokenSecret
|
this.tokenSecret = settings.tokenSecret
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
if (settings.styling) {
|
||||||
|
this.styling = {
|
||||||
|
...this.styling,
|
||||||
|
...settings.styling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.scannerFindCovers = !!settings.scannerFindCovers
|
this.scannerFindCovers = !!settings.scannerFindCovers
|
||||||
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
|
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
|
||||||
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
||||||
|
|
@ -204,6 +224,7 @@ class ServerSettings {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
tokenSecret: this.tokenSecret, // Do not return to client
|
tokenSecret: this.tokenSecret, // Do not return to client
|
||||||
|
styling: { ...this.styling },
|
||||||
scannerFindCovers: this.scannerFindCovers,
|
scannerFindCovers: this.scannerFindCovers,
|
||||||
scannerCoverProvider: this.scannerCoverProvider,
|
scannerCoverProvider: this.scannerCoverProvider,
|
||||||
scannerParseSubtitle: this.scannerParseSubtitle,
|
scannerParseSubtitle: this.scannerParseSubtitle,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue