2023-05-30 00:38:38 +02:00
|
|
|
<template>
|
|
|
|
<div>
|
|
|
|
<app-settings-content :header-text="$strings.HeaderEmailSettings" :description="''">
|
2024-05-05 19:07:35 +02:00
|
|
|
<template #header-items>
|
|
|
|
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
|
|
|
<a href="https://www.audiobookshelf.org/guides/send_to_ereader" target="_blank" class="inline-flex">
|
2024-07-08 18:36:37 +02:00
|
|
|
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
2024-05-05 19:07:35 +02:00
|
|
|
</a>
|
|
|
|
</ui-tooltip>
|
|
|
|
</template>
|
|
|
|
|
2023-05-30 00:38:38 +02:00
|
|
|
<form @submit.prevent="submitForm">
|
|
|
|
<div class="flex items-center -mx-1 mb-2">
|
|
|
|
<div class="w-full md:w-3/4 px-1">
|
|
|
|
<ui-text-input-with-label ref="hostInput" v-model="newSettings.host" :disabled="savingSettings" :label="$strings.LabelHost" />
|
|
|
|
</div>
|
|
|
|
<div class="w-full md:w-1/4 px-1">
|
|
|
|
<ui-text-input-with-label ref="portInput" v-model="newSettings.port" type="number" :disabled="savingSettings" :label="$strings.LabelPort" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex items-center mb-2 py-3">
|
2024-05-29 23:59:43 +02:00
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<!-- secure toggle -->
|
|
|
|
<div class="flex items-center">
|
|
|
|
<ui-toggle-switch labeledBy="email-settings-secure" v-model="newSettings.secure" :disabled="savingSettings" />
|
|
|
|
<ui-tooltip :text="$strings.LabelEmailSettingsSecureHelp">
|
|
|
|
<div class="pl-4 flex items-center">
|
|
|
|
<span id="email-settings-secure">{{ $strings.LabelEmailSettingsSecure }}</span>
|
2024-07-08 18:36:37 +02:00
|
|
|
<span class="material-symbols text-lg pl-1">info</span>
|
2024-05-29 23:59:43 +02:00
|
|
|
</div>
|
|
|
|
</ui-tooltip>
|
2023-05-30 00:38:38 +02:00
|
|
|
</div>
|
2024-05-29 23:59:43 +02:00
|
|
|
</div>
|
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<!-- reject unauthorized toggle -->
|
|
|
|
<div class="flex items-center">
|
|
|
|
<ui-toggle-switch labeledBy="email-settings-reject-unauthorized" v-model="newSettings.rejectUnauthorized" :disabled="savingSettings" />
|
|
|
|
<ui-tooltip :text="$strings.LabelEmailSettingsRejectUnauthorizedHelp">
|
|
|
|
<div class="pl-4 flex items-center">
|
|
|
|
<span id="email-settings-reject-unauthorized">{{ $strings.LabelEmailSettingsRejectUnauthorized }}</span>
|
2024-07-08 18:36:37 +02:00
|
|
|
<span class="material-symbols text-lg pl-1">info</span>
|
2024-05-29 23:59:43 +02:00
|
|
|
</div>
|
|
|
|
</ui-tooltip>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-05-30 00:38:38 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex items-center -mx-1 mb-2">
|
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<ui-text-input-with-label ref="userInput" v-model="newSettings.user" :disabled="savingSettings" :label="$strings.LabelUsername" />
|
|
|
|
</div>
|
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<ui-text-input-with-label ref="passInput" v-model="newSettings.pass" type="password" :disabled="savingSettings" :label="$strings.LabelPassword" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex items-center -mx-1 mb-2">
|
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<ui-text-input-with-label ref="fromInput" v-model="newSettings.fromAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsFromAddress" />
|
|
|
|
</div>
|
2023-06-12 06:53:51 +02:00
|
|
|
<div class="w-full md:w-1/2 px-1">
|
|
|
|
<ui-text-input-with-label ref="testInput" v-model="newSettings.testAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsTestAddress" />
|
|
|
|
</div>
|
2023-05-30 00:38:38 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between pt-4">
|
2023-06-13 00:12:52 +02:00
|
|
|
<ui-btn v-if="hasUpdates" :disabled="savingSettings" type="button" @click="resetChanges">{{ $strings.ButtonReset }}</ui-btn>
|
|
|
|
<ui-btn v-else :loading="sendingTest" :disabled="savingSettings || !newSettings.host" type="button" @click="sendTestClick">{{ $strings.ButtonTest }}</ui-btn>
|
2023-05-30 00:38:38 +02:00
|
|
|
<ui-btn :loading="savingSettings" :disabled="!hasUpdates" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
|
|
|
|
<div v-show="loading" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-25 flex items-center justify-center">
|
|
|
|
<ui-loading-indicator />
|
|
|
|
</div>
|
|
|
|
</app-settings-content>
|
|
|
|
|
2024-05-25 20:19:47 +02:00
|
|
|
<app-settings-content :header-text="$strings.HeaderEreaderDevices" :description="$strings.MessageEreaderDevices">
|
2023-10-28 20:27:53 +02:00
|
|
|
<template #header-items>
|
|
|
|
<div class="flex-grow" />
|
|
|
|
|
|
|
|
<ui-btn color="primary" small @click="addNewDeviceClick">{{ $strings.ButtonAddDevice }}</ui-btn>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<table v-if="existingEReaderDevices.length" class="tracksTable mt-4">
|
2023-05-30 00:38:38 +02:00
|
|
|
<tr>
|
|
|
|
<th class="text-left">{{ $strings.LabelName }}</th>
|
|
|
|
<th class="text-left">{{ $strings.LabelEmail }}</th>
|
2024-05-25 20:12:18 +02:00
|
|
|
<th class="text-left">{{ $strings.LabelAccessibleBy }}</th>
|
2023-05-30 00:38:38 +02:00
|
|
|
<th class="w-40"></th>
|
|
|
|
</tr>
|
|
|
|
<tr v-for="device in existingEReaderDevices" :key="device.name">
|
|
|
|
<td>
|
|
|
|
<p class="text-sm md:text-base text-gray-100">{{ device.name }}</p>
|
|
|
|
</td>
|
|
|
|
<td class="text-left">
|
|
|
|
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
|
|
|
|
</td>
|
2024-05-25 20:12:18 +02:00
|
|
|
<td class="text-left">
|
|
|
|
<p class="text-sm md:text-base text-gray-100">{{ getAccessibleBy(device) }}</p>
|
|
|
|
</td>
|
2023-05-30 00:38:38 +02:00
|
|
|
<td class="w-40">
|
|
|
|
<div class="flex justify-end items-center h-10">
|
|
|
|
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name" class="mx-1" @click="editDeviceClick(device)" />
|
|
|
|
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name" @click="deleteDeviceClick(device)" />
|
|
|
|
</div>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
2024-05-25 21:44:34 +02:00
|
|
|
<div v-else-if="!loading" class="text-center py-4">
|
2023-05-30 00:38:38 +02:00
|
|
|
<p class="text-lg text-gray-100">No Devices</p>
|
|
|
|
</div>
|
|
|
|
</app-settings-content>
|
|
|
|
|
2024-05-25 21:44:34 +02:00
|
|
|
<modals-emails-e-reader-device-modal v-model="showEReaderDeviceModal" :users="users" :existing-devices="existingEReaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" :loadUsers="loadUsers" />
|
2023-05-30 00:38:38 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
export default {
|
2023-06-16 00:41:27 +02:00
|
|
|
asyncData({ store, redirect }) {
|
|
|
|
if (!store.getters['user/getIsAdminOrUp']) {
|
|
|
|
redirect('/')
|
|
|
|
}
|
|
|
|
},
|
2023-05-30 00:38:38 +02:00
|
|
|
data() {
|
|
|
|
return {
|
2024-05-25 20:12:18 +02:00
|
|
|
users: [],
|
2023-05-30 00:38:38 +02:00
|
|
|
loading: false,
|
|
|
|
savingSettings: false,
|
|
|
|
sendingTest: false,
|
|
|
|
deletingDeviceName: null,
|
|
|
|
settings: null,
|
|
|
|
newSettings: {
|
|
|
|
host: null,
|
|
|
|
port: 465,
|
|
|
|
secure: true,
|
2024-05-29 23:59:43 +02:00
|
|
|
rejectUnauthorized: true,
|
2023-05-30 00:38:38 +02:00
|
|
|
user: null,
|
|
|
|
pass: null,
|
2023-06-12 06:53:51 +02:00
|
|
|
testAddress: null,
|
2023-05-30 00:38:38 +02:00
|
|
|
fromAddress: null
|
|
|
|
},
|
|
|
|
newEReaderDevice: {
|
|
|
|
name: '',
|
|
|
|
email: ''
|
|
|
|
},
|
|
|
|
selectedEReaderDevice: null,
|
|
|
|
showEReaderDeviceModal: false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
hasUpdates() {
|
|
|
|
if (!this.settings) return true
|
|
|
|
for (const key in this.newSettings) {
|
|
|
|
if (key === 'ereaderDevices') continue
|
|
|
|
if (this.newSettings[key] !== this.settings[key]) return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
existingEReaderDevices() {
|
|
|
|
return this.settings?.ereaderDevices || []
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
2023-06-13 00:12:52 +02:00
|
|
|
resetChanges() {
|
|
|
|
this.newSettings = {
|
|
|
|
...this.settings
|
|
|
|
}
|
|
|
|
},
|
2024-05-25 20:12:18 +02:00
|
|
|
async loadUsers() {
|
2024-05-25 21:44:34 +02:00
|
|
|
if (this.users.length) return
|
2024-05-25 20:12:18 +02:00
|
|
|
this.users = await this.$axios
|
|
|
|
.$get('/api/users')
|
|
|
|
.then((res) => {
|
|
|
|
return res.users.sort((a, b) => {
|
|
|
|
return a.createdAt - b.createdAt
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('Failed', error)
|
2024-05-25 21:44:34 +02:00
|
|
|
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
2024-05-25 20:12:18 +02:00
|
|
|
return []
|
|
|
|
})
|
|
|
|
},
|
|
|
|
getAccessibleBy(device) {
|
|
|
|
const user = device.availabilityOption
|
|
|
|
if (user === 'userOrUp') return 'Users (excluding Guests)'
|
|
|
|
if (user === 'guestOrUp') return 'Users (including Guests)'
|
|
|
|
if (user === 'specificUsers') {
|
|
|
|
return device.users.map((id) => this.users.find((u) => u.id === id)?.username).join(', ')
|
|
|
|
}
|
|
|
|
return 'Admins Only'
|
|
|
|
},
|
2023-05-30 00:38:38 +02:00
|
|
|
editDeviceClick(device) {
|
|
|
|
this.selectedEReaderDevice = device
|
|
|
|
this.showEReaderDeviceModal = true
|
|
|
|
},
|
|
|
|
deleteDeviceClick(device) {
|
|
|
|
const payload = {
|
|
|
|
message: `Are you sure you want to delete e-reader device "${device.name}"?`,
|
|
|
|
callback: (confirmed) => {
|
|
|
|
if (confirmed) {
|
|
|
|
this.deleteDevice(device)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
type: 'yesNo'
|
|
|
|
}
|
|
|
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
|
|
|
},
|
|
|
|
deleteDevice(device) {
|
|
|
|
const payload = {
|
|
|
|
ereaderDevices: this.existingEReaderDevices.filter((d) => d.name !== device.name)
|
|
|
|
}
|
|
|
|
this.deletingDeviceName = device.name
|
|
|
|
this.$axios
|
2023-07-05 01:15:43 +02:00
|
|
|
.$post(`/api/emails/ereader-devices`, payload)
|
2023-05-30 00:38:38 +02:00
|
|
|
.then((data) => {
|
|
|
|
this.ereaderDevicesUpdated(data.ereaderDevices)
|
|
|
|
this.$toast.success('Device deleted')
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('Failed to delete device', error)
|
|
|
|
this.$toast.error('Failed to delete device')
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.deletingDeviceName = null
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ereaderDevicesUpdated(ereaderDevices) {
|
|
|
|
this.settings.ereaderDevices = ereaderDevices
|
|
|
|
this.newSettings.ereaderDevices = ereaderDevices.map((d) => ({ ...d }))
|
2024-05-25 21:44:34 +02:00
|
|
|
|
|
|
|
// Load users if a device has availability set to specific users
|
|
|
|
if (ereaderDevices.some((device) => device.availabilityOption === 'specificUsers')) {
|
|
|
|
this.loadUsers()
|
|
|
|
}
|
2023-05-30 00:38:38 +02:00
|
|
|
},
|
|
|
|
addNewDeviceClick() {
|
|
|
|
this.selectedEReaderDevice = null
|
|
|
|
this.showEReaderDeviceModal = true
|
|
|
|
},
|
|
|
|
sendTestClick() {
|
|
|
|
this.sendingTest = true
|
|
|
|
this.$axios
|
|
|
|
.$post('/api/emails/test')
|
|
|
|
.then(() => {
|
|
|
|
this.$toast.success('Test Email Sent')
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('Failed to send test email', error)
|
|
|
|
const errorMsg = error.response.data || 'Failed to send test email'
|
|
|
|
this.$toast.error(errorMsg)
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.sendingTest = false
|
|
|
|
})
|
|
|
|
},
|
|
|
|
validateForm() {
|
|
|
|
for (const ref of [this.$refs.hostInput, this.$refs.portInput, this.$refs.userInput, this.$refs.passInput, this.$refs.fromInput]) {
|
|
|
|
if (ref?.blur) ref.blur()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.newSettings.port) {
|
|
|
|
this.newSettings.port = Number(this.newSettings.port)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
submitForm() {
|
|
|
|
if (!this.validateForm()) return
|
|
|
|
|
|
|
|
const updatePayload = {
|
|
|
|
host: this.newSettings.host,
|
|
|
|
port: this.newSettings.port,
|
|
|
|
secure: this.newSettings.secure,
|
2024-05-29 23:59:43 +02:00
|
|
|
rejectUnauthorized: this.newSettings.rejectUnauthorized,
|
2023-05-30 00:38:38 +02:00
|
|
|
user: this.newSettings.user,
|
|
|
|
pass: this.newSettings.pass,
|
2023-06-12 06:53:51 +02:00
|
|
|
testAddress: this.newSettings.testAddress,
|
2023-05-30 00:38:38 +02:00
|
|
|
fromAddress: this.newSettings.fromAddress
|
|
|
|
}
|
|
|
|
this.savingSettings = true
|
|
|
|
this.$axios
|
|
|
|
.$patch('/api/emails/settings', updatePayload)
|
|
|
|
.then((data) => {
|
|
|
|
this.settings = data.settings
|
|
|
|
this.newSettings = {
|
|
|
|
...data.settings
|
|
|
|
}
|
|
|
|
this.$toast.success('Email settings updated')
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('Failed to update email settings', error)
|
|
|
|
this.$toast.error('Failed to update email settings')
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.savingSettings = false
|
|
|
|
})
|
|
|
|
},
|
|
|
|
init() {
|
|
|
|
this.loading = true
|
|
|
|
|
|
|
|
this.$axios
|
|
|
|
.$get(`/api/emails/settings`)
|
2024-05-25 21:44:34 +02:00
|
|
|
.then(async (data) => {
|
|
|
|
// Load users if a device has availability set to specific users
|
|
|
|
if (data.settings.ereaderDevices.some((device) => device.availabilityOption === 'specificUsers')) {
|
|
|
|
await this.loadUsers()
|
|
|
|
}
|
|
|
|
|
2023-05-30 00:38:38 +02:00
|
|
|
this.settings = data.settings
|
|
|
|
this.newSettings = {
|
|
|
|
...this.settings
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('Failed to get email settings', error)
|
2024-05-13 23:58:41 +02:00
|
|
|
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
2023-05-30 00:38:38 +02:00
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.loading = false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.init()
|
|
|
|
},
|
|
|
|
beforeDestroy() {}
|
|
|
|
}
|
2024-05-05 19:07:35 +02:00
|
|
|
</script>
|