mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
rework Waiting/Error display and Overlay
This commit is contained in:
parent
957ca17b84
commit
9c85c42b98
5 changed files with 211 additions and 119 deletions
|
@ -27,7 +27,7 @@
|
||||||
class="error-btn ml-auto py-0"
|
class="error-btn ml-auto py-0"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
<small>View error</small>
|
<small v-t="'api_error.view_error'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|
||||||
<!-- TIME DISPLAY -->
|
<!-- TIME DISPLAY -->
|
||||||
|
|
|
@ -1,38 +1,90 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="error mt-4 mb-5" v-if="error">
|
<!-- This card receives style from `ViewLockOverlay` if used inside it -->
|
||||||
<h2>{{ $t('api_errors_titles.' + error.name) }} :/</h2>
|
<div>
|
||||||
|
<b-card-body>
|
||||||
|
<b-card-title v-t="'api_errors_titles.' + error.name" />
|
||||||
|
|
||||||
<em v-t="'api_error.sorry'" />
|
<em v-t="'api_error.sorry'" />
|
||||||
|
|
||||||
<div class="alert alert-info mt-4">
|
<div class="alert alert-info my-3">
|
||||||
<span v-html="$t('api_error.help')" />
|
<span v-html="$t('api_error.help')" />
|
||||||
<br>{{ $t('api_error.info') }}
|
<br>{{ $t('api_error.info') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 v-t="'error'" />
|
<!-- FIXME USE DD DL DT -->
|
||||||
<pre><code>"{{ error.code }}" {{ error.status }}</code></pre>
|
<p class="m-0">
|
||||||
|
<strong v-t="'error'" />: <code>"{{ error.code }}" {{ error.status }}</code>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong v-t="'action'" />: <code>"{{ error.method }}" {{ error.path }}</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h5 v-t="'action'" />
|
<p>
|
||||||
<pre><code>"{{ error.method }}" {{ error.path }}</code></pre>
|
<strong v-t="'api_error.error_message'" /> <span v-html="error.message" />
|
||||||
|
</p>
|
||||||
<h5>Message</h5>
|
|
||||||
<p v-html="error.message" />
|
|
||||||
|
|
||||||
<template v-if="error.traceback">
|
<template v-if="error.traceback">
|
||||||
<h5 v-t="'traceback'" />
|
<p>
|
||||||
<pre><code class="text-dark">{{ error.traceback }}</code></pre>
|
<strong v-t="'traceback'" />
|
||||||
|
</p>
|
||||||
|
<pre><code>{{ error.traceback }}</code></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="hasMessages">
|
||||||
|
<p class="my-2">
|
||||||
|
<strong v-t="'api_error.server_said'" />
|
||||||
|
</p>
|
||||||
|
<message-list-group :messages="action.messages" bordered />
|
||||||
|
</template>
|
||||||
|
</b-card-body>
|
||||||
|
|
||||||
|
<b-card-footer footer-bg-variant="danger">
|
||||||
|
<!-- TODO add copy error ? -->
|
||||||
|
<b-button
|
||||||
|
variant="light" size="sm"
|
||||||
|
v-t="'words.dismiss'" @click="dismiss"
|
||||||
|
/>
|
||||||
|
</b-card-footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
import MessageListGroup from '@/components/MessageListGroup'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ErrorPage',
|
name: 'ErrorPage',
|
||||||
|
|
||||||
computed: mapGetters(['error'])
|
components: {
|
||||||
|
MessageListGroup
|
||||||
|
},
|
||||||
|
|
||||||
// FIXME add redirect if they're no error (if reload or route entered by hand)
|
props: {
|
||||||
|
action: { type: Object, required: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['error']),
|
||||||
|
|
||||||
|
hasMessages () {
|
||||||
|
return this.action && this.action.messages.length > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
dismiss () {
|
||||||
|
if (this.error && this.error.method === 'GET') {
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
this.$store.dispatch('DELETE_ERROR')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
code, pre code {
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,38 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<b-overlay
|
<b-overlay
|
||||||
variant="white" rounded="sm" opacity="0.5"
|
variant="white" opacity="0.75"
|
||||||
no-center
|
no-center
|
||||||
:show="show"
|
:show="waiting || error !== null"
|
||||||
>
|
>
|
||||||
<slot name="default" />
|
<slot name="default" />
|
||||||
|
|
||||||
<template v-slot:overlay>
|
<template v-slot:overlay>
|
||||||
<b-card no-body>
|
<b-card no-body class="card-overlay" v-if="lastAction">
|
||||||
<div v-if="error === null" class="mt-3 px-3">
|
<b-card-header header-bg-variant="white">
|
||||||
<div class="custom-spinner" :class="spinner" />
|
<query-header :action="lastAction" status-size="lg" />
|
||||||
</div>
|
</b-card-header>
|
||||||
|
|
||||||
<b-card-body v-if="error">
|
<component :is="error ? 'ErrorDisplay' : 'WaitingDisplay'" :action="lastAction" />
|
||||||
<error-display />
|
|
||||||
</b-card-body>
|
|
||||||
|
|
||||||
<b-card-body v-else class="pb-4">
|
|
||||||
<b-card-title class="text-center m-0" v-t="'api_waiting'" />
|
|
||||||
|
|
||||||
<!-- PROGRESS BAR -->
|
|
||||||
<b-progress
|
|
||||||
v-if="progress" class="mt-4"
|
|
||||||
:max="progress.max" height=".5rem"
|
|
||||||
>
|
|
||||||
<b-progress-bar variant="success" :value="progress.values[0]" />
|
|
||||||
<b-progress-bar variant="warning" :value="progress.values[1]" animated />
|
|
||||||
<b-progress-bar variant="secondary" :value="progress.values[2]" striped />
|
|
||||||
</b-progress>
|
|
||||||
</b-card-body>
|
|
||||||
|
|
||||||
<b-card-footer v-if="error" class="justify-content-end">
|
|
||||||
<b-button variant="primary" v-t="'ok'" @click="$store.dispatch('DELETE_ERROR')" />
|
|
||||||
</b-card-footer>
|
|
||||||
</b-card>
|
</b-card>
|
||||||
</template>
|
</template>
|
||||||
</b-overlay>
|
</b-overlay>
|
||||||
|
@ -40,102 +20,52 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { ErrorDisplay } from '@/views/_partials'
|
import { ErrorDisplay, WaitingDisplay } from '@/views/_partials'
|
||||||
|
import QueryHeader from '@/components/QueryHeader'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ViewLockOverlay',
|
name: 'ViewLockOverlay',
|
||||||
|
|
||||||
computed: {
|
computed: mapGetters(['waiting', 'error', 'lastAction']),
|
||||||
...mapGetters(['waiting', 'lastAction', 'error', 'spinner']),
|
|
||||||
|
|
||||||
show () {
|
|
||||||
return this.waiting || this.error !== null
|
|
||||||
},
|
|
||||||
|
|
||||||
progress () {
|
|
||||||
if (!this.lastAction) return null
|
|
||||||
const progress = this.lastAction.progress
|
|
||||||
if (!progress) return null
|
|
||||||
return {
|
|
||||||
values: progress, max: progress.reduce((sum, value) => (sum + value), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ErrorDisplay
|
ErrorDisplay,
|
||||||
|
WaitingDisplay,
|
||||||
|
QueryHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.card {
|
// Style for `ErrorDisplay` and `WaitingDisplay`'s cards
|
||||||
|
.card-overlay {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 5vh;
|
top: 10vh;
|
||||||
margin: 0 5%;
|
margin: 0 5%;
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
margin: 0 10%;
|
margin: 0 10%;
|
||||||
}
|
}
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
margin: 0 20%;
|
margin: 0 15%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
::v-deep {
|
||||||
padding-bottom: 2rem;
|
.card-body {
|
||||||
max-height: 50vh;
|
padding: 1.5rem;
|
||||||
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.card-footer {
|
||||||
margin-top: 2rem;
|
padding: .5rem .75rem;
|
||||||
}
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
.custom-spinner {
|
|
||||||
animation: 4s 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 {
|
.card-header {
|
||||||
height: 32px;
|
padding: .5rem .75rem;
|
||||||
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;}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
107
app/src/views/_partials/WaitingDisplay.vue
Normal file
107
app/src/views/_partials/WaitingDisplay.vue
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<template>
|
||||||
|
<!-- This card receives style from `ViewLockOverlay` if used inside it -->
|
||||||
|
<b-card-body>
|
||||||
|
<b-card-title class="text-center mt-4" v-t="hasMessages ? 'api.processing' : 'api_waiting'" />
|
||||||
|
|
||||||
|
<!-- PROGRESS BAR -->
|
||||||
|
<b-progress
|
||||||
|
v-if="progress" class="mt-4"
|
||||||
|
:max="progress.max" height=".5rem"
|
||||||
|
>
|
||||||
|
<b-progress-bar variant="success" :value="progress.values[0]" />
|
||||||
|
<b-progress-bar variant="warning" :value="progress.values[1]" animated />
|
||||||
|
<b-progress-bar variant="secondary" :value="progress.values[2]" striped />
|
||||||
|
</b-progress>
|
||||||
|
<!-- OR SPINNER -->
|
||||||
|
<div v-else class="custom-spinner my-4" :class="spinner" />
|
||||||
|
|
||||||
|
<message-list-group
|
||||||
|
v-if="hasMessages" :messages="action.messages"
|
||||||
|
bordered fixed-height auto-scroll
|
||||||
|
/>
|
||||||
|
</b-card-body>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
import MessageListGroup from '@/components/MessageListGroup'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WaitingDisplay',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
MessageListGroup
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
action: { type: Object, required: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['spinner']),
|
||||||
|
|
||||||
|
hasMessages () {
|
||||||
|
return this.action && this.action.messages.length > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
progress () {
|
||||||
|
const progress = this.action.progress
|
||||||
|
if (!progress) return null
|
||||||
|
return {
|
||||||
|
values: progress, max: progress.reduce((sum, value) => (sum + value), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-spinner {
|
||||||
|
animation: 4s 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;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,8 @@
|
||||||
export { default as ErrorDisplay } from './ErrorDisplay'
|
export { default as ErrorDisplay } from './ErrorDisplay'
|
||||||
|
export { default as WaitingDisplay } from './WaitingDisplay'
|
||||||
|
|
||||||
export { default as HistoryConsole } from './HistoryConsole'
|
export { default as HistoryConsole } from './HistoryConsole'
|
||||||
export { default as ViewLockOverlay } from './ViewLockOverlay'
|
export { default as ViewLockOverlay } from './ViewLockOverlay'
|
||||||
|
|
||||||
export { default as DomainForm } from './DomainForm'
|
export { default as DomainForm } from './DomainForm'
|
||||||
export { default as PasswordForm } from './PasswordForm'
|
export { default as PasswordForm } from './PasswordForm'
|
||||||
|
|
Loading…
Reference in a new issue