mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
feat(web): introducing defineRemoteResource (Web Core) and useXoCollectionState (XO 6) to handle XO API resources (#8855)
This commit is contained in:
committed by
GitHub
parent
c1c88c6797
commit
e0b9bc2004
115
@xen-orchestra/web-core/lib/packages/remote-resource/README.md
Normal file
115
@xen-orchestra/web-core/lib/packages/remote-resource/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# `defineRemoteResource` documentation
|
||||
|
||||
This utility will create a function that can be used to fetch data from a remote resource (i.e., an API endpoint)
|
||||
|
||||
When called, a subscription to the resource is registered.
|
||||
|
||||
If multiple Components call that same function, a subscription will be registered for each of them.
|
||||
|
||||
They will share the same data and state, which means that the data will be fetched only once and shared across all Components.
|
||||
|
||||
The fetch will start automatically when at least one Component is using the resource, then, when the last Component releases the resource, the polling will stop.
|
||||
|
||||
The data is cached for a certain duration, so if a Component remounts in the frame time, the data will be retrieved from the cache to be displayed immediately, while the data is being fetched again in the background.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```typescript
|
||||
const useMyResource = defineRemoteResource({
|
||||
url: '/api/path/to/resource',
|
||||
})
|
||||
```
|
||||
|
||||
By default, the initial data (`TData`) will be `undefined`, the returned state will be `{ data: Ref<TData> }` and when new data is received, it will just replace the previous one.
|
||||
|
||||
```typescript
|
||||
const { data } = useMyResource()
|
||||
```
|
||||
|
||||
## Initial data, state and received data handling
|
||||
|
||||
The initial data, the state and what happen when data is retrieved can be customized by passing the `initialData`, `state` and `onDataReceived` properties:
|
||||
|
||||
```typescript
|
||||
const useMyResource = defineRemoteResource({
|
||||
url: '/api/path/to/resource',
|
||||
initialData: () => {} as Partial<MyResource>,
|
||||
state: (data) => {
|
||||
return {
|
||||
myResource: data,
|
||||
customProp: computed(() => data.foobar)
|
||||
}
|
||||
},
|
||||
onDataReceived: (currentData, receivedData) => {
|
||||
deepMerge(currentData.value, receivedData)
|
||||
}
|
||||
}
|
||||
|
||||
const { myResource, customProp } = useMyResource()
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
The context is available in the `$context` property of the returned state.
|
||||
|
||||
It contains the following properties:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------- | --------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `scope` | `EffectScope` | The Vue effect scope used to be shared across the resource and its dependencies |
|
||||
| `args` | `any[]` | The arguments passed to the `useMyResource` function |
|
||||
| `isReady` | `ComputedRef<boolean>` | Indicates if the resource is ready (i.e., the data has been fetched at least once) |
|
||||
| `hasError` | `ComputedRef<boolean>` | Indicates if an error occurred during the last fetch |
|
||||
| `lastError` | `ComputedRef<Error \| undefined>` | The last error that occurred during the fetch, if any |
|
||||
| `isEnabled` | `Ref<boolean>` | Whether the resource is enabled (execute the request) or not |
|
||||
| `enable` | `() => void` | Function to manually enable the resource |
|
||||
| `disable` | `() => void` | Function to manually disable the resource |
|
||||
| `forceReload` | `() => void` | Function to manually reload the resource |
|
||||
|
||||
```typescript
|
||||
const { myResource, $context } = useMyResource()
|
||||
```
|
||||
|
||||
It's also passed as the second argument of the `state` builder and can be used to extend the resource state.
|
||||
|
||||
```typescript
|
||||
const useMyResource = defineRemoteResource({
|
||||
url: '/api/path/to/resource',
|
||||
state: (data, context) => ({
|
||||
myResource: data,
|
||||
isMyResourceReady: context.isReady,
|
||||
}),
|
||||
})
|
||||
|
||||
const { myResource, isMyResourceReady } = useMyResource()
|
||||
```
|
||||
|
||||
## Dynamic URLs
|
||||
|
||||
You can define a dynamic URL by using a function that returns the URL string.
|
||||
|
||||
```typescript
|
||||
const useMyResource = defineRemoteResource({
|
||||
url: (id: string) => `/api/path/to/resource/${id}`,
|
||||
})
|
||||
|
||||
const { id } = defineProps<{
|
||||
id: string
|
||||
}>()
|
||||
|
||||
const { data } = useMyResource(() => id)
|
||||
```
|
||||
|
||||
## Polling and caching
|
||||
|
||||
By default, the resource will poll for updates every 30 seconds and cache the result for 10 seconds.
|
||||
|
||||
This can be customized by passing the `pollingIntervalMs` and `cacheDurationMs` properties:
|
||||
|
||||
```typescript
|
||||
const useMyResource = defineRemoteResource({
|
||||
url: '/api/path/to/resource',
|
||||
pollingIntervalMs: 60_000, // Poll every 60 seconds
|
||||
cacheDurationMs: 5 * 60_000, // Cache for 5 minutes
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,294 @@
|
||||
import type { ResourceContext, UseRemoteResource } from '@core/packages/remote-resource/types.ts'
|
||||
import type { VoidFunction } from '@core/types/utility.type.ts'
|
||||
import { ifElse } from '@core/utils/if-else.utils.ts'
|
||||
import { type MaybeRef, noop, useTimeoutPoll } from '@vueuse/core'
|
||||
import { merge } from 'lodash-es'
|
||||
import readNDJSONStream from 'ndjson-readablestream'
|
||||
import {
|
||||
computed,
|
||||
type ComputedRef,
|
||||
type EffectScope,
|
||||
getCurrentScope,
|
||||
type MaybeRefOrGetter,
|
||||
onScopeDispose,
|
||||
reactive,
|
||||
type Ref,
|
||||
ref,
|
||||
toRef,
|
||||
toValue,
|
||||
watch,
|
||||
} from 'vue'
|
||||
|
||||
const DEFAULT_CACHE_DURATION_MS = 10_000
|
||||
|
||||
const DEFAULT_POLLING_INTERVAL_MS = 30_000
|
||||
|
||||
export function defineRemoteResource<
|
||||
TData,
|
||||
TState extends object = { data: Ref<TData> },
|
||||
TArgs extends any[] = [],
|
||||
>(config: {
|
||||
url: string | ((...args: TArgs) => string)
|
||||
initialData: () => TData
|
||||
state?: (data: Ref<NoInfer<TData>>, context: ResourceContext<TArgs>) => TState
|
||||
onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
||||
cacheDurationMs?: number
|
||||
pollingIntervalMs?: number
|
||||
stream?: boolean
|
||||
}): UseRemoteResource<TState, TArgs>
|
||||
|
||||
export function defineRemoteResource<TData, TState extends object, TArgs extends any[] = []>(config: {
|
||||
url: string | ((...args: TArgs) => string)
|
||||
state?: (data: Ref<TData | undefined>, context: ResourceContext<TArgs>) => TState
|
||||
onDataReceived?: (data: Ref<TData | undefined>, receivedData: any) => void
|
||||
cacheDurationMs?: number
|
||||
pollingIntervalMs?: number
|
||||
stream?: boolean
|
||||
}): UseRemoteResource<TState, TArgs>
|
||||
|
||||
export function defineRemoteResource<
|
||||
TData,
|
||||
TState extends object = { data: Ref<TData> },
|
||||
TArgs extends any[] = [],
|
||||
>(config: {
|
||||
url: string | ((...args: TArgs) => string)
|
||||
initialData?: () => TData
|
||||
state?: (data: Ref<TData>, context: ResourceContext<TArgs>) => TState
|
||||
onDataReceived?: (data: Ref<NoInfer<TData>>, receivedData: any) => void
|
||||
cacheDurationMs?: number
|
||||
pollingIntervalMs?: number
|
||||
stream?: boolean
|
||||
}) {
|
||||
const cache = new Map<
|
||||
string,
|
||||
{
|
||||
count: number
|
||||
pause: VoidFunction
|
||||
resume: VoidFunction
|
||||
state: object
|
||||
isReady: Ref<boolean>
|
||||
isFetching: Ref<boolean>
|
||||
lastError: Ref<Error | undefined>
|
||||
hasError: ComputedRef<boolean>
|
||||
}
|
||||
>()
|
||||
|
||||
const buildUrl = typeof config.url === 'string' ? () => config.url as string : config.url
|
||||
|
||||
const buildData = config.initialData ?? (() => undefined as TData | undefined)
|
||||
|
||||
const buildState = config.state ?? ((data: Ref<TData>) => ({ data }))
|
||||
|
||||
const cacheDuration = config.cacheDurationMs ?? DEFAULT_CACHE_DURATION_MS
|
||||
|
||||
const pollingInterval = config.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS
|
||||
|
||||
const onDataReceived =
|
||||
config.onDataReceived ??
|
||||
((data: Ref<TData>, receivedData: any) => {
|
||||
if (!config.stream || data.value === undefined) {
|
||||
data.value = receivedData
|
||||
return
|
||||
}
|
||||
|
||||
if (Array.isArray(data.value) && Array.isArray(receivedData)) {
|
||||
data.value.push(...receivedData)
|
||||
return
|
||||
}
|
||||
|
||||
merge(data.value, receivedData)
|
||||
})
|
||||
|
||||
function subscribeToUrl(url: string) {
|
||||
const entry = cache.get(url)
|
||||
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
entry.count += 1
|
||||
|
||||
if (entry.count === 1) {
|
||||
entry.resume()
|
||||
}
|
||||
}
|
||||
|
||||
function unsubscribeFromUrl(url: string) {
|
||||
const entry = cache.get(url)
|
||||
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
entry.count -= 1
|
||||
|
||||
if (entry.count > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
entry.pause()
|
||||
|
||||
setTimeout(() => {
|
||||
cache.delete(url)
|
||||
}, cacheDuration)
|
||||
}
|
||||
|
||||
function registerUrl(url: string, context: ResourceContext<TArgs>) {
|
||||
if (cache.has(url)) {
|
||||
return
|
||||
}
|
||||
|
||||
const isReady = ref(false)
|
||||
|
||||
const isFetching = ref(false)
|
||||
|
||||
const lastError = ref<Error>()
|
||||
|
||||
const hasError = computed(() => lastError.value !== undefined)
|
||||
|
||||
const data = ref(buildData()) as Ref<TData>
|
||||
|
||||
async function execute() {
|
||||
try {
|
||||
isFetching.value = true
|
||||
|
||||
const response = await fetch(url)
|
||||
|
||||
if (!response.ok) {
|
||||
lastError.value = Error(`Failed to fetch: ${response.statusText}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
return
|
||||
}
|
||||
|
||||
if (config.stream) {
|
||||
for await (const event of readNDJSONStream(response.body)) {
|
||||
onDataReceived(data, event)
|
||||
}
|
||||
} else {
|
||||
onDataReceived(data, await response.json())
|
||||
}
|
||||
|
||||
isReady.value = true
|
||||
} catch (error) {
|
||||
lastError.value = error instanceof Error ? error : new Error(String(error))
|
||||
} finally {
|
||||
isFetching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
let pause: VoidFunction = noop
|
||||
let resume: VoidFunction = execute
|
||||
|
||||
if (pollingInterval > 0) {
|
||||
const timeoutPoll = useTimeoutPoll(execute, pollingInterval, {
|
||||
immediateCallback: true,
|
||||
immediate: false,
|
||||
})
|
||||
|
||||
pause = timeoutPoll.pause
|
||||
resume = timeoutPoll.resume
|
||||
}
|
||||
|
||||
const state = buildState(data, context)
|
||||
|
||||
cache.set(url, {
|
||||
count: 0,
|
||||
pause,
|
||||
resume,
|
||||
state,
|
||||
isReady,
|
||||
isFetching,
|
||||
lastError,
|
||||
hasError,
|
||||
})
|
||||
}
|
||||
|
||||
function initializeUrl(url: ComputedRef<string>, context: ResourceContext<TArgs>) {
|
||||
watch(
|
||||
url,
|
||||
(toUrl, fromUrl) => {
|
||||
registerUrl(toUrl, context)
|
||||
|
||||
if (context.isEnabled.value) {
|
||||
subscribeToUrl(toUrl)
|
||||
}
|
||||
|
||||
if (fromUrl) {
|
||||
unsubscribeFromUrl(fromUrl)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
}
|
||||
|
||||
return function useRemoteResource(
|
||||
optionsOrParentContext?: { isEnabled?: MaybeRef<boolean>; scope?: EffectScope },
|
||||
...args: { [K in keyof TArgs]: MaybeRefOrGetter<TArgs[K]> }
|
||||
) {
|
||||
const scope = optionsOrParentContext?.scope ?? getCurrentScope()
|
||||
|
||||
if (!scope) {
|
||||
throw new Error('No effect scope found. Please provide a scope or use this function within a Vue component.')
|
||||
}
|
||||
|
||||
const isEnabled = toRef(optionsOrParentContext?.isEnabled ?? true)
|
||||
|
||||
return scope.run(() => {
|
||||
const url = computed(() => buildUrl(...(args.map(arg => toValue(arg)) as TArgs)))
|
||||
|
||||
onScopeDispose(() => {
|
||||
unsubscribeFromUrl(url.value)
|
||||
})
|
||||
|
||||
const context: ResourceContext<TArgs> = {
|
||||
scope,
|
||||
args,
|
||||
isReady: computed(() => cache.get(url.value)?.isReady.value ?? false),
|
||||
isFetching: computed(() => cache.get(url.value)?.isFetching?.value ?? false),
|
||||
lastError: computed(() => cache.get(url.value)?.lastError.value),
|
||||
hasError: computed(() => cache.get(url.value)?.hasError.value ?? false),
|
||||
isEnabled,
|
||||
enable: () => {
|
||||
isEnabled.value = true
|
||||
},
|
||||
disable: () => {
|
||||
isEnabled.value = false
|
||||
},
|
||||
forceReload: () => {
|
||||
cache.get(url.value)?.pause()
|
||||
cache.get(url.value)?.resume()
|
||||
},
|
||||
}
|
||||
|
||||
initializeUrl(url, context)
|
||||
|
||||
const state = reactive({} as TState)
|
||||
|
||||
ifElse(
|
||||
isEnabled,
|
||||
() => subscribeToUrl(url.value),
|
||||
() => unsubscribeFromUrl(url.value)
|
||||
)
|
||||
|
||||
watch(
|
||||
url,
|
||||
() => {
|
||||
Object.assign(state, cache.get(url.value)!.state)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries(state).map(([key, value]) =>
|
||||
typeof value === 'function' ? [key, value] : [key, toRef(state, key as any)]
|
||||
)
|
||||
),
|
||||
$context: context,
|
||||
}
|
||||
})!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { MaybeRef } from '@vueuse/core'
|
||||
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
||||
import type { ComputedRef, EffectScope, Ref, ToRef } from 'vue'
|
||||
|
||||
export type ResourceContext<TArgs extends any[]> = {
|
||||
scope: EffectScope
|
||||
args: { [K in keyof TArgs]: MaybeRefOrGetter<TArgs[K]> }
|
||||
isReady: ComputedRef<boolean>
|
||||
isFetching: ComputedRef<boolean>
|
||||
hasError: ComputedRef<boolean>
|
||||
lastError: ComputedRef<Error | undefined>
|
||||
isEnabled: Ref<boolean>
|
||||
enable: () => void
|
||||
disable: () => void
|
||||
forceReload: () => void
|
||||
}
|
||||
|
||||
export type UseRemoteResource<TState, TArgs extends any[]> = (
|
||||
optionsOrParentContext?: {
|
||||
isEnabled?: MaybeRef<boolean>
|
||||
scope?: EffectScope
|
||||
},
|
||||
...args: { [K in keyof TArgs]: MaybeRefOrGetter<TArgs[K]> }
|
||||
) => {
|
||||
[K in keyof TState]: TState[K] extends (...args: any[]) => any ? TState[K] : ToRef<TState[K]>
|
||||
} & {
|
||||
$context: ResourceContext<TArgs>
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"human-format": "^1.2.1",
|
||||
"iterable-backoff": "^0.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ndjson-readablestream": "^1.2.0",
|
||||
"ndjson-readablestream": "^1.3.0",
|
||||
"placement.js": "^1.0.0-beta.5",
|
||||
"simple-icons": "^14.14.0",
|
||||
"vue-echarts": "^6.6.8"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"d3-time-format": "^4.1.0",
|
||||
"human-format": "^1.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ndjson-readablestream": "^1.2.0",
|
||||
"ndjson-readablestream": "^1.3.0",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"pinia": "^3.0.1",
|
||||
"postcss": "^8.5.3",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import type { HostState } from '@core/types/object-icon.type'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -61,6 +61,7 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isMasterHost } = useHostStore().subscribe()
|
||||
const { isMasterHost } = useXoHostCollection()
|
||||
|
||||
const isMaster = computed(() => isMasterHost(host.id))
|
||||
</script>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -32,10 +32,11 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady: isHostReady } = useHostStore().subscribe()
|
||||
const { vmsByHost, isReady: isVmReady } = useVmStore().subscribe()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
|
||||
const isReady = logicAnd(isHostReady, isVmReady)
|
||||
const { vmsByHost, areVmsReady } = useXoVmCollection()
|
||||
|
||||
const isReady = logicAnd(areHostsReady, areVmsReady)
|
||||
|
||||
const hostVms = computed(() => vmsByHost.value.get(host.id) ?? [])
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsQuickInfoCard class="host-dashboard-quick-info" :loading="!isReady">
|
||||
<VtsQuickInfoCard class="host-dashboard-quick-info" :loading="!areHostsReady">
|
||||
<VtsQuickInfoColumn>
|
||||
<VtsQuickInfoRow :label="t('state')">
|
||||
<template #value>
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { HOST_POWER_STATE, type XoHost } from '@/types/xo/host.type'
|
||||
import VtsIcon, { type IconAccent } from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsQuickInfoCard from '@core/components/quick-info-card/VtsQuickInfoCard.vue'
|
||||
@@ -75,7 +75,7 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getMasterHostByPoolId, isMasterHost, isReady } = useHostStore().subscribe()
|
||||
const { getMasterHostByPoolId, isMasterHost, areHostsReady } = useXoHostCollection()
|
||||
|
||||
const powerStateConfig: Record<
|
||||
string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<UiCard class="host-dashboard-ram-provisioning">
|
||||
<UiCardTitle>{{ t('ram-provisioning') }}</UiCardTitle>
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsLoadingHero v-if="!areHostsReady" type="card" />
|
||||
<template v-else>
|
||||
<UiProgressBar
|
||||
display-mode="percent"
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -45,7 +45,7 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady } = useHostStore().subscribe()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
|
||||
const ramUsage = computed(() => {
|
||||
const total = host.memory.size
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import { VM_POWER_STATE } from '@/types/xo/vm.type'
|
||||
import VtsDonutChartWithLegend, {
|
||||
@@ -30,10 +30,10 @@ const { host } = defineProps<{
|
||||
host: XoHost
|
||||
}>()
|
||||
|
||||
const { isReady: isHostReady } = useHostStore().subscribe()
|
||||
const { vmsByHost, isReady: isVmReady } = useVmStore().subscribe()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
const { vmsByHost, areVmsReady } = useXoVmCollection()
|
||||
|
||||
const isReady = logicAnd(isHostReady, isVmReady)
|
||||
const isReady = logicAnd(areHostsReady, areVmsReady)
|
||||
|
||||
const hostVms = computed(() => vmsByHost.value.get(host.id) ?? [])
|
||||
|
||||
|
||||
@@ -299,8 +299,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store.ts'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoPif } from '@/types/xo/pif.type.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
@@ -328,15 +328,15 @@ const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const { get: getNetwork } = useNetworkStore().subscribe()
|
||||
const { getBondsDevices } = usePifStore().subscribe()
|
||||
const { useGetNetworkById } = useXoNetworkCollection()
|
||||
const { getBondsDevices } = useXoPifCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const ipAddresses = computed(() => [pif.ip, ...pif.ipv6].filter(ip => ip))
|
||||
|
||||
const network = computed(() => getNetwork(pif.$network))
|
||||
const network = useGetNetworkById(() => pif.$network)
|
||||
|
||||
const networkNbd = computed(() => (network.value?.nbd ? t('on') : t('off')))
|
||||
|
||||
|
||||
@@ -41,10 +41,14 @@
|
||||
</UiButton>
|
||||
</UiTableActions>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="arePifsReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
<VtsDataTable :is-ready :has-error :no-data-message="pifs.length === 0 ? t('no-pif-detected') : undefined">
|
||||
<VtsDataTable
|
||||
:is-ready="arePifsReady"
|
||||
:has-error="hasPifFetchError"
|
||||
:no-data-message="pifs.length === 0 ? t('no-pif-detected') : undefined"
|
||||
>
|
||||
<template #thead>
|
||||
<tr>
|
||||
<template v-for="column of visibleColumns" :key="column.id">
|
||||
@@ -127,16 +131,17 @@
|
||||
<div>{{ t('no-result') }}</div>
|
||||
</VtsStateHero>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="arePifsReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store.ts'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoPif } from '@/types/xo/pif.type.ts'
|
||||
import { getPifStatus } from '@/utils/xo-records/pif.util.ts'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
import VtsDataTable from '@core/components/data-table/VtsDataTable.vue'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -175,8 +180,8 @@ const { pifs } = defineProps<{
|
||||
pifs: XoPif[]
|
||||
}>()
|
||||
|
||||
const { isReady, hasError, getPifStatus } = usePifStore().subscribe()
|
||||
const { get } = useNetworkStore().subscribe()
|
||||
const { arePifsReady, hasPifFetchError } = useXoPifCollection()
|
||||
const { getNetworkById } = useXoNetworkCollection()
|
||||
|
||||
const { t } = useI18n()
|
||||
const selectedPifId = useRouteQuery('id')
|
||||
@@ -196,7 +201,7 @@ const pifsIds = computed(() => pifs.map(pif => pif.id))
|
||||
|
||||
const { selected, areAllSelected } = useMultiSelect(pifsIds)
|
||||
|
||||
const getNetworkName = (pif: XoPif) => get(pif.$network)?.name_label ?? ''
|
||||
const getNetworkName = (pif: XoPif) => getNetworkById(pif.$network)?.name_label ?? ''
|
||||
|
||||
const getVlanData = (vlan: number) => (vlan !== -1 ? vlan : t('none'))
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import { HOST_POWER_STATE, type XoHost } from '@/types/xo/host.type.ts'
|
||||
import VtsEnabledState from '@core/components/enabled-state/VtsEnabledState.vue'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -75,10 +75,11 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getPoolById } = usePoolStore().subscribe()
|
||||
const { getMasterHostByPoolId, isMasterHost } = useHostStore().subscribe()
|
||||
const { useGetPoolById } = useXoPoolCollection()
|
||||
|
||||
const pool = computed(() => getPoolById(host.$pool))
|
||||
const { getMasterHostByPoolId, isMasterHost } = useXoHostCollection()
|
||||
|
||||
const pool = useGetPoolById(() => host.$pool)
|
||||
|
||||
const isMaster = computed(() => isMasterHost(host.id))
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePciStore } from '@/stores/xo-rest-api/pci.store'
|
||||
import { usePgpuStore } from '@/stores/xo-rest-api/pgpu.store'
|
||||
import { useXoPciCollection } from '@/remote-resources/use-xo-pci-collection.ts'
|
||||
import { useXoPgpuCollection } from '@/remote-resources/use-xo-pgpu-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -43,21 +43,21 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getPci, isReady: isPciReady } = usePciStore().subscribe()
|
||||
const { get: getGpu, isReady: isPgpuReady } = usePgpuStore().subscribe()
|
||||
const { getPciById, arePcisReady } = useXoPciCollection()
|
||||
const { getPgpuById, arePgpusReady } = useXoPgpuCollection()
|
||||
|
||||
const isReady = logicAnd(isPgpuReady, isPciReady)
|
||||
const isReady = logicAnd(arePgpusReady, arePcisReady)
|
||||
|
||||
const devicesNames = useArrayReduce(
|
||||
() => host.PGPUs ?? [],
|
||||
(acc, pGpuId) => {
|
||||
const pciId = getGpu(pGpuId)?.pci
|
||||
const pciId = getPgpuById(pGpuId)?.pci
|
||||
|
||||
if (!pciId) {
|
||||
return acc
|
||||
}
|
||||
|
||||
const deviceName = getPci(pciId)?.device_name
|
||||
const deviceName = getPciById(pciId)?.device_name
|
||||
|
||||
if (!deviceName) {
|
||||
return acc
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVmControllerStore } from '@/stores/xo-rest-api/vm-controller.store.ts'
|
||||
import { useXoVmControllerCollection } from '@/remote-resources/use-xo-vm-controller-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -26,14 +26,14 @@ const { host } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getController } = useVmControllerStore().subscribe()
|
||||
const { getVmControllerById } = useXoVmControllerCollection()
|
||||
|
||||
const controllerMemory = computed(() => {
|
||||
if (host.controlDomain === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const controllerDomain = getController(host.controlDomain)
|
||||
const controllerDomain = getVmControllerById(host.controlDomain)
|
||||
|
||||
if (controllerDomain === undefined) {
|
||||
return
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { HOST_POWER_STATE } from '@/types/xo/host.type'
|
||||
import type { XoPif } from '@/types/xo/pif.type'
|
||||
import { getPifStatus } from '@/utils/xo-records/pif.util.ts'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
||||
import UiObjectIcon from '@core/components/ui/object-icon/UiObjectIcon.vue'
|
||||
@@ -41,15 +41,14 @@ const { pif } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get } = useHostStore().subscribe()
|
||||
const { getPifStatus } = usePifStore().subscribe()
|
||||
const { getHostById } = useXoHostCollection()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const status = computed(() => getPifStatus(pif))
|
||||
|
||||
const pifHost = computed(() => {
|
||||
const host = get(pif.$host)
|
||||
const host = getHostById(pif.$host)
|
||||
|
||||
if (!host) {
|
||||
return
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useServerStore } from '@/stores/xo-rest-api/server.store.ts'
|
||||
import { useXoServerCollection } from '@/remote-resources/use-xo-server-collection.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiAlert from '@core/components/ui/alert/UiAlert.vue'
|
||||
@@ -38,7 +38,6 @@ import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { serverId } = defineProps<{
|
||||
@@ -50,10 +49,10 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { get: getServer } = useServerStore().subscribe()
|
||||
const { useGetServerById } = useXoServerCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const server = computed(() => getServer(serverId))
|
||||
const server = useGetServerById(() => serverId)
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -6,63 +6,59 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store.ts'
|
||||
import { useVmControllerStore } from '@/stores/xo-rest-api/vm-controller.store.ts'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store.ts'
|
||||
<script setup lang="ts" generic="TRecord extends XapiXoRecord">
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import { useXoVmControllerCollection } from '@/remote-resources/use-xo-vm-controller-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoSr } from '@/types/xo/sr.type.ts'
|
||||
import type { XoVmController } from '@/types/xo/vm-controller.type.ts'
|
||||
import type { XoVm } from '@/types/xo/vm.type.ts'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { faDatabase, faDesktop, faServer } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { XapiXoRecord } from '@vates/types'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { type, uuid } = defineProps<{
|
||||
type: XapiXoRecord['type'] | 'unknown'
|
||||
uuid: XapiXoRecord['uuid']
|
||||
type: TRecord['type'] | 'unknown'
|
||||
uuid: TRecord['uuid']
|
||||
}>()
|
||||
|
||||
const { records: hostRecords } = useHostStore().subscribe()
|
||||
const { records: vmRecords } = useVmStore().subscribe()
|
||||
const { records: vmControllerRecords } = useVmControllerStore().subscribe()
|
||||
const { records: srRecords } = useSrStore().subscribe()
|
||||
const { getHostById } = useXoHostCollection()
|
||||
const { getVmById } = useXoVmCollection()
|
||||
const { getVmControllerById } = useXoVmControllerCollection()
|
||||
const { getSrById } = useXoSrCollection()
|
||||
|
||||
const record = computed(() => {
|
||||
if (type === 'VM') {
|
||||
return vmRecords.value.find(vm => vm.id === uuid)
|
||||
switch (type) {
|
||||
case 'VM':
|
||||
return getVmById(uuid as XoVm['id'])
|
||||
case 'host':
|
||||
return getHostById(uuid as XoHost['id'])
|
||||
case 'VM-controller':
|
||||
return getVmControllerById(uuid as XoVmController['id'])
|
||||
case 'SR':
|
||||
return getSrById(uuid as XoSr['id'])
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (type === 'host') {
|
||||
return hostRecords.value.find(host => host.id === uuid)
|
||||
}
|
||||
|
||||
if (type === 'VM-controller') {
|
||||
return vmControllerRecords.value.find(vmController => vmController.id === uuid)
|
||||
}
|
||||
|
||||
if (type === 'SR') {
|
||||
return srRecords.value.find(sr => sr.id === uuid)
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const nameLabel = computed(() => record.value?.name_label ?? uuid)
|
||||
|
||||
const icon = computed(() => {
|
||||
if (type === 'VM' || type === 'VM-controller') {
|
||||
return faDesktop
|
||||
switch (type) {
|
||||
case 'VM':
|
||||
case 'VM-controller':
|
||||
return faDesktop
|
||||
case 'host':
|
||||
return faServer
|
||||
case 'SR':
|
||||
return faDatabase
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (type === 'host') {
|
||||
return faServer
|
||||
}
|
||||
|
||||
if (type === 'SR') {
|
||||
return faDatabase
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const route = computed(() => {
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<UiCardTitle>
|
||||
{{ t('alarms') }}
|
||||
<UiCounter
|
||||
v-if="alarms.length !== 0 && areAlarmsReady"
|
||||
v-if="alarms.length !== 0 && isReady"
|
||||
accent="danger"
|
||||
size="small"
|
||||
variant="primary"
|
||||
:value="alarms.length"
|
||||
/>
|
||||
</UiCardTitle>
|
||||
<VtsLoadingHero v-if="!areAlarmsReady" type="card" />
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsAllGoodHero v-else-if="alarms.length === 0" type="card" />
|
||||
<div v-else class="alarm-list-container">
|
||||
<UiAlarmList>
|
||||
@@ -33,12 +33,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import AlarmLink from '@/components/pool/dashboard/alarms/AlarmLink.vue'
|
||||
import { useAlarmStore } from '@/stores/xo-rest-api/alarm.store.ts'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store.ts'
|
||||
import { useVmControllerStore } from '@/stores/xo-rest-api/vm-controller.store.ts'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store.ts'
|
||||
import type { XoAlarm } from '@/types/xo/alarm.type.ts'
|
||||
import { useXoAlarmCollection } from '@/remote-resources/use-xo-alarm-collection.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import { useXoVmControllerCollection } from '@/remote-resources/use-xo-vm-controller-collection.ts'
|
||||
import type { XoPoolDashboard } from '@/types/xo/pool-dashboard.type.ts'
|
||||
import VtsAllGoodHero from '@core/components/state-hero/VtsAllGoodHero.vue'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
@@ -48,7 +47,7 @@ import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { computed } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { poolDashboard } = defineProps<{
|
||||
@@ -57,40 +56,17 @@ const { poolDashboard } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get } = useAlarmStore().subscribe()
|
||||
const { isReady: areHostsReady } = useHostStore().subscribe()
|
||||
const { isReady: areVmsReady } = useVmStore().subscribe()
|
||||
const { isReady: areVmControllersReady } = useVmControllerStore().subscribe()
|
||||
const { isReady: areSrsReady } = useSrStore().subscribe()
|
||||
const { useGetAlarmsByIds, areAlarmsReady } = useXoAlarmCollection()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
const { areVmsReady } = useXoVmCollection()
|
||||
const { areVmControllersReady } = useXoVmControllerCollection()
|
||||
const { areSrsReady } = useXoSrCollection()
|
||||
|
||||
const areAlarmsReady = computed(() => {
|
||||
return (
|
||||
poolDashboard?.alarms !== undefined &&
|
||||
areHostsReady.value &&
|
||||
areVmsReady.value &&
|
||||
areVmControllersReady.value &&
|
||||
areSrsReady.value
|
||||
)
|
||||
})
|
||||
const isReady = logicAnd(areAlarmsReady, areHostsReady, areVmsReady, areVmControllersReady, areSrsReady)
|
||||
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const alarms = computed(() => {
|
||||
if (!poolDashboard?.alarms) {
|
||||
return []
|
||||
}
|
||||
|
||||
return poolDashboard.alarms
|
||||
.reduce((acc, alarmId) => {
|
||||
const alarm = get(alarmId)
|
||||
if (alarm !== undefined) {
|
||||
acc.push(alarm)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [] as XoAlarm[])
|
||||
.sort((alarm1, alarm2) => new Date(alarm2.time).getTime() - new Date(alarm1.time).getTime())
|
||||
})
|
||||
const alarms = useGetAlarmsByIds(() => poolDashboard?.alarms ?? [])
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
</UiButton>
|
||||
</UiTableActions>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areNetworksReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
<VtsDataTable
|
||||
:is-ready
|
||||
:has-error
|
||||
:is-ready="areNetworksReady"
|
||||
:has-error="hasNetworkFetchError"
|
||||
:no-data-message="networks.length === 0 ? t('no-network-detected') : undefined"
|
||||
>
|
||||
<template #thead>
|
||||
@@ -104,14 +104,14 @@
|
||||
<div>{{ t('no-result') }}</div>
|
||||
</VtsStateHero>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areNetworksReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import VtsDataTable from '@core/components/data-table/VtsDataTable.vue'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -147,7 +147,7 @@ const { networks } = defineProps<{
|
||||
networks: XoNetwork[]
|
||||
}>()
|
||||
|
||||
const { isReady, hasError } = useNetworkStore().subscribe()
|
||||
const { areNetworksReady, hasNetworkFetchError } = useXoNetworkCollection()
|
||||
|
||||
const { t } = useI18n()
|
||||
const searchQuery = ref('')
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import PifRow from '@/components/pif/PifRow.vue'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
@@ -154,7 +154,7 @@ const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const { getPifsByNetworkId } = usePifStore().subscribe()
|
||||
const { getPifsByNetworkId } = useXoPifCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -44,12 +44,12 @@
|
||||
</UiButton>
|
||||
</UiTableActions>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areNetworksReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
<VtsDataTable
|
||||
:is-ready
|
||||
:has-error
|
||||
:is-ready="areNetworksReady"
|
||||
:has-error="hasNetworkFetchError"
|
||||
:no-data-message="networks.length === 0 ? t('no-network-detected') : undefined"
|
||||
>
|
||||
<template #thead>
|
||||
@@ -108,15 +108,15 @@
|
||||
<div>{{ t('no-result') }}</div>
|
||||
</VtsStateHero>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areNetworksReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store.ts'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
import VtsDataTable from '@core/components/data-table/VtsDataTable.vue'
|
||||
@@ -157,9 +157,9 @@ const { networks } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady, hasError } = useNetworkStore().subscribe()
|
||||
const { areNetworksReady, hasNetworkFetchError } = useXoNetworkCollection()
|
||||
|
||||
const { records: pifs } = usePifStore().subscribe()
|
||||
const { pifs } = useXoPifCollection()
|
||||
|
||||
const selectedNetworkId = useRouteQuery('id')
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UiTitle>
|
||||
{{ t('connections') }}
|
||||
</UiTitle>
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsLoadingHero v-if="!areServersReady" type="card" />
|
||||
<template v-else>
|
||||
<VtsQuickInfoRow :label="t('ip-address')" :value="server?.host" />
|
||||
<VtsQuickInfoRow :label="t('proxy-url')" :value="server?.httpProxy" />
|
||||
@@ -23,7 +23,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useServerStore } from '@/stores/xo-rest-api/server.store'
|
||||
import { useXoServerCollection } from '@/remote-resources/use-xo-server-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsEnabledState from '@core/components/enabled-state/VtsEnabledState.vue'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
@@ -37,7 +37,7 @@ const { pool } = defineProps<{ pool: XoPool }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { serverByPool, isReady } = useServerStore().subscribe()
|
||||
const { serverByPool, areServersReady } = useXoServerCollection()
|
||||
|
||||
const server = computed(() => serverByPool.value.get(pool.id)?.[0])
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UiTitle>
|
||||
{{ t('pool-management') }}
|
||||
</UiTitle>
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsLoadingHero v-if="!areHostsReady" type="card" />
|
||||
<template v-else>
|
||||
<VtsQuickInfoRow :label="t('master')">
|
||||
<template #value>
|
||||
@@ -35,7 +35,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsEnabledState from '@core/components/enabled-state/VtsEnabledState.vue'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
@@ -44,7 +44,6 @@ import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { faServer } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { pool } = defineProps<{
|
||||
@@ -53,7 +52,7 @@ const { pool } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getHostById, isReady } = useHostStore().subscribe()
|
||||
const { useGetHostById, areHostsReady } = useXoHostCollection()
|
||||
|
||||
const primaryHost = computed(() => getHostById(pool.master))
|
||||
const primaryHost = useGetHostById(() => pool.master)
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UiTitle>
|
||||
{{ t('networking') }}
|
||||
</UiTitle>
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsLoadingHero v-if="!areNetworksReady" type="card" />
|
||||
<template v-else>
|
||||
<VtsQuickInfoRow :label="t('backup-network')">
|
||||
<template #value>
|
||||
@@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
@@ -43,7 +43,7 @@ const { pool } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getNetworkById, isReady } = useNetworkStore().subscribe()
|
||||
const { getNetworkById, areNetworksReady } = useXoNetworkCollection()
|
||||
|
||||
const backupNetwork = computed(() =>
|
||||
pool.otherConfig['xo:backupNetwork']
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UiTitle>
|
||||
{{ t('storage-configuration') }}
|
||||
</UiTitle>
|
||||
<VtsLoadingHero v-if="!isReady" type="card" />
|
||||
<VtsLoadingHero v-if="!areSrsReady" type="card" />
|
||||
<template v-else>
|
||||
<VtsQuickInfoRow :label="t('default-storage-repository')">
|
||||
<template #value>
|
||||
@@ -40,7 +40,7 @@
|
||||
</VtsQuickInfoRow>
|
||||
<VtsQuickInfoRow :label="t('heartbeat-storage-repository')">
|
||||
<template #value>
|
||||
<ul v-if="haSrs !== undefined && haSrs.length > 0">
|
||||
<ul v-if="haSrs.length > 0">
|
||||
<li v-for="haSr in haSrs" :key="haSr.id">
|
||||
<VtsIcon :icon="faDatabase" accent="current" />
|
||||
{{ haSr.name_label }}
|
||||
@@ -55,17 +55,15 @@
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store'
|
||||
<script lang="ts" setup>
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { XoSr } from '@/types/xo/sr.type'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { faDatabase } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { pool } = defineProps<{
|
||||
@@ -74,20 +72,10 @@ const { pool } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getSrById, isReady } = useSrStore().subscribe()
|
||||
const { useGetSrById, useGetSrsByIds, areSrsReady } = useXoSrCollection()
|
||||
|
||||
const defaultSr = computed(() => (pool.default_SR ? getSrById(pool.default_SR) : undefined))
|
||||
const suspendSr = computed(() => (pool.suspendSr ? getSrById(pool.suspendSr) : undefined))
|
||||
const crashDumpSr = computed(() => (pool.crashDumpSr ? getSrById(pool.crashDumpSr) : undefined))
|
||||
const haSrs = computed(() => {
|
||||
if (pool.haSrs === undefined || pool.haSrs?.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
return pool.haSrs.reduce((acc, srId) => {
|
||||
const sr = getSrById(srId)
|
||||
|
||||
return sr ? [...acc, sr] : acc
|
||||
}, [] as XoSr[])
|
||||
})
|
||||
const defaultSr = useGetSrById(() => pool.default_SR)
|
||||
const suspendSr = useGetSrById(() => pool.suspendSr)
|
||||
const crashDumpSr = useGetSrById(() => pool.crashDumpSr)
|
||||
const haSrs = useGetSrsByIds(() => pool.haSrs ?? [])
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store.ts'
|
||||
import { useVmControllerStore } from '@/stores/xo-rest-api/vm-controller.store.ts'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import { useXoVmControllerCollection } from '@/remote-resources/use-xo-vm-controller-collection.ts'
|
||||
import type { XoVmController } from '@/types/xo/vm-controller.type.ts'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import { faDatabase, faDesktop, faServer } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -22,23 +22,23 @@ const { type, uuid } = defineProps<{
|
||||
uuid: XapiXoRecord['uuid']
|
||||
}>()
|
||||
|
||||
const { records: hostRecords } = useHostStore().subscribe()
|
||||
const { records: vmRecords } = useVmStore().subscribe()
|
||||
const { records: vmControllerRecords } = useVmControllerStore().subscribe()
|
||||
const { records: srRecords } = useSrStore().subscribe()
|
||||
const { hosts } = useXoHostCollection()
|
||||
const { vms } = useXoVmCollection()
|
||||
const { vmControllers } = useXoVmControllerCollection()
|
||||
const { srs } = useXoSrCollection()
|
||||
|
||||
const record = computed(() => {
|
||||
if (type === 'VM') {
|
||||
return vmRecords.value.find(vm => vm.id === uuid)
|
||||
return vms.value.find(vm => vm.id === uuid)
|
||||
}
|
||||
if (type === 'host') {
|
||||
return hostRecords.value.find(host => host.id === uuid)
|
||||
return hosts.value.find(host => host.id === uuid)
|
||||
}
|
||||
if (type === 'VM-controller') {
|
||||
return vmControllerRecords.value.find(vmController => vmController.id === uuid)
|
||||
return vmControllers.value.find(vmController => vmController.id === uuid)
|
||||
}
|
||||
if (type === 'SR') {
|
||||
return srRecords.value.find(sr => sr.id === uuid)
|
||||
return srs.value.find(sr => sr.id === uuid)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import AlarmLink from '@/components/site/dashboard/AlarmLink.vue'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store.ts'
|
||||
import { useVmControllerStore } from '@/stores/xo-rest-api/vm-controller.store.ts'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import { useXoVmControllerCollection } from '@/remote-resources/use-xo-vm-controller-collection.ts'
|
||||
import type { XoAlarm } from '@/types/xo/alarm.type.ts'
|
||||
import VtsAllGoodHero from '@core/components/state-hero/VtsAllGoodHero.vue'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
@@ -46,7 +46,7 @@ import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { computed } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { alarms } = defineProps<{
|
||||
@@ -55,14 +55,12 @@ const { alarms } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady: areHostsReady } = useHostStore().subscribe()
|
||||
const { isReady: areVmsReady } = useVmStore().subscribe()
|
||||
const { isReady: areVmControllersReady } = useVmControllerStore().subscribe()
|
||||
const { isReady: areSrsReady } = useSrStore().subscribe()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
const { areVmsReady } = useXoVmCollection()
|
||||
const { areVmControllersReady } = useXoVmControllerCollection()
|
||||
const { areSrsReady } = useXoSrCollection()
|
||||
|
||||
const areAlarmsReady = computed(
|
||||
() => areHostsReady.value && areVmsReady.value && areVmControllersReady.value && areSrsReady.value
|
||||
)
|
||||
const areAlarmsReady = logicAnd(areHostsReady, areVmsReady, areVmControllersReady, areSrsReady)
|
||||
|
||||
const uiStore = useUiStore()
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { BackupRepositories } from '@/requests/use-site-dashboard.request.ts'
|
||||
import type { BackupRepositories } from '@/remote-resources/use-xo-site-dashboard.ts'
|
||||
import VtsStackedBarWithLegend, {
|
||||
type StackedBarWithLegendProps,
|
||||
} from '@core/components/stacked-bar-with-legend/VtsStackedBarWithLegend.vue'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import BackupRepository from '@/components/site/dashboard/BackupRepository.vue'
|
||||
import S3BackupRepository from '@/components/site/dashboard/S3BackupRepository.vue'
|
||||
import StorageRepository from '@/components/site/dashboard/StorageRepository.vue'
|
||||
import type { BackupRepositories, StorageRepositories } from '@/requests/use-site-dashboard.request.ts'
|
||||
import type { BackupRepositories, StorageRepositories } from '@/remote-resources/use-xo-site-dashboard.ts'
|
||||
import type { XoDashboard } from '@/types/xo/dashboard.type.ts'
|
||||
import VtsDivider from '@core/components/divider/VtsDivider.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { StorageRepositories } from '@/requests/use-site-dashboard.request.ts'
|
||||
import type { StorageRepositories } from '@/remote-resources/use-xo-site-dashboard.ts'
|
||||
import VtsStackedBarWithLegend, {
|
||||
type StackedBarWithLegendProps,
|
||||
} from '@core/components/stacked-bar-with-legend/VtsStackedBarWithLegend.vue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isPoolReady" type="panel" />
|
||||
<VtsLoadingHero v-if="!arePoolsReady" type="panel" />
|
||||
<UiPanel v-else :class="{ 'mobile-drawer': uiStore.isMobile }">
|
||||
<template #header>
|
||||
<div :class="{ 'action-buttons-container': uiStore.isMobile }">
|
||||
@@ -190,8 +190,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
@@ -237,11 +237,11 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n()
|
||||
const uiStore = useUiStore()
|
||||
const { isReady: isPoolReady, get: getPoolById } = usePoolStore().subscribe()
|
||||
const { get: getHostById, hostsByPool } = useHostStore().subscribe()
|
||||
const { arePoolsReady, useGetPoolById } = useXoPoolCollection()
|
||||
const { useGetHostById, hostsByPool } = useXoHostCollection()
|
||||
|
||||
const pool = computed(() => (server.poolId ? getPoolById(server.poolId) : undefined))
|
||||
const primaryHost = computed(() => (server.master ? getHostById(server.master) : undefined))
|
||||
const pool = useGetPoolById(() => server.poolId)
|
||||
const primaryHost = useGetHostById(() => server.master)
|
||||
const hosts = computed(() => (server.poolId ? hostsByPool.value.get(server.poolId) : undefined))
|
||||
|
||||
const connectionStatus = useMapper(
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
</UiButton>
|
||||
</UiTableActions>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isServerReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areServersReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
<VtsDataTable
|
||||
:is-ready="isServerReady"
|
||||
:has-error
|
||||
:is-ready="areServersReady"
|
||||
:has-error="hasServerFetchError"
|
||||
:no-data-message="servers.length === 0 ? t('no-server-detected') : undefined"
|
||||
>
|
||||
<template #thead>
|
||||
@@ -117,15 +117,15 @@
|
||||
</VtsStateHero>
|
||||
<VtsStateHero v-if="!servers.length" image="no-data" type="page" />
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0" @toggle-select-all="toggleSelect">
|
||||
<UiTablePagination v-if="isServerReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areServersReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useServerStore } from '@/stores/xo-rest-api/server.store.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoServerCollection } from '@/remote-resources/use-xo-server-collection.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type.ts'
|
||||
import VtsDataTable from '@core/components/data-table/VtsDataTable.vue'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -166,8 +166,8 @@ const { servers } = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady: isServerReady, hasError } = useServerStore().subscribe()
|
||||
const { get: getHostById } = useHostStore().subscribe()
|
||||
const { areServersReady, hasServerFetchError } = useXoServerCollection()
|
||||
const { getHostById } = useXoHostCollection()
|
||||
const selectedServerId = useRouteQuery('id')
|
||||
|
||||
const searchQuery = ref('')
|
||||
@@ -222,7 +222,7 @@ const getPoolInfo = (server: XoServer) => {
|
||||
}
|
||||
|
||||
const getPrimaryHost = (server: XoServer) => {
|
||||
const masterHost = server.master ? getHostById(server.master) : undefined
|
||||
const masterHost = getHostById(server.master)
|
||||
return masterHost ? { label: masterHost.name_label, to: `/host/${masterHost.id}/`, icon: faServer } : undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
<template>
|
||||
<VtsQuickTaskButton :loading="!isReady" :tasks />
|
||||
<VtsQuickTaskButton :loading="!areTasksReady" :tasks="lastDayTasks" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTaskStore } from '@/stores/xo-rest-api/task.store'
|
||||
import { convertTaskToCore } from '@/utils/convert-task-to-core.util'
|
||||
import { useXoTaskCollection } from '@/remote-resources/use-xo-task-collection.ts'
|
||||
import VtsQuickTaskButton from '@core/components/task/VtsQuickTaskButton.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { records, isReady } = useTaskStore().subscribe()
|
||||
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
const tasks = computed(() => {
|
||||
const now = Date.now()
|
||||
return records.value.filter(task => now - task.start < ONE_DAY).map(task => convertTaskToCore(task))
|
||||
})
|
||||
const { lastDayTasks, areTasksReady } = useXoTaskCollection()
|
||||
</script>
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VmTreeList from '@/components/tree/VmTreeList.vue'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { HostBranch } from '@/types/tree.type'
|
||||
import type { HostState } from '@core/types/object-icon.type'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
@@ -52,8 +52,8 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isMasterHost } = useHostStore().subscribe()
|
||||
const { runningVms } = useVmStore().subscribe()
|
||||
const { isMasterHost } = useXoHostCollection()
|
||||
const { runningVms } = useXoVmCollection()
|
||||
|
||||
const isMaster = computed(() => isMasterHost(props.branch.data.id))
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<script lang="ts" setup>
|
||||
import HostTreeList from '@/components/tree/HostTreeList.vue'
|
||||
import VmTreeList from '@/components/tree/VmTreeList.vue'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { HostBranch, PoolBranch, VmLeaf } from '@/types/tree.type'
|
||||
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
|
||||
import VtsTreeList from '@core/components/tree/VtsTreeList.vue'
|
||||
@@ -41,7 +41,7 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { runningVms } = useVmStore().subscribe()
|
||||
const { runningVms } = useXoVmCollection()
|
||||
|
||||
const treeBranches = computed(
|
||||
() => props.branch.children.filter(child => child.discriminator === 'host') as HostBranch[]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PoolTreeList from '@/components/tree/PoolTreeList.vue'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { SiteBranch } from '@/types/tree.type'
|
||||
import VtsTreeItem from '@core/components/tree/VtsTreeItem.vue'
|
||||
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
|
||||
@@ -36,7 +36,7 @@ defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { runningVms } = useVmStore().subscribe()
|
||||
const { runningVms } = useXoVmCollection()
|
||||
|
||||
const runningVmsCount = computed(() => runningVms.value.length)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsQuickInfoCard class="vm-dashboard-quick-info" :loading="!isReady">
|
||||
<VtsQuickInfoCard class="vm-dashboard-quick-info" :loading="!areVmsReady">
|
||||
<VtsQuickInfoColumn>
|
||||
<VtsQuickInfoRow :label="t('state')">
|
||||
<template #value>
|
||||
@@ -48,7 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import { VM_POWER_STATE, type XoVm } from '@/types/xo/vm.type.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import VtsQuickInfoCard from '@core/components/quick-info-card/VtsQuickInfoCard.vue'
|
||||
@@ -71,7 +71,7 @@ const { vm } = defineProps<{
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const { isReady, getVmHost } = useVmStore().subscribe()
|
||||
const { areVmsReady, getVmHost } = useXoVmCollection()
|
||||
|
||||
const host = computed(() => getVmHost(vm))
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoVif } from '@/types/xo/vif.type'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
@@ -203,17 +203,17 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getNetwork } = useNetworkStore().subscribe()
|
||||
const { get: getVm } = useVmStore().subscribe()
|
||||
const { useGetNetworkById } = useXoNetworkCollection()
|
||||
const { getVmById } = useXoVmCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const ipAddresses = computed(() => {
|
||||
const addresses = getVm(vif.$VM)?.addresses
|
||||
const addresses = getVmById(vif.$VM)?.addresses
|
||||
|
||||
return addresses ? [...new Set(Object.values(addresses).sort())] : []
|
||||
})
|
||||
|
||||
const network = computed(() => getNetwork(vif.$network))
|
||||
const network = useGetNetworkById(() => vif.$network)
|
||||
|
||||
const status = computed(() => (vif.attached ? 'connected' : 'disconnected'))
|
||||
</script>
|
||||
|
||||
@@ -52,10 +52,14 @@
|
||||
</UiTableActions>
|
||||
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVifsReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
<VtsDataTable :is-ready :has-error :no-data-message="vifs.length === 0 ? t('no-vif-detected') : undefined">
|
||||
<VtsDataTable
|
||||
:is-ready="areVifsReady"
|
||||
:has-error="hasVifFetchError"
|
||||
:no-data-message="vifs.length === 0 ? t('no-vif-detected') : undefined"
|
||||
>
|
||||
<template #thead>
|
||||
<tr>
|
||||
<template v-for="column of visibleColumns" :key="column.id">
|
||||
@@ -126,16 +130,16 @@
|
||||
</template>
|
||||
</VtsDataTable>
|
||||
<UiTopBottomTable :selected-items="0" :total-items="0">
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVifsReady" v-bind="paginationBindings" />
|
||||
</UiTopBottomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { useVifStore } from '@/stores/xo-rest-api/vif.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoVifCollection } from '@/remote-resources/use-xo-vif-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoVif } from '@/types/xo/vif.type'
|
||||
import VtsConnectionStatus from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
import VtsDataTable from '@core/components/data-table/VtsDataTable.vue'
|
||||
@@ -173,14 +177,14 @@ const { vifs } = defineProps<{
|
||||
vifs: XoVif[]
|
||||
}>()
|
||||
|
||||
const { get: getNetwork } = useNetworkStore().subscribe()
|
||||
const { get: getVm } = useVmStore().subscribe()
|
||||
const { isReady, hasError } = useVifStore().subscribe()
|
||||
const { getNetworkById } = useXoNetworkCollection()
|
||||
const { getVmById } = useXoVmCollection()
|
||||
const { areVifsReady, hasVifFetchError } = useXoVifCollection()
|
||||
const { t } = useI18n()
|
||||
|
||||
const selectedVifId = useRouteQuery('id')
|
||||
|
||||
const getNetworkName = (vif: XoVif) => getNetwork(vif.$network)?.name_label ?? ''
|
||||
const getNetworkName = (vif: XoVif) => getNetworkById(vif.$network)?.name_label ?? ''
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
@@ -197,7 +201,7 @@ const filteredVifs = computed(() => {
|
||||
})
|
||||
|
||||
const getIpAddresses = (vif: XoVif) => {
|
||||
const addresses = getVm(vif.$VM)?.addresses
|
||||
const addresses = getVmById(vif.$VM)?.addresses
|
||||
|
||||
return addresses ? [...new Set(Object.values(addresses).sort())] : []
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { VM_OPERATION, type XoVm } from '@/types/xo/vm.type'
|
||||
import VtsEnabledState from '@core/components/enabled-state/VtsEnabledState.vue'
|
||||
import VtsQuickInfoRow from '@core/components/quick-info-row/VtsQuickInfoRow.vue'
|
||||
@@ -53,7 +53,7 @@ import { useI18n } from 'vue-i18n'
|
||||
const { vm } = defineProps<{ vm: XoVm }>()
|
||||
const { t } = useI18n()
|
||||
|
||||
const { get: getHostById } = useHostStore().subscribe()
|
||||
const { getHostById } = useXoHostCollection()
|
||||
|
||||
const affinityHostName = computed(() => (vm.affinityHost ? getHostById(vm.affinityHost)?.name_label : ''))
|
||||
const protectedOperations = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoSite } from '@/types/xo/site.type.ts'
|
||||
import { defineTree } from '@core/composables/tree/define-tree'
|
||||
import { useTreeFilter } from '@core/composables/tree-filter.composable'
|
||||
@@ -9,9 +9,9 @@ import { logicAnd } from '@vueuse/math'
|
||||
import { computed, useId } from 'vue'
|
||||
|
||||
export function useSiteTree() {
|
||||
const { records: pools, isReady: isPoolReady } = usePoolStore().subscribe()
|
||||
const { hostsByPool, isReady: isHostReady } = useHostStore().subscribe()
|
||||
const { vmsByHost, hostLessVmsByPool, isReady: isVmReady } = useVmStore().subscribe()
|
||||
const { pools, arePoolsReady } = useXoPoolCollection()
|
||||
const { hostsByPool, areHostsReady } = useXoHostCollection()
|
||||
const { vmsByHost, hostLessVmsByPool, areVmsReady } = useXoVmCollection()
|
||||
const { filter, predicate } = useTreeFilter()
|
||||
|
||||
const site: XoSite = {
|
||||
@@ -22,7 +22,7 @@ export function useSiteTree() {
|
||||
|
||||
const sites = computed(() => [site])
|
||||
|
||||
const isReady = logicAnd(isPoolReady, isHostReady, isVmReady)
|
||||
const isReady = logicAnd(arePoolsReady, areHostsReady, areVmsReady)
|
||||
|
||||
const definitions = computed(() =>
|
||||
defineTree(
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# `useXoCollectionState` documentation
|
||||
|
||||
This composable is used to manage the state of a collection of resources in Xen Orchestra, such as VMs, hosts, etc.
|
||||
|
||||
It is meant to be used in conjunction with the `defineRemoteResource` utility from the Web Core library.
|
||||
|
||||
```typescript
|
||||
const useVmCollection = defineRemoteResource({
|
||||
url: /path/to/vms',
|
||||
initialData: () => [] as XoVm[],
|
||||
state: (data, context) => useXoCollectionState(data, { context, baseName: 'vm' }),
|
||||
})
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------------- | ---------------------- | --------------------------------------------------------------------- |
|
||||
| `collection` | `Ref<XoRecord[]>` | The XO record collection |
|
||||
| `config.context` | `ResourceContext` | The context for the resource |
|
||||
| `config.baseName` | `string \| NameConfig` | The base name for the resource, used to generate the state properties |
|
||||
|
||||
# Returns
|
||||
|
||||
When using a `string` as `baseName`, the following properties will be generated:
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| `<name>s` | `Ref<XoRecord[]>` | The collection of records |
|
||||
| `get<Name>ById` | `(id: XoRecord['id'] \| undefined) => XoRecord \| undefined` | Function to get a record by its ID |
|
||||
| `useGet<Name>ById` | `(id: MaybeRefOrGetter<XoRecord['id']> \| undefined) => ComputedRef<XoRecord \| undefined>` | Reactive version of `get<Name>ById` |
|
||||
| `get<Name>sByIds` | `(ids: XoRecord['id'][]) => XoRecord[]` | Function to get multiple records by their IDs |
|
||||
| `useGet<Name>sByIds` | `(ids: MaybeRefOrGetter<XoRecord['id'][]>) => ComputedRef<XoRecord[]>` | Reactive version of `get<Name>sByIds` |
|
||||
| `has<Name>ById` | `(id: XoRecord['id'] \| undefined) => boolean` | Function to check if a record exists by its ID |
|
||||
| `useHas<Name>ById` | `(id: MaybeRefOrGetter<XoRecord['id']> \| undefined) => ComputedRef<boolean>` | Reactive version of `has<Name>ById` |
|
||||
| `are<Name>sReady` | `ComputedRef<boolean>` | Whether the collection is ready |
|
||||
| `are<Name>sFetching` | `ComputedRef<boolean>` | Whether the collection is fetching |
|
||||
| `has<Name>FetchError` | `ComputedRef<boolean>` | Whether there was an error fetching the collection |
|
||||
| `last<Name>FetchError` | `ComputedRef<Error \| undefined>` | The last error that occurred while fetching the collection |
|
||||
|
||||
> [!IMPORTANT] > `are<Name>sReady` becomes `true` when a fetch completes successfully, even if the collection is empty. It will stay `true` even if a later fetch fails.
|
||||
> `are<Name>sFetching` is `true` while the fetch is in progress, even if `are<Name>sReady` is `true`.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
const {
|
||||
vms,
|
||||
getVmById,
|
||||
useGetVmById,
|
||||
getVmsByIds,
|
||||
useGetVmsByIds,
|
||||
hasVmById,
|
||||
useHasVmById,
|
||||
areVmsReady,
|
||||
areVmsFetching,
|
||||
hasVmFetchError,
|
||||
lastVmFetchError,
|
||||
} = useXoCollectionState(vms, {
|
||||
context,
|
||||
baseName: 'vm',
|
||||
})
|
||||
```
|
||||
|
||||
### Using string tuple as `baseName` to handle irregular plural
|
||||
|
||||
```typescript
|
||||
const {
|
||||
proxies,
|
||||
getProxyById,
|
||||
useGetProxyById,
|
||||
getProxiesByIds,
|
||||
useGetProxiesByIds,
|
||||
hasProxyById,
|
||||
useHasProxyById,
|
||||
areProxysReady,
|
||||
areProxysFetching,
|
||||
hasProxyFetchError,
|
||||
lastProxyFetchError,
|
||||
} = useXoCollectionState(proxies, {
|
||||
context,
|
||||
baseName: ['proxy', 'proxies'],
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { NameConfig } from '@/composables/xo-collection-state/types.ts'
|
||||
|
||||
export function toNameConfig(baseName: string | [string, string] | NameConfig): NameConfig {
|
||||
if (typeof baseName === 'object' && !Array.isArray(baseName)) {
|
||||
return baseName
|
||||
}
|
||||
|
||||
const [singularName, pluralName] = Array.isArray(baseName) ? baseName : [baseName, `${baseName}s`]
|
||||
|
||||
const capitalizedSingularName = singularName.charAt(0).toUpperCase() + singularName.slice(1)
|
||||
const capitalizedPluralName = pluralName.charAt(0).toUpperCase() + pluralName.slice(1)
|
||||
|
||||
return {
|
||||
records: `${pluralName}`,
|
||||
getById: `get${capitalizedSingularName}ById`,
|
||||
getByIds: `get${capitalizedPluralName}ByIds`,
|
||||
useGetById: `useGet${capitalizedSingularName}ById`,
|
||||
useGetByIds: `useGet${capitalizedPluralName}ByIds`,
|
||||
hasById: `has${capitalizedSingularName}ById`,
|
||||
useHasById: `useHas${capitalizedSingularName}ById`,
|
||||
isReady: `are${capitalizedPluralName}Ready`,
|
||||
isFetching: `are${capitalizedPluralName}Fetching`,
|
||||
hasError: `has${capitalizedSingularName}FetchError`,
|
||||
lastError: `last${capitalizedSingularName}FetchError`,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { XoRecord } from '@/types/xo'
|
||||
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
|
||||
export type NameConfig = {
|
||||
records: string
|
||||
getById: string
|
||||
getByIds: string
|
||||
useGetById: string
|
||||
useGetByIds: string
|
||||
hasById: string
|
||||
useHasById: string
|
||||
isReady: string
|
||||
isFetching: string
|
||||
hasError: string
|
||||
lastError: string
|
||||
}
|
||||
|
||||
export type BaseName = string | [string, string] | NameConfig
|
||||
|
||||
export type DefaultState<TRecord extends XoRecord> = {
|
||||
records: Ref<TRecord[]>
|
||||
getById: (id: TRecord['id'] | undefined) => TRecord | undefined
|
||||
getByIds: (ids: TRecord['id'][]) => TRecord[]
|
||||
useGetById: (id: MaybeRefOrGetter<TRecord['id'] | undefined>) => ComputedRef<TRecord | undefined>
|
||||
useGetByIds: (id: MaybeRefOrGetter<TRecord['id'][]>) => ComputedRef<TRecord[]>
|
||||
hasById: (id: TRecord['id'] | undefined) => boolean
|
||||
useHasById: (id: MaybeRefOrGetter<TRecord['id'] | undefined>) => ComputedRef<boolean>
|
||||
isReady: ComputedRef<boolean>
|
||||
isFetching: ComputedRef<boolean>
|
||||
hasError: ComputedRef<boolean>
|
||||
lastError: ComputedRef<Error | undefined>
|
||||
}
|
||||
|
||||
export type CollectionState<TRecord extends XoRecord, TNameConfig extends NameConfig> = {
|
||||
[K in keyof NameConfig as TNameConfig[K]]: DefaultState<TRecord>[K]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { toNameConfig } from '@/composables/xo-collection-state/to-name-config.ts'
|
||||
import type { BaseName, CollectionState, NameConfig } from '@/composables/xo-collection-state/types.ts'
|
||||
import type { XoRecord } from '@/types/xo'
|
||||
import type { ResourceContext } from '@core/packages/remote-resource/types.ts'
|
||||
import { reactify } from '@vueuse/shared'
|
||||
import { type Ref } from 'vue'
|
||||
|
||||
export function useXoCollectionState<TRecord extends XoRecord, const TBaseName extends string>(
|
||||
collection: Ref<TRecord[]>,
|
||||
config: {
|
||||
context: ResourceContext<any[]>
|
||||
baseName: TBaseName
|
||||
}
|
||||
): CollectionState<
|
||||
TRecord,
|
||||
{
|
||||
records: `${TBaseName}s`
|
||||
getById: `get${Capitalize<TBaseName>}ById`
|
||||
getByIds: `get${Capitalize<TBaseName>}sByIds`
|
||||
useGetById: `useGet${Capitalize<TBaseName>}ById`
|
||||
useGetByIds: `useGet${Capitalize<TBaseName>}sByIds`
|
||||
hasById: `has${Capitalize<TBaseName>}ById`
|
||||
useHasById: `useHas${Capitalize<TBaseName>}ById`
|
||||
isReady: `are${Capitalize<TBaseName>}sReady`
|
||||
isFetching: `are${Capitalize<TBaseName>}sFetching`
|
||||
hasError: `has${Capitalize<TBaseName>}FetchError`
|
||||
lastError: `last${Capitalize<TBaseName>}FetchError`
|
||||
}
|
||||
>
|
||||
|
||||
export function useXoCollectionState<TRecord extends XoRecord, const TBaseName extends [string, string]>(
|
||||
collection: Ref<TRecord[]>,
|
||||
config: {
|
||||
context: ResourceContext<any[]>
|
||||
baseName: TBaseName
|
||||
}
|
||||
): CollectionState<
|
||||
TRecord,
|
||||
{
|
||||
records: `${TBaseName[1]}`
|
||||
getById: `get${Capitalize<TBaseName[0]>}ById`
|
||||
getByIds: `get${Capitalize<TBaseName[1]>}ByIds`
|
||||
useGetById: `useGet${Capitalize<TBaseName[0]>}ById`
|
||||
useGetByIds: `useGet${Capitalize<TBaseName[1]>}ByIds`
|
||||
hasById: `has${Capitalize<TBaseName[0]>}ById`
|
||||
useHasById: `useHas${Capitalize<TBaseName[0]>}ById`
|
||||
isReady: `are${Capitalize<TBaseName[1]>}Ready`
|
||||
isFetching: `are${Capitalize<TBaseName[1]>}Fetching`
|
||||
hasError: `has${Capitalize<TBaseName[0]>}FetchError`
|
||||
lastError: `last${Capitalize<TBaseName[0]>}FetchError`
|
||||
}
|
||||
>
|
||||
|
||||
export function useXoCollectionState<TRecord extends XoRecord, TNameConfig extends NameConfig>(
|
||||
collection: Ref<TRecord[]>,
|
||||
config: {
|
||||
context: ResourceContext<any[]>
|
||||
baseName: TNameConfig
|
||||
}
|
||||
): CollectionState<TRecord, TNameConfig>
|
||||
|
||||
export function useXoCollectionState<TRecord extends XoRecord, TNameConfig extends BaseName>(
|
||||
collection: Ref<TRecord[]>,
|
||||
config: {
|
||||
context: ResourceContext<any[]>
|
||||
baseName: TNameConfig
|
||||
}
|
||||
) {
|
||||
function getById(id: TRecord['id'] | undefined) {
|
||||
if (id === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return collection.value.find(record => record.id === id)
|
||||
}
|
||||
|
||||
function getByIds(ids: TRecord['id'][]) {
|
||||
const idSet = new Set(ids)
|
||||
return collection.value.filter(record => idSet.has(record.id))
|
||||
}
|
||||
|
||||
function hasById(id: TRecord['id'] | undefined) {
|
||||
if (id === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
return collection.value.some(record => record.id === id)
|
||||
}
|
||||
|
||||
const names = toNameConfig(config.baseName)
|
||||
|
||||
return {
|
||||
[names.records]: collection,
|
||||
[names.getById]: getById,
|
||||
[names.useGetById]: reactify(getById),
|
||||
[names.getByIds]: getByIds,
|
||||
[names.useGetByIds]: reactify(getByIds),
|
||||
[names.hasById]: hasById,
|
||||
[names.useHasById]: reactify(hasById),
|
||||
[names.isReady]: config.context.isReady,
|
||||
[names.hasError]: config.context.hasError,
|
||||
[names.lastError]: config.context.lastError,
|
||||
} satisfies CollectionState<TRecord, NameConfig>
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!arePoolsReady" type="page" />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<SiteHeader />
|
||||
<component :is="Component" />
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SiteHeader from '@/components/site/SiteHeader.vue'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
|
||||
|
||||
useDefaultTab('/(site)', 'dashboard')
|
||||
|
||||
const { isReady } = usePoolStore().subscribe()
|
||||
const { arePoolsReady } = useXoPoolCollection()
|
||||
</script>
|
||||
|
||||
@@ -33,14 +33,15 @@ import PoolsStatus from '@/components/site/dashboard/PoolsStatus.vue'
|
||||
import Repositories from '@/components/site/dashboard/Repositories.vue'
|
||||
import ResourcesOverview from '@/components/site/dashboard/ResourcesOverview.vue'
|
||||
import VmsStatus from '@/components/site/dashboard/VmsStatus.vue'
|
||||
import { useSiteDashboard } from '@/requests/use-site-dashboard.request.ts'
|
||||
import { useAlarmStore } from '@/stores/xo-rest-api/alarm.store.ts'
|
||||
import { useXoAlarmCollection } from '@/remote-resources/use-xo-alarm-collection.ts'
|
||||
import { useXoSiteDashboard } from '@/remote-resources/use-xo-site-dashboard.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const { dashboard, backupRepositories, storageRepositories } = useSiteDashboard()
|
||||
const { records: alarms } = useAlarmStore().subscribe()
|
||||
const { dashboard, backupRepositories, storageRepositories } = useXoSiteDashboard()
|
||||
|
||||
const { alarms } = useXoAlarmCollection()
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<script setup lang="ts">
|
||||
import PoolsSidePanel from '@/components/site/pools/PoolsSidePanel.vue'
|
||||
import PoolsTable from '@/components/site/pools/PoolsTable.vue'
|
||||
import { useServerStore } from '@/stores/xo-rest-api/server.store'
|
||||
import { useXoServerCollection } from '@/remote-resources/use-xo-server-collection.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type'
|
||||
import VtsNoSelectionHero from '@core/components/state-hero/VtsNoSelectionHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -21,11 +21,11 @@ import UiPanel from '@core/components/ui/panel/UiPanel.vue'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
|
||||
const { records: servers, get } = useServerStore().subscribe()
|
||||
const { servers, getServerById } = useXoServerCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const selectedServer = useRouteQuery<XoServer | undefined>('id', {
|
||||
toData: id => get(id as XoServer['id']),
|
||||
toData: id => getServerById(id as XoServer['id']),
|
||||
toQuery: server => server?.id ?? '',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!areHostsReady" type="page" />
|
||||
<VtsObjectNotFoundHero v-else-if="!host" :id="route.params.id" type="page" />
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<HostHeader v-if="uiStore.hasUi" :host />
|
||||
@@ -9,21 +9,20 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import HostHeader from '@/components/host/HostHeader.vue'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import VtsObjectNotFoundHero from '@core/components/state-hero/VtsObjectNotFoundHero.vue'
|
||||
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router/auto'
|
||||
|
||||
useDefaultTab('/host/[id]', 'dashboard')
|
||||
|
||||
const route = useRoute<'/host/[id]'>()
|
||||
|
||||
const { isReady, get } = useHostStore().subscribe()
|
||||
const { areHostsReady, useGetHostById } = useXoHostCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const host = computed(() => get(route.params.id as XoHost['id']))
|
||||
const host = useGetHostById(() => route.params.id as XoHost['id'])
|
||||
</script>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { HOST_OPERATION, type XoHost } from '@/types/xo/host.type'
|
||||
import { isHostOperationPending } from '@/utils/xo-records/host.util.ts'
|
||||
import VtsActionsConsole from '@core/components/console/VtsActionsConsole.vue'
|
||||
import VtsClipboardConsole from '@core/components/console/VtsClipboardConsole.vue'
|
||||
import VtsLayoutConsole from '@core/components/console/VtsLayoutConsole.vue'
|
||||
@@ -27,8 +27,6 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isHostOperationPending } = useHostStore().subscribe()
|
||||
|
||||
const STOP_OPERATIONS = [HOST_OPERATION.SHUTDOWN, HOST_OPERATION.REBOOT]
|
||||
|
||||
const url = computed(
|
||||
|
||||
@@ -13,21 +13,22 @@
|
||||
<script setup lang="ts">
|
||||
import HostPifSidePanel from '@/components/host/network/HostPifSidePanel.vue'
|
||||
import HostPifTable from '@/components/host/network/HostPifTable.vue'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoPif } from '@/types/xo/pif.type'
|
||||
import VtsNoSelectionHero from '@core/components/state-hero/VtsNoSelectionHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiPanel from '@core/components/ui/panel/UiPanel.vue'
|
||||
import { useRouteQuery } from '@core/composables/route-query.composable'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { useArrayFilter } from '@vueuse/shared'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router/auto'
|
||||
|
||||
const { records } = usePifStore().subscribe()
|
||||
const { pifsByHost } = useXoPifCollection()
|
||||
const uiStore = useUiStore()
|
||||
const route = useRoute<'/host/[id]'>()
|
||||
|
||||
const pifs = useArrayFilter(records, pif => pif.$host === route.params.id)
|
||||
const pifs = computed(() => pifsByHost.value.get(route.params.id as XoHost['id']) ?? [])
|
||||
|
||||
const selectedPif = useRouteQuery<XoPif | undefined>('id', {
|
||||
toData: id => pifs.value.find(pif => pif.id === id),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!areVmsReady" type="page" />
|
||||
<UiCard v-else class="vms">
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-vms', { n: vms.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVmsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
<VtsTable vertical-border>
|
||||
<thead>
|
||||
@@ -30,13 +30,13 @@
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-vms', { n: vms.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVmsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import type { VmState } from '@core/types/object-icon.type'
|
||||
import VtsCellObject from '@core/components/cell-object/VtsCellObject.vue'
|
||||
@@ -61,7 +61,7 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady, vmsByHost } = useVmStore().subscribe()
|
||||
const { areVmsReady, vmsByHost } = useXoVmCollection()
|
||||
|
||||
const definitions = computed(() =>
|
||||
defineTree(vmsByHost.value.get(props.host.id) ?? [], {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!arePoolsReady" type="page" />
|
||||
<VtsObjectNotFoundHero v-else-if="!pool" :id="route.params.id" type="page" />
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<PoolHeader :pool />
|
||||
@@ -9,19 +9,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PoolHeader from '@/components/pool/PoolHeader.vue'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import VtsObjectNotFoundHero from '@core/components/state-hero/VtsObjectNotFoundHero.vue'
|
||||
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
useDefaultTab('/pool/[id]', 'dashboard')
|
||||
|
||||
const route = useRoute<'/pool/[id]'>()
|
||||
|
||||
const { isReady, get } = usePoolStore().subscribe()
|
||||
const { arePoolsReady, useGetPoolById } = useXoPoolCollection()
|
||||
|
||||
const pool = computed(() => get(route.params.id as XoPool['id']))
|
||||
const pool = useGetPoolById(() => route.params.id as XoPool['id'])
|
||||
</script>
|
||||
|
||||
@@ -36,7 +36,7 @@ import PoolDashboardRamUsage from '@/components/pool/dashboard/PoolDashboardRamU
|
||||
import PoolDashboardStatus from '@/components/pool/dashboard/PoolDashboardStatus.vue'
|
||||
import PoolDashboardStoragesUsage from '@/components/pool/dashboard/PoolDashboardStoragesUsage.vue'
|
||||
import { useFetchStats } from '@/composables/fetch-stats.composable.ts'
|
||||
import { usePoolDashboard } from '@/requests/use-pool-dashboard.request.ts'
|
||||
import { useXoPoolDashboard } from '@/remote-resources/use-xo-pool-dashboard.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import { GRANULARITY } from '@/utils/rest-api-stats.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
@@ -45,7 +45,7 @@ const { pool } = defineProps<{ pool: XoPool }>()
|
||||
|
||||
const { data, isFetching, error } = useFetchStats('pool', () => pool.id, GRANULARITY.Hours)
|
||||
|
||||
const { dashboard: poolDashboard } = usePoolDashboard(() => pool.id)
|
||||
const { poolDashboard } = useXoPoolDashboard({}, () => pool.id)
|
||||
|
||||
const uiStore = useUiStore()
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!areHostsReady" type="page" />
|
||||
<UiCard v-else class="hosts">
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-hosts', { n: hosts.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areHostsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
<VtsTable vertical-border>
|
||||
<thead>
|
||||
@@ -34,13 +34,13 @@
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-hosts', { n: hosts.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areHostsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { HostState } from '@core/types/object-icon.type'
|
||||
import VtsCellObject from '@core/components/cell-object/VtsCellObject.vue'
|
||||
@@ -65,7 +65,7 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady, hostsByPool } = useHostStore().subscribe()
|
||||
const { areHostsReady, hostsByPool } = useXoHostCollection()
|
||||
|
||||
const definitions = computed(() =>
|
||||
defineTree(hostsByPool.value.get(props.pool.id) ?? [], {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import PoolHostInternalNetworksTable from '@/components/pool/network/PoolHostInternalNetworksTable.vue'
|
||||
import PoolNetworkSidePanel from '@/components/pool/network/PoolNetworkSidePanel.vue'
|
||||
import PoolNetworksTable from '@/components/pool/network/PoolNetworksTable.vue'
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsNoSelectionHero from '@core/components/state-hero/VtsNoSelectionHero.vue'
|
||||
@@ -29,7 +29,7 @@ const { pool } = defineProps<{
|
||||
pool: XoPool
|
||||
}>()
|
||||
|
||||
const { networksWithoutPifs, networksWithPifs, get } = useNetworkStore().subscribe()
|
||||
const { networksWithoutPifs, networksWithPifs, getNetworkById } = useXoNetworkCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const internalNetworks = computed(() => networksWithoutPifs.value.filter(network => network.$pool === pool.id))
|
||||
@@ -37,7 +37,7 @@ const internalNetworks = computed(() => networksWithoutPifs.value.filter(network
|
||||
const networks = computed(() => networksWithPifs.value.filter(network => network.$pool === pool.id))
|
||||
|
||||
const selectedNetwork = useRouteQuery<XoNetwork | undefined>('id', {
|
||||
toData: id => get(id as XoNetwork['id']),
|
||||
toData: id => getNetworkById(id as XoNetwork['id']),
|
||||
toQuery: network => network?.id ?? '',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -19,10 +19,10 @@ import PoolGeneralInfo from '@/components/pool/system/PoolGeneralInfo.vue'
|
||||
import PoolManagement from '@/components/pool/system/PoolManagement.vue'
|
||||
import PoolNetworking from '@/components/pool/system/PoolNetworking.vue'
|
||||
import PoolStorageConfiguration from '@/components/pool/system/PoolStorageConfiguration.vue'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { useServerStore } from '@/stores/xo-rest-api/server.store'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoServerCollection } from '@/remote-resources/use-xo-server-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import VtsColumn from '@core/components/column/VtsColumn.vue'
|
||||
import VtsColumns from '@core/components/columns/VtsColumns.vue'
|
||||
@@ -31,10 +31,10 @@ import { logicAnd } from '@vueuse/math'
|
||||
|
||||
defineProps<{ pool: XoPool }>()
|
||||
|
||||
const { isReady: isServersReady } = useServerStore().subscribe()
|
||||
const { isReady: isHostsReady } = useHostStore().subscribe()
|
||||
const { isReady: isNetworksReady } = useNetworkStore().subscribe()
|
||||
const { isReady: isSrsReady } = useSrStore().subscribe()
|
||||
const { areServersReady } = useXoServerCollection()
|
||||
const { areHostsReady } = useXoHostCollection()
|
||||
const { areNetworksReady } = useXoNetworkCollection()
|
||||
const { areSrsReady } = useXoSrCollection()
|
||||
|
||||
const isReady = logicAnd(isServersReady, isHostsReady, isNetworksReady, isSrsReady)
|
||||
const isReady = logicAnd(areServersReady, areHostsReady, areNetworksReady, areSrsReady)
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!areVmsReady" type="page" />
|
||||
<UiCard v-else class="vms">
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-vms', { n: vms.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVmsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
<VtsTable vertical-border>
|
||||
<thead>
|
||||
@@ -32,13 +32,13 @@
|
||||
<div class="pagination-container">
|
||||
<!-- TODO: update with item selection button when available -->
|
||||
<p class="typo-body-regular-small count">{{ t('n-vms', { n: vms.length }) }}</p>
|
||||
<UiTablePagination v-if="isReady" v-bind="paginationBindings" />
|
||||
<UiTablePagination v-if="areVmsReady" v-bind="paginationBindings" />
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { VmState } from '@core/types/object-icon.type'
|
||||
import VtsCellObject from '@core/components/cell-object/VtsCellObject.vue'
|
||||
@@ -57,18 +57,18 @@ import { faAlignLeft, faDesktop } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
const { pool } = defineProps<{
|
||||
pool: XoPool
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isReady, records } = useVmStore().subscribe()
|
||||
const { areVmsReady, vmsByPool } = useXoVmCollection()
|
||||
|
||||
const vmsByPool = computed(() => records.value.filter(vm => vm.$pool === props.pool.id))
|
||||
const poolVms = computed(() => vmsByPool.value.get(pool.id) ?? [])
|
||||
|
||||
const definitions = computed(() =>
|
||||
defineTree(vmsByPool.value ?? [], {
|
||||
defineTree(poolVms.value, {
|
||||
getLabel: 'name_label',
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<VtsLoadingHero v-if="!isReady" type="page" />
|
||||
<VtsLoadingHero v-if="!areVmsReady" type="page" />
|
||||
<VtsObjectNotFoundHero v-else-if="!vm" :id="route.params.id" type="page" />
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<VmHeader v-if="uiStore.hasUi" :vm />
|
||||
@@ -9,21 +9,20 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VmHeader from '@/components/vm/VmHeader.vue'
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import { useXoVmCollection } from '@/remote-resources/use-xo-vm-collection.ts'
|
||||
import type { XoVm } from '@/types/xo/vm.type'
|
||||
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
|
||||
import VtsObjectNotFoundHero from '@core/components/state-hero/VtsObjectNotFoundHero.vue'
|
||||
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
|
||||
import { useUiStore } from '@core/stores/ui.store'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
useDefaultTab('/vm/[id]', 'dashboard')
|
||||
|
||||
const route = useRoute<'/vm/[id]'>()
|
||||
|
||||
const { isReady, get: getVm } = useVmStore().subscribe()
|
||||
const { areVmsReady, useGetVmById } = useXoVmCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const vm = computed(() => getVm(route.params.id as XoVm['id']))
|
||||
const vm = useGetVmById(() => route.params.id as XoVm['id'])
|
||||
</script>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVmStore } from '@/stores/xo-rest-api/vm.store'
|
||||
import type { XoVm } from '@/types/xo/vm.type'
|
||||
import { VM_OPERATION } from '@/types/xo/vm.type'
|
||||
import { isVmOperatingPending } from '@/utils/xo-records/vm.util.ts'
|
||||
import VtsActionsConsole from '@core/components/console/VtsActionsConsole.vue'
|
||||
import VtsClipboardConsole from '@core/components/console/VtsClipboardConsole.vue'
|
||||
import VtsLayoutConsole from '@core/components/console/VtsLayoutConsole.vue'
|
||||
@@ -28,8 +28,6 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isVmOperatingPending } = useVmStore().subscribe()
|
||||
|
||||
const STOP_OPERATIONS = [
|
||||
VM_OPERATION.SHUTDOWN,
|
||||
VM_OPERATION.CLEAN_SHUTDOWN,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<script setup lang="ts">
|
||||
import VmVifSidePanel from '@/components/vm/network/VmVifSidePanel.vue'
|
||||
import VmVifsTable from '@/components/vm/network/VmVifsTable.vue'
|
||||
import { useVifStore } from '@/stores/xo-rest-api/vif.store.ts'
|
||||
import { useXoVifCollection } from '@/remote-resources/use-xo-vif-collection.ts'
|
||||
import type { XoVif } from '@/types/xo/vif.type.ts'
|
||||
import VtsNoSelectionHero from '@core/components/state-hero/VtsNoSelectionHero.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
@@ -23,15 +23,15 @@ import { useUiStore } from '@core/stores/ui.store.ts'
|
||||
import { useArrayFilter } from '@vueuse/shared'
|
||||
import { useRoute } from 'vue-router/auto'
|
||||
|
||||
const { records } = useVifStore().subscribe()
|
||||
const { vifs: rawVifs, getVifById } = useXoVifCollection()
|
||||
const uiStore = useUiStore()
|
||||
|
||||
const route = useRoute<'/vm/[id]'>()
|
||||
|
||||
const vifs = useArrayFilter(records, vif => vif.$VM === route.params.id)
|
||||
const vifs = useArrayFilter(rawVifs, vif => vif.$VM === route.params.id)
|
||||
|
||||
const selectedVif = useRouteQuery<XoVif | undefined>('id', {
|
||||
toData: id => vifs.value.find(vif => vif.id === id),
|
||||
toData: id => getVifById(id as XoVif['id']),
|
||||
toQuery: vif => vif?.id ?? '',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -327,15 +327,15 @@
|
||||
import NetworkSelect from '@/components/select/NetworkSelect.vue'
|
||||
import SrSelect from '@/components/select/SrSelect.vue'
|
||||
import { createVM } from '@/jobs/vm-create.job.ts'
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import { useNetworkStore } from '@/stores/xo-rest-api/network.store'
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store'
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store'
|
||||
import { useSrStore } from '@/stores/xo-rest-api/sr.store'
|
||||
import { useVbdStore } from '@/stores/xo-rest-api/vbd.store'
|
||||
import { useVdiStore } from '@/stores/xo-rest-api/vdi.store'
|
||||
import { useVifStore } from '@/stores/xo-rest-api/vif.store'
|
||||
import { useVmTemplateStore } from '@/stores/xo-rest-api/vm-template.store'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import { useXoNetworkCollection } from '@/remote-resources/use-xo-network-collection.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import { useXoSrCollection } from '@/remote-resources/use-xo-sr-collection.ts'
|
||||
import { useXoVbdCollection } from '@/remote-resources/use-xo-vbd-collection.ts'
|
||||
import { useXoVdiCollection } from '@/remote-resources/use-xo-vdi-collection.ts'
|
||||
import { useXoVifCollection } from '@/remote-resources/use-xo-vif-collection.ts'
|
||||
import { useXoVmTemplateCollection } from '@/remote-resources/use-xo-vm-template-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import type { Vdi, Vif, VifToSend, VmState } from '@/types/xo/new-vm.type'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
@@ -387,15 +387,15 @@ const isOpen = ref(false)
|
||||
|
||||
const isBusy = ref(false)
|
||||
|
||||
const { records: networks, get: getNetwork } = useNetworkStore().subscribe()
|
||||
const { getPifsByNetworkId } = usePifStore().subscribe()
|
||||
const { records: pools } = usePoolStore().subscribe()
|
||||
const { vmsTemplatesByPool } = useVmTemplateStore().subscribe()
|
||||
const { records: srs, vdiIsosBySrName } = useSrStore().subscribe()
|
||||
const { get: getVbd } = useVbdStore().subscribe()
|
||||
const { get: getVdi } = useVdiStore().subscribe()
|
||||
const { get: getVif } = useVifStore().subscribe()
|
||||
const { hostsByPool } = useHostStore().subscribe()
|
||||
const { networks, getNetworkById } = useXoNetworkCollection()
|
||||
const { getPifsByNetworkId } = useXoPifCollection()
|
||||
const { pools } = useXoPoolCollection()
|
||||
const { srs, vdiIsosBySrName } = useXoSrCollection()
|
||||
const { getVbdById } = useXoVbdCollection()
|
||||
const { getVdiById } = useXoVdiCollection()
|
||||
const { getVifById } = useXoVifCollection()
|
||||
const { hostsByPool } = useXoHostCollection()
|
||||
const { vmsTemplatesByPool } = useXoVmTemplateCollection()
|
||||
|
||||
const vmState = reactive<VmState>({
|
||||
name: '',
|
||||
@@ -529,13 +529,13 @@ const getVmTemplateVdis = (template: XoVmTemplate) =>
|
||||
|
||||
const getExistingVdis = (template: XoVmTemplate) => {
|
||||
return template.$VBDs.reduce<Vdi[]>((acc, vbdId) => {
|
||||
const vbd = getVbd(vbdId)
|
||||
const vbd = getVbdById(vbdId)
|
||||
|
||||
if (vbd === undefined || vbd.is_cd_drive) {
|
||||
return acc
|
||||
}
|
||||
|
||||
const vdi = getVdi(vbd.VDI)
|
||||
const vdi = getVdiById(vbd.VDI)
|
||||
|
||||
if (vdi === undefined) {
|
||||
console.error('VDI not found')
|
||||
@@ -584,14 +584,14 @@ const getExistingVifs = (template: XoVmTemplate): Vif[] => {
|
||||
return []
|
||||
}
|
||||
return template.VIFs.reduce<Vif[]>((acc, vifId) => {
|
||||
const vif = getVif(vifId)
|
||||
const vif = getVifById(vifId)
|
||||
|
||||
if (vif === undefined) {
|
||||
console.error('VIF not found')
|
||||
return acc
|
||||
}
|
||||
|
||||
const network = getNetwork(vif.$network)
|
||||
const network = getNetworkById(vif.$network)
|
||||
|
||||
if (network === undefined) {
|
||||
console.error('Network not found')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoAlarm } from '@/types/xo/alarm.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { createDateSorter } from '@core/utils/date-sorter.utils.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
|
||||
export const useXoAlarmCollection = defineRemoteResource({
|
||||
url: '/rest/v0/alarms?fields=id,time,body,object',
|
||||
initialData: () => [] as XoAlarm[],
|
||||
state: (rawAlarms, context) => {
|
||||
const alarms = useSorted(rawAlarms, createDateSorter('time'))
|
||||
|
||||
return useXoCollectionState(alarms, {
|
||||
context,
|
||||
baseName: 'alarm',
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import { useXoPoolCollection } from '@/remote-resources/use-xo-pool-collection.ts'
|
||||
import { type XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoHostCollection = defineRemoteResource({
|
||||
url: '/rest/v0/hosts?fields=id,name_label,name_description,power_state,controlDomain,residentVms,$pool,current_operations,address,startTime,version,bios_strings,cpus,CPUs,memory,tags,iscsiIqn,powerOnMode,build,otherConfig,multipathing,logging,enabled,agentStartTime,PGPUs',
|
||||
initialData: () => [] as XoHost[],
|
||||
state: (rawHosts, context) => {
|
||||
const hosts = useSorted(rawHosts, sortByNameLabel)
|
||||
|
||||
const state = useXoCollectionState(hosts, {
|
||||
context,
|
||||
baseName: 'host',
|
||||
})
|
||||
|
||||
const { pools, getPoolById } = useXoPoolCollection(context)
|
||||
|
||||
const hostsByPool = computed(() => {
|
||||
const hostsByPoolMap = new Map<XoPool['id'], XoHost[]>()
|
||||
|
||||
hosts.value.forEach(host => {
|
||||
const poolId = host.$pool
|
||||
|
||||
if (!hostsByPoolMap.has(poolId)) {
|
||||
hostsByPoolMap.set(poolId, [])
|
||||
}
|
||||
|
||||
hostsByPoolMap.get(poolId)!.push(host)
|
||||
})
|
||||
|
||||
return hostsByPoolMap
|
||||
})
|
||||
|
||||
function isMasterHost(hostId: XoHost['id']) {
|
||||
return !!pools.value.find(pool => pool.master === hostId)
|
||||
}
|
||||
|
||||
function getMasterHostByPoolId(poolId: XoPool['id']) {
|
||||
const masterHostId = getPoolById(poolId)?.master
|
||||
|
||||
if (masterHostId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return state.getHostById(masterHostId)
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
hostsByPool,
|
||||
isMasterHost,
|
||||
getMasterHostByPoolId,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import { useXoPifCollection } from '@/remote-resources/use-xo-pif-collection.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoNetworkCollection = defineRemoteResource({
|
||||
url: '/rest/v0/networks?fields=id,defaultIsLocked,name_label,nbd,tags,$pool,name_description,MTU,PIFs,other_config',
|
||||
initialData: () => [] as XoNetwork[],
|
||||
state: (rawNetworks, context) => {
|
||||
const { hostMasterPifsByNetwork } = useXoPifCollection(context)
|
||||
|
||||
const networks = useSorted(rawNetworks, sortByNameLabel)
|
||||
|
||||
const networksWithPifs = computed(() => {
|
||||
const networkIds = Array.from(hostMasterPifsByNetwork.value.keys())
|
||||
|
||||
return networks.value.filter(network => networkIds.includes(network.id) && network.PIFs.length > 0)
|
||||
})
|
||||
|
||||
const networksWithoutPifs = computed(() => networks.value.filter(network => network.PIFs.length === 0))
|
||||
|
||||
return {
|
||||
...useXoCollectionState(networks, {
|
||||
context,
|
||||
baseName: 'network',
|
||||
}),
|
||||
networksWithPifs,
|
||||
networksWithoutPifs,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoPci } from '@/types/xo/pci.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoPciCollection = defineRemoteResource({
|
||||
url: '/rest/v0/pcis?fields=id,device_name',
|
||||
initialData: () => [] as XoPci[],
|
||||
state: (pcis, context) =>
|
||||
useXoCollectionState(pcis, {
|
||||
context,
|
||||
baseName: 'pci',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoPgpu } from '@/types/xo/pgpu.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoPgpuCollection = defineRemoteResource({
|
||||
url: '/rest/v0/pgpus?fields=id,pci',
|
||||
initialData: () => [] as XoPgpu[],
|
||||
state: (pgpus, context) =>
|
||||
useXoCollectionState(pgpus, {
|
||||
context,
|
||||
baseName: 'pgpu',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import type { XoPif } from '@/types/xo/pif.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoPifCollection = defineRemoteResource({
|
||||
url: '/rest/v0/pifs?fields=$host,$network,attached,carrier,device,dns,gateway,id,ip,ipv6,mac,management,mode,mtu,netmask,speed,vlan,isBondMaster,bondSlaves',
|
||||
initialData: () => [] as XoPif[],
|
||||
state: (pifs, context) => {
|
||||
const state = useXoCollectionState(pifs, {
|
||||
context,
|
||||
baseName: 'pif',
|
||||
})
|
||||
|
||||
const { isMasterHost } = useXoHostCollection(context)
|
||||
|
||||
const hostMasterPifsByNetwork = computed(() => {
|
||||
const hostMasterPifsByNetworkMap = new Map<XoNetwork['id'], XoPif[]>()
|
||||
|
||||
pifs.value
|
||||
.filter(pif => isMasterHost(pif.$host))
|
||||
.forEach(pif => {
|
||||
const networkId = pif.$network
|
||||
if (!hostMasterPifsByNetworkMap.has(networkId)) {
|
||||
hostMasterPifsByNetworkMap.set(networkId, [])
|
||||
}
|
||||
hostMasterPifsByNetworkMap.get(networkId)?.push(pif)
|
||||
})
|
||||
|
||||
return hostMasterPifsByNetworkMap
|
||||
})
|
||||
|
||||
function getPifsByNetworkId(networkId: XoNetwork['id']) {
|
||||
return pifs.value.filter(pif => pif.$network === networkId)
|
||||
}
|
||||
|
||||
const pifsByHost = computed(() => {
|
||||
const pifsByHostMap = new Map<XoHost['id'], XoPif[]>()
|
||||
|
||||
pifs.value.forEach(pif => {
|
||||
const hostId = pif.$host
|
||||
if (!pifsByHostMap.has(hostId)) {
|
||||
pifsByHostMap.set(hostId, [])
|
||||
}
|
||||
|
||||
pifsByHostMap.get(hostId)!.push(pif)
|
||||
})
|
||||
|
||||
return pifsByHostMap
|
||||
})
|
||||
|
||||
function getBondsDevices(pif: XoPif) {
|
||||
return pif.isBondMaster ? pif.bondSlaves.flatMap(slaveId => state.getPifById(slaveId)?.device ?? []) : []
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
hostMasterPifsByNetwork,
|
||||
pifsByHost,
|
||||
getPifsByNetworkId,
|
||||
getBondsDevices,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
|
||||
export const useXoPoolCollection = defineRemoteResource({
|
||||
url: '/rest/v0/pools?fields=id,name_label,master,default_SR,tags,otherConfig,auto_poweron,HA_enabled,migrationCompression,suspendSr,crashDumpSr,haSrs',
|
||||
initialData: () => [] as XoPool[],
|
||||
state: (rawPools, context) => {
|
||||
const pools = useSorted(rawPools, sortByNameLabel)
|
||||
|
||||
return useXoCollectionState(pools, {
|
||||
context,
|
||||
baseName: 'pool',
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { XoPoolDashboard } from '@/types/xo/pool-dashboard.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export const useXoPoolDashboard = defineRemoteResource({
|
||||
url: (poolId: string) => `/rest/v0/pools/${poolId}/dashboard?ndjson=true`,
|
||||
stream: true,
|
||||
state: (poolDashboard: Ref<XoPoolDashboard | undefined>) => ({ poolDashboard }),
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoServerCollection = defineRemoteResource({
|
||||
url: '/rest/v0/servers?fields=host,httpProxy,username,readOnly,allowUnauthorized,label,poolId,poolNameLabel,id,status,master,error,poolNameDescription',
|
||||
initialData: () => [] as XoServer[],
|
||||
state: (servers, context) => {
|
||||
const serverByPool = computed(() => {
|
||||
const serverByPoolMap = new Map<XoPool['id'], XoServer[]>()
|
||||
|
||||
servers.value.forEach(server => {
|
||||
const poolId = server.poolId
|
||||
|
||||
if (!poolId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!serverByPoolMap.has(poolId)) {
|
||||
serverByPoolMap.set(poolId, [])
|
||||
}
|
||||
serverByPoolMap.get(poolId)!.push(server)
|
||||
})
|
||||
|
||||
return serverByPoolMap
|
||||
})
|
||||
|
||||
return {
|
||||
...useXoCollectionState(servers, {
|
||||
context,
|
||||
baseName: 'server',
|
||||
}),
|
||||
serverByPool,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { XoDashboard } from '@/types/xo/dashboard.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import type { Info, Scale } from 'human-format'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoSiteDashboard = defineRemoteResource({
|
||||
url: '/rest/v0/dashboard?ndjson=true',
|
||||
stream: true,
|
||||
initialData: () => ({}) as XoDashboard,
|
||||
state: (dashboard, context) => {
|
||||
const backupRepositories = computed<BackupRepositories | undefined>(() => {
|
||||
if (dashboard.value?.backupRepositories === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
available: formatSizeRaw(dashboard.value.backupRepositories.other.size.available, 1),
|
||||
backups: formatSizeRaw(dashboard.value.backupRepositories.other.size.backups, 1),
|
||||
other: formatSizeRaw(dashboard.value.backupRepositories.other.size.other ?? 0, 1),
|
||||
total: formatSizeRaw(dashboard.value.backupRepositories.other.size.total, 1),
|
||||
used: formatSizeRaw(dashboard.value.backupRepositories.other.size.used ?? 0, 1),
|
||||
}
|
||||
})
|
||||
|
||||
const storageRepositories = computed<StorageRepositories | undefined>(() => {
|
||||
if (dashboard.value?.storageRepositories === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
total: formatSizeRaw(dashboard.value.storageRepositories.size.total, 1),
|
||||
used: formatSizeRaw(dashboard.value.storageRepositories.size.used, 1),
|
||||
available: formatSizeRaw(dashboard.value.storageRepositories.size.available, 1),
|
||||
replicated: formatSizeRaw(dashboard.value.storageRepositories.size.replicated, 1),
|
||||
other: formatSizeRaw(dashboard.value.storageRepositories.size.other, 1),
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
isDashboardReady: context.isReady,
|
||||
backupRepositories,
|
||||
storageRepositories,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export type BackupRepositories = {
|
||||
available: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
backups: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
other: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
total: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
used: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
}
|
||||
|
||||
export type StorageRepositories = {
|
||||
total: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
used: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
available: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
replicated: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
other: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import { useXoVdiCollection } from '@/remote-resources/use-xo-vdi-collection.ts'
|
||||
import type { XoSr } from '@/types/xo/sr.type.ts'
|
||||
import type { XoVdi } from '@/types/xo/vdi.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoSrCollection = defineRemoteResource({
|
||||
url: '/rest/v0/srs?fields=id,name_label,name_description,$pool,content_type,physical_usage,size,SR_type,VDIs',
|
||||
initialData: () => [] as XoSr[],
|
||||
state: (srs, context) => {
|
||||
const { getVdiById } = useXoVdiCollection(context)
|
||||
|
||||
const state = useXoCollectionState(srs, {
|
||||
context,
|
||||
baseName: 'sr',
|
||||
})
|
||||
|
||||
const isoVdiIds = computed(() =>
|
||||
srs.value.reduce((acc, sr) => {
|
||||
if (sr.VDIs) {
|
||||
sr.VDIs.forEach(vdiId => acc.add(vdiId))
|
||||
}
|
||||
|
||||
return acc
|
||||
}, new Set<XoVdi['id']>())
|
||||
)
|
||||
|
||||
const vdiIsosBySrName = computed(() => {
|
||||
const groupedVDIs: Record<string, XoVdi[]> = {}
|
||||
|
||||
isoVdiIds.value.forEach(vdiId => {
|
||||
const vdi = getVdiById(vdiId)
|
||||
|
||||
if (vdi) {
|
||||
const srName = state.getSrById(vdi.$SR)?.name_label ?? 'Unknown SR'
|
||||
|
||||
if (!groupedVDIs[srName]) {
|
||||
groupedVDIs[srName] = []
|
||||
}
|
||||
|
||||
groupedVDIs[srName].push(vdi)
|
||||
}
|
||||
})
|
||||
|
||||
return groupedVDIs
|
||||
})
|
||||
|
||||
return {
|
||||
...state,
|
||||
vdiIsosBySrName,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoTask } from '@/types/xo/task.type.ts'
|
||||
import { convertTaskToCore } from '@/utils/convert-task-to-core.util.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
export const useXoTaskCollection = defineRemoteResource({
|
||||
url: '/rest/v0/tasks?fields=id,start,end,properties,status,progress,tasks',
|
||||
initialData: () => [] as XoTask[],
|
||||
state: (tasks, context) => {
|
||||
const lastDayTasks = computed(() => {
|
||||
const now = Date.now()
|
||||
return tasks.value.filter(task => now - task.start < ONE_DAY).map(task => convertTaskToCore(task))
|
||||
})
|
||||
|
||||
return {
|
||||
...useXoCollectionState(tasks, {
|
||||
context,
|
||||
baseName: 'task',
|
||||
}),
|
||||
lastDayTasks,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoVbd } from '@/types/xo/vbd.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoVbdCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vbds?fields=id,name_label,name_description,VDI,is_cd_drive,position',
|
||||
initialData: () => [] as XoVbd[],
|
||||
state: (vbds, context) =>
|
||||
useXoCollectionState(vbds, {
|
||||
context,
|
||||
baseName: 'vbd',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoVdi } from '@/types/xo/vdi.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoVdiCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vdis?fields=id,name_label,name_description,$VBDs,$SR,size,$pool',
|
||||
initialData: () => [] as XoVdi[],
|
||||
state: (vdis, context) =>
|
||||
useXoCollectionState(vdis, {
|
||||
context,
|
||||
baseName: 'vdi',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoVif } from '@/types/xo/vif.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoVifCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vifs?fields=$VM,$network,attached,device,txChecksumming,id,lockingMode,MAC,MTU',
|
||||
initialData: () => [] as XoVif[],
|
||||
state: (vifs, context) =>
|
||||
useXoCollectionState(vifs, {
|
||||
context,
|
||||
baseName: 'vif',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import { useXoHostCollection } from '@/remote-resources/use-xo-host-collection.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import { VM_POWER_STATE, type XoVm } from '@/types/xo/vm.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoVmCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vms?fields=id,name_label,name_description,power_state,$container,$pool,other,current_operations,CPUs,addresses,tags,os_version,virtualizationMode,secureBoot,VTPMs,viridian,isNestedVirtEnabled,memory,VGPUs,high_availability,auto_poweron,startDelay,vga,videoram,pvDriversVersion,cpuWeight,cpuCap,cpuMask,coresPerSocket,mainIpAddress,nicType,affinityHost,suspendSr,blockedOperations,hasVendorDevice,startTime,installTime,pvDriversDetected',
|
||||
initialData: () => [] as XoVm[],
|
||||
state: (rawVms, context) => {
|
||||
const { getHostById } = useXoHostCollection(context)
|
||||
|
||||
const vms = useSorted(rawVms, sortByNameLabel)
|
||||
|
||||
const runningVms = computed(() => vms.value.filter(vm => vm.power_state === VM_POWER_STATE.RUNNING))
|
||||
|
||||
const vmsByHost = computed(() => createVmsByHostMap(vms.value, false))
|
||||
|
||||
const hostLessVmsByPool = computed(() => createVmsByHostMap(vms.value, true))
|
||||
|
||||
const vmsByPool = computed(() => {
|
||||
return vms.value.reduce((acc, vm) => {
|
||||
if (!acc.has(vm.$pool)) {
|
||||
acc.set(vm.$pool, [])
|
||||
}
|
||||
|
||||
acc.get(vm.$pool)!.push(vm)
|
||||
|
||||
return acc
|
||||
}, new Map<XoPool['id'], XoVm[]>())
|
||||
})
|
||||
|
||||
function getVmHost(vm: XoVm): XoHost | undefined {
|
||||
const vmHostId = extractVmHostId(vm)
|
||||
|
||||
if (vmHostId === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getHostById(vmHostId)
|
||||
}
|
||||
|
||||
return {
|
||||
...useXoCollectionState(vms, {
|
||||
context,
|
||||
baseName: 'vm',
|
||||
}),
|
||||
runningVms,
|
||||
vmsByHost,
|
||||
vmsByPool,
|
||||
getVmHost,
|
||||
hostLessVmsByPool,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function createVmsByHostMap<THostLess extends boolean>(vms: XoVm[], hostLess: THostLess) {
|
||||
const vmsMap = new Map<THostLess extends true ? XoPool['id'] : XoHost['id'], XoVm[]>()
|
||||
|
||||
vms.forEach(vm => {
|
||||
const hasHost = vm.$container !== vm.$pool
|
||||
|
||||
if (hasHost && hostLess) {
|
||||
return
|
||||
}
|
||||
|
||||
const id = vm.$container as THostLess extends true ? XoPool['id'] : XoHost['id']
|
||||
|
||||
if (!vmsMap.has(id)) {
|
||||
vmsMap.set(id, [])
|
||||
}
|
||||
|
||||
vmsMap.get(id)!.push(vm)
|
||||
})
|
||||
|
||||
return vmsMap
|
||||
}
|
||||
|
||||
function extractVmHostId(vm: XoVm) {
|
||||
return vm.$container === vm.$pool ? undefined : (vm.$container as XoHost['id'])
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoVmController } from '@/types/xo/vm-controller.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
|
||||
export const useXoVmControllerCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vm-controllers?fields=id,name_label,power_state,memory,$container',
|
||||
initialData: () => [] as XoVmController[],
|
||||
state: (vmControllers, context) =>
|
||||
useXoCollectionState(vmControllers, {
|
||||
context,
|
||||
baseName: 'vmController',
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import type { XoVmTemplate } from '@/types/xo/vm-template.type.ts'
|
||||
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util.ts'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useXoVmTemplateCollection = defineRemoteResource({
|
||||
url: '/rest/v0/vm-templates?fields=id,uuid,name_label,name_description,$pool,template_info,VIFs,$VBDs,boot,CPUs,memory,tags,isDefaultTemplate',
|
||||
initialData: () => [] as XoVmTemplate[],
|
||||
state: (rawTemplates, context) => {
|
||||
const templates = useSorted(rawTemplates, sortByNameLabel)
|
||||
|
||||
const vmsTemplatesByPool = computed(() => {
|
||||
const vmTemplatesByPoolMap = new Map<XoPool['id'], XoVmTemplate[]>()
|
||||
|
||||
templates.value.forEach(template => {
|
||||
const poolId = template.$pool
|
||||
|
||||
if (!vmTemplatesByPoolMap.has(poolId)) {
|
||||
vmTemplatesByPoolMap.set(poolId, [])
|
||||
}
|
||||
|
||||
vmTemplatesByPoolMap.get(poolId)!.push(template)
|
||||
})
|
||||
|
||||
return vmTemplatesByPoolMap
|
||||
})
|
||||
|
||||
return {
|
||||
...useXoCollectionState(templates, {
|
||||
context,
|
||||
baseName: 'template',
|
||||
}),
|
||||
vmsTemplatesByPool,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { XoPoolDashboard } from '@/types/xo/pool-dashboard.type.ts'
|
||||
import { defineRequest } from '@core/packages/request/define-request.ts'
|
||||
import { merge } from 'lodash-es'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const usePoolDashboard = defineRequest({
|
||||
url: (poolId: string) => `/rest/v0/pools/${poolId}/dashboard?fields=*&ndjson=true`,
|
||||
state: () => ({
|
||||
dashboard: ref({} as XoPoolDashboard),
|
||||
}),
|
||||
onUrlChange: ({ dashboard }) => {
|
||||
dashboard.value = {} as XoPoolDashboard
|
||||
},
|
||||
onDataReceived: ({ dashboard }, data) => {
|
||||
merge(dashboard.value, data)
|
||||
},
|
||||
})
|
||||
@@ -1,68 +0,0 @@
|
||||
import type { XoDashboard } from '@/types/xo/dashboard.type.ts'
|
||||
import { defineRequest } from '@core/packages/request/define-request.ts'
|
||||
import { formatSizeRaw } from '@core/utils/size.util.ts'
|
||||
import type { Info, Scale } from 'human-format'
|
||||
import { merge } from 'lodash-es'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export const useSiteDashboard = defineRequest({
|
||||
url: '/rest/v0/dashboard?fields=*&ndjson=true',
|
||||
state: buildState,
|
||||
onDataReceived: ({ dashboard }, data) => {
|
||||
merge(dashboard.value, data)
|
||||
},
|
||||
})
|
||||
|
||||
export type BackupRepositories = {
|
||||
available: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
backups: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
other: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
total: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
used: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
}
|
||||
|
||||
export type StorageRepositories = {
|
||||
total: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
used: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
available: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
replicated: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
other: Info<Scale<'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'>>
|
||||
}
|
||||
|
||||
function buildState() {
|
||||
const dashboard = ref({} as XoDashboard)
|
||||
|
||||
const backupRepositories = computed<BackupRepositories | undefined>(() => {
|
||||
if (dashboard.value.backupRepositories === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
available: formatSizeRaw(dashboard.value.backupRepositories.other.size.available, 1),
|
||||
backups: formatSizeRaw(dashboard.value.backupRepositories.other.size.backups, 1),
|
||||
other: formatSizeRaw(dashboard.value.backupRepositories.other.size.other ?? 0, 1),
|
||||
total: formatSizeRaw(dashboard.value.backupRepositories.other.size.total, 1),
|
||||
used: formatSizeRaw(dashboard.value.backupRepositories.other.size.used ?? 0, 1),
|
||||
}
|
||||
})
|
||||
|
||||
const storageRepositories = computed<StorageRepositories | undefined>(() => {
|
||||
if (dashboard.value.storageRepositories === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
total: formatSizeRaw(dashboard.value.storageRepositories.size.total, 1),
|
||||
used: formatSizeRaw(dashboard.value.storageRepositories.size.used, 1),
|
||||
available: formatSizeRaw(dashboard.value.storageRepositories.size.available, 1),
|
||||
replicated: formatSizeRaw(dashboard.value.storageRepositories.size.replicated, 1),
|
||||
other: formatSizeRaw(dashboard.value.storageRepositories.size.other, 1),
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
backupRepositories,
|
||||
storageRepositories,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { XoAlarm } from '@/types/xo/alarm.type.ts'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { createDateSorter } from '@core/utils/date-sorter.utils.ts'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useAlarmStore = defineStore('alarm', () => {
|
||||
const config = createXoStoreConfig('alarm', {
|
||||
sortBy: createDateSorter<XoAlarm>('time'),
|
||||
})
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
import { usePoolStore } from '@/stores/xo-rest-api/pool.store'
|
||||
import { HOST_OPERATION, type XoHost } from '@/types/xo/host.type'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
|
||||
import { castArray } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useHostStore = defineStore('host', () => {
|
||||
const deps = {
|
||||
poolStore: usePoolStore(),
|
||||
}
|
||||
|
||||
const poolContext = deps.poolStore.getContext()
|
||||
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('host', {
|
||||
sortBy: sortByNameLabel,
|
||||
})
|
||||
|
||||
const isMasterHost = (hostId: XoHost['id']) => !!poolContext.records.value.find(pool => pool.master === hostId)
|
||||
|
||||
const hostsByPool = computed(() => {
|
||||
const hostsByPoolMap = new Map<XoPool['id'], XoHost[]>()
|
||||
|
||||
baseContext.records.value.forEach(host => {
|
||||
const poolId = host.$pool
|
||||
|
||||
if (!hostsByPoolMap.has(poolId)) {
|
||||
hostsByPoolMap.set(poolId, [])
|
||||
}
|
||||
|
||||
hostsByPoolMap.get(poolId)!.push(host)
|
||||
})
|
||||
|
||||
return hostsByPoolMap
|
||||
})
|
||||
|
||||
const getMasterHostByPoolId = (poolId: XoPool['id']) => {
|
||||
const masterHostId = poolContext.get(poolId)?.master
|
||||
|
||||
if (masterHostId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return baseContext.get(masterHostId)
|
||||
}
|
||||
|
||||
const isHostOperationPending = (host: XoHost, operations: HOST_OPERATION[] | HOST_OPERATION) => {
|
||||
const currentOperations = Object.values(host.current_operations)
|
||||
|
||||
return castArray(operations).some(operation => currentOperations.includes(operation))
|
||||
}
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
isMasterHost,
|
||||
hostsByPool,
|
||||
getMasterHostByPoolId,
|
||||
isHostOperationPending,
|
||||
}
|
||||
|
||||
return createSubscribableStoreContext({ context, ...configRest }, deps)
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
import { usePifStore } from '@/stores/xo-rest-api/pif.store'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useNetworkStore = defineStore('network', () => {
|
||||
const deps = {
|
||||
pifStore: usePifStore(),
|
||||
}
|
||||
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('network', {
|
||||
sortBy: sortByNameLabel,
|
||||
})
|
||||
|
||||
const pifContext = deps.pifStore.getContext()
|
||||
|
||||
const networksWithPifs = computed(() => {
|
||||
const networkIds = Array.from(pifContext.hostMasterPifsByNetwork.value.keys())
|
||||
|
||||
return baseContext.records.value.filter(network => {
|
||||
return networkIds.includes(network.id) && network.PIFs.length > 0
|
||||
})
|
||||
})
|
||||
|
||||
const networksWithoutPifs = computed(() => baseContext.records.value.filter(network => network.PIFs.length === 0))
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
networksWithPifs,
|
||||
networksWithoutPifs,
|
||||
}
|
||||
return createSubscribableStoreContext({ context, ...configRest }, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const usePciStore = defineStore('pci', () => {
|
||||
const config = createXoStoreConfig('pci')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const usePgpuStore = defineStore('pgpu', () => {
|
||||
const config = createXoStoreConfig('pgpu')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import type { XoNetwork } from '@/types/xo/network.type'
|
||||
import type { XoPif } from '@/types/xo/pif.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import type { ConnectionStatus } from '@core/components/connection-status/VtsConnectionStatus.vue'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const usePifStore = defineStore('pif', () => {
|
||||
const deps = {
|
||||
hostStore: useHostStore(),
|
||||
}
|
||||
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('pif')
|
||||
|
||||
const hostContext = deps.hostStore.getContext()
|
||||
|
||||
const hostMasterPifsByNetwork = computed(() => {
|
||||
const hostMasterPifsByNetworkMap = new Map<XoNetwork['id'], XoPif[]>()
|
||||
|
||||
baseContext.records.value
|
||||
.filter(pif => hostContext.isMasterHost(pif.$host))
|
||||
.forEach(pif => {
|
||||
const networkId = pif.$network
|
||||
if (!hostMasterPifsByNetworkMap.has(networkId)) {
|
||||
hostMasterPifsByNetworkMap.set(networkId, [])
|
||||
}
|
||||
hostMasterPifsByNetworkMap.get(networkId)?.push(pif)
|
||||
})
|
||||
|
||||
return hostMasterPifsByNetworkMap
|
||||
})
|
||||
|
||||
const getPifsByNetworkId = (networkId: XoNetwork['id']) => {
|
||||
return baseContext.records.value.filter(pif => pif.$network === networkId)
|
||||
}
|
||||
|
||||
const getPifStatus = (pif: XoPif): ConnectionStatus => {
|
||||
if (!pif.attached) {
|
||||
return 'disconnected'
|
||||
}
|
||||
|
||||
if (!pif.carrier) {
|
||||
return 'disconnected-from-physical-device'
|
||||
}
|
||||
|
||||
return 'connected'
|
||||
}
|
||||
|
||||
const pifsByHost = computed(() => {
|
||||
const pifsByHostMap = new Map<XoHost['id'], XoPif[]>()
|
||||
|
||||
baseContext.records.value.forEach(pif => {
|
||||
const hostId = pif.$host
|
||||
if (!pifsByHostMap.has(hostId)) {
|
||||
pifsByHostMap.set(hostId, [])
|
||||
}
|
||||
|
||||
pifsByHostMap.get(hostId)!.push(pif)
|
||||
})
|
||||
|
||||
return pifsByHostMap
|
||||
})
|
||||
|
||||
const getBondsDevices = (pif: XoPif) => {
|
||||
return pif.isBondMaster ? pif.bondSlaves.flatMap(slaveId => baseContext.get(slaveId)?.device ?? []) : []
|
||||
}
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
hostMasterPifsByNetwork,
|
||||
pifsByHost,
|
||||
getPifsByNetworkId,
|
||||
getPifStatus,
|
||||
getBondsDevices,
|
||||
}
|
||||
return createSubscribableStoreContext({ context, ...configRest }, deps)
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const usePoolStore = defineStore('pool', () => {
|
||||
const config = createXoStoreConfig('pool', {
|
||||
sortBy: sortByNameLabel,
|
||||
})
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { XoServer } from '@/types/xo/server.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useServerStore = defineStore('server', () => {
|
||||
const { context: baseContext, ...config } = createXoStoreConfig('server')
|
||||
|
||||
const serverByPool = computed(() => {
|
||||
const serverByPoolMap = new Map<XoPool['id'], XoServer[]>()
|
||||
|
||||
baseContext.records.value.forEach(server => {
|
||||
const poolId = server.poolId
|
||||
|
||||
if (!poolId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!serverByPoolMap.has(poolId)) {
|
||||
serverByPoolMap.set(poolId, [])
|
||||
}
|
||||
serverByPoolMap.get(poolId)!.push(server)
|
||||
})
|
||||
|
||||
return serverByPoolMap
|
||||
})
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
serverByPool,
|
||||
}
|
||||
|
||||
return createSubscribableStoreContext({ context, ...config }, {})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useVdiStore } from '@/stores/xo-rest-api/vdi.store'
|
||||
import type { XoSr } from '@/types/xo/sr.type'
|
||||
import type { XoVdi } from '@/types/xo/vdi.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useSrStore = defineStore('sr', () => {
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('sr')
|
||||
|
||||
const deps = {
|
||||
vdiStore: useVdiStore(),
|
||||
}
|
||||
const vdiContext = deps.vdiStore.getContext()
|
||||
|
||||
const srs = computed(() => baseContext.records.value)
|
||||
const getSrName = (id: XoSr['id']) => baseContext.get(id)?.name_label
|
||||
|
||||
const isoSrs = computed(() => srs.value.filter(sr => sr.SR_type === 'iso'))
|
||||
|
||||
const isoVdiIds = computed(() =>
|
||||
isoSrs.value.reduce((acc, sr) => {
|
||||
if (sr.VDIs) {
|
||||
sr.VDIs.forEach(vdiId => acc.add(vdiId))
|
||||
}
|
||||
|
||||
return acc
|
||||
}, new Set<XoVdi['id']>())
|
||||
)
|
||||
|
||||
const vdiIsosBySrName = computed(() => {
|
||||
const groupedVDIs: Record<string, XoVdi[]> = {}
|
||||
|
||||
isoVdiIds.value.forEach(vdiId => {
|
||||
const vdi = vdiContext.get(vdiId)
|
||||
|
||||
if (vdi) {
|
||||
const srName = getSrName(vdi.$SR) || 'Unknown SR'
|
||||
if (!groupedVDIs[srName]) {
|
||||
groupedVDIs[srName] = []
|
||||
}
|
||||
groupedVDIs[srName].push(vdi)
|
||||
}
|
||||
})
|
||||
|
||||
return groupedVDIs
|
||||
})
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
vdiIsosBySrName,
|
||||
}
|
||||
|
||||
return createSubscribableStoreContext({ context, ...configRest }, deps)
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useTaskStore = defineStore('task', () => {
|
||||
const config = createXoStoreConfig('task', { pollInterval: 5000 })
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useVbdStore = defineStore('vbd', () => {
|
||||
const config = createXoStoreConfig('vbd')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useVdiStore = defineStore('vdi', () => {
|
||||
const config = createXoStoreConfig('vdi')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useVifStore = defineStore('vif', () => {
|
||||
const config = createXoStoreConfig('vif')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util.ts'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util.ts'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useVmControllerStore = defineStore('vm-controller', () => {
|
||||
const config = createXoStoreConfig('vm-controller')
|
||||
|
||||
return createSubscribableStoreContext(config, {})
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { XoVmTemplate } from '@/types/xo/vm-template.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useVmTemplateStore = defineStore('vm-template', () => {
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('vm-template', {
|
||||
sortBy: sortByNameLabel,
|
||||
})
|
||||
|
||||
const vmsTemplatesByPool = computed(() => {
|
||||
const vmTemplatesByPoolMap = new Map<XoPool['id'], XoVmTemplate[]>()
|
||||
baseContext.records.value.forEach(template => {
|
||||
const poolId = template.$pool
|
||||
if (!vmTemplatesByPoolMap.has(poolId)) {
|
||||
vmTemplatesByPoolMap.set(poolId, [])
|
||||
}
|
||||
vmTemplatesByPoolMap.get(poolId)?.push(template)
|
||||
})
|
||||
|
||||
return vmTemplatesByPoolMap
|
||||
})
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
vmsTemplatesByPool,
|
||||
}
|
||||
|
||||
return createSubscribableStoreContext({ context, ...configRest }, {})
|
||||
})
|
||||
@@ -1,82 +0,0 @@
|
||||
import { useHostStore } from '@/stores/xo-rest-api/host.store.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type'
|
||||
import type { XoPool } from '@/types/xo/pool.type'
|
||||
import type { XoVm } from '@/types/xo/vm.type'
|
||||
import { VM_OPERATION, VM_POWER_STATE } from '@/types/xo/vm.type'
|
||||
import { createXoStoreConfig } from '@/utils/create-xo-store-config.util'
|
||||
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
|
||||
import { sortByNameLabel } from '@core/utils/sort-by-name-label.util'
|
||||
import { castArray } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export const useVmStore = defineStore('vm', () => {
|
||||
const deps = {
|
||||
hostStore: useHostStore(),
|
||||
}
|
||||
|
||||
const hostContext = deps.hostStore.getContext()
|
||||
|
||||
const { context: baseContext, ...configRest } = createXoStoreConfig('vm', {
|
||||
sortBy: sortByNameLabel,
|
||||
})
|
||||
|
||||
const runningVms = computed(() => baseContext.records.value.filter(vm => vm.power_state === VM_POWER_STATE.RUNNING))
|
||||
|
||||
const vmsByHost = computed(() => createVmsMap(baseContext.records.value, false))
|
||||
|
||||
const hostLessVmsByPool = computed(() => createVmsMap(baseContext.records.value, true))
|
||||
|
||||
const isVmOperatingPending = (vm: XoVm, operations: VM_OPERATION[] | VM_OPERATION) => {
|
||||
const currentOperations = Object.values(vm.current_operations)
|
||||
|
||||
return castArray(operations).some(operation => currentOperations.includes(operation))
|
||||
}
|
||||
|
||||
const getVmHost = (vm: XoVm): XoHost | undefined => {
|
||||
const vmHostId = extractVmHostId(vm)
|
||||
|
||||
if (vmHostId === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return hostContext.get(vmHostId)
|
||||
}
|
||||
|
||||
const context = {
|
||||
...baseContext,
|
||||
runningVms,
|
||||
vmsByHost,
|
||||
getVmHost,
|
||||
hostLessVmsByPool,
|
||||
isVmOperatingPending,
|
||||
}
|
||||
|
||||
return createSubscribableStoreContext({ context, ...configRest }, {})
|
||||
})
|
||||
|
||||
function createVmsMap<THostLess extends boolean>(vms: XoVm[], hostLess: THostLess) {
|
||||
const vmsMap = new Map<THostLess extends true ? XoPool['id'] : XoHost['id'], XoVm[]>()
|
||||
|
||||
vms.forEach(vm => {
|
||||
const hasHost = vm.$container !== vm.$pool
|
||||
|
||||
if (hasHost && hostLess) {
|
||||
return
|
||||
}
|
||||
|
||||
const id = vm.$container as THostLess extends true ? XoPool['id'] : XoHost['id']
|
||||
|
||||
if (!vmsMap.has(id)) {
|
||||
vmsMap.set(id, [])
|
||||
}
|
||||
|
||||
vmsMap.get(id)!.push(vm)
|
||||
})
|
||||
|
||||
return vmsMap
|
||||
}
|
||||
|
||||
function extractVmHostId(vm: XoVm) {
|
||||
return vm.$container === vm.$pool ? undefined : (vm.$container as XoHost['id'])
|
||||
}
|
||||
@@ -1,68 +1,34 @@
|
||||
import { xoApiDefinition } from '@/utils/xo-api-definition.util'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { XoAlarm } from '@/types/xo/alarm.type.ts'
|
||||
import type { XoHost } from '@/types/xo/host.type.ts'
|
||||
import type { XoNetwork } from '@/types/xo/network.type.ts'
|
||||
import type { XoPci } from '@/types/xo/pci.type.ts'
|
||||
import type { XoPgpu } from '@/types/xo/pgpu.type.ts'
|
||||
import type { XoPif } from '@/types/xo/pif.type.ts'
|
||||
import type { XoPool } from '@/types/xo/pool.type.ts'
|
||||
import type { XoServer } from '@/types/xo/server.type.ts'
|
||||
import type { XoSr } from '@/types/xo/sr.type.ts'
|
||||
import type { XoTask } from '@/types/xo/task.type.ts'
|
||||
import type { XoVbd } from '@/types/xo/vbd.type.ts'
|
||||
import type { XoVdi } from '@/types/xo/vdi.type.ts'
|
||||
import type { XoVif } from '@/types/xo/vif.type.ts'
|
||||
import type { XoVmController } from '@/types/xo/vm-controller.type.ts'
|
||||
import type { XoVmTemplate } from '@/types/xo/vm-template.type.ts'
|
||||
import type { XoVm } from '@/types/xo/vm.type.ts'
|
||||
|
||||
type XoApiDefinition = typeof xoApiDefinition
|
||||
|
||||
type XoRecordMapping = {
|
||||
[K in keyof XoApiDefinition]: {
|
||||
type: XoApiDefinition[K]['type']
|
||||
definition: XoApiDefinition[K]['handler'] extends (data: any) => infer T ? T : never
|
||||
}
|
||||
}
|
||||
|
||||
export type ApiDefinition = Record<
|
||||
string,
|
||||
{
|
||||
type: 'single' | 'collection'
|
||||
path: string
|
||||
fields: string
|
||||
handler: (data: any) => any
|
||||
stream: boolean
|
||||
}
|
||||
>
|
||||
|
||||
// SINGLE RECORD
|
||||
|
||||
type XoSingleRecordMapping = {
|
||||
[K in keyof XoRecordMapping as XoRecordMapping[K]['type'] extends 'single'
|
||||
? K
|
||||
: never]: XoRecordMapping[K]['definition']
|
||||
}
|
||||
|
||||
export type XoSingleRecordType = keyof XoSingleRecordMapping
|
||||
|
||||
export type XoSingleRecord = XoSingleRecordMapping[XoSingleRecordType]
|
||||
|
||||
export type XoSingleRecordContext<TRecord extends XoSingleRecord> = {
|
||||
record: ComputedRef<TRecord | undefined>
|
||||
isFetching: Readonly<Ref<boolean>>
|
||||
isReady: Readonly<Ref<boolean>>
|
||||
lastError: Readonly<Ref<string | undefined>>
|
||||
hasError: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
export type TypeToSingleRecord<TType extends XoSingleRecordType> = XoSingleRecordMapping[TType]
|
||||
|
||||
// COLLECTION RECORD
|
||||
|
||||
type XoCollectionRecordMapping = {
|
||||
[K in keyof XoRecordMapping as XoRecordMapping[K]['type'] extends 'collection'
|
||||
? K
|
||||
: never]: XoRecordMapping[K]['definition']
|
||||
}
|
||||
|
||||
export type XoCollectionRecordType = keyof XoCollectionRecordMapping
|
||||
|
||||
export type XoCollectionRecord = XoCollectionRecordMapping[XoCollectionRecordType]
|
||||
|
||||
export type XoCollectionRecordContext<TRecord extends XoCollectionRecord> = {
|
||||
records: ComputedRef<TRecord[]>
|
||||
get: (id: TRecord['id']) => TRecord | undefined
|
||||
has: (id: TRecord['id']) => boolean
|
||||
isFetching: Readonly<Ref<boolean>>
|
||||
isReady: Readonly<Ref<boolean>>
|
||||
lastError: Readonly<Ref<string | undefined>>
|
||||
hasError: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
export type TypeToCollectionRecord<TType extends XoCollectionRecordType> = XoCollectionRecordMapping[TType]
|
||||
export type XoRecord =
|
||||
| XoAlarm
|
||||
| XoPool
|
||||
| XoHost
|
||||
| XoVm
|
||||
| XoSr
|
||||
| XoTask
|
||||
| XoPif
|
||||
| XoVbd
|
||||
| XoVdi
|
||||
| XoVif
|
||||
| XoNetwork
|
||||
| XoVmTemplate
|
||||
| XoVmController
|
||||
| XoServer
|
||||
| XoPci
|
||||
| XoPgpu
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user