linting…

This commit is contained in:
Axolotle 2020-07-15 16:39:24 +02:00
parent d69bb8b906
commit 6f8457381a
15 changed files with 577 additions and 546 deletions

View file

@ -2,16 +2,22 @@
<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">
<img alt="Yunohost logo" src="./assets/logo.png">
</b-navbar-brand>
<b-navbar-nav class="ml-auto"> <b-navbar-nav class="ml-auto">
<li class="nav-item"> <li class="nav-item">
<b-button href="/yunohost/sso" variant="primary" block size="sm"> <b-button href="/yunohost/sso"
{{ $t('user_interface_link') }} <icon iname="user"/> variant="primary" size="sm" block
>
{{ $t('user_interface_link') }} <icon iname="user" />
</b-button> </b-button>
</li> </li>
<li class="nav-item" v-show="connected"> <li class="nav-item" v-show="connected">
<b-button @click.prevent="logout" to="/logout" variant="outline-dark" block size="sm" > <b-button @click.prevent="logout" to="/logout"
{{ $t('logout') }} <icon iname="sign-out"/> variant="outline-dark" block size="sm"
>
{{ $t('logout') }} <icon iname="sign-out" />
</b-button> </b-button>
</li> </li>
</b-navbar-nav> </b-navbar-nav>
@ -19,25 +25,35 @@
</header> </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>
<b-link href="https://yunohost.org">
YunoHost
</b-link>
</template>
<template v-slot:version>
{{ yunohostInfos.version }}
</template>
<template v-slot:repo>
{{ yunohostInfos.repo }}
</template>
</i18n> </i18n>
</b-nav> </b-nav>
</nav> </nav>
@ -50,29 +66,28 @@ 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: { computed: {
...mapState(['connected', 'yunohostInfos']), ...mapState(['connected', 'yunohostInfos'])
}, },
methods: { methods: {
async logout() { async logout () {
await api.logout() await api.logout()
this.$store.commit('CONNECTED', false); this.$store.commit('CONNECTED', false)
this.$router.push('/login') this.$router.push('/login')
}, }
}, },
// This hook is only triggered at page reload so the value of state.connected // This hook is only triggered at page reload so the value of state.connected
// always come from the localStorage // always come from the localStorage
async created() { async created () {
if (!this.$store.state.connected) { if (!this.$store.state.connected) {
// user is not connected: allow the login view to be rendered. // user is not connected: allow the login view to be rendered.
this.isReady = true this.isReady = true
@ -87,11 +102,11 @@ export default {
// Session expired, reset the 'connected' state and redirect with a query // 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 // FIXME is there a case where the error may not be a 401 therefor requires
// better handling ? // better handling ?
this.$store.commit('CONNECTED', false); this.$store.commit('CONNECTED', false)
this.$router.push({name: 'login', query: {redirect: this.$route.path}}) this.$router.push({ name: 'login', query: { redirect: this.$route.path } })
} finally { } finally {
// in any case allow the router-view to be rendered // in any case allow the router-view to be rendered
this.isReady = true; this.isReady = true
} }
} }
} }

View file

@ -2,7 +2,7 @@
<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"
@ -22,24 +22,24 @@ export default {
return this.$route.params return this.$route.params
}, },
breadcrumb: function () { breadcrumb: function () {
return this.$route.meta.breadcrumb.map(({name, trad, param}) => { return this.$route.meta.breadcrumb.map(({ name, trad, param }) => {
let text = '' let text = ''
// if a traduction key string has been given and we also need to pass // if a traduction key string has been given and we also need to pass
// the route param as a variable. // the route param as a variable.
if (trad && param) { if (trad && param) {
text = this.$i18n.t(trad, {[param]: this.params[param]}) text = this.$i18n.t(trad, { [param]: this.params[param] })
} else if (trad) { } else if (trad) {
text = this.$i18n.t(trad) text = this.$i18n.t(trad)
} else { } else {
text = this.params[param] text = this.params[param]
} }
return {name, text} return { name, text }
}) })
}, },
lastIndex: function () { lastIndex: function () {
return this.breadcrumb.length - 1 return this.breadcrumb.length - 1
}, }
}, }
} }
</script> </script>

View file

@ -1,11 +1,13 @@
<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>

View file

@ -1 +1 @@
export {default as Icon} from './Icon' export { default as Icon } from './Icon'

View file

@ -1,26 +1,21 @@
function objectToParams (object) {
const urlParams = new URLSearchParams()
function objectToParams(object) {
const urlParams = new URLSearchParams();
for (const [key, value] of Object.entries(object)) { for (const [key, value] of Object.entries(object)) {
urlParams.append(key, value) 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',
@ -32,11 +27,11 @@ export default {
// "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) { get (uri) {
return fetch('/api/' + uri, this.options) return fetch('/api/' + uri, this.options)
.then(response => handleResponse(response)) .then(response => handleResponse(response))
.catch(err => { .catch(err => {
@ -44,20 +39,19 @@ export default {
}) })
}, },
login(password) { login (password) {
return fetch('/api/login', { return fetch('/api/login', {
method: 'POST', method: 'POST',
body: objectToParams({password}), body: objectToParams({ password }),
...this.options ...this.options
}).then(response => (response.ok)) }).then(response => (response.ok))
}, },
logout() { logout () {
return fetch('/api/logout', this.options).then(response => (response.ok)) return fetch('/api/logout', this.options).then(response => (response.ok))
}, },
getVersion() { getVersion () {
return fetch('/api/versions', this.options) return fetch('/api/versions', this.options).then(response => handleResponse(response))
.then(response => handleResponse(response))
} }
} }

View file

@ -7,11 +7,10 @@ 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)
} }
@ -19,5 +18,5 @@ new Vue({
i18n, i18n,
router, router,
store, store,
render: h => h(App), render: h => h(App)
}).$mount('#app') }).$mount('#app')

View file

@ -1,7 +1,6 @@
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 () {
@ -17,7 +16,7 @@ function loadLocaleMessages () {
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

View file

@ -3,7 +3,6 @@ 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({
@ -18,9 +17,8 @@ router.beforeEach((to, from, next) => {
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;

View file

@ -1,7 +1,6 @@
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({
@ -11,15 +10,15 @@ export default new Vuex.Store({
}, },
// Mutations must be synchronous. They are used to change the store state. // Mutations must be synchronous. They are used to change the store state.
mutations: { mutations: {
['CONNECTED'] (state, connected) { 'CONNECTED' (state, connected) {
localStorage.setItem('connected', connected) localStorage.setItem('connected', connected)
state.connected = connected state.connected = connected
if (!connected) { if (!connected) {
state.yunohostInfos = null state.yunohostInfos = null
} }
}, },
['YUNOHOST_INFOS'] (state, data) { 'YUNOHOST_INFOS' (state, data) {
console.log('version changed', data); console.log('version changed', data)
state.yunohostInfos = data state.yunohostInfos = data
} }
}, },

View file

@ -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' }]
}
},
{
name: 'user',
path: '/user/:name',
component: User,
props: true,
meta: {
breadcrumb: [ breadcrumb: [
{name: 'users', trad: 'users'}, { name: 'users', trad: 'users' },
{name: 'user', param: 'name'} { name: 'user', param: 'name' }
] ]
}}, }
}
] ]
export default routes export default routes

View file

@ -2,9 +2,9 @@
<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>
@ -16,17 +16,17 @@ export default {
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>

View file

@ -4,18 +4,20 @@
<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
id="input-password"
required type="password"
v-model="password" v-model="password"
type="password" :placeholder="$t('administration_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">
{{ $t('login') }}
</b-button>
</template> </template>
</b-input-group> </b-input-group>
<b-form-invalid-feedback :state="isValid"> <b-form-invalid-feedback :state="isValid">
@ -27,29 +29,28 @@
<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) {
// }, // },

View file

@ -1,12 +1,12 @@
<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>
@ -15,7 +15,9 @@
</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">
{{ user.mail }}
</b-col>
</b-row> </b-row>
<b-row> <b-row>
<b-col><strong>{{ $t('user_mailbox_quota') }}</strong></b-col> <b-col><strong>{{ $t('user_mailbox_quota') }}</strong></b-col>
@ -42,18 +44,26 @@
<!-- skeleton --> <!-- skeleton -->
<template v-else> <template v-else>
<b-row v-for="(n, index) in 6" :key="index"> <b-row v-for="(n, index) in 6" :key="index">
<b-col><strong class="rounded"></strong></b-col> <b-col>
<b-col><span v-if="n <= 4" class="rounded"></span></b-col> <strong class="rounded" />
</b-col>
<b-col>
<span v-if="n <= 4" class="rounded" />
</b-col>
</b-row> </b-row>
</template> </template>
</div> </div>
</div> </div>
<template v-slot:footer > <template v-slot:footer>
<div class="d-flex d-flex justify-content-end"> <div class="d-flex d-flex justify-content-end">
<b-button :to="user ? {name: 'user-edit', params: {user: user}} : null" :variant="user ? 'info' : 'dark'" > <b-button :to="user ? {name: 'user-edit', params: {user: user}} : null"
:variant="user ? 'info' : 'dark'"
>
{{ user ? $t('user_username_edit', {name: user.username}) : '' }} {{ user ? $t('user_username_edit', {name: user.username}) : '' }}
</b-button> </b-button>
<b-button :variant="user ? 'danger' : 'dark'" class="ml-2">{{ user ? $t('delete') : '' }}</b-button> <b-button :variant="user ? 'danger' : 'dark'" class="ml-2">
{{ user ? $t('delete') : '' }}
</b-button>
</div> </div>
</template> </template>
</b-card> </b-card>
@ -66,15 +76,20 @@ 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() { data: function () {
return {
user: undefined
}
},
async created () {
const data = await api.get('users/' + this.name) const data = await api.get('users/' + this.name)
if (!data) return; if (!data) return
this.user = data this.user = data
}, },
components: { components: {
@ -130,7 +145,6 @@ ul {
h2 { h2 {
height: #{2 * 1.2}rem; height: #{2 * 1.2}rem;
} }
.col { .col {

View file

@ -1,28 +1,30 @@
<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}">
{{ user.mail }}
</p>
</div> </div>
<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>
</template> </template>
@ -42,7 +44,7 @@ export default {
}, },
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
@ -52,7 +54,7 @@ export default {
}, },
components: { components: {
Breadcrumb Breadcrumb
}, }
} }
</script> </script>