mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
linting…
This commit is contained in:
parent
d69bb8b906
commit
6f8457381a
15 changed files with 577 additions and 546 deletions
249
app/src/App.vue
249
app/src/App.vue
|
@ -1,48 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app" class="container">
|
<div id="app" class="container">
|
||||||
<header>
|
<header>
|
||||||
<b-navbar>
|
<b-navbar>
|
||||||
<b-navbar-brand to="/" exact exact-active-class="active"><img alt="Yunohost logo" src="./assets/logo.png"></b-navbar-brand>
|
<b-navbar-brand to="/" exact exact-active-class="active">
|
||||||
<b-navbar-nav class="ml-auto">
|
<img alt="Yunohost logo" src="./assets/logo.png">
|
||||||
<li class="nav-item">
|
</b-navbar-brand>
|
||||||
<b-button href="/yunohost/sso" variant="primary" block size="sm">
|
<b-navbar-nav class="ml-auto">
|
||||||
{{ $t('user_interface_link') }} <icon iname="user"/>
|
<li class="nav-item">
|
||||||
</b-button>
|
<b-button href="/yunohost/sso"
|
||||||
</li>
|
variant="primary" size="sm" block
|
||||||
<li class="nav-item" v-show="connected">
|
>
|
||||||
<b-button @click.prevent="logout" to="/logout" variant="outline-dark" block size="sm" >
|
{{ $t('user_interface_link') }} <icon iname="user" />
|
||||||
{{ $t('logout') }} <icon iname="sign-out"/>
|
</b-button>
|
||||||
</b-button>
|
</li>
|
||||||
</li>
|
<li class="nav-item" v-show="connected">
|
||||||
</b-navbar-nav>
|
<b-button @click.prevent="logout" to="/logout"
|
||||||
</b-navbar>
|
variant="outline-dark" block size="sm"
|
||||||
</header>
|
>
|
||||||
|
{{ $t('logout') }} <icon iname="sign-out" />
|
||||||
|
</b-button>
|
||||||
|
</li>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<router-view v-if="isReady"/>
|
<router-view v-if="isReady" />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<nav>
|
<nav>
|
||||||
<b-nav>
|
<b-nav>
|
||||||
<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">
|
||||||
<icon iname="book"/> Documentation
|
<icon iname="book" /> Documentation
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-item href="https://yunohost.org/help" target="_blank" link-classes='text-secondary'>
|
<b-nav-item href="https://yunohost.org/help" target="_blank" link-classes="text-secondary">
|
||||||
<icon iname="life-ring"/> Need help?
|
<icon iname="life-ring" /> Need help?
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-item href="https://donate.yunohost.org/" target="_blank" link-classes='text-secondary'>
|
<b-nav-item href="https://donate.yunohost.org/" target="_blank" link-classes="text-secondary">
|
||||||
<icon iname="heart"/> Donate
|
<icon iname="heart" /> Donate
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<i18n v-if="yunohostInfos" path="footer_version" tag="b-nav-text" class="ml-auto" id="yunohost-version">
|
<i18n v-if="yunohostInfos" path="footer_version" tag="b-nav-text"
|
||||||
<template v-slot:ynh><b-link href="https://yunohost.org">YunoHost</b-link></template>
|
id="yunohost-version" class="ml-auto"
|
||||||
<template v-slot:version>{{ yunohostInfos.version }}</template>
|
>
|
||||||
<template v-slot:repo>{{ yunohostInfos.repo }}</template>
|
<template v-slot:ynh>
|
||||||
</i18n>
|
<b-link href="https://yunohost.org">
|
||||||
</b-nav>
|
YunoHost
|
||||||
</nav>
|
</b-link>
|
||||||
</footer>
|
</template>
|
||||||
</div>
|
<template v-slot:version>
|
||||||
|
{{ yunohostInfos.version }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:repo>
|
||||||
|
{{ yunohostInfos.repo }}
|
||||||
|
</template>
|
||||||
|
</i18n>
|
||||||
|
</b-nav>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -50,50 +66,49 @@ import { mapState } from 'vuex'
|
||||||
|
|
||||||
import api from '@/helpers/api'
|
import api from '@/helpers/api'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
// isReady blocks the rendering of the rooter-view until we have a true info
|
// isReady blocks the rendering of the rooter-view until we have a true info
|
||||||
// about the connected state of the user.
|
// about the connected state of the user.
|
||||||
isReady: false,
|
isReady: false
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['connected', 'yunohostInfos']),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async logout() {
|
|
||||||
await api.logout()
|
|
||||||
this.$store.commit('CONNECTED', false);
|
|
||||||
this.$router.push('/login')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// This hook is only triggered at page reload so the value of state.connected
|
|
||||||
// always come from the localStorage
|
|
||||||
async created() {
|
|
||||||
if (!this.$store.state.connected) {
|
|
||||||
// user is not connected: allow the login view to be rendered.
|
|
||||||
this.isReady = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// localStorage 'connected' value may be true, but session may have expired.
|
|
||||||
// Try to get the yunohost version.
|
|
||||||
try {
|
|
||||||
const data = await api.getVersion()
|
|
||||||
this.$store.commit('YUNOHOST_INFOS', data.yunohost)
|
|
||||||
} catch (err) {
|
|
||||||
// Session expired, reset the 'connected' state and redirect with a query
|
|
||||||
// FIXME is there a case where the error may not be a 401 therefor requires
|
|
||||||
// better handling ?
|
|
||||||
this.$store.commit('CONNECTED', false);
|
|
||||||
this.$router.push({name: 'login', query: {redirect: this.$route.path}})
|
|
||||||
} finally {
|
|
||||||
// in any case allow the router-view to be rendered
|
|
||||||
this.isReady = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['connected', 'yunohostInfos'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async logout () {
|
||||||
|
await api.logout()
|
||||||
|
this.$store.commit('CONNECTED', false)
|
||||||
|
this.$router.push('/login')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// This hook is only triggered at page reload so the value of state.connected
|
||||||
|
// always come from the localStorage
|
||||||
|
async created () {
|
||||||
|
if (!this.$store.state.connected) {
|
||||||
|
// user is not connected: allow the login view to be rendered.
|
||||||
|
this.isReady = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// localStorage 'connected' value may be true, but session may have expired.
|
||||||
|
// Try to get the yunohost version.
|
||||||
|
try {
|
||||||
|
const data = await api.getVersion()
|
||||||
|
this.$store.commit('YUNOHOST_INFOS', data.yunohost)
|
||||||
|
} catch (err) {
|
||||||
|
// Session expired, reset the 'connected' state and redirect with a query
|
||||||
|
// FIXME is there a case where the error may not be a 401 therefor requires
|
||||||
|
// better handling ?
|
||||||
|
this.$store.commit('CONNECTED', false)
|
||||||
|
this.$router.push({ name: 'login', query: { redirect: this.$route.path } })
|
||||||
|
} finally {
|
||||||
|
// in any case allow the router-view to be rendered
|
||||||
|
this.isReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -101,46 +116,46 @@ export default {
|
||||||
@import '@/scss/main.scss';
|
@import '@/scss/main.scss';
|
||||||
|
|
||||||
#app > header {
|
#app > header {
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: .2rem 0;
|
|
||||||
}
|
|
||||||
icon {
|
|
||||||
margin-left: .5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: .2rem 0;
|
||||||
|
}
|
||||||
|
icon {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#app > footer {
|
#app > footer {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
& + .nav-item a::before {
|
& + .nav-item a::before {
|
||||||
content: "•";
|
content: "•";
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: -1.15rem;
|
margin-left: -1.15rem;
|
||||||
}
|
|
||||||
&:first-child {
|
|
||||||
margin-left: -1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
&:first-child {
|
||||||
|
margin-left: -1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
<template>
|
<template>
|
||||||
<b-breadcrumb>
|
<b-breadcrumb>
|
||||||
<b-breadcrumb-item to="/">
|
<b-breadcrumb-item to="/">
|
||||||
<span class="sr-only">{{ $t('home') }}</span>
|
<span class="sr-only">{{ $t('home') }}</span>
|
||||||
<icon iname="home"/>
|
<icon iname="home" />
|
||||||
</b-breadcrumb-item>
|
</b-breadcrumb-item>
|
||||||
<b-breadcrumb-item
|
<b-breadcrumb-item
|
||||||
v-for="(route, index) in breadcrumb"
|
v-for="(route, index) in breadcrumb"
|
||||||
:key="index"
|
:key="index"
|
||||||
:to="{name: route.name}"
|
:to="{name: route.name}"
|
||||||
:active="index == lastIndex ? true : false"
|
:active="index == lastIndex ? true : false"
|
||||||
>
|
>
|
||||||
{{ route.text }}
|
{{ route.text }}
|
||||||
</b-breadcrumb-item>
|
</b-breadcrumb-item>
|
||||||
</b-breadcrumb>
|
</b-breadcrumb>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
params: function () {
|
params: function () {
|
||||||
return this.$route.params
|
return this.$route.params
|
||||||
},
|
|
||||||
breadcrumb: function () {
|
|
||||||
return this.$route.meta.breadcrumb.map(({name, trad, param}) => {
|
|
||||||
let text = ''
|
|
||||||
// if a traduction key string has been given and we also need to pass
|
|
||||||
// the route param as a variable.
|
|
||||||
if (trad && param) {
|
|
||||||
text = this.$i18n.t(trad, {[param]: this.params[param]})
|
|
||||||
} else if (trad) {
|
|
||||||
text = this.$i18n.t(trad)
|
|
||||||
} else {
|
|
||||||
text = this.params[param]
|
|
||||||
}
|
|
||||||
return {name, text}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lastIndex: function () {
|
|
||||||
return this.breadcrumb.length - 1
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
breadcrumb: function () {
|
||||||
|
return this.$route.meta.breadcrumb.map(({ name, trad, param }) => {
|
||||||
|
let text = ''
|
||||||
|
// if a traduction key string has been given and we also need to pass
|
||||||
|
// the route param as a variable.
|
||||||
|
if (trad && param) {
|
||||||
|
text = this.$i18n.t(trad, { [param]: this.params[param] })
|
||||||
|
} else if (trad) {
|
||||||
|
text = this.$i18n.t(trad)
|
||||||
|
} else {
|
||||||
|
text = this.params[param]
|
||||||
|
}
|
||||||
|
return { name, text }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
lastIndex: function () {
|
||||||
|
return this.breadcrumb.length - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<span :class="'icon fa fa-' + iname" aria-hidden="true"></span>
|
<span :class="'icon fa fa-' + iname" aria-hidden="true" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'icon',
|
name: 'Icon',
|
||||||
props: ['iname'],
|
props: {
|
||||||
|
iname: { type: String, required: true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.icon {
|
.icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.lg {
|
||||||
|
width: 3rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fs-sm {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
width: 1rem;
|
}
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&.lg {
|
|
||||||
width: 3rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fs-sm {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export {default as Icon} from './Icon'
|
export { default as Icon } from './Icon'
|
||||||
|
|
|
@ -1,63 +1,57 @@
|
||||||
|
function objectToParams (object) {
|
||||||
|
const urlParams = new URLSearchParams()
|
||||||
function objectToParams(object) {
|
for (const [key, value] of Object.entries(object)) {
|
||||||
const urlParams = new URLSearchParams();
|
urlParams.append(key, value)
|
||||||
for (const [key, value] of Object.entries(object)) {
|
}
|
||||||
urlParams.append(key, value)
|
return urlParams
|
||||||
}
|
|
||||||
return urlParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleResponse (response, type = 'json') {
|
||||||
function handleResponse(response, type = 'json') {
|
return response.ok ? response[type]() : handleErrors(response)
|
||||||
return response.ok ? response[type]() : handleErrors(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleErrors (response) {
|
||||||
function handleErrors(response) {
|
if (response.status === 401) {
|
||||||
if (response.status == 401) {
|
throw new Error('Unauthorized')
|
||||||
throw new Error('Unauthorized');
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
options: {
|
options: {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
// FIXME is it important to keep this previous `Accept` header ?
|
// FIXME is it important to keep this previous `Accept` header ?
|
||||||
// 'Accept': 'application/json, text/javascript, */*; q=0.01',
|
// 'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||||
// Auto header is :
|
// Auto header is :
|
||||||
// "Accept": "*/*",
|
// "Accept": "*/*",
|
||||||
|
|
||||||
// Also is this still important ? (needed by back-end)
|
// Also is this still important ? (needed by back-end)
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get(uri) {
|
|
||||||
return fetch('/api/' + uri, this.options)
|
|
||||||
.then(response => handleResponse(response))
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
login(password) {
|
|
||||||
return fetch('/api/login', {
|
|
||||||
method: 'POST',
|
|
||||||
body: objectToParams({password}),
|
|
||||||
...this.options
|
|
||||||
}).then(response => (response.ok))
|
|
||||||
},
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
return fetch('/api/logout', this.options).then(response => (response.ok))
|
|
||||||
},
|
|
||||||
|
|
||||||
getVersion() {
|
|
||||||
return fetch('/api/versions', this.options)
|
|
||||||
.then(response => handleResponse(response))
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get (uri) {
|
||||||
|
return fetch('/api/' + uri, this.options)
|
||||||
|
.then(response => handleResponse(response))
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
login (password) {
|
||||||
|
return fetch('/api/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: objectToParams({ password }),
|
||||||
|
...this.options
|
||||||
|
}).then(response => (response.ok))
|
||||||
|
},
|
||||||
|
|
||||||
|
logout () {
|
||||||
|
return fetch('/api/logout', this.options).then(response => (response.ok))
|
||||||
|
},
|
||||||
|
|
||||||
|
getVersion () {
|
||||||
|
return fetch('/api/versions', this.options).then(response => handleResponse(response))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,16 @@ import store from './plugins/store'
|
||||||
|
|
||||||
import * as globalsComponents from './components/globals'
|
import * as globalsComponents from './components/globals'
|
||||||
|
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
// Register global components
|
// Register global components
|
||||||
for (let component of Object.values(globalsComponents)) {
|
for (const component of Object.values(globalsComponents)) {
|
||||||
Vue.component(component.name, component)
|
Vue.component(component.name, component)
|
||||||
}
|
}
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
i18n,
|
i18n,
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
render: h => h(App),
|
render: h => h(App)
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueI18n from 'vue-i18n'
|
import VueI18n from 'vue-i18n'
|
||||||
|
|
||||||
|
|
||||||
Vue.use(VueI18n)
|
Vue.use(VueI18n)
|
||||||
|
|
||||||
function loadLocaleMessages () {
|
function loadLocaleMessages () {
|
||||||
const locales = require.context('../locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
|
const locales = require.context('../locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
|
||||||
const messages = {}
|
const messages = {}
|
||||||
locales.keys().forEach(key => {
|
locales.keys().forEach(key => {
|
||||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
||||||
if (matched && matched.length > 1) {
|
if (matched && matched.length > 1) {
|
||||||
const locale = matched[1]
|
const locale = matched[1]
|
||||||
messages[locale] = locales(key)
|
messages[locale] = locales(key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBrowserLocale() {
|
function getBrowserLocale () {
|
||||||
const navigatorLocale = navigator.languages !== undefined
|
const navigatorLocale = navigator.languages !== undefined
|
||||||
? navigator.languages[0]
|
? navigator.languages[0]
|
||||||
: navigator.language
|
: navigator.language
|
||||||
|
|
||||||
return !navigatorLocale
|
return !navigatorLocale
|
||||||
? process.env.VUE_APP_I18N_LOCALE || 'en'
|
? process.env.VUE_APP_I18N_LOCALE || 'en'
|
||||||
: navigatorLocale
|
: navigatorLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new VueI18n({
|
export default new VueI18n({
|
||||||
locale: getBrowserLocale(),
|
locale: getBrowserLocale(),
|
||||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
||||||
// TODO : chunk locales json and lazy load them
|
// TODO : chunk locales json and lazy load them
|
||||||
messages: loadLocaleMessages()
|
messages: loadLocaleMessages()
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,24 +3,22 @@ import VueRouter from 'vue-router'
|
||||||
import routes from '../routes'
|
import routes from '../routes'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
// mode: 'history', // this allow all routes to be real ones (without '#')
|
// mode: 'history', // this allow all routes to be real ones (without '#')
|
||||||
base: process.env.BASE_URL,
|
base: process.env.BASE_URL,
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
// Before each route request hook
|
// Before each route request hook
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
// Allow if connected or route is not protected
|
// Allow if connected or route is not protected
|
||||||
if (store.state.connected || to.meta.noAuth) {
|
if (store.state.connected || to.meta.noAuth) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
next({name: 'login', query: {redirect: to.path}})
|
next({ name: 'login', query: { redirect: to.path } })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
export default router;
|
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
connected: localStorage.getItem('connected') === 'true',
|
connected: localStorage.getItem('connected') === 'true',
|
||||||
yunohostInfos: null
|
yunohostInfos: null
|
||||||
|
},
|
||||||
|
// Mutations must be synchronous. They are used to change the store state.
|
||||||
|
mutations: {
|
||||||
|
'CONNECTED' (state, connected) {
|
||||||
|
localStorage.setItem('connected', connected)
|
||||||
|
state.connected = connected
|
||||||
|
if (!connected) {
|
||||||
|
state.yunohostInfos = null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// Mutations must be synchronous. They are used to change the store state.
|
'YUNOHOST_INFOS' (state, data) {
|
||||||
mutations: {
|
console.log('version changed', data)
|
||||||
['CONNECTED'] (state, connected) {
|
state.yunohostInfos = data
|
||||||
localStorage.setItem('connected', connected)
|
|
||||||
state.connected = connected
|
|
||||||
if (!connected) {
|
|
||||||
state.yunohostInfos = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['YUNOHOST_INFOS'] (state, data) {
|
|
||||||
console.log('version changed', data);
|
|
||||||
state.yunohostInfos = data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Actions may be asynchronous. They are used to commit mutations.
|
|
||||||
actions: {
|
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Actions may be asynchronous. They are used to commit mutations.
|
||||||
|
actions: {
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,21 +3,29 @@ import Login from './views/Login'
|
||||||
import Users from './views/Users'
|
import Users from './views/Users'
|
||||||
import User from './views/User'
|
import User from './views/User'
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{name: 'home', path: '/', component: Home},
|
{ name: 'home', path: '/', component: Home },
|
||||||
{name: 'login', path: '/login', component: Login, meta: {
|
{ name: 'login', path: '/login', component: Login, meta: { noAuth: true } },
|
||||||
noAuth: true
|
{
|
||||||
}},
|
name: 'users',
|
||||||
{name: 'users', path: '/users', component: Users, meta: {
|
path: '/users',
|
||||||
breadcrumb: [{name: 'users', trad: 'users'}]
|
component: Users,
|
||||||
}},
|
meta: {
|
||||||
{name: 'user', path: '/user/:name', component: User, props: true, meta: {
|
breadcrumb: [{ name: 'users', trad: 'users' }]
|
||||||
breadcrumb: [
|
}
|
||||||
{name: 'users', trad: 'users'},
|
},
|
||||||
{name: 'user', param: 'name'}
|
{
|
||||||
]
|
name: 'user',
|
||||||
}},
|
path: '/user/:name',
|
||||||
|
component: User,
|
||||||
|
props: true,
|
||||||
|
meta: {
|
||||||
|
breadcrumb: [
|
||||||
|
{ name: 'users', trad: 'users' },
|
||||||
|
{ name: 'user', param: 'name' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export default routes
|
export default routes
|
||||||
|
|
|
@ -18,20 +18,20 @@
|
||||||
|
|
||||||
// Bootstrap overrides
|
// Bootstrap overrides
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
.list-group-item-action {
|
.list-group-item-action {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fork-awesome overrides
|
// Fork-awesome overrides
|
||||||
.fa-fw {
|
.fa-fw {
|
||||||
width: 1.25em !important;
|
width: 1.25em !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<b-list-group>
|
<b-list-group>
|
||||||
<b-list-group-item v-for="item in menu" :key="item.id" :to="item.uri">
|
<b-list-group-item v-for="item in menu" :key="item.id" :to="item.uri">
|
||||||
<icon :iname="item.icon" class="lg"/>
|
<icon :iname="item.icon" class="lg" />
|
||||||
<h2>{{ $t(item.translation) }}</h2>
|
<h2>{{ $t(item.translation) }}</h2>
|
||||||
<icon iname="chevron-right" class="lg fs-sm ml-auto"/>
|
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
menu: [
|
menu: [
|
||||||
{id: 0, uri: '/users', icon: 'users', translation: 'users'},
|
{ id: 0, uri: '/users', icon: 'users', translation: 'users' },
|
||||||
{id: 1, uri: '/domains', icon: 'globe', translation: 'domains'},
|
{ id: 1, uri: '/domains', icon: 'globe', translation: 'domains' },
|
||||||
{id: 2, uri: '/apps', icon: 'cubes', translation: 'applications'},
|
{ id: 2, uri: '/apps', icon: 'cubes', translation: 'applications' },
|
||||||
{id: 3, uri: '/update', icon: 'refresh', translation: 'system_update'},
|
{ id: 3, uri: '/update', icon: 'refresh', translation: 'system_update' },
|
||||||
{id: 4, uri: '/services', icon: 'cog', translation: 'services'},
|
{ id: 4, uri: '/services', icon: 'cog', translation: 'services' },
|
||||||
{id: 5, uri: '/tools', icon: 'wrench', translation: 'tools'},
|
{ id: 5, uri: '/tools', icon: 'wrench', translation: 'tools' },
|
||||||
{id: 6, uri: '/diagnosis', icon: 'stethoscope', translation: 'diagnosis'},
|
{ id: 6, uri: '/diagnosis', icon: 'stethoscope', translation: 'diagnosis' },
|
||||||
{id: 7, uri: '/backup', icon: 'archive', translation: 'backup'},
|
{ id: 7, uri: '/backup', icon: 'archive', translation: 'backup' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,58 +1,59 @@
|
||||||
<template>
|
<template>
|
||||||
<b-form @submit.prevent="login">
|
<b-form @submit.prevent="login">
|
||||||
<!-- TODO add hidden domain input -->
|
<!-- TODO add hidden domain input -->
|
||||||
<b-input-group>
|
<b-input-group>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<b-input-group-text>
|
<b-input-group-text>
|
||||||
<label class="sr-only" for="input-password" >{{ $t('password') }}</label>
|
<label class="sr-only" for="input-password">{{ $t('password') }}</label>
|
||||||
<icon iname="lock" class="sm"/>
|
<icon iname="lock" class="sm" />
|
||||||
</b-input-group-text>
|
</b-input-group-text>
|
||||||
</template>
|
</template>
|
||||||
<b-form-input required id="input-password"
|
<b-form-input
|
||||||
v-model="password"
|
id="input-password"
|
||||||
type="password"
|
required type="password"
|
||||||
:placeholder="$t('administration_password')"
|
v-model="password"
|
||||||
:state="isValid"
|
:placeholder="$t('administration_password')" :state="isValid"
|
||||||
></b-form-input>
|
/>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<b-button type="submit" variant="success">{{ $t('login') }}</b-button>
|
<b-button type="submit" variant="success">
|
||||||
</template>
|
{{ $t('login') }}
|
||||||
</b-input-group>
|
</b-button>
|
||||||
<b-form-invalid-feedback :state="isValid">
|
</template>
|
||||||
{{ $t('wrong_password') }}
|
</b-input-group>
|
||||||
</b-form-invalid-feedback>
|
<b-form-invalid-feedback :state="isValid">
|
||||||
</b-form>
|
{{ $t('wrong_password') }}
|
||||||
|
</b-form-invalid-feedback>
|
||||||
|
</b-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import api from '@/helpers/api'
|
import api from '@/helpers/api'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
password: '',
|
password: '',
|
||||||
isValid: null,
|
isValid: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async login() {
|
async login () {
|
||||||
const connected = await api.login(this.password)
|
const connected = await api.login(this.password)
|
||||||
if (connected) {
|
if (connected) {
|
||||||
this.$store.commit('CONNECTED', true);
|
this.$store.commit('CONNECTED', true)
|
||||||
this.$router.push(this.$route.query.redirect || '/')
|
this.$router.push(this.$route.query.redirect || '/')
|
||||||
const infos = await api.getVersion();
|
const infos = await api.getVersion()
|
||||||
this.$store.commit('YUNOHOST_INFOS', infos.yunohost)
|
this.$store.commit('YUNOHOST_INFOS', infos.yunohost)
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('CONNECTED', false);
|
this.$store.commit('CONNECTED', false)
|
||||||
this.isValid = false
|
this.isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// TODO checkInstall
|
// TODO checkInstall
|
||||||
// beforeRouteEnter (to, from, next) {
|
// beforeRouteEnter (to, from, next) {
|
||||||
// },
|
// },
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,63 +1,73 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<breadcrumb/>
|
<breadcrumb />
|
||||||
<b-card :class="{skeleton: !user}">
|
<b-card :class="{skeleton: !user}">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h2>{{ user ? user.fullname : '' }}</h2>
|
<h2>{{ user ? user.fullname : '' }}</h2>
|
||||||
</template>
|
</template>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<icon iname="user" class="fa-fw"></icon>
|
<icon iname="user" class="fa-fw" />
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<template v-if="user">
|
<template v-if="user">
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col><strong>{{ $t('user_username') }}</strong></b-col>
|
<b-col><strong>{{ $t('user_username') }}</strong></b-col>
|
||||||
<b-col>{{ user.username }}</b-col>
|
<b-col>{{ user.username }}</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col><strong>{{ $t('user_email') }}</strong></b-col>
|
<b-col><strong>{{ $t('user_email') }}</strong></b-col>
|
||||||
<b-col class="font-italic">{{ user.mail }}</b-col>
|
<b-col class="font-italic">
|
||||||
</b-row>
|
{{ user.mail }}
|
||||||
<b-row>
|
</b-col>
|
||||||
<b-col><strong>{{ $t('user_mailbox_quota') }}</strong></b-col>
|
</b-row>
|
||||||
<b-col>{{ user['mailbox-quota'].limit }}</b-col>
|
<b-row>
|
||||||
</b-row>
|
<b-col><strong>{{ $t('user_mailbox_quota') }}</strong></b-col>
|
||||||
<b-row>
|
<b-col>{{ user['mailbox-quota'].limit }}</b-col>
|
||||||
<b-col><strong>{{ $t('user_mailbox_use') }}</strong></b-col>
|
</b-row>
|
||||||
<b-col>{{ user['mailbox-quota'].use }}</b-col>
|
<b-row>
|
||||||
</b-row>
|
<b-col><strong>{{ $t('user_mailbox_use') }}</strong></b-col>
|
||||||
<b-row v-for="(trad, mailType) in {'mail-aliases': 'user_emailaliases', 'mail-forward': 'user_emailforward'}" :key="mailType">
|
<b-col>{{ user['mailbox-quota'].use }}</b-col>
|
||||||
<b-col><strong>{{ $t(trad) }}</strong></b-col>
|
</b-row>
|
||||||
<b-col>
|
<b-row v-for="(trad, mailType) in {'mail-aliases': 'user_emailaliases', 'mail-forward': 'user_emailforward'}" :key="mailType">
|
||||||
<ul v-if="user[mailType] && user[mailType].length > 1">
|
<b-col><strong>{{ $t(trad) }}</strong></b-col>
|
||||||
<li v-for="(alias, index) in user[mailType]" :key="index">
|
<b-col>
|
||||||
{{ alias }}
|
<ul v-if="user[mailType] && user[mailType].length > 1">
|
||||||
</li>
|
<li v-for="(alias, index) in user[mailType]" :key="index">
|
||||||
</ul>
|
{{ alias }}
|
||||||
<template v-else-if="user[mailType]">
|
</li>
|
||||||
{{ user[mailType] }}
|
</ul>
|
||||||
</template>
|
<template v-else-if="user[mailType]">
|
||||||
</b-col>
|
{{ user[mailType] }}
|
||||||
</b-row>
|
</template>
|
||||||
</template>
|
</b-col>
|
||||||
<!-- skeleton -->
|
</b-row>
|
||||||
<template v-else>
|
</template>
|
||||||
<b-row v-for="(n, index) in 6" :key="index">
|
<!-- skeleton -->
|
||||||
<b-col><strong class="rounded"></strong></b-col>
|
<template v-else>
|
||||||
<b-col><span v-if="n <= 4" class="rounded"></span></b-col>
|
<b-row v-for="(n, index) in 6" :key="index">
|
||||||
</b-row>
|
<b-col>
|
||||||
</template>
|
<strong class="rounded" />
|
||||||
</div>
|
</b-col>
|
||||||
</div>
|
<b-col>
|
||||||
<template v-slot:footer >
|
<span v-if="n <= 4" class="rounded" />
|
||||||
<div class="d-flex d-flex justify-content-end">
|
</b-col>
|
||||||
<b-button :to="user ? {name: 'user-edit', params: {user: user}} : null" :variant="user ? 'info' : 'dark'" >
|
</b-row>
|
||||||
{{ user ? $t('user_username_edit', {name: user.username}) : '' }}
|
</template>
|
||||||
</b-button>
|
</div>
|
||||||
<b-button :variant="user ? 'danger' : 'dark'" class="ml-2">{{ user ? $t('delete') : '' }}</b-button>
|
</div>
|
||||||
</div>
|
<template v-slot:footer>
|
||||||
</template>
|
<div class="d-flex d-flex justify-content-end">
|
||||||
</b-card>
|
<b-button :to="user ? {name: 'user-edit', params: {user: user}} : null"
|
||||||
</div>
|
:variant="user ? 'info' : 'dark'"
|
||||||
|
>
|
||||||
|
{{ user ? $t('user_username_edit', {name: user.username}) : '' }}
|
||||||
|
</b-button>
|
||||||
|
<b-button :variant="user ? 'danger' : 'dark'" class="ml-2">
|
||||||
|
{{ user ? $t('delete') : '' }}
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</b-card>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -65,21 +75,26 @@ import api from '@/helpers/api'
|
||||||
import Breadcrumb from '@/components/Breadcrumb'
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'User',
|
name: 'User',
|
||||||
props: ['name'],
|
props: {
|
||||||
data: function () {
|
name: {
|
||||||
return {
|
type: Object,
|
||||||
user: undefined,
|
required: true
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
const data = await api.get('users/' + this.name)
|
|
||||||
if (!data) return;
|
|
||||||
this.user = data
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Breadcrumb
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
user: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
const data = await api.get('users/' + this.name)
|
||||||
|
if (!data) return
|
||||||
|
this.user = data
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Breadcrumb
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -87,68 +102,67 @@ export default {
|
||||||
@import '@/scss/main.scss';
|
@import '@/scss/main.scss';
|
||||||
|
|
||||||
.card-body > div {
|
.card-body > div {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon.fa-user {
|
.icon.fa-user {
|
||||||
font-size: 10rem;
|
font-size: 10rem;
|
||||||
padding-right: 3rem;
|
padding-right: 3rem;
|
||||||
padding-left: 1.75rem;
|
padding-left: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
+ .row {
|
+ .row {
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
height: #{2 * 1.2}rem;
|
height: #{2 * 1.2}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
background-color: $skeleton-color;
|
||||||
|
height: 1.5rem;
|
||||||
|
max-width: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
strong {
|
||||||
& > * {
|
max-width: 12rem;
|
||||||
display: block;
|
|
||||||
background-color: $skeleton-color;
|
|
||||||
height: 1.5rem;
|
|
||||||
max-width: 8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
max-width: 12rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
height: calc(2.25rem + 2px);
|
height: calc(2.25rem + 2px);
|
||||||
width: 7rem;
|
width: 7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="users">
|
<div class="users">
|
||||||
<breadcrumb/>
|
<breadcrumb />
|
||||||
<template v-if="users === null">
|
<template v-if="users === null">
|
||||||
<b-alert variant="warning" show>
|
<b-alert variant="warning" show>
|
||||||
<icon iname="exclamation-triangle" class="fa-fw mr-1"/>
|
<icon iname="exclamation-triangle" class="fa-fw mr-1" />
|
||||||
{{ $t('users_no') }}
|
{{ $t('users_no') }}
|
||||||
</b-alert>
|
</b-alert>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<b-list-group :class="{skeleton: !users}">
|
<b-list-group :class="{skeleton: !users}">
|
||||||
<b-list-group-item
|
<b-list-group-item
|
||||||
class="d-flex justify-content-between align-items-center pr-0"
|
v-for="(user, index) in (users ? users : 3)"
|
||||||
v-for="(user, index) in (users ? users : 3)"
|
:key="index"
|
||||||
:key="index"
|
:to="users ? { name: 'user', params: { name: user.username }} : null"
|
||||||
:to="users ? { name: 'user', params: { name: user.username }} : null"
|
class="d-flex justify-content-between align-items-center pr-0"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h5 :class="{rounded: !users}">
|
<h5 :class="{rounded: !users}">
|
||||||
{{ user.username }}
|
{{ user.username }}
|
||||||
<small>({{ user.fullname }})</small>
|
<small>({{ user.fullname }})</small>
|
||||||
</h5>
|
</h5>
|
||||||
<p :class="{rounded: !users}">{{ user.mail }}</p>
|
<p :class="{rounded: !users}">
|
||||||
</div>
|
{{ user.mail }}
|
||||||
<icon iname="chevron-right" class="lg fs-sm ml-auto"/>
|
</p>
|
||||||
</b-list-group-item>
|
</div>
|
||||||
</b-list-group>
|
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||||
</template>
|
</b-list-group-item>
|
||||||
</div>
|
</b-list-group>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -34,25 +36,25 @@ import api from '@/helpers/api'
|
||||||
import Breadcrumb from '@/components/Breadcrumb'
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Users',
|
name: 'Users',
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
users: undefined
|
users: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
},
|
},
|
||||||
async created() {
|
async created () {
|
||||||
const data = await api.get('users')
|
const data = await api.get('users')
|
||||||
if (!data || Object.keys(data.users).length === 0) {
|
if (!data || Object.keys(data.users).length === 0) {
|
||||||
this.users = null
|
this.users = null
|
||||||
} else {
|
} else {
|
||||||
this.users = data.users
|
this.users = data.users
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Breadcrumb
|
Breadcrumb
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -60,22 +62,22 @@ export default {
|
||||||
@import '@/scss/_variables.scss';
|
@import '@/scss/_variables.scss';
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0
|
margin: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
@each $i, $opacity in 1 .75, 2 .5, 3 .25 {
|
@each $i, $opacity in 1 .75, 2 .5, 3 .25 {
|
||||||
.list-group-item:nth-child(#{$i}) { opacity: $opacity; }
|
.list-group-item:nth-child(#{$i}) { opacity: $opacity; }
|
||||||
}
|
}
|
||||||
|
|
||||||
h5, p {
|
h5, p {
|
||||||
background-color: $skeleton-color;
|
background-color: $skeleton-color;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Add table
Reference in a new issue