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"
|
||||
variant="danger"
|
||||
>
|
||||
<small>View error</small>
|
||||
<small v-t="'api_error.view_error'" />
|
||||
</b-button>
|
||||
|
||||
<!-- TIME DISPLAY -->
|
||||
|
|
|
@ -1,38 +1,90 @@
|
|||
<template>
|
||||
<div class="error mt-4 mb-5" v-if="error">
|
||||
<h2>{{ $t('api_errors_titles.' + error.name) }} :/</h2>
|
||||
<!-- This card receives style from `ViewLockOverlay` if used inside it -->
|
||||
<div>
|
||||
<b-card-body>
|
||||
<b-card-title v-t="'api_errors_titles.' + error.name" />
|
||||
|
||||
<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')" />
|
||||
<br>{{ $t('api_error.info') }}
|
||||
</div>
|
||||
|
||||
<h5 v-t="'error'" />
|
||||
<pre><code>"{{ error.code }}" {{ error.status }}</code></pre>
|
||||
<!-- FIXME USE DD DL DT -->
|
||||
<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'" />
|
||||
<pre><code>"{{ error.method }}" {{ error.path }}</code></pre>
|
||||
|
||||
<h5>Message</h5>
|
||||
<p v-html="error.message" />
|
||||
<p>
|
||||
<strong v-t="'api_error.error_message'" /> <span v-html="error.message" />
|
||||
</p>
|
||||
|
||||
<template v-if="error.traceback">
|
||||
<h5 v-t="'traceback'" />
|
||||
<pre><code class="text-dark">{{ error.traceback }}</code></pre>
|
||||
<p>
|
||||
<strong v-t="'traceback'" />
|
||||
</p>
|
||||
<pre><code>{{ error.traceback }}</code></pre>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import MessageListGroup from '@/components/MessageListGroup'
|
||||
|
||||
export default {
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
code, pre code {
|
||||
color: $black;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,38 +1,18 @@
|
|||
<template>
|
||||
<b-overlay
|
||||
variant="white" rounded="sm" opacity="0.5"
|
||||
variant="white" opacity="0.75"
|
||||
no-center
|
||||
:show="show"
|
||||
:show="waiting || error !== null"
|
||||
>
|
||||
<slot name="default" />
|
||||
|
||||
<template v-slot:overlay>
|
||||
<b-card no-body>
|
||||
<div v-if="error === null" class="mt-3 px-3">
|
||||
<div class="custom-spinner" :class="spinner" />
|
||||
</div>
|
||||
<b-card no-body class="card-overlay" v-if="lastAction">
|
||||
<b-card-header header-bg-variant="white">
|
||||
<query-header :action="lastAction" status-size="lg" />
|
||||
</b-card-header>
|
||||
|
||||
<b-card-body v-if="error">
|
||||
<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>
|
||||
<component :is="error ? 'ErrorDisplay' : 'WaitingDisplay'" :action="lastAction" />
|
||||
</b-card>
|
||||
</template>
|
||||
</b-overlay>
|
||||
|
@ -40,102 +20,52 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { ErrorDisplay } from '@/views/_partials'
|
||||
import { ErrorDisplay, WaitingDisplay } from '@/views/_partials'
|
||||
import QueryHeader from '@/components/QueryHeader'
|
||||
|
||||
export default {
|
||||
name: 'ViewLockOverlay',
|
||||
|
||||
computed: {
|
||||
...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)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: mapGetters(['waiting', 'error', 'lastAction']),
|
||||
|
||||
components: {
|
||||
ErrorDisplay
|
||||
ErrorDisplay,
|
||||
WaitingDisplay,
|
||||
QueryHeader
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
// Style for `ErrorDisplay` and `WaitingDisplay`'s cards
|
||||
.card-overlay {
|
||||
position: sticky;
|
||||
top: 5vh;
|
||||
top: 10vh;
|
||||
margin: 0 5%;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin: 0 10%;
|
||||
}
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin: 0 20%;
|
||||
}
|
||||
margin: 0 15%;
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.card-body {
|
||||
padding-bottom: 2rem;
|
||||
max-height: 50vh;
|
||||
padding: 1.5rem;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.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;}
|
||||
.card-footer {
|
||||
padding: .5rem .75rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
&.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;}
|
||||
}
|
||||
.card-header {
|
||||
padding: .5rem .75rem;
|
||||
}
|
||||
}
|
||||
</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 WaitingDisplay } from './WaitingDisplay'
|
||||
|
||||
export { default as HistoryConsole } from './HistoryConsole'
|
||||
export { default as ViewLockOverlay } from './ViewLockOverlay'
|
||||
|
||||
export { default as DomainForm } from './DomainForm'
|
||||
export { default as PasswordForm } from './PasswordForm'
|
||||
|
|
Loading…
Reference in a new issue