Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
resources
/
scripts
/
admin
/
views
/
settings
:
UpdateAppSetting-20250318162429.vue
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<template> <BaseSettingCard :title="$t('settings.update_app.title')" :description="$t('settings.update_app.description')" > <div class="pb-8 ml-0"> <label class="text-sm not-italic font-medium input-label"> {{ $t('settings.update_app.current_version') }} </label> <div class="w-full border-b-2 border-gray-100 border-solid pb-4"> <div class=" box-border inline-block w-auto p-3 my-2 text-sm text-gray-600 bg-gray-200 border border-gray-200 border-solid rounded-md version " > {{ currentVersion }} </div> </div> <div class="w-full pt-4"> <BaseCheckbox v-model="insiderChannel" :label="$t('settings.update_app.insider_consent')"/> </div> <BaseButton :loading="isCheckingforUpdate" :disabled="isCheckingforUpdate || isUpdating" variant="primary-outline" class="mt-6" @click="checkUpdate" > {{ $t('settings.update_app.check_update') }} </BaseButton> <BaseDivider v-if="isUpdateAvailable" class="mt-6 mb-4" /> <div v-show="!isUpdating" v-if="isUpdateAvailable" class="mt-4 content"> <BaseHeading type="heading-title" class="mb-2"> {{ $t('settings.update_app.avail_update') }} </BaseHeading> <div class="rounded-md bg-primary-50 p-4 mb-3"> <div class="flex"> <div class="shrink-0"> <BaseIcon name="InformationCircleIcon" class="h-5 w-5 text-primary-400" aria-hidden="true" /> </div> <div class="ml-3"> <h3 class="text-sm font-medium text-primary-800"> {{ $t('general.note') }} </h3> <div class="mt-2 text-sm text-primary-700"> <p> {{ $t('settings.update_app.update_warning') }} </p> </div> </div> </div> </div> <label class="text-sm not-italic font-medium input-label"> {{ $t('settings.update_app.next_version') }} </label> <br /> <div class=" box-border inline-block w-auto p-3 my-2 text-sm text-gray-600 bg-gray-200 border border-gray-200 border-solid rounded-md version " > {{ updateData.version }} </div> <div class=" pl-5 mt-4 mb-2 text-sm leading-snug text-gray-500 update-description " style="white-space: pre-wrap; max-width: 480px" v-if="description" v-html="description" ></div> <div class=" pl-5 mt-2 mb-8 text-sm leading-snug text-gray-500 update-changelog " style="white-space: pre-wrap; max-width: 480px" v-if="changelog" v-html="changelog" ></div> <label class="text-sm not-italic font-medium input-label"> {{ $t('settings.update_app.requirements') }} </label> <table class="w-1/2 mt-2 border-2 border-gray-200 BaseTable-fixed"> <tr v-for="(ext, i) in requiredExtentions" :key="i" class="p-2 border-2 border-gray-200" > <td width="70%" class="p-2 text-sm truncate"> {{ i }} </td> <td width="30%" class="p-2 text-sm text-right"> <span v-if="ext" class="inline-block w-4 h-4 ml-3 mr-2 bg-green-500 rounded-full" /> <span v-else class="inline-block w-4 h-4 ml-3 mr-2 bg-red-500 rounded-full" /> </td> </tr> </table> <BaseButton class="mt-10" variant="primary" @click="onUpdateApp"> {{ $t('settings.update_app.update') }} </BaseButton> </div> <div v-if="isUpdating" class="relative flex justify-between mt-4 content"> <div> <h6 class="m-0 mb-3 font-medium sw-section-title"> {{ $t('settings.update_app.update_progress') }} </h6> <p class="mb-8 text-sm leading-snug text-gray-500" style="max-width: 480px" > {{ $t('settings.update_app.progress_text') }} </p> </div> <LoadingIcon class="absolute right-0 h-6 m-1 animate-spin text-primary-400" /> </div> <ul v-if="isUpdating" class="w-full p-0 list-none"> <li v-for="step in updateSteps" :key="step.stepUrl" class=" flex justify-between w-full py-3 border-b border-gray-200 border-solid last:border-b-0 " > <p class="m-0 text-sm leading-8">{{ $t(step.translationKey) }}</p> <div class="flex flex-row items-center"> <span v-if="step.time" class="mr-3 text-xs text-gray-500"> {{ step.time }} </span> <span :class="statusClass(step)" class="block py-1 text-sm text-center uppercase rounded-full" style="width: 88px" > {{ getStatus(step) }} </span> </div> </li> </ul> </div> </BaseSettingCard> </template> <script setup> import { useNotificationStore } from '@/scripts/stores/notification' import axios from 'axios' import LoadingIcon from '@/scripts/components/icons/LoadingIcon.vue' import { reactive, ref, onMounted, computed } from 'vue' import { useI18n } from 'vue-i18n' import { handleError } from '@/scripts/helpers/error-handling' import { useCompanyStore } from '@/scripts/admin/stores/company' import { useExchangeRateStore } from '@/scripts/admin/stores/exchange-rate' import { useDialogStore } from '@/scripts/stores/dialog' import BaseCheckbox from "@/scripts/components/base/BaseCheckbox.vue"; const notificationStore = useNotificationStore() const dialogStore = useDialogStore() const { t, tm } = useI18n() const comapnyStore = useCompanyStore() const exchangeRateStore = useExchangeRateStore() let isUpdateAvailable = ref(false) let isCheckingforUpdate = ref(false) let description = ref('') let changelog = ref(''); let currentVersion = ref('') let insiderChannel = ref('') let requiredExtentions = ref(null) let deletedFiles = ref(null) let isUpdating = ref(false) const updateSteps = reactive([ { translationKey: 'settings.update_app.download_zip_file', stepUrl: '/api/v1/update/download', time: null, started: false, completed: false, }, { translationKey: 'settings.update_app.unzipping_package', stepUrl: '/api/v1/update/unzip', time: null, started: false, completed: false, }, { translationKey: 'settings.update_app.copying_files', stepUrl: '/api/v1/update/copy', time: null, started: false, completed: false, }, { translationKey: 'settings.update_app.deleting_files', stepUrl: '/api/v1/update/delete', time: null, started: false, completed: false, }, { translationKey: 'settings.update_app.running_migrations', stepUrl: '/api/v1/update/migrate', time: null, started: false, completed: false, }, { translationKey: 'settings.update_app.finishing_update', stepUrl: '/api/v1/update/finish', time: null, started: false, completed: false, }, ]) const updateData = reactive({ isMinor: Boolean, installed: '', version: '', }) let minPhpVesrion = ref(null) window.addEventListener('beforeunload', (event) => { if (isUpdating.value) { event.returnValue = 'Update is in progress!' } }) // Created axios.get('/api/v1/app/version').then((res) => { currentVersion.value = res.data.version insiderChannel.value = res.data.channel === 'insider' }) // comapnyStore // .fetchCompanySettings(['bulk_exchange_rate_configured']) // .then((res) => { // isExchangeRateUpdated.value = // res.data.bulk_exchange_rate_configured === 'YES' // }) // Comuted props const allowToUpdate = computed(() => { if (requiredExtentions.value !== null) { return Object.keys(requiredExtentions.value).every((k) => { return requiredExtentions.value[k] }) } return true }) function statusClass(step) { const status = getStatus(step) switch (status) { case 'pending': return 'text-primary-800 bg-gray-200' case 'finished': return 'text-teal-500 bg-teal-100' case 'running': return 'text-blue-400 bg-blue-100' case 'error': return 'text-danger bg-red-200' default: return '' } } async function checkUpdate() { try { isCheckingforUpdate.value = true let response = await axios.get('/api/v1/check/update', { params: { channel: insiderChannel ? 'insider' : '' } }); isCheckingforUpdate.value = false if (!response.data.release) { notificationStore.showNotification({ title: 'Info!', type: 'info', message: t('settings.update_app.latest_message'), }) return; } if (response.data) { updateData.isMinor = response.data.is_minor updateData.version = response.data.release.version description.value = response.data.release.description changelog.value = response.data.release.changelog requiredExtentions.value = response.data.release.extensions isUpdateAvailable.value = true minPhpVesrion.value = response.data.release.min_php_version deletedFiles.value = response.data.release.deleted_files } } catch (e) { isUpdateAvailable.value = false isCheckingforUpdate.value = false handleError(e) } } function onUpdateApp() { dialogStore .openDialog({ title: t('general.are_you_sure'), message: t('settings.update_app.update_warning'), yesLabel: t('general.ok'), noLabel: t('general.cancel'), variant: 'danger', hideNoButton: false, size: 'lg', }) .then(async (res) => { if (res) { let path = null if (!allowToUpdate.value) { notificationStore.showNotification({ type: 'error', message: 'Your current configuration does not match the update requirements. Please try again after all the requirements are fulfilled.', }) return true } for (let index = 0; index < updateSteps.length; index++) { let currentStep = updateSteps[index] try { isUpdating.value = true currentStep.started = true let updateParams = { version: updateData.version, installed: currentVersion.value, path: path || null, } let requestResponse = await axios.post( currentStep.stepUrl, updateParams ) currentStep.completed = true if (requestResponse.data && requestResponse.data.path) { path = requestResponse.data.path } // on finish if (currentStep.translationKey == 'settings.update_app.finishing_update') { isUpdating.value = false notificationStore.showNotification({ type: 'success', message: t('settings.update_app.update_success'), }) setTimeout(() => { location.reload() }, 3000) } } catch (error) { currentStep.started = false currentStep.completed = true handleError(error) onUpdateFailed(currentStep.translationKey) return false } } } }) } function onUpdateFailed(translationKey) { let stepName = t(translationKey) if (stepName.value) { onUpdateApp() return } isUpdating.value = false } function getStatus(step) { if (step.started && step.completed) { return 'finished' } else if (step.started && !step.completed) { return 'running' } else if (!step.started && !step.completed) { return 'pending' } else { return 'error' } } </script> <style> .update-changelog ul { list-style: disc!important; margin-left: 30px; } .update-changelog li { margin-bottom: 4px; } </style>