mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
add YnhConsole component to display history
This commit is contained in:
parent
23aa8bba64
commit
a86e90e49a
4 changed files with 223 additions and 32 deletions
|
@ -3,7 +3,10 @@
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<header>
|
<header>
|
||||||
<b-navbar>
|
<b-navbar>
|
||||||
<b-navbar-brand :to="{ name: 'home' }" exact exact-active-class="active">
|
<b-navbar-brand
|
||||||
|
:to="{ name: 'home' }" :disabled="waiting"
|
||||||
|
exact exact-active-class="active"
|
||||||
|
>
|
||||||
<img alt="Yunohost logo" src="./assets/logo.png">
|
<img alt="Yunohost logo" src="./assets/logo.png">
|
||||||
</b-navbar-brand>
|
</b-navbar-brand>
|
||||||
|
|
||||||
|
@ -40,8 +43,12 @@
|
||||||
<router-view v-else class="static" />
|
<router-view v-else class="static" />
|
||||||
</main>
|
</main>
|
||||||
</api-wait-overlay>
|
</api-wait-overlay>
|
||||||
|
|
||||||
|
<!-- CONSOLE/HISTORY -->
|
||||||
|
<ynh-console @height-changed="consoleHeight = $event" class="mt-auto" />
|
||||||
|
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER -->
|
||||||
<footer>
|
<footer :style="'padding-bottom: ' + consoleHeight + 'px;'">
|
||||||
<nav>
|
<nav>
|
||||||
<b-nav class="justify-content-center">
|
<b-nav class="justify-content-center">
|
||||||
<b-nav-item href="https://yunohost.org/docs" target="_blank" link-classes="text-secondary">
|
<b-nav-item href="https://yunohost.org/docs" target="_blank" link-classes="text-secondary">
|
||||||
|
@ -79,22 +86,27 @@
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
import ApiWaitOverlay from '@/components/ApiWaitOverlay'
|
import ApiWaitOverlay from '@/components/ApiWaitOverlay'
|
||||||
|
import YnhConsole from '@/components/YnhConsole'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
|
||||||
data: () => ({
|
data () {
|
||||||
transitionName: null
|
return {
|
||||||
}),
|
transitionName: null,
|
||||||
|
// Value used to add padding to the footer so the opened console never hides content
|
||||||
|
consoleHeight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['connected', 'yunohost', 'transitions'])
|
...mapGetters(['connected', 'yunohost', 'transitions', 'waiting'])
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
// Set the css class to animate the components transition
|
// Set the css class to animate the components transition
|
||||||
'$route' (to, from) {
|
'$route' (to, from) {
|
||||||
if (!this.transitions) return
|
if (!this.transitions || from.name === null) return
|
||||||
// Use the breadcrumb array length as a direction indicator
|
// Use the breadcrumb array length as a direction indicator
|
||||||
const toDepth = to.meta.breadcrumb.length
|
const toDepth = to.meta.breadcrumb.length
|
||||||
const fromDepth = from.meta.breadcrumb.length
|
const fromDepth = from.meta.breadcrumb.length
|
||||||
|
@ -109,7 +121,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ApiWaitOverlay
|
ApiWaitOverlay,
|
||||||
|
YnhConsole
|
||||||
},
|
},
|
||||||
|
|
||||||
// This hook is only triggered at page first load
|
// This hook is only triggered at page first load
|
||||||
|
@ -126,9 +139,19 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
// Global import of Bootstrap and custom styles
|
||||||
@import '@/scss/main.scss';
|
@import '@/scss/main.scss';
|
||||||
|
</style>
|
||||||
|
|
||||||
#app > header {
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
::v-deep#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
border-bottom: $thin-border;
|
border-bottom: $thin-border;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
@ -146,28 +169,6 @@ export default {
|
||||||
li {
|
li {
|
||||||
margin: .2rem 0;
|
margin: .2rem 0;
|
||||||
}
|
}
|
||||||
icon {
|
|
||||||
margin-left: .5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#app > footer {
|
|
||||||
padding: 1rem 0;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
& + .nav-item a::before {
|
|
||||||
content: "•";
|
|
||||||
width: 1rem;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: -1.15rem;
|
|
||||||
}
|
|
||||||
&:first-child {
|
|
||||||
margin-left: -1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,4 +198,33 @@ main {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#console {
|
||||||
|
// Allows the console to be tabbed before the footer links while remaining visually
|
||||||
|
// the last element of the page
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding: 1rem 0 3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
& + .nav-item a::before {
|
||||||
|
content: "•";
|
||||||
|
width: 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: -1.15rem;
|
||||||
|
}
|
||||||
|
&:first-child {
|
||||||
|
margin-left: -1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
145
app/src/components/YnhConsole.vue
Normal file
145
app/src/components/YnhConsole.vue
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<div id="console">
|
||||||
|
<b-list-group>
|
||||||
|
<!-- HISTORY BAR -->
|
||||||
|
<b-list-group-item class="d-flex align-items-center" :class="{ 'bg-best text-white': open }">
|
||||||
|
<h6 class="m-0">
|
||||||
|
<icon iname="history" /> {{ $t('history.title') }}
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<!-- LAST ACTION -->
|
||||||
|
<small v-if="lastAction">
|
||||||
|
<u v-t="'history.last_action'" />
|
||||||
|
{{ lastAction.uri | readableUri }} ({{ $t('history.methods.' + lastAction.method) }})
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<b-button
|
||||||
|
v-b-toggle:collapse
|
||||||
|
class="ml-2 px-1 py-0" size="sm" :variant="open ? 'light' : 'outline-dark'"
|
||||||
|
>
|
||||||
|
<icon iname="chevron-right" /><span class="sr-only">{{ $t('words.collapse') }}</span>
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</b-list-group-item>
|
||||||
|
|
||||||
|
<!-- ACTION LIST -->
|
||||||
|
<b-collapse id="collapse" v-model="open">
|
||||||
|
<b-list-group-item class="p-0" id="history" ref="history">
|
||||||
|
<!-- ACTION -->
|
||||||
|
<b-list-group v-for="(action, i) in history" :key="i" flush>
|
||||||
|
<!-- ACTION DESC -->
|
||||||
|
<b-list-group-item class="sticky-top d-flex align-items-center" variant="dark">
|
||||||
|
<div>
|
||||||
|
<strong>{{ $t('action') }}:</strong>
|
||||||
|
{{ action.uri | readableUri }}
|
||||||
|
<small>({{ $t('history.methods.' + action.method) }})</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<time :datetime="action.date | hour" class="ml-auto">{{ action.date | hour }}</time>
|
||||||
|
</b-list-group-item>
|
||||||
|
|
||||||
|
<!-- ACTION MESSAGE -->
|
||||||
|
<b-list-group-item v-for="({ type, text }, j) in action.messages" :key="j">
|
||||||
|
<icon iname="comment" :class="'text-' + type" /> <span v-html="text" />
|
||||||
|
</b-list-group-item>
|
||||||
|
</b-list-group>
|
||||||
|
</b-list-group-item>
|
||||||
|
</b-collapse>
|
||||||
|
</b-list-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'YnhConsole',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
height: { type: [Number, String], default: 30 }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
async open (value) {
|
||||||
|
// In case it is needed.
|
||||||
|
this.$emit('toggle', value)
|
||||||
|
if (!value) {
|
||||||
|
// Reset footer padding.
|
||||||
|
this.$emit('height-changed', 0)
|
||||||
|
} else {
|
||||||
|
// Wait for DOM update.
|
||||||
|
await this.$nextTick()
|
||||||
|
// Send history's elem height so the footer can update its padding.
|
||||||
|
this.$emit('height-changed', this.$refs.history.clientHeight)
|
||||||
|
// Scroll to the last action.
|
||||||
|
const lastActionItem = document.querySelector('#history > .list-group:last-of-type')
|
||||||
|
if (lastActionItem) {
|
||||||
|
lastActionItem.scrollIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'lastAction.messages' (a, b) {
|
||||||
|
if (!this.open) return
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document.querySelector('#history > .list-group:last-of-type').scrollIntoView()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: mapGetters(['history', 'lastAction']),
|
||||||
|
|
||||||
|
filters: {
|
||||||
|
readableUri (uri) {
|
||||||
|
return uri.split('?')[0].replace('/', ' > ')
|
||||||
|
},
|
||||||
|
|
||||||
|
hour (date) {
|
||||||
|
return new Date(date).toLocaleTimeString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#console {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 10;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
width: calc(100% + 3rem);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
margin-left: -15px;
|
||||||
|
width: calc(100% + 30px);
|
||||||
|
|
||||||
|
& > .list-group {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#history {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collapse {
|
||||||
|
// disable collapse animation
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
padding: $tooltip-padding-y $tooltip-padding-x;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,6 +7,7 @@
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"all_apps": "All apps",
|
"all_apps": "All apps",
|
||||||
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
|
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
|
||||||
|
"api_waiting": "Waiting for the server's response...",
|
||||||
"app_actions": "Actions",
|
"app_actions": "Actions",
|
||||||
"app_actions_label": "Perform actions",
|
"app_actions_label": "Perform actions",
|
||||||
"app_change_label": "Change Label",
|
"app_change_label": "Change Label",
|
||||||
|
@ -166,6 +167,16 @@
|
||||||
"groups_and_permissions": "Groups and permissions",
|
"groups_and_permissions": "Groups and permissions",
|
||||||
"groups_and_permissions_manage": "Manage groups and permissions",
|
"groups_and_permissions_manage": "Manage groups and permissions",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
|
"history": {
|
||||||
|
"title": "History",
|
||||||
|
"last_action": "Last action:",
|
||||||
|
"action": "Action:",
|
||||||
|
"methods": {
|
||||||
|
"POST": "create/execute",
|
||||||
|
"PUT": "modify",
|
||||||
|
"DELETE": "delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"hook_adminjs_group_configuration": "System configurations",
|
"hook_adminjs_group_configuration": "System configurations",
|
||||||
"hook_conf_cron": "Automatic tasks",
|
"hook_conf_cron": "Automatic tasks",
|
||||||
|
@ -245,6 +256,7 @@
|
||||||
"passwords_dont_match": "Passwords don't match",
|
"passwords_dont_match": "Passwords don't match",
|
||||||
"passwords_too_short": "Password is too short",
|
"passwords_too_short": "Password is too short",
|
||||||
"path": "Path",
|
"path": "Path",
|
||||||
|
"perform": "Perform",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"username": "johndoe",
|
"username": "johndoe",
|
||||||
"firstname": "John",
|
"firstname": "John",
|
||||||
|
@ -294,6 +306,7 @@
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"run": "Run",
|
"run": "Run",
|
||||||
|
"running": "Running",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"search": {
|
"search": {
|
||||||
"domain": "Search for domains...",
|
"domain": "Search for domains...",
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
// Style overrides happens after dependencies imports
|
// Style overrides happens after dependencies imports
|
||||||
|
|
||||||
// Bootstrap overrides
|
// Bootstrap overrides
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Source Sans Pro", "Helvetica Neue", "Fira Sans", Helvetica, Arial, sans-serif;
|
font-family: "Source Sans Pro", "Helvetica Neue", "Fira Sans", Helvetica, Arial, sans-serif;
|
||||||
|
@ -104,7 +108,6 @@ body {
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fork-awesome overrides
|
// Fork-awesome overrides
|
||||||
.fa-fw {
|
.fa-fw {
|
||||||
width: 1.25em !important;
|
width: 1.25em !important;
|
||||||
|
|
Loading…
Reference in a new issue