mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
refactor: turn data store to global state with cache handing
This commit is contained in:
parent
66f48649d2
commit
13dc8de182
4 changed files with 268 additions and 310 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { useCache, type StorePath } from '@/composables/data'
|
||||||
import { useInfos } from '@/composables/useInfos'
|
import { useInfos } from '@/composables/useInfos'
|
||||||
import {
|
import {
|
||||||
useRequests,
|
useRequests,
|
||||||
|
@ -19,8 +20,7 @@ export type HumanKey = {
|
||||||
export type APIQuery = {
|
export type APIQuery = {
|
||||||
method?: RequestMethod
|
method?: RequestMethod
|
||||||
uri: string
|
uri: string
|
||||||
cachePath?: string
|
cachePath?: StorePath
|
||||||
cacheParams?: Obj
|
|
||||||
data?: Obj
|
data?: Obj
|
||||||
humanKey?: string | HumanKey
|
humanKey?: string | HumanKey
|
||||||
showModal?: boolean
|
showModal?: boolean
|
||||||
|
@ -95,11 +95,10 @@ export default {
|
||||||
* @returns Promise that resolve the api response data
|
* @returns Promise that resolve the api response data
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
async fetch<T extends any = Obj | string>({
|
async fetch<T extends any = any>({
|
||||||
uri,
|
uri,
|
||||||
method = 'GET',
|
method = 'GET',
|
||||||
cachePath = undefined,
|
cachePath = undefined,
|
||||||
cacheParams = undefined,
|
|
||||||
data = undefined,
|
data = undefined,
|
||||||
humanKey = undefined,
|
humanKey = undefined,
|
||||||
showModal = method !== 'GET',
|
showModal = method !== 'GET',
|
||||||
|
@ -107,6 +106,9 @@ export default {
|
||||||
initial = false,
|
initial = false,
|
||||||
asFormData = true,
|
asFormData = true,
|
||||||
}: APIQuery): Promise<T> {
|
}: APIQuery): Promise<T> {
|
||||||
|
const cache = cachePath ? useCache<T>(method, cachePath) : undefined
|
||||||
|
if (method === 'GET' && cache?.content) return cache.content
|
||||||
|
|
||||||
const { locale } = useSettings()
|
const { locale } = useSettings()
|
||||||
const { startRequest, endRequest } = useRequests()
|
const { startRequest, endRequest } = useRequests()
|
||||||
|
|
||||||
|
@ -136,14 +138,18 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/yunohost/api/' + uri, options)
|
const response = await fetch('/yunohost/api/' + uri, options)
|
||||||
const responseData = await getResponseData(response)
|
|
||||||
endRequest({ request, success: response.ok })
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw getError(request, response, responseData as string | APIErrorData)
|
const errorData = await getResponseData<string | APIErrorData>(response)
|
||||||
|
endRequest({ request, success: false })
|
||||||
|
throw getError(request, response, errorData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return responseData as T
|
const responseData = await getResponseData<T>(response)
|
||||||
|
cache?.update(responseData)
|
||||||
|
endRequest({ request, success: true })
|
||||||
|
|
||||||
|
return responseData
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,18 +164,18 @@ export default {
|
||||||
* @returns Promise that resolves an array of server responses
|
* @returns Promise that resolves an array of server responses
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
async fetchAll(
|
async fetchAll<T extends any[] = any[]>(
|
||||||
queries: APIQuery[],
|
queries: APIQuery[],
|
||||||
{ showModal = false, initial = false } = {},
|
{ showModal = false, initial = false } = {},
|
||||||
) {
|
): Promise<T> {
|
||||||
const results: Array<Obj | string> = []
|
const results = []
|
||||||
for (const query of queries) {
|
for (const query of queries) {
|
||||||
if (showModal) query.showModal = true
|
if (showModal) query.showModal = true
|
||||||
if (initial) query.initial = true
|
if (initial) query.initial = true
|
||||||
results.push(await this.fetch(query))
|
results.push(await this.fetch(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results as T
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,7 +186,7 @@ export default {
|
||||||
* @returns Promise that resolve the api response data or an error
|
* @returns Promise that resolve the api response data or an error
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
get<T extends any = Obj | string>(
|
get<T extends any = any>(
|
||||||
query: string | Omit<APIQuery, 'method' | 'data'>,
|
query: string | Omit<APIQuery, 'method' | 'data'>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return this.fetch(typeof query === 'string' ? { uri: query } : query)
|
return this.fetch(typeof query === 'string' ? { uri: query } : query)
|
||||||
|
@ -194,9 +200,7 @@ export default {
|
||||||
* @returns Promise that resolve the api response data or an error
|
* @returns Promise that resolve the api response data or an error
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
post<T extends any = Obj | string>(
|
post<T extends any = any>(query: Omit<APIQuery, 'method'>): Promise<T> {
|
||||||
query: Omit<APIQuery, 'method'>,
|
|
||||||
): Promise<T> {
|
|
||||||
return this.fetch({ ...query, method: 'POST' })
|
return this.fetch({ ...query, method: 'POST' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -208,9 +212,7 @@ export default {
|
||||||
* @returns Promise that resolve the api response data or an error
|
* @returns Promise that resolve the api response data or an error
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
put<T extends any = Obj | string>(
|
put<T extends any = any>(query: Omit<APIQuery, 'method'>): Promise<T> {
|
||||||
query: Omit<APIQuery, 'method'>,
|
|
||||||
): Promise<T> {
|
|
||||||
return this.fetch({ ...query, method: 'PUT' })
|
return this.fetch({ ...query, method: 'PUT' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -222,9 +224,7 @@ export default {
|
||||||
* @returns Promise that resolve the api response data or an error
|
* @returns Promise that resolve the api response data or an error
|
||||||
* @throws Throw an `APIError` or subclass depending on server response
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
delete<T extends any = Obj | string>(
|
delete<T extends any = any>(query: Omit<APIQuery, 'method'>): Promise<T> {
|
||||||
query: Omit<APIQuery, 'method'>,
|
|
||||||
): Promise<T> {
|
|
||||||
return this.fetch({ ...query, method: 'DELETE' })
|
return this.fetch({ ...query, method: 'DELETE' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,15 @@ import type { APIErrorData } from './api'
|
||||||
* @param response - A fetch `Response` object.
|
* @param response - A fetch `Response` object.
|
||||||
* @returns Parsed response's json or response's text.
|
* @returns Parsed response's json or response's text.
|
||||||
*/
|
*/
|
||||||
export async function getResponseData(response: Response) {
|
export async function getResponseData<T extends any = any>(
|
||||||
|
response: Response,
|
||||||
|
): Promise<T> {
|
||||||
// FIXME the api should always return json as response
|
// FIXME the api should always return json as response
|
||||||
const responseText = await response.text()
|
const responseText = await response.text()
|
||||||
try {
|
try {
|
||||||
return JSON.parse(responseText) as Obj
|
return JSON.parse(responseText)
|
||||||
} catch {
|
} catch {
|
||||||
return responseText
|
return responseText as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,312 +1,224 @@
|
||||||
import api from '@/api'
|
import { createGlobalState } from '@vueuse/core'
|
||||||
import { isEmptyValue } from '@/helpers/commons'
|
import { computed, reactive, ref, toValue, type MaybeRefOrGetter } from 'vue'
|
||||||
|
|
||||||
|
import type { RequestMethod } from '@/api/api'
|
||||||
|
import { isObjectLiteral } from '@/helpers/commons'
|
||||||
import { stratify } from '@/helpers/data/tree'
|
import { stratify } from '@/helpers/data/tree'
|
||||||
import { reactive } from 'vue'
|
import type { Obj } from '@/types/commons'
|
||||||
|
import type {
|
||||||
|
DomainDetail,
|
||||||
|
Group,
|
||||||
|
Permission,
|
||||||
|
UserDetails,
|
||||||
|
UserItem,
|
||||||
|
} from '@/types/core/data'
|
||||||
|
|
||||||
export function getParentDomain(domain, domains, highest = false) {
|
function arrayOrNull<T extends any[]>(items: T): T | null {
|
||||||
const method = highest ? 'lastIndexOf' : 'indexOf'
|
return items.length ? items : null
|
||||||
let i = domain[method]('.')
|
|
||||||
while (i !== -1) {
|
|
||||||
const dn = domain.slice(i + 1)
|
|
||||||
if (domains.includes(dn)) return dn
|
|
||||||
i = domain[method]('.', i + (highest ? -1 : 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const useData = createGlobalState(() => {
|
||||||
state: () => ({
|
const users = ref<Obj<UserItem>>({})
|
||||||
main_domain: undefined,
|
const userDetails = ref<Obj<UserDetails>>({})
|
||||||
domains: undefined, // Array
|
const groups = ref<Obj<Group>>({})
|
||||||
domains_details: {},
|
const permissions = ref<Obj<Permission>>({})
|
||||||
users: undefined, // basic user data: Object {username: {data}}
|
const mainDomain = ref<string | undefined>()
|
||||||
users_details: {}, // precise user data: Object {username: {data}}
|
const domains = ref<string[] | undefined>()
|
||||||
groups: undefined,
|
const domainDetails = ref<Obj<DomainDetail>>({})
|
||||||
permissions: undefined,
|
|
||||||
|
function update(
|
||||||
|
method: RequestMethod,
|
||||||
|
payload: any,
|
||||||
|
key: DataKeys,
|
||||||
|
param?: string,
|
||||||
|
) {
|
||||||
|
if (key === 'users') {
|
||||||
|
if (method === 'GET') users.value = payload.users
|
||||||
|
else if (method === 'POST')
|
||||||
|
users.value[payload.username] = {
|
||||||
|
...payload,
|
||||||
|
'mailbox-quota': 'Pas de quota',
|
||||||
|
groups: [],
|
||||||
|
}
|
||||||
|
} else if (key === 'userDetails' && param) {
|
||||||
|
if (method === 'GET' || method === 'PUT') {
|
||||||
|
userDetails.value[param] = payload[param]
|
||||||
|
} else if (method === 'DELETE') {
|
||||||
|
delete userDetails.value[param]
|
||||||
|
delete users.value[param]
|
||||||
|
}
|
||||||
|
} else if (key === 'permissions') {
|
||||||
|
if (method === 'GET') {
|
||||||
|
permissions.value = payload.permissions
|
||||||
|
} else if (method === 'PUT' && param) {
|
||||||
|
permissions.value[param] = payload
|
||||||
|
}
|
||||||
|
} else if (key === 'groups') {
|
||||||
|
if (method === 'GET') {
|
||||||
|
groups.value = payload.groups
|
||||||
|
} else if (method === 'POST') {
|
||||||
|
groups.value[payload.name] = { members: [], permissions: [] }
|
||||||
|
} else if (method === 'PUT' && param) {
|
||||||
|
groups.value[param] = payload
|
||||||
|
} else if (method === 'DELETE' && param) {
|
||||||
|
delete groups.value[param]
|
||||||
|
}
|
||||||
|
} else if (key === 'domains') {
|
||||||
|
if (method === 'GET') {
|
||||||
|
domains.value = payload.domains
|
||||||
|
mainDomain.value = payload.main
|
||||||
|
} else if (param) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
// FIXME api should at least return the domain name on
|
||||||
|
domains.value?.push(param)
|
||||||
|
} else if (method === 'PUT') {
|
||||||
|
mainDomain.value = param
|
||||||
|
} else if (method === 'DELETE') {
|
||||||
|
domains.value?.splice(domains.value.indexOf(param), 1)
|
||||||
|
delete domainDetails.value[param]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key === 'domainDetails' && param && method === 'GET') {
|
||||||
|
domainDetails.value[param] = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME rm?
|
||||||
|
throw new Error(
|
||||||
|
`couldnt update the cache, key: ${key}, method: ${method}, param: ${param}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
users,
|
||||||
|
userDetails,
|
||||||
|
groups,
|
||||||
|
permissions,
|
||||||
|
|
||||||
|
mainDomain,
|
||||||
|
domains,
|
||||||
|
domainDetails,
|
||||||
|
|
||||||
|
update,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useUsersAndGroups(username?: MaybeRefOrGetter<string>) {
|
||||||
|
const { users, userDetails } = useData()
|
||||||
|
return {
|
||||||
|
users: computed(() => {
|
||||||
|
return arrayOrNull(Object.values(users.value))
|
||||||
|
}),
|
||||||
|
usernames: computed(() => {
|
||||||
|
return arrayOrNull(Object.keys(users.value))
|
||||||
|
}),
|
||||||
|
user: computed(() => {
|
||||||
|
if (!username) return
|
||||||
|
return userDetails.value[toValue(username)]
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mutations: {
|
|
||||||
SET_DOMAINS(state, [{ domains, main }]) {
|
|
||||||
state.domains = domains
|
|
||||||
state.main_domain = main
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_DOMAINS_DETAILS(state, [name, details]) {
|
|
||||||
state.domains_details[name] = details
|
|
||||||
},
|
|
||||||
|
|
||||||
UPDATE_DOMAINS_DETAILS(state, payload) {
|
|
||||||
// FIXME use a common function to execute the same code ?
|
|
||||||
this.commit('SET_DOMAINS_DETAILS', payload)
|
|
||||||
},
|
|
||||||
|
|
||||||
DEL_DOMAINS_DETAILS(state, [name]) {
|
|
||||||
delete state.domains_details[name]
|
|
||||||
if (state.domains) {
|
|
||||||
delete state.domains[name]
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
ADD_DOMAINS(state, [{ domain }]) {
|
export function useDomains(domain_?: MaybeRefOrGetter<string>) {
|
||||||
state.domains.push(domain)
|
const { mainDomain, domains, domainDetails } = useData()
|
||||||
},
|
|
||||||
|
|
||||||
DEL_DOMAINS(state, [domain]) {
|
const orderedDomains = computed(() => {
|
||||||
state.domains.splice(state.domains.indexOf(domain), 1)
|
if (!domains.value) return
|
||||||
},
|
|
||||||
|
|
||||||
// Now applied thru 'SET_DOMAINS'
|
|
||||||
// 'SET_MAIN_DOMAIN' (state, [response]) {
|
|
||||||
// state.main_domain = response.current_main_domain
|
|
||||||
// },
|
|
||||||
|
|
||||||
UPDATE_MAIN_DOMAIN(state, [domain]) {
|
|
||||||
state.main_domain = domain
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_USERS(state, [users]) {
|
|
||||||
state.users = users || null
|
|
||||||
},
|
|
||||||
|
|
||||||
ADD_USERS(state, [user]) {
|
|
||||||
if (!state.users) state.users = {}
|
|
||||||
state.users[user.username] = user
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_USERS_DETAILS(state, [username, userData]) {
|
|
||||||
state.users_details[username] = userData
|
|
||||||
if (!state.users) return
|
|
||||||
const user = state.users[username]
|
|
||||||
for (const key of ['fullname', 'mail']) {
|
|
||||||
if (user[key] !== userData[key]) {
|
|
||||||
user[key] = userData[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
UPDATE_USERS_DETAILS(state, payload) {
|
|
||||||
// FIXME use a common function to execute the same code ?
|
|
||||||
this.commit('SET_USERS_DETAILS', payload)
|
|
||||||
},
|
|
||||||
|
|
||||||
DEL_USERS_DETAILS(state, [username]) {
|
|
||||||
delete state.users_details[username]
|
|
||||||
if (state.users) {
|
|
||||||
delete state.users[username]
|
|
||||||
if (Object.keys(state.users).length === 0) {
|
|
||||||
state.users = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_GROUPS(state, [groups]) {
|
|
||||||
state.groups = groups
|
|
||||||
},
|
|
||||||
|
|
||||||
ADD_GROUPS(state, [{ name }]) {
|
|
||||||
if (state.groups !== undefined) {
|
|
||||||
state.groups[name] = { members: [], permissions: [] }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
UPDATE_GROUPS(state, [data, { groupName }]) {
|
|
||||||
state.groups[groupName] = data
|
|
||||||
},
|
|
||||||
|
|
||||||
DEL_GROUPS(state, [groupname]) {
|
|
||||||
delete state.groups[groupname]
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_PERMISSIONS(state, [permissions]) {
|
|
||||||
state.permissions = permissions
|
|
||||||
},
|
|
||||||
|
|
||||||
UPDATE_PERMISSIONS(state, [_, { groupName, action, permId }]) {
|
|
||||||
// FIXME hacky way to update the store
|
|
||||||
const permissions = state.groups[groupName].permissions
|
|
||||||
if (action === 'add') {
|
|
||||||
permissions.push(permId)
|
|
||||||
} else if (action === 'remove') {
|
|
||||||
const index = permissions.indexOf(permId)
|
|
||||||
if (index > -1) permissions.splice(index, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
GET(
|
|
||||||
{ state, commit, rootState },
|
|
||||||
{
|
|
||||||
uri,
|
|
||||||
param,
|
|
||||||
storeKey = uri,
|
|
||||||
humanKey,
|
|
||||||
noCache,
|
|
||||||
options,
|
|
||||||
...extraParams
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const currentState = param ? state[storeKey][param] : state[storeKey]
|
|
||||||
// if data has already been queried, simply return
|
|
||||||
const ignoreCache = !rootState.cache || noCache || false
|
|
||||||
if (currentState !== undefined && !ignoreCache) return currentState
|
|
||||||
return api
|
|
||||||
.fetch('GET', param ? `${uri}/${param}` : uri, null, humanKey, options)
|
|
||||||
.then((responseData) => {
|
|
||||||
// FIXME here's an ugly fix to be able to also cache the main domain when querying domains
|
|
||||||
const data =
|
|
||||||
storeKey === 'domains'
|
|
||||||
? responseData
|
|
||||||
: responseData[storeKey]
|
|
||||||
? responseData[storeKey]
|
|
||||||
: responseData
|
|
||||||
commit(
|
|
||||||
'SET_' + storeKey.toUpperCase(),
|
|
||||||
[param, data, extraParams].filter((item) => !isEmptyValue(item)),
|
|
||||||
)
|
|
||||||
return param ? state[storeKey][param] : state[storeKey]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
POST(
|
|
||||||
{ state, commit },
|
|
||||||
{ uri, storeKey = uri, data, humanKey, options, ...extraParams },
|
|
||||||
) {
|
|
||||||
return api
|
|
||||||
.fetch('POST', uri, data, humanKey, options)
|
|
||||||
.then((responseData) => {
|
|
||||||
// FIXME api/domains returns null
|
|
||||||
if (responseData === null) responseData = data
|
|
||||||
responseData = responseData[storeKey]
|
|
||||||
? responseData[storeKey]
|
|
||||||
: responseData
|
|
||||||
commit(
|
|
||||||
'ADD_' + storeKey.toUpperCase(),
|
|
||||||
[responseData, extraParams].filter((item) => !isEmptyValue(item)),
|
|
||||||
)
|
|
||||||
return state[storeKey]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
PUT(
|
|
||||||
{ state, commit },
|
|
||||||
{ uri, param, storeKey = uri, data, humanKey, options, ...extraParams },
|
|
||||||
) {
|
|
||||||
return api
|
|
||||||
.fetch('PUT', param ? `${uri}/${param}` : uri, data, humanKey, options)
|
|
||||||
.then((responseData) => {
|
|
||||||
const data = responseData[storeKey]
|
|
||||||
? responseData[storeKey]
|
|
||||||
: responseData
|
|
||||||
commit(
|
|
||||||
'UPDATE_' + storeKey.toUpperCase(),
|
|
||||||
[param, data, extraParams].filter((item) => !isEmptyValue(item)),
|
|
||||||
)
|
|
||||||
return param ? state[storeKey][param] : state[storeKey]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
DELETE(
|
|
||||||
{ commit },
|
|
||||||
{ uri, param, storeKey = uri, data, humanKey, options, ...extraParams },
|
|
||||||
) {
|
|
||||||
return api
|
|
||||||
.fetch(
|
|
||||||
'DELETE',
|
|
||||||
param ? `${uri}/${param}` : uri,
|
|
||||||
data,
|
|
||||||
humanKey,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
commit(
|
|
||||||
'DEL_' + storeKey.toUpperCase(),
|
|
||||||
[param, extraParams].filter((item) => !isEmptyValue(item)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
RESET_CACHE_DATA({ state }, keys = Object.keys(state)) {
|
|
||||||
for (const key of keys) {
|
|
||||||
if (key === 'users_details') {
|
|
||||||
state[key] = {}
|
|
||||||
} else {
|
|
||||||
state[key] = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
users: (state) => {
|
|
||||||
if (state.users) return Object.values(state.users)
|
|
||||||
return state.users
|
|
||||||
},
|
|
||||||
|
|
||||||
userNames: (state) => {
|
|
||||||
if (state.users) return Object.keys(state.users)
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
|
|
||||||
user: (state) => (name) => state.users_details[name], // not cached
|
|
||||||
|
|
||||||
domains: (state) => state.domains,
|
|
||||||
|
|
||||||
orderedDomains: (state) => {
|
|
||||||
if (!state.domains) return
|
|
||||||
|
|
||||||
const splittedDomains = Object.fromEntries(
|
const splittedDomains = Object.fromEntries(
|
||||||
state.domains.map((domain) => {
|
domains.value.map((domain) => {
|
||||||
// Keep the main part of the domain and the extension together
|
// Keep the main part of the domain and the extension together
|
||||||
// eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this']
|
// eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this']
|
||||||
domain = domain.split('.')
|
const domainParts = domain.split('.')
|
||||||
domain.push(domain.pop() + domain.pop())
|
domainParts.push(domainParts.pop()! + domainParts.pop()!)
|
||||||
return [domain, domain.reverse()]
|
return [domain, domainParts.reverse()]
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return state.domains.sort(
|
return domains.value.sort((a, b) =>
|
||||||
(a, b) => splittedDomains[a] > splittedDomains[b],
|
splittedDomains[a] > splittedDomains[b] ? 1 : -1,
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
|
|
||||||
domainsTree: (state, getters) => {
|
return {
|
||||||
// This getter will not return any reactive data, make sure to assign its output
|
mainDomain,
|
||||||
// to a component's `data`.
|
domain: computed(() => {
|
||||||
// FIXME manage to store the result in the store to allow reactive data (trigger an
|
if (!domain_) return
|
||||||
// action when state.domain change)
|
return domainDetails.value[toValue(domain_)]
|
||||||
const domains = getters.orderedDomains
|
}),
|
||||||
|
domains,
|
||||||
|
domainsAsChoices: computed(() => {
|
||||||
|
return domains.value?.map((domain) => ({
|
||||||
|
value: domain,
|
||||||
|
text: domain === mainDomain.value ? domain + ' ★' : domain,
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
orderedDomains,
|
||||||
|
domainsTree: computed(() => {
|
||||||
|
const domains = orderedDomains.value
|
||||||
if (!domains) return
|
if (!domains) return
|
||||||
const dataset = reactive(
|
const dataset = reactive(
|
||||||
domains.map((name) => ({
|
domains.map((domain) => ({
|
||||||
// data to build a hierarchy
|
// data to build a hierarchy
|
||||||
name,
|
name: domain,
|
||||||
parent: getParentDomain(name, domains),
|
parent: domainDetails.value[domain].topest_parent,
|
||||||
// utility data that will be used by `RecursiveListGroup` component
|
// utility data that will be used by `RecursiveListGroup` component
|
||||||
to: { name: 'domain-info', params: { name } },
|
to: { name: 'domain-info', params: { name: domain } },
|
||||||
opened: true,
|
opened: true,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
return stratify(dataset)
|
return stratify(dataset)
|
||||||
},
|
}),
|
||||||
|
}
|
||||||
domain: (state) => (name) => state.domains_details[name],
|
}
|
||||||
|
|
||||||
highestDomainParentName: (state, getters) => (name) => {
|
type StoreKeys = 'users' | 'permissions' | 'groups' | 'mainDomain' | 'domains'
|
||||||
return getParentDomain(name, getters.orderedDomains, true)
|
type StoreKeysParam =
|
||||||
},
|
| 'userDetails'
|
||||||
|
| 'groups'
|
||||||
mainDomain: (state) => state.main_domain,
|
| 'permissions'
|
||||||
|
| 'mainDomain'
|
||||||
domainsAsChoices: (state) => {
|
| 'domainDetails'
|
||||||
const mainDomain = state.main_domain
|
| 'domains'
|
||||||
return state.domains.map((domain) => {
|
type DataKeys = StoreKeys | StoreKeysParam
|
||||||
return {
|
export type StorePath = `${StoreKeys}` | `${StoreKeysParam}.${string}`
|
||||||
value: domain,
|
|
||||||
text: domain === mainDomain ? domain + ' ★' : domain,
|
export function useCache<T extends any = any>(
|
||||||
|
method: RequestMethod,
|
||||||
|
cachePath: StorePath,
|
||||||
|
) {
|
||||||
|
const [key, param] = cachePath.split('.') as
|
||||||
|
| [StoreKeys, undefined]
|
||||||
|
| [StoreKeysParam, string]
|
||||||
|
const data = useData()
|
||||||
|
// FIXME get global cache policy setting
|
||||||
|
// Add noCache arg? not used
|
||||||
|
|
||||||
|
if (!(key in data)) {
|
||||||
|
throw new Error('Trying to get cache of inexistant data')
|
||||||
|
}
|
||||||
|
const d = data[key].value
|
||||||
|
let content = d as T
|
||||||
|
if (param) {
|
||||||
|
if (isObjectLiteral(d) && !Array.isArray(d)) {
|
||||||
|
content = d[param] as T
|
||||||
|
} else {
|
||||||
|
throw new Error('Trying to get param on non object data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
update: (payload: T) => data.update(method, payload, key, param),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetCache(keys: DataKeys[]) {
|
||||||
|
const data = useData()
|
||||||
|
for (const key of keys) {
|
||||||
|
if (['domains', 'mainDomain'].includes(key)) {
|
||||||
|
data[key].value = undefined
|
||||||
|
} else {
|
||||||
|
data[key].value = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
44
app/src/types/core/data.ts
Normal file
44
app/src/types/core/data.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
export type UserItem = {
|
||||||
|
username: string
|
||||||
|
fullname: string
|
||||||
|
mail: string
|
||||||
|
'mailbox-quota': string
|
||||||
|
groups: string[]
|
||||||
|
}
|
||||||
|
export type UserDetails = {
|
||||||
|
username: string
|
||||||
|
fullname: string
|
||||||
|
mail: string
|
||||||
|
'mail-aliases': string[]
|
||||||
|
'mail-forward': string[]
|
||||||
|
'mailbox-quota': { limit: string; use: string }
|
||||||
|
}
|
||||||
|
export type Permission = {
|
||||||
|
allowed: string[]
|
||||||
|
corresponding_users: string[]
|
||||||
|
auth_header: boolean
|
||||||
|
label: string
|
||||||
|
show_tile: boolean
|
||||||
|
protected: boolean
|
||||||
|
url: string | null
|
||||||
|
additional_urls: string[]
|
||||||
|
}
|
||||||
|
export type Group = {
|
||||||
|
members: string[]
|
||||||
|
permissions: string[]
|
||||||
|
}
|
||||||
|
export type DomainDetail = {
|
||||||
|
certificate: {
|
||||||
|
subject: string
|
||||||
|
CA_name: string
|
||||||
|
CA_type: string // enumlike
|
||||||
|
validity: number
|
||||||
|
style: string // enumlike
|
||||||
|
summary: string // enum
|
||||||
|
ACME_eligible: boolean
|
||||||
|
}
|
||||||
|
registrar: string // or null ?
|
||||||
|
apps: { name: string; id: string; path: string }[]
|
||||||
|
main: boolean
|
||||||
|
topest_parent: string | null
|
||||||
|
}
|
Loading…
Reference in a new issue