mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Update store and overlay components to new api
This commit is contained in:
parent
b130aeda29
commit
9bc365f32a
10 changed files with 223 additions and 173 deletions
|
@ -3,8 +3,8 @@
|
||||||
v-bind="$attrs" ref="self"
|
v-bind="$attrs" ref="self"
|
||||||
flush :class="{ 'fixed-height': fixedHeight, 'bordered': bordered }"
|
flush :class="{ 'fixed-height': fixedHeight, 'bordered': bordered }"
|
||||||
>
|
>
|
||||||
<b-list-group-item v-for="({ type, text }, i) in messages" :key="i">
|
<b-list-group-item v-for="({ color, text }, i) in messages" :key="i">
|
||||||
<span class="status" :class="'bg-' + type" />
|
<span class="status" :class="'bg-' + color" />
|
||||||
<span v-html="text" />
|
<span v-html="text" />
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
|
|
|
@ -1,38 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="query-header w-100" v-on="$listeners" v-bind="$attrs">
|
<div class="query-header w-100" v-on="$listeners" v-bind="$attrs">
|
||||||
<!-- STATUS -->
|
<!-- STATUS -->
|
||||||
<span class="status" :class="['bg-' + color, statusSize]" :aria-label="$t('api.query_status.' + action.status)" />
|
<span class="status" :class="['bg-' + color, statusSize]" :aria-label="$t('api.query_status.' + request.status)" />
|
||||||
|
|
||||||
<!-- ACTION DESCRIPTION -->
|
<!-- REQUEST DESCRIPTION -->
|
||||||
<strong class="action-desc">
|
<strong class="request-desc">
|
||||||
{{ action.uri | readableUri }}
|
{{ request.uri | readableUri }}
|
||||||
<small>({{ $t('history.methods.' + action.method) }})</small>
|
<small>({{ $t('history.methods.' + request.method) }})</small>
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<div>
|
<div v-if="request.errors || request.warnings">
|
||||||
<!-- WEBSOCKET ERRORS COUNT -->
|
<!-- WEBSOCKET ERRORS COUNT -->
|
||||||
<span class="count" v-if="errorsCount">
|
<span class="count" v-if="request.errors">
|
||||||
{{ errorsCount }}<icon iname="bug" class="text-danger ml-1" />
|
{{ request.errors }}<icon iname="bug" class="text-danger ml-1" />
|
||||||
</span>
|
</span>
|
||||||
<!-- WEBSOCKET WARNINGS COUNT -->
|
<!-- WEBSOCKET WARNINGS COUNT -->
|
||||||
<span class="count" v-if="warningsCount">
|
<span class="count" v-if="request.warnings">
|
||||||
{{ warningsCount }}<icon iname="warning" class="text-warning ml-1" />
|
{{ request.warnings }}<icon iname="warning" class="text-warning ml-1" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- VIEW ERRO BUTTON -->
|
<!-- VIEW ERROR BUTTON -->
|
||||||
<b-button
|
<b-button
|
||||||
v-if="showError && action.status === 'error'"
|
v-if="showError && request.error"
|
||||||
size="sm" pill
|
size="sm" pill
|
||||||
class="error-btn ml-auto py-0"
|
class="error-btn ml-auto py-0"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
@click="reviewError"
|
||||||
>
|
>
|
||||||
<small v-t="'api_error.view_error'" />
|
<small v-t="'api_error.view_error'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|
||||||
<!-- TIME DISPLAY -->
|
<!-- TIME DISPLAY -->
|
||||||
<time v-if="showTime" :datetime="action.date | hour" :class="!showError || action.status !== 'error' ? 'ml-auto' : 'ml-2'">
|
<time v-if="showTime" :datetime="request.date | hour" :class="request.error ? 'ml-2' : 'ml-auto'">
|
||||||
{{ action.date | hour }}
|
{{ request.date | hour }}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,11 +43,10 @@ export default {
|
||||||
name: 'QueryHeader',
|
name: 'QueryHeader',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
action: { type: Object, required: true },
|
request: { type: Object, required: true },
|
||||||
statusSize: { type: String, default: '' },
|
statusSize: { type: String, default: '' },
|
||||||
showTime: { type: Boolean, default: false },
|
showTime: { type: Boolean, default: false },
|
||||||
showError: { type: Boolean, default: false },
|
showError: { type: Boolean, default: false }
|
||||||
truncate: { type: Boolean, default: true }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -57,21 +57,27 @@ export default {
|
||||||
warning: 'warning',
|
warning: 'warning',
|
||||||
error: 'danger'
|
error: 'danger'
|
||||||
}
|
}
|
||||||
return statuses[this.action.status]
|
return statuses[this.request.status]
|
||||||
},
|
},
|
||||||
|
|
||||||
errorsCount () {
|
errorsCount () {
|
||||||
return this.action.messages.filter(({ type }) => type === 'danger').length
|
return this.request.messages.filter(({ type }) => type === 'danger').length
|
||||||
},
|
},
|
||||||
|
|
||||||
warningsCount () {
|
warningsCount () {
|
||||||
return this.action.messages.filter(({ type }) => type === 'warning').length
|
return this.request.messages.filter(({ type }) => type === 'warning').length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
reviewError () {
|
||||||
|
this.$store.dispatch('REVIEW_ERROR', this.request)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
readableUri (uri) {
|
readableUri (uri) {
|
||||||
return uri.split('?')[0].replace('/', ' > ')
|
return uri.split('?')[0].split('/').join(' > ') // replace('/', ' > ')
|
||||||
},
|
},
|
||||||
|
|
||||||
hour (date) {
|
hour (date) {
|
||||||
|
@ -110,6 +116,11 @@ div {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
min-width: 3.5rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -117,7 +128,7 @@ div {
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(xs) {
|
@include media-breakpoint-down(xs) {
|
||||||
.xs-hide .action-desc {
|
.xs-hide .request-desc {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,7 +203,6 @@
|
||||||
"history": {
|
"history": {
|
||||||
"title": "History",
|
"title": "History",
|
||||||
"last_action": "Last action:",
|
"last_action": "Last action:",
|
||||||
"current_action": "Current action:",
|
|
||||||
"methods": {
|
"methods": {
|
||||||
"DELETE": "delete",
|
"DELETE": "delete",
|
||||||
"GET": "read",
|
"GET": "read",
|
||||||
|
|
|
@ -29,7 +29,7 @@ const router = new VueRouter({
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (store.getters.error) {
|
if (store.getters.error) {
|
||||||
store.dispatch('DELETE_ERROR')
|
store.dispatch('DISMISS_ERROR', true)
|
||||||
}
|
}
|
||||||
// Allow if connected or route is not protected
|
// Allow if connected or route is not protected
|
||||||
if (store.getters.connected || to.meta.noAuth) {
|
if (store.getters.connected || to.meta.noAuth) {
|
||||||
|
|
|
@ -90,40 +90,21 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
'FETCH' ({ state, commit, rootState }, { uri, param, storeKey = uri, cache = rootState.cache }) {
|
'GET' ({ state, commit, rootState }, { uri, param, storeKey = uri, options = {} }) {
|
||||||
|
const noCache = !rootState.cache || options.noCache || false
|
||||||
const currentState = param ? state[storeKey][param] : state[storeKey]
|
const currentState = param ? state[storeKey][param] : state[storeKey]
|
||||||
// if data has already been queried, simply return
|
// if data has already been queried, simply return
|
||||||
if (currentState !== undefined && cache) return currentState
|
if (currentState !== undefined && !noCache) return currentState
|
||||||
|
|
||||||
return api.get(param ? `${uri}/${param}` : uri).then(responseData => {
|
return api.fetch('GET', param ? `${uri}/${param}` : uri, null, options).then(responseData => {
|
||||||
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||||
commit('SET_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
commit('SET_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
||||||
return param ? state[storeKey][param] : state[storeKey]
|
return param ? state[storeKey][param] : state[storeKey]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
'FETCH_ALL' ({ state, commit, rootState }, queries) {
|
'POST' ({ state, commit }, { uri, storeKey = uri, data, options }) {
|
||||||
return Promise.all(queries.map(({ uri, param, storeKey = uri, cache = rootState.cache }) => {
|
return api.fetch('POST', uri, data, options).then(responseData => {
|
||||||
const currentState = param ? state[storeKey][param] : state[storeKey]
|
|
||||||
// if data has already been queried, simply return the state as cached
|
|
||||||
if (currentState !== undefined && cache) {
|
|
||||||
return { cached: currentState }
|
|
||||||
}
|
|
||||||
return api.get(param ? `${uri}/${param}` : uri).then(responseData => {
|
|
||||||
return { storeKey, param, responseData }
|
|
||||||
})
|
|
||||||
})).then(responsesData => {
|
|
||||||
return responsesData.map(({ storeKey, param, responseData, cached = undefined }) => {
|
|
||||||
if (cached !== undefined) return cached
|
|
||||||
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
|
||||||
commit('SET_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
|
||||||
return param ? state[storeKey][param] : state[storeKey]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
'POST' ({ state, commit }, { uri, data, storeKey = uri }) {
|
|
||||||
return api.post(uri, data).then(responseData => {
|
|
||||||
// FIXME api/domains returns null
|
// FIXME api/domains returns null
|
||||||
if (responseData === null) responseData = data
|
if (responseData === null) responseData = data
|
||||||
responseData = responseData[storeKey] ? responseData[storeKey] : responseData
|
responseData = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||||
|
@ -132,16 +113,16 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
'PUT' ({ state, commit }, { uri, param, data, storeKey = uri }) {
|
'PUT' ({ state, commit }, { uri, param, storeKey = uri, data, options }) {
|
||||||
return api.put(param ? `${uri}/${param}` : uri, data).then(responseData => {
|
return api.fetch('PUT', param ? `${uri}/${param}` : uri, data, options).then(responseData => {
|
||||||
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||||
commit('UPDATE_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
commit('UPDATE_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
||||||
return param ? state[storeKey][param] : state[storeKey]
|
return param ? state[storeKey][param] : state[storeKey]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
'DELETE' ({ commit }, { uri, param, data = {}, storeKey = uri }) {
|
'DELETE' ({ commit }, { uri, param, storeKey = uri, data, options }) {
|
||||||
return api.delete(param ? `${uri}/${param}` : uri, data).then(() => {
|
return api.fetch('DELETE', param ? `${uri}/${param}` : uri, data, options).then(() => {
|
||||||
commit('DEL_' + storeKey.toUpperCase(), param)
|
commit('DEL_' + storeKey.toUpperCase(), param)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -164,8 +145,7 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// not cached
|
user: state => name => state.users_details[name], // not cached
|
||||||
user: state => name => state.users_details[name],
|
|
||||||
|
|
||||||
domains: state => state.domains,
|
domains: state => state.domains,
|
||||||
|
|
||||||
|
|
|
@ -5,95 +5,70 @@ import { timeout } from '@/helpers/commons'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
state: {
|
state: {
|
||||||
host: window.location.host,
|
host: window.location.host, // String
|
||||||
connected: localStorage.getItem('connected') === 'true',
|
connected: localStorage.getItem('connected') === 'true', // Boolean
|
||||||
yunohost: null, // yunohost app infos: Object {version, repo}
|
yunohost: null, // Object { version, repo }
|
||||||
error: null,
|
waiting: false, // Boolean
|
||||||
waiting: false,
|
history: [], // Array of `request`
|
||||||
history: []
|
requests: [], // Array of `request`
|
||||||
|
error: null // null || request
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
'SET_CONNECTED' (state, connected) {
|
'SET_CONNECTED' (state, boolean) {
|
||||||
localStorage.setItem('connected', connected)
|
localStorage.setItem('connected', boolean)
|
||||||
state.connected = connected
|
state.connected = boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
'SET_YUNOHOST_INFOS' (state, yunohost) {
|
'SET_YUNOHOST_INFOS' (state, yunohost) {
|
||||||
state.yunohost = yunohost
|
state.yunohost = yunohost
|
||||||
},
|
},
|
||||||
|
|
||||||
'UPDATE_WAITING' (state, boolean) {
|
'SET_WAITING' (state, boolean) {
|
||||||
state.waiting = boolean
|
state.waiting = boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
'ADD_HISTORY_ENTRY' (state, [uri, method, date]) {
|
'ADD_REQUEST' (state, request) {
|
||||||
state.history.push({ uri, method, date, status: 'pending', messages: [] })
|
if (state.requests.length > 10) {
|
||||||
|
// We do not remove requests right after it resolves since an error might bring
|
||||||
|
// one back to life but we can safely remove some here.
|
||||||
|
state.requests.shift()
|
||||||
|
}
|
||||||
|
state.requests.push(request)
|
||||||
},
|
},
|
||||||
|
|
||||||
'UPDATE_LAST_HISTORY_ENTRY' (state, [key, value]) {
|
'UPDATE_REQUEST' (state, { request, key, value }) {
|
||||||
Vue.set(state.history[state.history.length - 1], key, value)
|
// This rely on data persistance and reactivity.
|
||||||
|
Vue.set(request, key, value)
|
||||||
},
|
},
|
||||||
|
|
||||||
'ADD_MESSAGE' (state, message) {
|
'REMOVE_REQUEST' (state, request) {
|
||||||
state.history[state.history.length - 1].messages.push(message)
|
const index = state.requests.lastIndexOf(request)
|
||||||
|
state.requests.splice(index, 1)
|
||||||
},
|
},
|
||||||
|
|
||||||
'UPDATE_PROGRESS' (state, progress) {
|
'ADD_HISTORY_ACTION' (state, request) {
|
||||||
Vue.set(state.history[state.history.length - 1], 'progress', progress)
|
state.history.push(request)
|
||||||
},
|
},
|
||||||
|
|
||||||
'SET_ERROR' (state, error) {
|
'ADD_MESSAGE' (state, { message, type }) {
|
||||||
state.error = error
|
const request = state.history[state.history.length - 1]
|
||||||
|
request.messages.push(message)
|
||||||
|
if (['error', 'warning'].includes(type)) {
|
||||||
|
request[type + 's']++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'SET_ERROR' (state, request) {
|
||||||
|
if (request) {
|
||||||
|
state.error = request
|
||||||
|
} else {
|
||||||
|
state.error = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
'LOGIN' ({ dispatch }, password) {
|
|
||||||
// Entering a wrong password will trigger a 401 api response.
|
|
||||||
// action `DISCONNECT` will then be triggered by the response handler but will not
|
|
||||||
// redirect to `/login` so the view can display the catched error.
|
|
||||||
return api.post('login', { password }).then(() => {
|
|
||||||
dispatch('CONNECT')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
'LOGOUT' ({ dispatch }) {
|
|
||||||
return api.get('logout').then(() => {
|
|
||||||
dispatch('DISCONNECT')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
'RESET_CONNECTED' ({ commit }) {
|
|
||||||
commit('SET_CONNECTED', false)
|
|
||||||
commit('SET_YUNOHOST_INFOS', null)
|
|
||||||
},
|
|
||||||
|
|
||||||
'DISCONNECT' ({ dispatch, commit }, route) {
|
|
||||||
dispatch('RESET_CONNECTED')
|
|
||||||
commit('UPDATE_WAITING', false)
|
|
||||||
if (router.currentRoute.name === 'login') return
|
|
||||||
router.push({
|
|
||||||
name: 'login',
|
|
||||||
// Add a redirect query if next route is not unknown (like `logout`) or `login`
|
|
||||||
query: route && !['login', null].includes(route.name)
|
|
||||||
? { redirect: route.path }
|
|
||||||
: {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
'CONNECT' ({ commit, dispatch }) {
|
|
||||||
commit('SET_CONNECTED', true)
|
|
||||||
dispatch('GET_YUNOHOST_INFOS')
|
|
||||||
router.push(router.currentRoute.query.redirect || { name: 'home' })
|
|
||||||
},
|
|
||||||
|
|
||||||
'GET_YUNOHOST_INFOS' ({ commit }) {
|
|
||||||
return api.get('versions').then(versions => {
|
|
||||||
commit('SET_YUNOHOST_INFOS', versions.yunohost)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
'CHECK_INSTALL' ({ dispatch }, retry = 2) {
|
'CHECK_INSTALL' ({ dispatch }, retry = 2) {
|
||||||
// this action will try to query the `/installed` route 3 times every 5 s with
|
// this action will try to query the `/installed` route 3 times every 5 s with
|
||||||
// a timeout of the same delay.
|
// a timeout of the same delay.
|
||||||
|
@ -108,29 +83,85 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
'WAITING_FOR_RESPONSE' ({ commit }, [uri, method]) {
|
'CONNECT' ({ commit, dispatch }) {
|
||||||
commit('UPDATE_WAITING', true)
|
commit('SET_CONNECTED', true)
|
||||||
commit('ADD_HISTORY_ENTRY', [uri, method, Date.now()])
|
dispatch('GET_YUNOHOST_INFOS')
|
||||||
|
router.push(router.currentRoute.query.redirect || { name: 'home' })
|
||||||
},
|
},
|
||||||
|
|
||||||
'SERVER_RESPONDED' ({ state, commit }, success) {
|
'RESET_CONNECTED' ({ commit }) {
|
||||||
const action = state.history.length ? state.history[state.history.length - 1] : null
|
commit('SET_CONNECTED', false)
|
||||||
if (action) {
|
commit('SET_YUNOHOST_INFOS', null)
|
||||||
|
},
|
||||||
|
|
||||||
|
'DISCONNECT' ({ dispatch }, route = router.currentRoute) {
|
||||||
|
dispatch('RESET_CONNECTED')
|
||||||
|
if (router.currentRoute.name === 'login') return
|
||||||
|
router.push({
|
||||||
|
name: 'login',
|
||||||
|
// Add a redirect query if next route is not unknown (like `logout`) or `login`
|
||||||
|
query: route && !['login', null].includes(route.name)
|
||||||
|
? { redirect: route.path }
|
||||||
|
: {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
'LOGIN' ({ dispatch }, password) {
|
||||||
|
return api.post('login', { password }, { websocket: false }).then(() => {
|
||||||
|
dispatch('CONNECT')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
'LOGOUT' ({ dispatch }) {
|
||||||
|
dispatch('DISCONNECT')
|
||||||
|
return api.get('logout')
|
||||||
|
},
|
||||||
|
|
||||||
|
'GET_YUNOHOST_INFOS' ({ commit }) {
|
||||||
|
return api.get('versions').then(versions => {
|
||||||
|
commit('SET_YUNOHOST_INFOS', versions.yunohost)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
'INIT_REQUEST' ({ commit }, { method, uri, initial, wait, websocket }) {
|
||||||
|
let request = { method, uri, initial, status: 'pending' }
|
||||||
|
if (websocket) {
|
||||||
|
request = { ...request, messages: [], date: Date.now(), warnings: 0, errors: 0 }
|
||||||
|
commit('ADD_HISTORY_ACTION', request)
|
||||||
|
}
|
||||||
|
commit('ADD_REQUEST', request)
|
||||||
|
if (wait) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Display the waiting modal only if the request takes some time.
|
||||||
|
if (request.status === 'pending') {
|
||||||
|
commit('SET_WAITING', true)
|
||||||
|
}
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
},
|
||||||
|
|
||||||
|
'END_REQUEST' ({ commit }, { request, success, wait }) {
|
||||||
let status = success ? 'success' : 'error'
|
let status = success ? 'success' : 'error'
|
||||||
if (status === 'success' && action.messages.some(msg => msg.type === 'danger' || msg.type === 'warning')) {
|
if (success && (request.warnings || request.errors)) {
|
||||||
status = 'warning'
|
status = 'warning'
|
||||||
}
|
}
|
||||||
commit('UPDATE_LAST_HISTORY_ENTRY', ['status', status])
|
|
||||||
|
commit('UPDATE_REQUEST', { request, key: 'status', value: status })
|
||||||
|
if (wait) {
|
||||||
|
// Remove the overlay after a short delay to allow an error to display withtout flickering.
|
||||||
|
setTimeout(() => {
|
||||||
|
commit('SET_WAITING', false)
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
commit('UPDATE_WAITING', false)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'DISPATCH_MESSAGE' ({ commit }, messages) {
|
'DISPATCH_MESSAGE' ({ commit }, { request, messages }) {
|
||||||
const typeToColor = { error: 'danger' }
|
|
||||||
for (const type in messages) {
|
for (const type in messages) {
|
||||||
const message = {
|
const message = {
|
||||||
text: messages[type],
|
text: messages[type],
|
||||||
type: type in typeToColor ? typeToColor[type] : type
|
color: type === 'error' ? 'danger' : type
|
||||||
}
|
}
|
||||||
let progressBar = message.text.match(/^\[#*\+*\.*\] > /)
|
let progressBar = message.text.match(/^\[#*\+*\.*\] > /)
|
||||||
if (progressBar) {
|
if (progressBar) {
|
||||||
|
@ -140,15 +171,15 @@ export default {
|
||||||
for (const char of progressBar) {
|
for (const char of progressBar) {
|
||||||
if (char in progress) progress[char] += 1
|
if (char in progress) progress[char] += 1
|
||||||
}
|
}
|
||||||
commit('UPDATE_PROGRESS', Object.values(progress))
|
commit('UPDATE_REQUEST', { request, key: 'progress', value: Object.values(progress) })
|
||||||
}
|
}
|
||||||
if (message.text) {
|
if (message.text) {
|
||||||
commit('ADD_MESSAGE', message)
|
commit('ADD_MESSAGE', { request, message, type })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'HANDLE_ERROR' ({ state, commit, dispatch }, error) {
|
'HANDLE_ERROR' ({ commit, dispatch }, error) {
|
||||||
if (error.code === 401) {
|
if (error.code === 401) {
|
||||||
// Unauthorized
|
// Unauthorized
|
||||||
dispatch('DISCONNECT')
|
dispatch('DISCONNECT')
|
||||||
|
@ -156,23 +187,47 @@ export default {
|
||||||
// Errors that have produced logs
|
// Errors that have produced logs
|
||||||
router.push({ name: 'tool-log', params: { name: error.logRef } })
|
router.push({ name: 'tool-log', params: { name: error.logRef } })
|
||||||
} else {
|
} else {
|
||||||
|
// The request is temporarely stored in the error for reference, but we reverse
|
||||||
|
// the ownership to stay generic.
|
||||||
|
const request = error.request
|
||||||
|
delete error.request
|
||||||
|
Vue.set(request, 'error', error)
|
||||||
// Display the error in a modal on the current view.
|
// Display the error in a modal on the current view.
|
||||||
commit('SET_ERROR', error)
|
commit('SET_ERROR', request)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
'DELETE_ERROR' ({ commit }) {
|
'REVIEW_ERROR' ({ commit }, request) {
|
||||||
|
request.review = true
|
||||||
|
commit('SET_ERROR', request)
|
||||||
|
},
|
||||||
|
|
||||||
|
'DISMISS_ERROR' ({ commit, state }, { initial, review = false }) {
|
||||||
|
if (initial && !review) {
|
||||||
|
// In case of an initial request (data that is needed by a view to render itself),
|
||||||
|
// try to go back so the user doesn't get stuck at a never ending skeleton view.
|
||||||
|
if (history.length > 2) {
|
||||||
|
history.back()
|
||||||
|
} else {
|
||||||
|
// if the url was opened in a new tab, return to home
|
||||||
|
router.push({ name: 'home' })
|
||||||
|
}
|
||||||
|
}
|
||||||
commit('SET_ERROR', null)
|
commit('SET_ERROR', null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
host: state => state.host,
|
host: state => state.host,
|
||||||
connected: state => (state.connected),
|
connected: state => state.connected,
|
||||||
yunohost: state => (state.yunohost),
|
yunohost: state => state.yunohost,
|
||||||
error: state => state.error,
|
error: state => state.error,
|
||||||
waiting: state => state.waiting,
|
waiting: state => state.waiting,
|
||||||
history: state => state.history,
|
history: state => state.history,
|
||||||
lastAction: state => state.history[state.history.length - 1]
|
lastAction: state => state.history[state.history.length - 1],
|
||||||
|
currentRequest: state => {
|
||||||
|
const request = state.requests.find(({ status }) => status === 'pending')
|
||||||
|
return request || state.requests[state.requests.length - 1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,11 @@
|
||||||
<pre><code>{{ error.traceback }}</code></pre>
|
<pre><code>{{ error.traceback }}</code></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="hasMessages">
|
<template v-if="messages">
|
||||||
<p class="my-2">
|
<p class="my-2">
|
||||||
<strong v-t="'api_error.server_said'" />
|
<strong v-t="'api_error.server_said'" />
|
||||||
</p>
|
</p>
|
||||||
<message-list-group :messages="action.messages" bordered />
|
<message-list-group :messages="messages" bordered />
|
||||||
</template>
|
</template>
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
|
|
||||||
|
@ -49,35 +49,34 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
|
|
||||||
import MessageListGroup from '@/components/MessageListGroup'
|
import MessageListGroup from '@/components/MessageListGroup'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ErrorPage',
|
name: 'ErrorDisplay',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MessageListGroup
|
MessageListGroup
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
action: { type: Object, required: true }
|
request: { type: [Object, null], default: null }
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['error']),
|
error () {
|
||||||
|
return this.request.error
|
||||||
|
},
|
||||||
|
|
||||||
hasMessages () {
|
messages () {
|
||||||
return this.action && this.action.messages.length > 0
|
const messages = this.request.messages
|
||||||
|
if (messages && messages.length > 0) return messages
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
dismiss () {
|
dismiss () {
|
||||||
if (this.error && this.error.method === 'GET') {
|
this.$store.dispatch('DISMISS_ERROR', this.request)
|
||||||
history.back()
|
|
||||||
}
|
|
||||||
this.$store.dispatch('DELETE_ERROR')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
@click.prevent="onLastActionClick"
|
@click.prevent="onLastActionClick"
|
||||||
@keyup.enter.space.prevent="onLastActionClick"
|
@keyup.enter.space.prevent="onLastActionClick"
|
||||||
>
|
>
|
||||||
<small>{{ $t('history.' + (lastAction.status === 'pending' ? 'current_action' : 'last_action')) }}</small>
|
<small>{{ $t('history.last_action') }}</small>
|
||||||
</b-button>
|
</b-button>
|
||||||
<query-header v-if="lastAction" :action="lastAction" class="w-auto ml-2 xs-hide" />
|
<query-header v-if="lastAction" :request="lastAction" class="w-auto ml-2 xs-hide" />
|
||||||
</b-card-header>
|
</b-card-header>
|
||||||
|
|
||||||
<b-collapse id="console-collapse" v-model="open">
|
<b-collapse id="console-collapse" v-model="open">
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<!-- ACTION DESC -->
|
<!-- ACTION DESC -->
|
||||||
<query-header
|
<query-header
|
||||||
role="tab" v-b-toggle="action.messages.length ? 'messages-collapse-' + i : false"
|
role="tab" v-b-toggle="action.messages.length ? 'messages-collapse-' + i : false"
|
||||||
:action="action" show-time show-error
|
:request="action" show-time show-error
|
||||||
/>
|
/>
|
||||||
</b-card-header>
|
</b-card-header>
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
<slot name="default" />
|
<slot name="default" />
|
||||||
|
|
||||||
<template v-slot:overlay>
|
<template v-slot:overlay>
|
||||||
<b-card no-body class="card-overlay" v-if="lastAction">
|
<b-card no-body class="card-overlay">
|
||||||
<b-card-header header-bg-variant="white">
|
<b-card-header header-bg-variant="white">
|
||||||
<query-header :action="lastAction" status-size="lg" />
|
<query-header :request="error || currentRequest" status-size="lg" />
|
||||||
</b-card-header>
|
</b-card-header>
|
||||||
|
|
||||||
<component :is="error ? 'ErrorDisplay' : 'WaitingDisplay'" :action="lastAction" />
|
<component v-if="error" :is="'ErrorDisplay'" :request="error" />
|
||||||
|
<component v-else :is="'WaitingDisplay'" :request="currentRequest" />
|
||||||
</b-card>
|
</b-card>
|
||||||
</template>
|
</template>
|
||||||
</b-overlay>
|
</b-overlay>
|
||||||
|
@ -26,13 +27,13 @@ import QueryHeader from '@/components/QueryHeader'
|
||||||
export default {
|
export default {
|
||||||
name: 'ViewLockOverlay',
|
name: 'ViewLockOverlay',
|
||||||
|
|
||||||
computed: mapGetters(['waiting', 'error', 'lastAction']),
|
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ErrorDisplay,
|
ErrorDisplay,
|
||||||
WaitingDisplay,
|
WaitingDisplay,
|
||||||
QueryHeader
|
QueryHeader
|
||||||
}
|
},
|
||||||
|
|
||||||
|
computed: mapGetters(['waiting', 'error', 'currentRequest'])
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -53,8 +54,13 @@ export default {
|
||||||
::v-deep {
|
::v-deep {
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
|
padding-bottom: 0;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<div v-else class="custom-spinner my-4" :class="spinner" />
|
<div v-else class="custom-spinner my-4" :class="spinner" />
|
||||||
|
|
||||||
<message-list-group
|
<message-list-group
|
||||||
v-if="hasMessages" :messages="action.messages"
|
v-if="hasMessages" :messages="request.messages"
|
||||||
bordered fixed-height auto-scroll
|
bordered fixed-height auto-scroll
|
||||||
/>
|
/>
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
|
@ -35,28 +35,28 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
action: { type: Object, required: true }
|
request: { type: Object, required: true }
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['spinner']),
|
...mapGetters(['spinner']),
|
||||||
|
|
||||||
hasMessages () {
|
hasMessages () {
|
||||||
return this.action && this.action.messages.length > 0
|
return this.request.messages && this.request.messages.length > 0
|
||||||
},
|
},
|
||||||
|
|
||||||
progress () {
|
progress () {
|
||||||
const progress = this.action.progress
|
const progress = this.request.progress
|
||||||
if (!progress) return null
|
if (!progress) return null
|
||||||
return {
|
return {
|
||||||
values: progress, max: progress.reduce((sum, value) => (sum + value), 0)
|
values: progress,
|
||||||
|
max: progress.reduce((sum, value) => (sum + value), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.custom-spinner {
|
.custom-spinner {
|
||||||
animation: 4s linear infinite;
|
animation: 4s linear infinite;
|
||||||
|
|
Loading…
Add table
Reference in a new issue