mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-27 19:51:09 -04:00
368 lines
10 KiB
Vue
368 lines
10 KiB
Vue
<template>
|
|
<sw-card variant="setting-card">
|
|
<template slot="header">
|
|
<h6 class="sw-section-title">
|
|
{{ $t('settings.update_app.title') }}
|
|
</h6>
|
|
<p
|
|
class="mt-2 text-sm leading-snug text-gray-500"
|
|
style="max-width: 680px"
|
|
>
|
|
{{ $t('settings.update_app.description') }}
|
|
</p>
|
|
</template>
|
|
|
|
<div class="m-0">
|
|
<label class="text-sm not-italic font-medium input-label">
|
|
{{ $t('settings.update_app.current_version') }}
|
|
</label>
|
|
|
|
<label
|
|
class="box-border flex w-16 p-3 my-2 text-sm text-gray-500 bg-gray-200 border border-gray-200 border-solid rounded-md version"
|
|
>
|
|
{{ currentVersion }}
|
|
</label>
|
|
|
|
<sw-button
|
|
:loading="isCheckingforUpdate"
|
|
:disabled="isCheckingforUpdate || isUpdating"
|
|
variant="primary-outline"
|
|
class="mt-6"
|
|
@click="checkUpdate"
|
|
>
|
|
{{ $t('settings.update_app.check_update') }}
|
|
</sw-button>
|
|
|
|
<sw-divider v-if="isUpdateAvailable" class="mt-2 mb-4" />
|
|
|
|
<div v-show="!isUpdating" v-if="isUpdateAvailable" class="mt-4 content">
|
|
<h6 class="mb-8 sw-section-title">
|
|
{{ $t('settings.update_app.avail_update') }}
|
|
</h6>
|
|
<label class="text-sm not-italic font-medium input-label">
|
|
{{ $t('settings.update_app.next_version') }} </label
|
|
><br />
|
|
<label
|
|
class="box-border flex w-16 p-3 my-2 text-sm text-gray-500 bg-gray-200 border border-gray-200 border-solid rounded-md version"
|
|
>
|
|
{{ updateData.version }}
|
|
</label>
|
|
<div
|
|
class="pl-5 mt-4 mb-8 text-sm leading-snug text-gray-500 update-description"
|
|
style="white-space: pre-wrap; max-width: 480px"
|
|
v-html="description"
|
|
></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 table-fixed">
|
|
<tr
|
|
class="p-2 border-2 border-gray-200"
|
|
v-for="(ext, i) in requiredExtentions"
|
|
:key="i"
|
|
>
|
|
<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 rounded-full bg-success"
|
|
/>
|
|
<span
|
|
v-else
|
|
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-danger"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<sw-button
|
|
size="lg"
|
|
class="mt-10"
|
|
variant="primary"
|
|
@click="onUpdateApp"
|
|
>
|
|
{{ $t('settings.update_app.update') }}
|
|
</sw-button>
|
|
</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>
|
|
<loading-icon
|
|
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
|
|
class="flex justify-between w-full py-3 border-b border-gray-200 border-solid last:border-b-0"
|
|
v-for="step in updateSteps"
|
|
>
|
|
<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="block py-1 text-sm text-center uppercase rounded-full"
|
|
:class="statusClass(step)"
|
|
style="width: 88px"
|
|
>
|
|
{{ getStatus(step) }}
|
|
</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</sw-card>
|
|
</template>
|
|
|
|
<script>
|
|
import LoadingIcon from '../../components/icon/LoadingIcon'
|
|
|
|
export default {
|
|
components: {
|
|
LoadingIcon,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
isShowProgressBar: false,
|
|
isUpdateAvailable: false,
|
|
isUpdating: false,
|
|
isCheckingforUpdate: false,
|
|
progress: 10,
|
|
interval: null,
|
|
description: '',
|
|
currentVersion: '',
|
|
requiredExtentions: null,
|
|
deletedFiles: null,
|
|
updateSteps: [
|
|
{
|
|
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,
|
|
},
|
|
],
|
|
updateData: {
|
|
isMinor: Boolean,
|
|
installed: '',
|
|
version: '',
|
|
},
|
|
requiredExtentions: null,
|
|
minPhpVesrion: null,
|
|
}
|
|
},
|
|
computed: {
|
|
allowToUpdate() {
|
|
if (this.requiredExtentions !== null) {
|
|
return Object.keys(this.requiredExtentions).every((k) => {
|
|
return this.requiredExtentions[k]
|
|
})
|
|
}
|
|
},
|
|
hasUiUpdate() {
|
|
return this.updateData.version != '4.0.0'
|
|
},
|
|
},
|
|
created() {
|
|
window.addEventListener('beforeunload', (event) => {
|
|
if (this.isUpdating) {
|
|
event.returnValue = 'Update is in progress!'
|
|
}
|
|
})
|
|
},
|
|
|
|
mounted() {
|
|
window.axios.get('/api/v1/app/version').then((res) => {
|
|
this.currentVersion = res.data.version
|
|
})
|
|
},
|
|
|
|
methods: {
|
|
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'
|
|
}
|
|
},
|
|
|
|
statusClass(step) {
|
|
const status = this.getStatus(step)
|
|
|
|
switch (status) {
|
|
case 'pending':
|
|
return 'text-primary-800 bg-gray-200'
|
|
break
|
|
case 'finished':
|
|
return 'text-teal-500 bg-teal-100'
|
|
break
|
|
case 'running':
|
|
return 'text-blue-400 bg-blue-100'
|
|
break
|
|
case 'error':
|
|
return 'text-danger bg-red-200'
|
|
break
|
|
}
|
|
},
|
|
|
|
async checkUpdate() {
|
|
try {
|
|
this.isCheckingforUpdate = true
|
|
let response = await window.axios.get('/api/v1/check/update')
|
|
this.isCheckingforUpdate = false
|
|
|
|
if (!response.data.version) {
|
|
window.toastr['info'](this.$t('settings.update_app.latest_message'))
|
|
return
|
|
}
|
|
|
|
if (response.data) {
|
|
this.updateData.isMinor = response.data.is_minor
|
|
this.updateData.version = response.data.version.version
|
|
this.description = response.data.version.description
|
|
this.requiredExtentions = response.data.version.extensions
|
|
this.isUpdateAvailable = true
|
|
this.requiredExtentions = response.data.version.extensions
|
|
this.minPhpVesrion = response.data.version.minimum_php_version
|
|
this.deletedFiles = response.data.version.deleted_files
|
|
}
|
|
} catch (e) {
|
|
this.isUpdateAvailable = false
|
|
this.isCheckingforUpdate = false
|
|
window.toastr['error']('Something went wrong')
|
|
}
|
|
},
|
|
|
|
async onUpdateApp() {
|
|
let path = null
|
|
if (!this.allowToUpdate) {
|
|
window.toastr['error'](
|
|
'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 < this.updateSteps.length; index++) {
|
|
let currentStep = this.updateSteps[index]
|
|
try {
|
|
this.isUpdating = true
|
|
currentStep.started = true
|
|
let updateParams = {
|
|
version: this.updateData.version,
|
|
installed: this.currentVersion,
|
|
deleted_files: this.deletedFiles,
|
|
path: path || null,
|
|
}
|
|
|
|
let requestResponse = await window.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'
|
|
) {
|
|
this.isUpdating = false
|
|
window.toastr['success'](
|
|
this.$t('settings.update_app.update_success')
|
|
)
|
|
|
|
setTimeout(() => {
|
|
location.reload()
|
|
}, 3000)
|
|
}
|
|
} catch (error) {
|
|
currentStep.started = false
|
|
currentStep.completed = true
|
|
window.toastr['error'](this.$t('validation.something_went_wrong'))
|
|
this.onUpdateFailed(currentStep.translationKey)
|
|
return false
|
|
}
|
|
}
|
|
},
|
|
|
|
onUpdateFailed(translationKey) {
|
|
let stepName = this.$t(translationKey)
|
|
swal({
|
|
title: this.$t('settings.update_app.update_failed'),
|
|
text: this.$tc('settings.update_app.update_failed_text', stepName, {
|
|
step: stepName,
|
|
}),
|
|
buttons: [this.$t('general.cancel'), this.$t('general.retry')],
|
|
}).then(async (value) => {
|
|
if (value) {
|
|
this.onUpdateApp()
|
|
return
|
|
}
|
|
this.isUpdating = false
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.update-description ul li {
|
|
list-style: disc !important;
|
|
margin-bottom: 4px;
|
|
}
|
|
</style>
|