refactor: turn infos store to global state

This commit is contained in:
axolotle 2024-08-05 21:39:00 +02:00
parent deae1324e1
commit 60b5134dcf
13 changed files with 227 additions and 264 deletions

View file

@ -1,23 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useStore } from 'vuex'
import { useInfos } from '@/composables/useInfos'
import { useRequests } from '@/composables/useRequests' import { useRequests } from '@/composables/useRequests'
import { useSettings } from '@/composables/useSettings' import { useSettings } from '@/composables/useSettings'
import { useStoreGetters } from '@/store/utils'
import { HistoryConsole } from '@/views/_partials' import { HistoryConsole } from '@/views/_partials'
const store = useStore() const { ssoLink, connected, yunohost, logout, onAppCreated } = useInfos()
const { connected, yunohost, ssoLink } = useStoreGetters()
const { locked } = useRequests() const { locked } = useRequests()
const { spinner, dark } = useSettings() const { spinner, dark } = useSettings()
async function logout() { onAppCreated()
store.dispatch('LOGOUT')
}
store.dispatch('ON_APP_CREATED')
onMounted(() => { onMounted(() => {
const copypastaCode = ['ArrowDown', 'ArrowDown', 'ArrowUp', 'ArrowUp'] const copypastaCode = ['ArrowDown', 'ArrowDown', 'ArrowUp', 'ArrowUp']

View file

@ -1,3 +1,4 @@
import { useInfos } from '@/composables/useInfos'
import { import {
useRequests, useRequests,
type APIRequestAction, type APIRequestAction,
@ -242,10 +243,10 @@ export default {
delay = 2000, delay = 2000,
initialDelay = 0, initialDelay = 0,
}: ReconnectingArgs = {}) { }: ReconnectingArgs = {}) {
const { getYunoHostVersion } = useInfos()
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
function reconnect(n: number) { function reconnect(n: number) {
store getYunoHostVersion()
.dispatch('GET_YUNOHOST_INFOS')
.then(resolve) .then(resolve)
.catch((err: APIError) => { .catch((err: APIError) => {
if (err instanceof APIUnauthorizedError) { if (err instanceof APIUnauthorizedError) {

View file

@ -4,6 +4,7 @@
*/ */
import errors from '@/api/errors' import errors from '@/api/errors'
import { useInfos } from '@/composables/useInfos'
import { import {
STATUS_VARIANT, STATUS_VARIANT,
type APIRequest, type APIRequest,
@ -39,10 +40,9 @@ export async function getResponseData(response: Response) {
* @returns Promise that resolve on websocket 'open' or 'error' event. * @returns Promise that resolve on websocket 'open' or 'error' event.
*/ */
export function openWebSocket(request: APIRequestAction): Promise<Event> { export function openWebSocket(request: APIRequestAction): Promise<Event> {
const { host } = useInfos()
return new Promise((resolve) => { return new Promise((resolve) => {
const ws = new WebSocket( const ws = new WebSocket(`wss://${host.value}/yunohost/api/messages`)
`wss://${store.getters.host}/yunohost/api/messages`,
)
ws.onmessage = ({ data }) => { ws.onmessage = ({ data }) => {
const messages: Record<'info' | 'success' | 'warning' | 'error', string> = const messages: Record<'info' | 'success' | 'warning' | 'error', string> =
JSON.parse(data) JSON.parse(data)

View file

@ -1,16 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStoreGetters } from '@/store/utils' import { useInfos } from '@/composables/useInfos'
const { breadcrumb } = useStoreGetters() const { breadcrumb } = useInfos()
</script> </script>
<style lang="scss" scoped>
.breadcrumb {
border: none;
background-color: transparent;
}
</style>
<template> <template>
<BBreadcrumb v-if="breadcrumb.length"> <BBreadcrumb v-if="breadcrumb.length">
<BBreadcrumbItem to="/"> <BBreadcrumbItem to="/">
@ -19,12 +12,19 @@ const { breadcrumb } = useStoreGetters()
</BBreadcrumbItem> </BBreadcrumbItem>
<BBreadcrumbItem <BBreadcrumbItem
v-for="({ name, text }, i) in breadcrumb" v-for="({ to, text }, i) in breadcrumb"
:key="name" :key="i"
:to="{ name }" :to="to"
:active="i === breadcrumb.length - 1" :active="i === breadcrumb.length - 1"
> >
{{ text }} {{ text }}
</BBreadcrumbItem> </BBreadcrumbItem>
</BBreadcrumb> </BBreadcrumb>
</template> </template>
<style lang="scss" scoped>
.breadcrumb {
border: none;
background-color: transparent;
}
</style>

View file

@ -10,13 +10,13 @@ import {
ModalWaiting, ModalWaiting,
ModalWarning, ModalWarning,
} from '@/components/modals' } from '@/components/modals'
import { useInfos } from '@/composables/useInfos'
import { useRequests } from '@/composables/useRequests' import { useRequests } from '@/composables/useRequests'
import { useSettings } from '@/composables/useSettings' import { useSettings } from '@/composables/useSettings'
import { useStoreGetters } from '@/store/utils'
import type { VueClass } from '@/types/commons' import type { VueClass } from '@/types/commons'
const router = useRouter() const router = useRouter()
const { routerKey } = useStoreGetters() const { routerKey } = useInfos()
const { reconnecting, currentRequest, dismissModal } = useRequests() const { reconnecting, currentRequest, dismissModal } = useRequests()
const { transitions, transitionName, dark } = useSettings() const { transitions, transitionName, dark } = useSettings()

View file

@ -1,129 +1,113 @@
import { createGlobalState, useLocalStorage } from '@vueuse/core'
import { computed, ref } from 'vue'
import type {
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
RouteRecordNormalized,
} from 'vue-router'
import { useRouter } from 'vue-router'
import api from '@/api' import api from '@/api'
import { useRequests, type ReconnectingArgs } from '@/composables/useRequests'
import { isEmptyValue, timeout } from '@/helpers/commons' import { isEmptyValue, timeout } from '@/helpers/commons'
import i18n from '@/i18n' import i18n from '@/i18n'
import router from '@/router' import { useStoreGetters } from '@/store/utils'
import type { CustomRoute, RouteFromTo } from '@/types/commons'
import { useRequests, type ReconnectingArgs } from './useRequests'
export default { export const useInfos = createGlobalState(() => {
state: { const router = useRouter()
host: window.location.host, // String
installed: null,
connected: localStorage.getItem('connected') === 'true', // Boolean
yunohost: null, // Object { version, repo }
routerKey: undefined, // String if current route has params
breadcrumb: [], // Array of routes
transitionName: null, // String of CSS class if transitions are enabled
},
mutations: { const host = ref(window.location.host)
SET_INSTALLED(state, boolean) { const installed = ref<boolean | undefined>()
state.installed = boolean const connected = useLocalStorage('connected', false)
}, const yunohost = ref<{ version: string; repo: string } | undefined>()
const routerKey = ref<string | undefined>()
const breadcrumb = ref<CustomRoute[]>([])
SET_CONNECTED(state, boolean) { const { mainDomain } = useStoreGetters()
localStorage.setItem('connected', boolean) const ssoLink = computed(() => {
state.connected = boolean return `//${mainDomain.value ?? host.value}/yunohost/sso`
}, })
SET_YUNOHOST_INFOS(state, yunohost) { // INIT
state.yunohost = yunohost
},
SET_ROUTER_KEY(state, key) { async function _checkInstall(retry = 2) {
state.routerKey = key
},
SET_BREADCRUMB(state, breadcrumb) {
state.breadcrumb = breadcrumb
},
SET_TRANSITION_NAME(state, transitionName) {
state.transitionName = transitionName
},
},
actions: {
async ON_APP_CREATED({ dispatch, state }) {
await dispatch('CHECK_INSTALL')
if (!state.installed) {
router.push({ name: 'post-install' })
} else {
dispatch('CONNECT')
}
},
async CHECK_INSTALL({ dispatch, commit }, retry = 2) {
// this action will try to query the `/installed` route 3 times every 5 s with // this action will try to query the `/installed` route 3 times every 5 s with
// a timeout of the same delay. // a timeout of the same delay.
// FIXME need testing with api not responding // FIXME need testing with api not responding
try { try {
const { installed } = await timeout(api.get('installed'), 5000) const data = await timeout(
commit('SET_INSTALLED', installed) api.get<{ installed: boolean }>('installed'),
return installed 5000,
)
installed.value = data.installed
} catch (err) { } catch (err) {
if (retry > 0) { if (retry > 0) {
return dispatch('CHECK_INSTALL', --retry) return _checkInstall(--retry)
} }
throw err throw err
} }
}, }
async CONNECT({ commit, dispatch }) { async function onAppCreated() {
await _checkInstall()
if (!installed.value) {
router.push({ name: 'post-install' })
} else {
_onLogin()
}
}
function getYunoHostVersion() {
return api.get('versions').then((versions) => {
yunohost.value = versions.yunohost
})
}
// CONNECTION
async function _onLogin() {
// If the user is not connected, the first action will throw // If the user is not connected, the first action will throw
// and login prompt will be shown automaticly // and login prompt will be shown automaticly
await dispatch('GET_YUNOHOST_INFOS') await getYunoHostVersion()
commit('SET_CONNECTED', true) connected.value = true
await api.get({ uri: 'domains', storeKey: 'domains' }) await api.get({ uri: 'domains', cachePath: 'domainList' })
}, }
RESET_CONNECTED({ commit }) { function onLogout(route?: RouteLocationNormalizedLoaded) {
commit('SET_CONNECTED', false) connected.value = false
commit('SET_YUNOHOST_INFOS', null) yunohost.value = undefined
},
DISCONNECT({ dispatch }, route) {
// FIXME vue3 currentRoute is now a ref (currentRoute.value)
dispatch('RESET_CONNECTED')
if (router.currentRoute.value.name === 'login') return
const previousRoute = route ?? router.currentRoute.value const previousRoute = route ?? router.currentRoute.value
if (previousRoute.name === 'login') return
router.push({ router.push({
name: 'login', name: 'login',
// Add a redirect query if next route is not unknown (like `logout`) or `login` // Add a redirect query if next route is not unknown (like `logout`) or `login`
query: query:
previousRoute && !['login', null].includes(previousRoute.name) previousRoute && !['login', null].includes(previousRoute.name as any)
? { redirect: previousRoute.path } ? { redirect: previousRoute.path }
: {}, : {},
}) })
}, }
LOGIN({ dispatch }, credentials) { function login(credentials: string) {
return api return api
.post('login', { credentials }, null, { websocket: false }) .post({ uri: 'login', data: { credentials }, websocket: false })
.then(() => { .then(() => _onLogin())
return dispatch('CONNECT') }
})
},
LOGOUT({ dispatch }) { function logout() {
dispatch('DISCONNECT') onLogout()
return api.get('logout') return api.get('logout')
}, }
TRY_TO_RECONNECT({ commit }, args?: ReconnectingArgs) { function tryToReconnect(args?: ReconnectingArgs) {
// FIXME This is very ugly arguments forwarding, will use proper component way of doing this when switching to Vue 3 (teleport)
useRequests().reconnecting.value = args useRequests().reconnecting.value = args
}, }
GET_YUNOHOST_INFOS({ commit }) { function updateRouterKey({ to }: RouteFromTo) {
return api.get('versions').then((versions) => {
commit('SET_YUNOHOST_INFOS', versions.yunohost)
})
},
UPDATE_ROUTER_KEY({ commit }, { to, from }) {
if (isEmptyValue(to.params)) { if (isEmptyValue(to.params)) {
commit('SET_ROUTER_KEY', undefined) routerKey.value = undefined
return return
} }
// If the next route uses the same component as the previous one, Vue will not // If the next route uses the same component as the previous one, Vue will not
@ -136,21 +120,22 @@ export default {
? to.meta.routerParams.map((key) => to.params[key]) ? to.meta.routerParams.map((key) => to.params[key])
: Object.values(to.params) : Object.values(to.params)
commit('SET_ROUTER_KEY', `${to.name}-${params.join('-')}`) routerKey.value = `${to.name?.toString()}-${params.join('-')}`
}, }
UPDATE_BREADCRUMB({ commit }, { to, from }) { function updateBreadcrumb({ to }: RouteFromTo) {
function getRouteNames(route) { function getRouteNames(route: RouteLocationNormalized): string[] {
if (route.meta.breadcrumb) return route.meta.breadcrumb if (route.meta.breadcrumb) return route.meta.breadcrumb
const parentRoute = route.matched const parentRoute = route.matched
.slice() .slice()
.reverse() .reverse()
.find((route) => route.meta.breadcrumb) .find((route) => route.meta.breadcrumb)
if (parentRoute) return parentRoute.meta.breadcrumb return parentRoute?.meta.breadcrumb || []
return []
} }
function formatRoute(route) { function formatRoute(
route: RouteRecordNormalized | RouteLocationNormalized,
) {
const { trad, param } = route.meta.args || {} const { trad, param } = route.meta.args || {}
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
@ -159,22 +144,20 @@ export default {
text = i18n.global.t(trad, { [param]: to.params[param] }) text = i18n.global.t(trad, { [param]: to.params[param] })
} else if (trad) { } else if (trad) {
text = i18n.global.t(trad) text = i18n.global.t(trad)
} else { } else if (param) {
text = to.params[param] text = to.params[param] as string
} }
return { name: route.name, text } return { to: { name: route.name! }, text }
} }
const routeNames = getRouteNames(to) const routeNames = getRouteNames(to)
const allRoutes = router.getRoutes() const allRoutes = router.getRoutes()
const breadcrumb = routeNames.map((name) => { breadcrumb.value = routeNames.map((name) => {
const route = allRoutes.find((route) => route.name === name) const route = allRoutes.find((route) => route.name === name)!
return formatRoute(route) return formatRoute(route)
}) })
commit('SET_BREADCRUMB', breadcrumb) function getTitle(breadcrumb: CustomRoute[]) {
function getTitle(breadcrumb) {
if (breadcrumb.length === 0) return formatRoute(to).text if (breadcrumb.length === 0) return formatRoute(to).text
return (breadcrumb.length > 2 ? breadcrumb.slice(-2) : breadcrumb) return (breadcrumb.length > 2 ? breadcrumb.slice(-2) : breadcrumb)
.map((route) => route.text) .map((route) => route.text)
@ -183,30 +166,24 @@ export default {
} }
// Display a simplified breadcrumb as the document title. // Display a simplified breadcrumb as the document title.
document.title = `${getTitle(breadcrumb)} | ${i18n.global.t('yunohost_admin')}` document.title = `${getTitle(breadcrumb.value)} | ${i18n.global.t('yunohost_admin')}`
}, }
UPDATE_TRANSITION_NAME({ state, commit }, { to, from }) { return {
// Use the breadcrumb array length as a direction indicator host,
const toDepth = (to.meta.breadcrumb || []).length installed,
const fromDepth = (from.meta.breadcrumb || []).length connected,
commit( yunohost,
'SET_TRANSITION_NAME', routerKey,
toDepth < fromDepth ? 'slide-right' : 'slide-left', breadcrumb,
) ssoLink,
}, onAppCreated,
}, getYunoHostVersion,
onLogout,
getters: { login,
host: (state) => state.host, logout,
installed: (state) => state.installed, tryToReconnect,
connected: (state) => state.connected, updateRouterKey,
yunohost: (state) => state.yunohost, updateBreadcrumb,
routerKey: (state) => state.routerKey, }
breadcrumb: (state) => state.breadcrumb, })
transitionName: (state) => state.transitionName,
ssoLink: (state, getters) => {
return `//${getters.mainDomain ?? state.host}/yunohost/sso`
},
},
}

View file

@ -7,8 +7,8 @@ import type { APIQuery, RequestMethod } from '@/api/api'
import { APIErrorLog, type APIError } from '@/api/errors' import { APIErrorLog, type APIError } from '@/api/errors'
import { isObjectLiteral } from '@/helpers/commons' import { isObjectLiteral } from '@/helpers/commons'
import i18n from '@/i18n' import i18n from '@/i18n'
import store from '@/store'
import type { StateVariant } from '@/types/commons' import type { StateVariant } from '@/types/commons'
import { useInfos } from './useInfos'
export type RequestStatus = 'pending' | 'success' | 'warning' | 'error' export type RequestStatus = 'pending' | 'success' | 'warning' | 'error'
@ -162,7 +162,7 @@ export const useRequests = createGlobalState(() => {
err.log() err.log()
if (err.code === 401) { if (err.code === 401) {
// Unauthorized // Unauthorized
store.dispatch('DISCONNECT') useInfos().onLogout()
} else if (err instanceof APIErrorLog) { } else if (err instanceof APIErrorLog) {
// Errors that have produced logs // Errors that have produced logs
router.push({ name: 'tool-log', params: { name: err.logRef } }) router.push({ name: 'tool-log', params: { name: err.logRef } })

View file

@ -1,8 +1,8 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import { useInfos } from '@/composables/useInfos'
import { useRequests } from '@/composables/useRequests' import { useRequests } from '@/composables/useRequests'
import { useSettings } from '@/composables/useSettings' import { useSettings } from '@/composables/useSettings'
import store from '@/store'
import routes from './routes' import routes from './routes'
const router = createRouter({ const router = createRouter({
@ -39,20 +39,22 @@ router.beforeEach((to, from, next) => {
dismissModal(currentRequest.value.id) dismissModal(currentRequest.value.id)
} }
if (to.name === 'post-install' && store.getters.installed) { const { installed, connected, onLogout } = useInfos()
if (to.name === 'post-install' && installed.value) {
return next('/') return next('/')
} }
// Allow if connected or route is not protected // Allow if connected or route is not protected
if (store.getters.connected || to.meta.noAuth) { if (connected.value || to.meta.noAuth) {
next() next()
} else { } else {
store.dispatch('DISCONNECT', to) onLogout(to)
} }
}) })
router.afterEach((to, from) => { router.afterEach((to, from) => {
store.dispatch('UPDATE_ROUTER_KEY', { to, from }) const { updateRouterKey, updateBreadcrumb } = useInfos()
store.dispatch('UPDATE_BREADCRUMB', { to, from }) updateRouterKey({ to, from })
updateBreadcrumb({ to, from })
}) })
export default router export default router

View file

@ -2,11 +2,10 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter, type LocationQueryValue } from 'vue-router' import { useRouter, type LocationQueryValue } from 'vue-router'
import { useStore } from 'vuex'
import { useForm } from '@/composables/form' import { useForm } from '@/composables/form'
import { useInfos } from '@/composables/useInfos'
import { alphalownumdot_, minLength, required } from '@/helpers/validators' import { alphalownumdot_, minLength, required } from '@/helpers/validators'
import { useStoreGetters } from '@/store/utils'
import type { FieldProps, FormFieldDict } from '@/types/form' import type { FieldProps, FormFieldDict } from '@/types/form'
const props = withDefaults( const props = withDefaults(
@ -19,10 +18,8 @@ const props = withDefaults(
) )
const { t } = useI18n() const { t } = useI18n()
const store = useStore()
const router = useRouter() const router = useRouter()
const { login, installed } = useInfos()
const { installed } = useStoreGetters()
type Form = typeof form.value type Form = typeof form.value
const form = ref({ const form = ref({
@ -57,8 +54,7 @@ const { v, onSubmit } = useForm(form, fields)
const onLogin = onSubmit((onError) => { const onLogin = onSubmit((onError) => {
const { username, password } = form.value const { username, password } = form.value
const credentials = [username, password].join(':') const credentials = [username, password].join(':')
store login(credentials)
.dispatch('LOGIN', credentials)
.then(() => { .then(() => {
if (props.forceReload) { if (props.forceReload) {
window.location.href = '/yunohost/admin/' window.location.href = '/yunohost/admin/'

View file

@ -2,11 +2,11 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import api from '@/api' import api from '@/api'
import { APIBadRequestError, type APIError } from '@/api/errors' import { APIBadRequestError, type APIError } from '@/api/errors'
import { useAutoModal } from '@/composables/useAutoModal' import { useAutoModal } from '@/composables/useAutoModal'
import { useInfos } from '@/composables/useInfos'
import { useInitialQueries } from '@/composables/useInitialQueries' import { useInitialQueries } from '@/composables/useInitialQueries'
import { isEmptyValue } from '@/helpers/commons' import { isEmptyValue } from '@/helpers/commons'
import { readableDate } from '@/helpers/filters/date' import { readableDate } from '@/helpers/filters/date'
@ -19,7 +19,6 @@ const props = defineProps<{
const { t } = useI18n() const { t } = useI18n()
const router = useRouter() const router = useRouter()
const store = useStore()
const modalConfirm = useAutoModal() const modalConfirm = useAutoModal()
const { loading } = useInitialQueries( const { loading } = useInitialQueries(
[{ uri: `backups/${props.name}?with_details` }], [{ uri: `backups/${props.name}?with_details` }],
@ -128,9 +127,9 @@ async function deleteBackup() {
} }
function downloadBackup() { function downloadBackup() {
const host = store.getters.host const { host } = useInfos()
window.open( window.open(
`https://${host}/yunohost/api/backups/${props.name}/download`, `https://${host.value}/yunohost/api/backups/${props.name}/download`,
'_blank', '_blank',
) )
} }

View file

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import api from '@/api' import api from '@/api'
import { useAutoModal } from '@/composables/useAutoModal' import { useAutoModal } from '@/composables/useAutoModal'
import { useInfos } from '@/composables/useInfos'
const { t } = useI18n() const { t } = useI18n()
const store = useStore()
const modalConfirm = useAutoModal() const modalConfirm = useAutoModal()
const { tryToReconnect } = useInfos()
async function triggerAction(action) { async function triggerAction(action) {
const confirmed = await modalConfirm(t('confirm_reboot_action_' + action)) const confirmed = await modalConfirm(t('confirm_reboot_action_' + action))
@ -15,11 +15,7 @@ async function triggerAction(action) {
api.put({ uri: action + '?force', humanKey: action }).then(() => { api.put({ uri: action + '?force', humanKey: action }).then(() => {
const delay = action === 'reboot' ? 4000 : 10000 const delay = action === 'reboot' ? 4000 : 10000
store.dispatch('TRY_TO_RECONNECT', { tryToReconnect({ attemps: Infinity, origin: action, delay })
attemps: Infinity,
origin: action,
delay,
})
}) })
} }
</script> </script>

View file

@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import api from '@/api' import api from '@/api'
import CardCollapse from '@/components/CardCollapse.vue' import CardCollapse from '@/components/CardCollapse.vue'
import { useAutoModal } from '@/composables/useAutoModal' import { useAutoModal } from '@/composables/useAutoModal'
import { useInfos } from '@/composables/useInfos'
import { useInitialQueries } from '@/composables/useInitialQueries' import { useInitialQueries } from '@/composables/useInitialQueries'
const { t } = useI18n() const { t } = useI18n()
const store = useStore() const { tryToReconnect } = useInfos()
const modalConfirm = useAutoModal() const modalConfirm = useAutoModal()
const { loading } = useInitialQueries( const { loading } = useInitialQueries(
[{ method: 'PUT', uri: 'update/all', humanKey: 'update' }], [{ method: 'PUT', uri: 'update/all', humanKey: 'update' }],
@ -105,7 +105,7 @@ async function performSystemUpgrade() {
api.put({ uri: 'upgrade/system', humanKey: 'upgrade.system' }).then(() => { api.put({ uri: 'upgrade/system', humanKey: 'upgrade.system' }).then(() => {
if (system.value.some(({ name }) => name.includes('yunohost'))) { if (system.value.some(({ name }) => name.includes('yunohost'))) {
store.dispatch('TRY_TO_RECONNECT', { tryToReconnect({
attemps: 1, attemps: 1,
origin: 'upgrade_system', origin: 'upgrade_system',
initialDelay: 2000, initialDelay: 2000,

View file

@ -1,13 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { type ComputedRef } from 'vue' import { type ComputedRef } from 'vue'
import { useStore } from 'vuex'
import { useInfos } from '@/composables/useInfos'
import { useInitialQueries } from '@/composables/useInitialQueries' import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch' import { useSearch } from '@/composables/useSearch'
import { useStoreGetters } from '@/store/utils' import { useStoreGetters } from '@/store/utils'
import type { Obj } from '@/types/commons' import type { Obj } from '@/types/commons'
const store = useStore()
const { loading } = useInitialQueries([ const { loading } = useInitialQueries([
{ {
uri: 'users?fields=username&fields=fullname&fields=mail&fields=mailbox-quota&fields=groups', uri: 'users?fields=username&fields=fullname&fields=mail&fields=mailbox-quota&fields=groups',
@ -23,8 +22,8 @@ const [search, filteredUsers] = useSearch(
) )
function downloadExport() { function downloadExport() {
const host = store.getters.host const { host } = useInfos()
window.open(`https://${host}/yunohost/api/users/export`, '_blank') window.open(`https://${host.value}/yunohost/api/users/export`, '_blank')
} }
</script> </script>