mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Notification edit/delete and UI updates #996
This commit is contained in:
		
							parent
							
								
									37a3fdb606
								
							
						
					
					
						commit
						8e8046541e
					
				
							
								
								
									
										118
									
								
								client/components/cards/NotificationCard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								client/components/cards/NotificationCard.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="w-full border border-white border-opacity-10 rounded-xl p-4" :class="notification.enabled ? 'bg-primary bg-opacity-25' : 'bg-error bg-opacity-5'">
 | 
			
		||||
    <div class="flex items-center">
 | 
			
		||||
      <p class="text-lg font-semibold">{{ notification.eventName }}</p>
 | 
			
		||||
      <div class="flex-grow" />
 | 
			
		||||
 | 
			
		||||
      <ui-btn v-if="notification.enabled" :loading="sendingTest" small class="mr-2" @click.stop="sendTestClick">Test</ui-btn>
 | 
			
		||||
      <ui-btn v-else :loading="enabling" small color="success" class="mr-2" @click="enableNotification">Enable</ui-btn>
 | 
			
		||||
 | 
			
		||||
      <ui-icon-btn bg-color="warning" :size="7" icon-font-size="1.2rem" icon="edit" class="mr-2" @click="editNotification" />
 | 
			
		||||
      <ui-icon-btn bg-color="error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotificationClick" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="pt-4">
 | 
			
		||||
      <p class="text-gray-300 text-sm">{{ notification.urls.join(', ') }}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    notification: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default: () => {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      sendingTest: false,
 | 
			
		||||
      enabling: false,
 | 
			
		||||
      deleting: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    sendTestClick() {
 | 
			
		||||
      const payload = {
 | 
			
		||||
        message: `Send a test notification to event ${this.notification.eventName}?`,
 | 
			
		||||
        callback: (confirmed) => {
 | 
			
		||||
          if (confirmed) {
 | 
			
		||||
            this.sendTest()
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        type: 'yesNo'
 | 
			
		||||
      }
 | 
			
		||||
      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
			
		||||
    },
 | 
			
		||||
    sendTest() {
 | 
			
		||||
      this.sendingTest = true
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$get(`/api/notifications/${this.notification.id}/test`)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          this.$toast.success('Triggered test notification')
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error)
 | 
			
		||||
          const errorMsg = error.response ? error.response.data : null
 | 
			
		||||
          this.$toast.error(`Failed: ${errorMsg}` || 'Failed to trigger test notification')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.sendingTest = false
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    enableNotification() {
 | 
			
		||||
      this.enabling = true
 | 
			
		||||
      const payload = {
 | 
			
		||||
        id: this.notification.id,
 | 
			
		||||
        enabled: true
 | 
			
		||||
      }
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$patch(`/api/notifications/${this.notification.id}`, payload)
 | 
			
		||||
        .then((updatedSettings) => {
 | 
			
		||||
          this.$emit('update', updatedSettings)
 | 
			
		||||
          this.$toast.success('Notification enabled')
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed to update notification', error)
 | 
			
		||||
          this.$toast.error('Failed to update notification')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.enabling = false
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    deleteNotificationClick() {
 | 
			
		||||
      const payload = {
 | 
			
		||||
        message: `Are you sure you want to delete this notification?`,
 | 
			
		||||
        callback: (confirmed) => {
 | 
			
		||||
          if (confirmed) {
 | 
			
		||||
            this.deleteNotification()
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        type: 'yesNo'
 | 
			
		||||
      }
 | 
			
		||||
      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
			
		||||
    },
 | 
			
		||||
    deleteNotification() {
 | 
			
		||||
      this.deleting = true
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$delete(`/api/notifications/${this.notification.id}`)
 | 
			
		||||
        .then((updatedSettings) => {
 | 
			
		||||
          this.$emit('update', updatedSettings)
 | 
			
		||||
          this.$toast.success('Deleted notification')
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error)
 | 
			
		||||
          this.$toast.error('Failed to delete notification')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.deleting = false
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    editNotification() {
 | 
			
		||||
      this.$emit('edit', this.notification)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {}
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@ -201,7 +201,28 @@ export default {
 | 
			
		||||
        this.submitUpdate()
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    submitUpdate() {},
 | 
			
		||||
    submitUpdate() {
 | 
			
		||||
      this.processing = true
 | 
			
		||||
 | 
			
		||||
      const payload = {
 | 
			
		||||
        ...this.newNotification
 | 
			
		||||
      }
 | 
			
		||||
      console.log('Sending update notification', payload)
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$patch(`/api/notifications/${payload.id}`, payload)
 | 
			
		||||
        .then((updatedSettings) => {
 | 
			
		||||
          this.$emit('update', updatedSettings)
 | 
			
		||||
          this.$toast.success('Notification updated')
 | 
			
		||||
          this.show = false
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed to update notification', error)
 | 
			
		||||
          this.$toast.error('Failed to update notification')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.processing = false
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    submitCreate() {
 | 
			
		||||
      this.processing = true
 | 
			
		||||
 | 
			
		||||
@ -211,8 +232,10 @@ export default {
 | 
			
		||||
      console.log('Sending create notification', payload)
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$post('/api/notifications', payload)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
        .then((updatedSettings) => {
 | 
			
		||||
          this.$emit('update', updatedSettings)
 | 
			
		||||
          this.$toast.success('Notification created')
 | 
			
		||||
          this.show = false
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed to create notification', error)
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
 | 
			
		||||
    <div ref="content" class="relative text-white" :style="{ height: modalHeight, width: modalWidth }" v-click-outside="clickedOutside">
 | 
			
		||||
      <div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
 | 
			
		||||
        <p class="text-base mb-8 mt-2 px-1">{{ message }}</p>
 | 
			
		||||
        <p class="text-lg mb-8 mt-2 px-1">{{ message }}</p>
 | 
			
		||||
        <div class="flex px-1 items-center">
 | 
			
		||||
          <ui-btn v-if="isYesNo" color="primary" @click="nevermind">Cancel</ui-btn>
 | 
			
		||||
          <div class="flex-grow" />
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ export default {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      e.preventDefault()
 | 
			
		||||
      this.$emit('click')
 | 
			
		||||
      this.$emit('click', e)
 | 
			
		||||
      e.stopPropagation()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -5,9 +5,9 @@
 | 
			
		||||
      <p class="mb-6">Insert some text here describing this feature</p>
 | 
			
		||||
 | 
			
		||||
      <form @submit.prevent="submitForm">
 | 
			
		||||
        <ui-text-input-with-label v-model="appriseApiUrl" label="Apprise API Url" />
 | 
			
		||||
        <ui-text-input-with-label ref="apiUrlInput" v-model="appriseApiUrl" :disabled="savingSettings" label="Apprise API Url" />
 | 
			
		||||
        <div class="flex items-center justify-end pt-4">
 | 
			
		||||
          <ui-btn type="submit">Save</ui-btn>
 | 
			
		||||
          <ui-btn :loading="savingSettings" type="submit">Save</ui-btn>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
 | 
			
		||||
@ -22,23 +22,11 @@
 | 
			
		||||
        <p class="text-lg text-gray-200">No notifications</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <template v-for="notification in notifications">
 | 
			
		||||
        <div :key="notification.id" class="w-full bg-primary rounded-xl p-4">
 | 
			
		||||
          <div class="flex items-center">
 | 
			
		||||
            <p class="text-lg font-semibold">{{ notification.eventName }}</p>
 | 
			
		||||
            <div class="flex-grow" />
 | 
			
		||||
 | 
			
		||||
            <ui-btn :loading="sendingTest" small class="mr-2" @click="sendTest(notification)">Test</ui-btn>
 | 
			
		||||
 | 
			
		||||
            <ui-icon-btn bg-color="error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotification(notification)" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="pt-4">
 | 
			
		||||
            <p class="text-gray-300">{{ notification.urls.join(', ') }}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <cards-notification-card :key="notification.id" :notification="notification" @update="updateSettings" @edit="editNotification" />
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <modals-notification-edit-modal v-model="showEditModal" :notification="selectedNotification" :notification-data="notificationData" />
 | 
			
		||||
    <modals-notification-edit-modal v-model="showEditModal" :notification="selectedNotification" :notification-data="notificationData" @update="updateSettings" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -47,6 +35,7 @@ export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      loading: false,
 | 
			
		||||
      savingSettings: false,
 | 
			
		||||
      appriseApiUrl: null,
 | 
			
		||||
      notifications: [],
 | 
			
		||||
      notificationSettings: null,
 | 
			
		||||
@ -58,33 +47,13 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  computed: {},
 | 
			
		||||
  methods: {
 | 
			
		||||
    deleteNotification(notification) {
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$delete(`/api/notifications/${notification.id}`)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          this.$toast.success('Deleted notification')
 | 
			
		||||
          this.notificationSettings.notifications = this.notificationSettings.notifications.filter((n) => n.id !== notification.id)
 | 
			
		||||
          this.notifications = this.notificationSettings.notifications
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error)
 | 
			
		||||
          this.$toast.error('Failed to delete notification')
 | 
			
		||||
        })
 | 
			
		||||
    updateSettings(settings) {
 | 
			
		||||
      this.notificationSettings = settings
 | 
			
		||||
      this.notifications = settings.notifications
 | 
			
		||||
    },
 | 
			
		||||
    sendTest(notification) {
 | 
			
		||||
      this.sendingTest = true
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$get(`/api/notifications/${notification.id}/test`)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          this.$toast.success('Triggered test notification')
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error)
 | 
			
		||||
          this.$toast.error('Failed to trigger test notification')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.sendingTest = false
 | 
			
		||||
        })
 | 
			
		||||
    editNotification(notification) {
 | 
			
		||||
      this.selectedNotification = notification
 | 
			
		||||
      this.showEditModal = true
 | 
			
		||||
    },
 | 
			
		||||
    clickCreate() {
 | 
			
		||||
      this.selectedNotification = null
 | 
			
		||||
@ -92,15 +61,20 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    submitForm() {
 | 
			
		||||
      if (this.notificationSettings && this.notificationSettings.appriseApiUrl == this.appriseApiUrl) {
 | 
			
		||||
        this.$toast.info('No update necessary')
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.$refs.apiUrlInput) {
 | 
			
		||||
        this.$refs.apiUrlInput.blur()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // TODO: Validate apprise api url
 | 
			
		||||
 | 
			
		||||
      const updatePayload = {
 | 
			
		||||
        appriseApiUrl: this.appriseApiUrl || null
 | 
			
		||||
      }
 | 
			
		||||
      this.loading = true
 | 
			
		||||
      this.savingSettings = true
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$patch('/api/notifications', updatePayload)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
@ -111,7 +85,7 @@ export default {
 | 
			
		||||
          this.$toast.error('Failed to update notification settings')
 | 
			
		||||
        })
 | 
			
		||||
        .finally(() => {
 | 
			
		||||
          this.loading = false
 | 
			
		||||
          this.savingSettings = false
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    async init() {
 | 
			
		||||
@ -127,7 +101,6 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
      this.notificationSettings = notificationResponse.settings
 | 
			
		||||
      this.notificationData = notificationResponse.data
 | 
			
		||||
      console.log('Notification response', notificationResponse)
 | 
			
		||||
      this.appriseApiUrl = this.notificationSettings.appriseApiUrl
 | 
			
		||||
      this.notifications = this.notificationSettings.notifications || []
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -18,36 +18,37 @@ class NotificationController {
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getData(req, res) {
 | 
			
		||||
    res.json(this.notificationManager.getData())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createNotification(req, res) {
 | 
			
		||||
    const success = this.db.notificationSettings.createNotification(req.body)
 | 
			
		||||
 | 
			
		||||
    if (success) {
 | 
			
		||||
      await this.db.updateEntity('settings', this.db.notificationSettings)
 | 
			
		||||
    }
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getData(req, res) {
 | 
			
		||||
    res.json(this.notificationManager.getData())
 | 
			
		||||
    res.json(this.db.notificationSettings)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async deleteNotification(req, res) {
 | 
			
		||||
    if (this.db.notificationSettings.removeNotification(req.notification.id)) {
 | 
			
		||||
      await this.db.updateEntity('settings', this.db.notificationSettings)
 | 
			
		||||
    }
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
    res.json(this.db.notificationSettings)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateNotification(req, res) {
 | 
			
		||||
    const success = this.db.notificationSettings.updateNotification(req.body)
 | 
			
		||||
 | 
			
		||||
    console.log('Update notification', success, req.body)
 | 
			
		||||
    if (success) {
 | 
			
		||||
      await this.db.updateEntity('settings', this.db.notificationSettings)
 | 
			
		||||
    }
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
    res.json(this.db.notificationSettings)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sendNotificationTest(req, res) {
 | 
			
		||||
    if (!this.db.notificationSettings.isUsable) return res.status(500).send('Apprise is not configured')
 | 
			
		||||
    this.notificationManager.onTest()
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ class Notification {
 | 
			
		||||
    const keysToUpdate = ['libraryId', 'eventName', 'urls', 'titleTemplate', 'bodyTemplate', 'enabled', 'type']
 | 
			
		||||
    var hasUpdated = false
 | 
			
		||||
    for (const key of keysToUpdate) {
 | 
			
		||||
      if (payload[key]) {
 | 
			
		||||
      if (payload[key] !== undefined) {
 | 
			
		||||
        if (key === 'urls') {
 | 
			
		||||
          if (payload[key].join(',') !== this.urls.join(',')) {
 | 
			
		||||
            this.urls = [...payload[key]]
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
const Logger = require('../../Logger')
 | 
			
		||||
const Notification = require('../Notification')
 | 
			
		||||
 | 
			
		||||
class NotificationSettings {
 | 
			
		||||
@ -58,7 +59,7 @@ class NotificationSettings {
 | 
			
		||||
 | 
			
		||||
  createNotification(payload) {
 | 
			
		||||
    if (!payload) return false
 | 
			
		||||
    // TODO: validate
 | 
			
		||||
    if (!payload.eventName || !payload.urls.length) return false
 | 
			
		||||
 | 
			
		||||
    const notification = new Notification()
 | 
			
		||||
    notification.setData(payload)
 | 
			
		||||
@ -69,7 +70,10 @@ class NotificationSettings {
 | 
			
		||||
  updateNotification(payload) {
 | 
			
		||||
    if (!payload) return false
 | 
			
		||||
    const notification = this.notifications.find(n => n.id === payload.id)
 | 
			
		||||
    if (!notification) return false
 | 
			
		||||
    if (!notification) {
 | 
			
		||||
      Logger.error(`[NotificationSettings] updateNotification: Notification not found ${payload.id}`)
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return notification.update(payload)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user