mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge pull request #417 from YunoHost/enh-upgrade
Enh upgrade & power utils with reconnection mecanism
This commit is contained in:
commit
22c867f11d
14 changed files with 268 additions and 166 deletions
|
@ -189,5 +189,34 @@ export default {
|
|||
delete (uri, data = {}, humanKey = null, options = {}) {
|
||||
if (typeof uri === 'string') return this.fetch('DELETE', uri, data, humanKey, options)
|
||||
return store.dispatch('DELETE', { ...uri, data, humanKey, options })
|
||||
},
|
||||
|
||||
/**
|
||||
* Api reconnection helper. Resolve when server is reachable or fail after n attemps
|
||||
*
|
||||
* @param {Number} attemps - number of attemps before rejecting
|
||||
* @param {Number} delay - delay between calls to the API in ms.
|
||||
* @param {Number} initialDelay - delay before calling the API for the first time in ms.
|
||||
* @return {Promise<undefined|Error>}
|
||||
*/
|
||||
tryToReconnect ({ attemps = 5, delay = 2000, initialDelay = 0 } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const api = this
|
||||
|
||||
function reconnect (n) {
|
||||
api.get('logout', {}, { key: 'reconnecting' }).then(resolve).catch(err => {
|
||||
if (err.name === 'APIUnauthorizedError') {
|
||||
resolve()
|
||||
} else if (n < 1) {
|
||||
reject(err)
|
||||
} else {
|
||||
setTimeout(() => reconnect(n - 1), delay)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (initialDelay > 0) setTimeout(() => reconnect(attemps), initialDelay)
|
||||
else reconnect(attemps)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
79
app/src/components/globals/Spinner.vue
Normal file
79
app/src/components/globals/Spinner.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div v-bind="$attr" :class="['custom-spinner', spinner]" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Spinner',
|
||||
|
||||
computed: {
|
||||
...mapGetters(['spinner'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-spinner {
|
||||
animation: 8s linear infinite;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.pacman {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-image: url('../../assets/spinners/pacman.gif');
|
||||
animation-name: back-and-forth-pacman;
|
||||
|
||||
@keyframes back-and-forth-pacman {
|
||||
0%, 100% { transform: scale(1); margin-left: 0; }
|
||||
49% { transform: scale(1); margin-left: calc(100% - 24px);}
|
||||
50% { transform: scale(-1); margin-left: calc(100% - 24px);}
|
||||
99% { transform: scale(-1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.magikarp {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
background-image: url('../../assets/spinners/magikarp.gif');
|
||||
animation-name: back-and-forth-magikarp;
|
||||
|
||||
@keyframes back-and-forth-magikarp {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 32px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 32px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.nyancat {
|
||||
height: 40px;
|
||||
width: 100px;
|
||||
background-image: url('../../assets/spinners/nyancat.gif');
|
||||
animation-name: back-and-forth-nyancat;
|
||||
|
||||
@keyframes back-and-forth-nyancat {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.spookycat {
|
||||
height: 40px;
|
||||
width: 65px;
|
||||
background-image: url('../../assets/spinners/spookycat.gif');
|
||||
animation-name: back-and-forth-spookycat;
|
||||
|
||||
@keyframes back-and-forth-spookycat {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
import i18n from '@/i18n'
|
||||
import store from '@/store'
|
||||
import i18n from '@/i18n'
|
||||
import supportedLocales from './supportedLocales'
|
||||
|
||||
let dateFnsLocale
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import { initDefaultLocales } from './helpers'
|
||||
|
||||
// Plugin Initialization
|
||||
Vue.use(VueI18n)
|
||||
|
||||
export default new VueI18n({})
|
||||
|
||||
// Load default locales translations files and setup store data
|
||||
initDefaultLocales()
|
||||
|
|
|
@ -21,6 +21,17 @@
|
|||
"pending": "In progress",
|
||||
"success": "Successfully completed",
|
||||
"warning": "Successfully completed with errors or alerts"
|
||||
},
|
||||
"reconnecting": {
|
||||
"title": "Trying to communicate with the server...",
|
||||
"failed": "Looks like the server is not responding. You can try to reconnect again or try to run `systemctl restart yunohost-api` thru ssh.",
|
||||
"reason": {
|
||||
"unknown": "Connection with the server has been closed for unknown reasons.",
|
||||
"reboot": "Your server is rebooting and will not be reachable for some time. A login prompt will be available as soon as the server is reachable.",
|
||||
"shutdown": "Your server is shutting down and is no longer reachable. Turn it back on and a login prompt will be available as soon as the server is reachable.",
|
||||
"upgrade_system": "Connection with the server has been closed due to yunohost upgrade. Waiting for the server to be reachable again…"
|
||||
},
|
||||
"success": "The server is now reachable! You can try to login"
|
||||
}
|
||||
},
|
||||
"api_error": {
|
||||
|
@ -363,6 +374,7 @@
|
|||
"rerun_diagnosis": "Rerun diagnosis",
|
||||
"restore": "Restore",
|
||||
"restart": "Restart",
|
||||
"retry": "Retry",
|
||||
"human_routes": {
|
||||
"adminpw": "Change admin password",
|
||||
"apps": {
|
||||
|
@ -422,6 +434,7 @@
|
|||
},
|
||||
"postinstall": "Run the post-install",
|
||||
"reboot": "Reboot the server",
|
||||
"reconnecting": "Reconnecting",
|
||||
"services": {
|
||||
"restart": "Restart the service '{name}'",
|
||||
"start": "Start the service '{name}'",
|
||||
|
@ -473,15 +486,10 @@
|
|||
"tools_adminpw": "Change administration password",
|
||||
"tools_adminpw_current": "Current password",
|
||||
"tools_adminpw_current_placeholder": "Enter your current password",
|
||||
"tools_power_up": "Your server seems to be accessible, you can now try to login.",
|
||||
"tools_reboot": "Reboot your server",
|
||||
"tools_reboot_btn": "Reboot",
|
||||
"tools_reboot_done": "Rebooting...",
|
||||
"tools_rebooting": "Your server is rebooting. To return to the web administration interface you need to wait for your server to be up. You can wait for the login form to show up or check by refreshing this page (F5).",
|
||||
"tools_shutdown": "Shutdown your server",
|
||||
"tools_shutdown_btn": "Shutdown",
|
||||
"tools_shutdown_done": "Shutting down...",
|
||||
"tools_shuttingdown": "Your server is powering off. As long as your server is off, you won't be able to use the web administration.",
|
||||
"tools_shutdown_reboot": "Shutdown/Reboot",
|
||||
"tools_webadmin": {
|
||||
"language": "Language",
|
||||
|
|
|
@ -3,11 +3,12 @@ import App from './App.vue'
|
|||
import BootstrapVue from 'bootstrap-vue'
|
||||
import VueShowdown from 'vue-showdown'
|
||||
|
||||
import i18n from './i18n'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
|
||||
import { registerGlobalErrorHandlers } from './api'
|
||||
import { initDefaultLocales } from './i18n/helpers'
|
||||
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
@ -53,11 +54,13 @@ requireComponent.keys().forEach((fileName) => {
|
|||
|
||||
registerGlobalErrorHandlers()
|
||||
|
||||
// Load default locales translations files and setup store data
|
||||
initDefaultLocales()
|
||||
|
||||
const app = new Vue({
|
||||
i18n,
|
||||
router,
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
render: h => h(App)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import api from '@/api'
|
||||
import router from '@/router'
|
||||
import i18n from '@/i18n'
|
||||
import api from '@/api'
|
||||
import { timeout, isObjectLiteral } from '@/helpers/commons'
|
||||
|
||||
export default {
|
||||
|
@ -10,6 +10,7 @@ export default {
|
|||
connected: localStorage.getItem('connected') === 'true', // Boolean
|
||||
yunohost: null, // Object { version, repo }
|
||||
waiting: false, // Boolean
|
||||
reconnecting: null, // null|Object { attemps, delay, initialDelay }
|
||||
history: [], // Array of `request`
|
||||
requests: [], // Array of `request`
|
||||
error: null, // null || request
|
||||
|
@ -31,6 +32,10 @@ export default {
|
|||
state.waiting = boolean
|
||||
},
|
||||
|
||||
'SET_RECONNECTING' (state, args) {
|
||||
state.reconnecting = args
|
||||
},
|
||||
|
||||
'ADD_REQUEST' (state, request) {
|
||||
if (state.requests.length > 10) {
|
||||
// We do not remove requests right after it resolves since an error might bring
|
||||
|
@ -103,7 +108,6 @@ export default {
|
|||
'CONNECT' ({ commit, dispatch }) {
|
||||
commit('SET_CONNECTED', true)
|
||||
dispatch('GET_YUNOHOST_INFOS')
|
||||
router.push(router.currentRoute.query.redirect || { name: 'home' })
|
||||
},
|
||||
|
||||
'RESET_CONNECTED' ({ commit }) {
|
||||
|
@ -134,6 +138,12 @@ export default {
|
|||
return api.get('logout')
|
||||
},
|
||||
|
||||
'TRY_TO_RECONNECT' ({ commit, dispatch }, args = {}) {
|
||||
// FIXME This is very ugly arguments forwarding, will use proper component way of doing this when switching to Vue 3 (teleport)
|
||||
commit('SET_RECONNECTING', args)
|
||||
dispatch('RESET_CONNECTED')
|
||||
},
|
||||
|
||||
'GET_YUNOHOST_INFOS' ({ commit }) {
|
||||
return api.get('versions').then(versions => {
|
||||
commit('SET_YUNOHOST_INFOS', versions.yunohost)
|
||||
|
@ -145,7 +155,7 @@ export default {
|
|||
const { key, ...args } = isObjectLiteral(humanKey) ? humanKey : { key: humanKey }
|
||||
const humanRoute = key ? i18n.t('human_routes.' + key, args) : `[${method}] /${uri}`
|
||||
|
||||
let request = { method, uri, humanRoute, initial, status: 'pending' }
|
||||
let request = { method, uri, humanRouteKey: key, humanRoute, initial, status: 'pending' }
|
||||
if (websocket) {
|
||||
request = { ...request, messages: [], date: Date.now(), warnings: 0, errors: 0 }
|
||||
commit('ADD_HISTORY_ACTION', request)
|
||||
|
@ -253,7 +263,7 @@ export default {
|
|||
|
||||
'DISMISS_WARNING' ({ commit, state }, request) {
|
||||
commit('SET_WAITING', false)
|
||||
delete request.showWarningMessage
|
||||
Vue.delete(request, 'showWarningMessage')
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -263,6 +273,7 @@ export default {
|
|||
yunohost: state => state.yunohost,
|
||||
error: state => state.error,
|
||||
waiting: state => state.waiting,
|
||||
reconnecting: state => state.reconnecting,
|
||||
history: state => state.history,
|
||||
lastAction: state => state.history[state.history.length - 1],
|
||||
currentRequest: state => {
|
||||
|
|
|
@ -33,7 +33,8 @@ export default {
|
|||
name: 'Login',
|
||||
|
||||
props: {
|
||||
skipInstallCheck: { type: Boolean, default: false }
|
||||
skipInstallCheck: { type: Boolean, default: false },
|
||||
forceReload: { type: Boolean, default: false }
|
||||
},
|
||||
|
||||
data () {
|
||||
|
@ -47,7 +48,13 @@ export default {
|
|||
|
||||
methods: {
|
||||
login () {
|
||||
this.$store.dispatch('LOGIN', this.password).catch(err => {
|
||||
this.$store.dispatch('LOGIN', this.password).then(() => {
|
||||
if (this.forceReload) {
|
||||
window.location.href = '/yunohost/admin/'
|
||||
} else {
|
||||
this.$router.push(this.$router.currentRoute.query.redirect || { name: 'home' })
|
||||
}
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIUnauthorizedError') throw err
|
||||
this.isValid = false
|
||||
})
|
||||
|
|
78
app/src/views/_partials/ReconnectingDisplay.vue
Normal file
78
app/src/views/_partials/ReconnectingDisplay.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<!-- This card receives style from `ViewLockOverlay` if used inside it -->
|
||||
<b-card-body>
|
||||
<b-card-title class="text-center my-4" v-t="'api.reconnecting.title'" />
|
||||
|
||||
<template v-if="status === 'reconnecting'">
|
||||
<spinner class="mb-4" />
|
||||
|
||||
<b-alert
|
||||
v-if="origin"
|
||||
v-t="'api.reconnecting.reason.' + origin"
|
||||
:variant="origin === 'unknow' ? 'warning' : 'info'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="status === 'failed'">
|
||||
<b-alert variant="danger">
|
||||
<markdown-item :label="$t('api.reconnecting.failed')" />
|
||||
</b-alert>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<b-button
|
||||
variant="success" v-t="'retry'" class="ml-auto"
|
||||
@click="tryToReconnect()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="status === 'success'">
|
||||
<b-alert variant="success" v-t="'api.reconnecting.success'" />
|
||||
|
||||
<login-view skip-install-check force-reload />
|
||||
</template>
|
||||
</b-card-body>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import api from '@/api'
|
||||
import LoginView from '@/views/Login'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ReconnectingDisplay',
|
||||
|
||||
components: {
|
||||
LoginView
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
status: 'reconnecting',
|
||||
origin: undefined
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['reconnecting'])
|
||||
},
|
||||
|
||||
methods: {
|
||||
tryToReconnect (initialDelay = 0) {
|
||||
this.status = 'reconnecting'
|
||||
api.tryToReconnect({ ...this.reconnecting, initialDelay }).then(() => {
|
||||
this.status = 'success'
|
||||
}).catch(() => {
|
||||
this.status = 'failed'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.origin = this.reconnecting.origin || 'unknown'
|
||||
this.tryToReconnect(this.reconnecting.initialDelay)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -2,7 +2,7 @@
|
|||
<b-overlay
|
||||
variant="white" opacity="0.75"
|
||||
no-center
|
||||
:show="waiting || error !== null"
|
||||
:show="waiting || reconnecting || error !== null"
|
||||
>
|
||||
<slot name="default" />
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { ErrorDisplay, WarningDisplay, WaitingDisplay } from '@/views/_partials'
|
||||
import { ErrorDisplay, WarningDisplay, WaitingDisplay, ReconnectingDisplay } from '@/views/_partials'
|
||||
import QueryHeader from '@/components/QueryHeader'
|
||||
|
||||
export default {
|
||||
|
@ -30,18 +30,22 @@ export default {
|
|||
ErrorDisplay,
|
||||
WarningDisplay,
|
||||
WaitingDisplay,
|
||||
ReconnectingDisplay,
|
||||
QueryHeader
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['waiting', 'error', 'currentRequest']),
|
||||
...mapGetters(['waiting', 'reconnecting', 'error', 'currentRequest']),
|
||||
|
||||
component () {
|
||||
const { error, currentRequest: request } = this
|
||||
const { error, reconnecting, currentRequest: request } = this
|
||||
|
||||
if (error) {
|
||||
return { name: 'ErrorDisplay', request: error }
|
||||
} else if (request.showWarningMessage) {
|
||||
return { name: 'WarningDisplay', request }
|
||||
} else if (reconnecting) {
|
||||
return { name: 'ReconnectingDisplay' }
|
||||
} else {
|
||||
return { name: 'WaitingDisplay', request }
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<b-progress-bar variant="secondary" :value="progress.values[2]" striped />
|
||||
</b-progress>
|
||||
<!-- OR SPINNER -->
|
||||
<div v-else class="custom-spinner my-4" :class="spinner" />
|
||||
<spinner v-else class="my-4" />
|
||||
|
||||
<message-list-group
|
||||
v-if="hasMessages" :messages="request.messages"
|
||||
|
@ -24,8 +24,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import MessageListGroup from '@/components/MessageListGroup'
|
||||
|
||||
export default {
|
||||
|
@ -40,8 +38,6 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['spinner']),
|
||||
|
||||
hasMessages () {
|
||||
return this.request.messages && this.request.messages.length > 0
|
||||
},
|
||||
|
@ -57,66 +53,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-spinner {
|
||||
animation: 8s linear infinite;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.pacman {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-image: url('../../assets/spinners/pacman.gif');
|
||||
animation-name: back-and-forth-pacman;
|
||||
|
||||
@keyframes back-and-forth-pacman {
|
||||
0%, 100% { transform: scale(1); margin-left: 0; }
|
||||
49% { transform: scale(1); margin-left: calc(100% - 24px);}
|
||||
50% { transform: scale(-1); margin-left: calc(100% - 24px);}
|
||||
99% { transform: scale(-1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.magikarp {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
background-image: url('../../assets/spinners/magikarp.gif');
|
||||
animation-name: back-and-forth-magikarp;
|
||||
|
||||
@keyframes back-and-forth-magikarp {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 32px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 32px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.nyancat {
|
||||
height: 40px;
|
||||
width: 100px;
|
||||
background-image: url('../../assets/spinners/nyancat.gif');
|
||||
animation-name: back-and-forth-nyancat;
|
||||
|
||||
@keyframes back-and-forth-nyancat {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
|
||||
&.spookycat {
|
||||
height: 40px;
|
||||
width: 65px;
|
||||
background-image: url('../../assets/spinners/spookycat.gif');
|
||||
animation-name: back-and-forth-spookycat;
|
||||
|
||||
@keyframes back-and-forth-spookycat {
|
||||
0%, 100% { transform: scale(1, 1); margin-left: 0; }
|
||||
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
|
||||
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
|
||||
99% { transform: scale(-1, 1); margin-left: 0;}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export { default as ErrorDisplay } from './ErrorDisplay'
|
||||
export { default as WarningDisplay } from './WarningDisplay'
|
||||
export { default as WaitingDisplay } from './WaitingDisplay'
|
||||
export { default as ReconnectingDisplay } from './ReconnectingDisplay'
|
||||
|
||||
export { default as HistoryConsole } from './HistoryConsole'
|
||||
export { default as ViewLockOverlay } from './ViewLockOverlay'
|
||||
|
|
|
@ -1,63 +1,35 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="canReconnect">
|
||||
<b-alert variant="success" v-t="'tools_power_up'" />
|
||||
<login-view />
|
||||
</template>
|
||||
<card :title="$t('operations')" icon="wrench">
|
||||
<!-- REBOOT -->
|
||||
<b-form-group
|
||||
label-cols="5" label-cols-md="4" label-cols-lg="3"
|
||||
:label="$t('tools_reboot')" label-for="reboot"
|
||||
>
|
||||
<b-button @click="triggerAction('reboot')" variant="danger" id="reboot">
|
||||
<icon iname="refresh" /> {{ $t('tools_reboot_btn') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
|
||||
<div v-else-if="inProcess">
|
||||
<b-alert variant="info" v-t="'tools_' + action + '_done'" />
|
||||
|
||||
<b-alert variant="warning">
|
||||
<icon :iname="action === 'reboot' ? 'refresh' : 'power-off'" />
|
||||
{{ $t(action === 'reboot' ? 'tools_rebooting' : 'tools_shuttingdown') }}
|
||||
</b-alert>
|
||||
</div>
|
||||
|
||||
<card v-else :title="$t('operations')" icon="wrench">
|
||||
<!-- REBOOT -->
|
||||
<b-form-group
|
||||
label-cols="5" label-cols-md="4" label-cols-lg="3"
|
||||
:label="$t('tools_reboot')" label-for="reboot"
|
||||
>
|
||||
<b-button @click="triggerAction('reboot')" variant="danger" id="reboot">
|
||||
<icon iname="refresh" /> {{ $t('tools_reboot_btn') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
|
||||
<!-- SHUTDOWN -->
|
||||
<b-form-group
|
||||
label-cols="5" label-cols-md="4" label-cols-lg="3"
|
||||
:label="$t('tools_shutdown')" label-for="shutdown"
|
||||
>
|
||||
<b-button @click="triggerAction('shutdown')" variant="danger" id="shutdown">
|
||||
<icon iname="power-off" /> {{ $t('tools_shutdown_btn') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</card>
|
||||
</div>
|
||||
<!-- SHUTDOWN -->
|
||||
<b-form-group
|
||||
label-cols="5" label-cols-md="4" label-cols-lg="3"
|
||||
:label="$t('tools_shutdown')" label-for="shutdown"
|
||||
>
|
||||
<b-button @click="triggerAction('shutdown')" variant="danger" id="shutdown">
|
||||
<icon iname="power-off" /> {{ $t('tools_shutdown_btn') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import LoginView from '@/views/Login'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ToolPower',
|
||||
|
||||
components: {
|
||||
LoginView
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
action: '',
|
||||
inProcess: false,
|
||||
canReconnect: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async triggerAction (action) {
|
||||
const confirmed = await this.$askConfirmation(
|
||||
|
@ -67,30 +39,8 @@ export default {
|
|||
|
||||
this.action = action
|
||||
api.put(action + '?force', {}, action).then(() => {
|
||||
// Use 'RESET_CONNECTED' and not 'DISCONNECT' else user will be redirect to login
|
||||
this.$store.dispatch('RESET_CONNECTED')
|
||||
this.inProcess = true
|
||||
return this.tryToReconnect(4000)
|
||||
}).then(() => {
|
||||
this.canReconnect = true
|
||||
})
|
||||
},
|
||||
|
||||
tryToReconnect (delay = 2000) {
|
||||
// FIXME need to be tested out of webpack-dev-server
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
// Try to get a response from the server after boot/reboot
|
||||
api.get('logout').catch(err => {
|
||||
if (err.name === 'APIUnauthorizedError') {
|
||||
// Means the server is accessible
|
||||
resolve()
|
||||
} else {
|
||||
// FIXME could be improved by checking error types since yunohost
|
||||
resolve(this.tryToReconnect())
|
||||
}
|
||||
})
|
||||
}, delay)
|
||||
const delay = action === 'reboot' ? 4000 : 10000
|
||||
this.$store.dispatch('TRY_TO_RECONNECT', { attemps: Infinity, origin: action, delay })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,9 @@ export default {
|
|||
} else if (type === 'apps') {
|
||||
this.apps = null
|
||||
} else {
|
||||
if (this.system.some(({ name }) => name.includes('yunohost'))) {
|
||||
this.$store.dispatch('TRY_TO_RECONNECT', { attemps: 1, origin: 'upgrade_system', initialDelay: 2000 })
|
||||
}
|
||||
this.system = null
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue