Finish button of BS update

This commit is contained in:
Pig Fang 2018-09-13 18:10:23 +08:00
parent 6bfe30b394
commit 43051bad90
7 changed files with 188 additions and 115 deletions

View File

@ -0,0 +1,113 @@
<template>
<span>
<button
v-if="!updating"
class="btn btn-primary"
:disabled="!canUpdate"
@click="update"
>{{ $t('admin.updateButton') }}</button>
<button v-else disabled class="btn btn-primary">
<i class="fa fa-spinner fa-spin"></i> {{ $t('admin.preparing') }}
</button>
<div
id="modal-start-download"
class="modal fade"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" v-t="'admin.downloading'"></h4>
</div>
<div class="modal-body">
<p>{{ $t('admin.updateSize') }}<span>{{ total }}</span> KB</p>
<div class="progress">
<div
class="progress-bar progress-bar-striped active"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
:style="{ width: `${percentage}%` }"
>
<span>{{ ~~percentage }}</span>%
</div>
</div>
</div>
</div>
</div>
</div>
</span>
</template>
<script>
import { swal } from '../../js/notify';
const POLLING_INTERVAL = 500;
export default {
name: 'UpdateButton',
data: () => ({
canUpdate: blessing.extra.canUpdate,
updating: false,
total: 0,
downloaded: 0,
}),
computed: {
percentage() {
return this.downloaded / this.total * 100;
}
},
methods: {
async update() {
this.updating = true;
await this.takeAction('prepare-download');
this.updating && $('#modal-start-download').modal({
backdrop: 'static',
keyboard: false
});
setTimeout(this.polling, POLLING_INTERVAL);
this.updating && await this.takeAction('start-download');
this.updating && await this.takeAction('extract');
this.updating = false;
if (this.downloaded) {
await swal({ type: 'success', text: this.$t('admin.updateCompleted') });
window.location = blessing.base_url;
}
},
async takeAction(action) {
const { errno, msg } = await this.$http.post('/admin/update/download', {
action
});
if (errno && errno !== 0) {
swal({ type: 'error', text: msg });
this.updating = false;
}
},
async polling() {
const { downloaded, total } = await this.$http.get(
'/admin/update/download',
{ action: 'get-progress' }
);
this.downloaded = ~~(+downloaded / 1024);
this.total = ~~(+total / 1024);
this.updating && setTimeout(this.polling, POLLING_INTERVAL);
}
}
};
</script>

View File

@ -44,6 +44,11 @@ export default [
component: () => import('./admin/Market'),
el: '.content'
},
{
path: 'admin/update',
component: () => import('./admin/Update'),
el: '#update-button'
},
{
path: 'auth/login',
component: () => import('./auth/Login'),

View File

@ -1,110 +0,0 @@
'use strict';
async function downloadUpdates() {
console.log('Prepare trno download');
try {
const preparation = await fetch({
url: url('admin/update/download?action=prepare-download'),
type: 'GET',
dataType: 'json',
beforeSend: function() {
$('#update-button').html(
'<i class="fa fa-spinner fa-spin"></i> ' + trans('admin.preparing')
).prop('disabled', 'disabled');
}
});
console.log(preparation);
const { file_size: fileSize } = preparation;
$('#file-size').html(fileSize);
$('#modal-start-download').modal({
'backdrop': 'static',
'keyboard': false
});
console.log('Start downloading');
// Downloading progress polling
const interval_id = setInterval(progressPolling(fileSize), 300);
const download = await fetch({
url: url('admin/update/download?action=start-download'),
type: 'POST',
dataType: 'json'
});
clearInterval(interval_id);
console.log('Downloading finished');
console.log(download);
$('.modal-title').html('<i class="fa fa-spinner fa-spin"></i> ' + trans('admin.extracting'));
$('.modal-body').append(`<p>${trans('admin.downloadCompleted')}</p>`);
console.log('Start extracting');
const extract = await fetch({
url: url('admin/update/download?action=extract'),
type: 'POST',
dataType: 'json'
});
console.log('Package extracted and files are covered');
$('#modal-start-download').modal('toggle');
swal({
type: 'success',
html: extract.msg
}).then(function () {
window.location = url('/');
}, function () {
window.location = url('/');
});
} catch (error) {
showAjaxError(error);
}
}
function progressPolling(fileSize) {
return async () => {
try {
const { size } = await fetch({
url: url('admin/update/download?action=get-file-size'),
type: 'GET'
});
const progress = (size / fileSize * 100).toFixed(2);
$('#imported-progress').html(progress);
$('.progress-bar')
.css('width', progress + '%')
.attr('aria-valuenow', progress);
} catch (error) {
// No need to show error if failed to get size
}
};
}
async function checkForUpdates() {
try {
const data = await fetch({ url: url('admin/update/check') });
if (data.available === true) {
const dom = `<span class="label label-primary pull-right">v${data.latest}</span>`;
$(`[href="${url('admin/update')}"]`).append(dom);
}
} catch (error) {
//
}
}
if (process.env.NODE_ENV === 'test') {
module.exports = {
checkForUpdates,
progressPolling,
downloadUpdates,
};
}

View File

@ -0,0 +1,55 @@
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import { flushPromises } from '../../utils';
import Update from '@/components/admin/Update.vue';
import '@/js/notify';
jest.mock('@/js/notify');
afterEach(() => {
window.blessing.extra = { canUpdate: true };
});
test('button should be disabled if update is unavailable', () => {
window.blessing.extra = { canUpdate: false };
const wrapper = mount(Update);
expect(wrapper.find('.btn').attributes('disabled')).toBe('disabled');
});
test('perform update', async () => {
window.$ = jest.fn(() => ({
modal() {}
}));
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1 })
.mockResolvedValue({});
Vue.prototype.$http.get
.mockResolvedValue({ total: 2048, downloaded: 2048 });
const wrapper = mount(Update);
const button = wrapper.find('.btn');
button.trigger('click');
await flushPromises();
expect(window.$).not.toBeCalled();
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/update/download',
{ action: 'prepare-download' }
);
button.trigger('click');
jest.runOnlyPendingTimers();
await flushPromises();
expect(window.$).toBeCalled();
expect(Vue.prototype.$http.get).toBeCalledWith(
'/admin/update/download',
{ action: 'get-progress' }
);
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/update/download',
{ action: 'start-download' }
);
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/update/download',
{ action: 'extract' }
);
});

View File

@ -291,9 +291,11 @@ admin:
may be not compatible with the current version of Blessing Skin, and
enabling it may cause unexpected problems. Do you really want to enable the
plugin?
updateButton: Update Now
updateSize: "Size of package:"
preparing: Preparing
downloadCompleted: Update package download completed.
extracting: Extracting update package..
downloading: Downloading update package...
updateCompleted: Update completed.
change-color:
title: Change theme color
success: Theme color updated.

View File

@ -285,9 +285,11 @@ admin:
noDependenciesNotice: >-
此插件没有声明任何依赖关系,这代表它有可能并不兼容此版本的 Blessing
Skin请将此插件升级至可能的最新版本。强行启用可能导致无法预料的后果。你确定要启用此插件吗
updateButton: 马上升级
updateSize: 更新包大小:
preparing: 正在准备
downloadCompleted: 更新包下载完成
extracting: 正在解压更新包
downloading: 正在下载更新包
updateCompleted: 更新完成
change-color:
title: 更改配色
success: 修改配色成功

View File

@ -94,7 +94,7 @@
@endif
</div><!-- /.box-body -->
<div class="box-footer">
<a class="btn btn-primary" id="update-button" {!! !$info['new_version_available'] ? 'disabled="disabled"' : 'onclick="downloadUpdates();"' !!}>@lang('admin.update.info.button')</a>
<span id="update-button"></span>
{!! trans('admin.update.info.check-github', ['url' => 'https://github.com/printempw/blessing-skin-server/releases']) !!}
</div>
</div>
@ -132,4 +132,10 @@
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<script>
blessing.extra = {
canUpdate: {{ $info['new_version_available'] ? 'true' : 'false' }}
}
</script>
@endsection