Compare commits

...

1 Commits

Author SHA1 Message Date
Thierry
fb02eb3486 feat(lite/xapi-subscriptions): add an immediate option + TypeScript enhancement
- `subscribe({ immediate: false })` allows to defer the subscription
- Extracted typing to its own file
- Enhanced `subscribe` signature with overloading
- Enhanced Host/VM stores `subscribe` signature and typing
2023-05-31 15:20:32 +02:00
5 changed files with 220 additions and 140 deletions

View File

@@ -1,13 +1,12 @@
import type {
RawObjectType,
RawXenApiRecord,
XenApiHost,
XenApiHostMetrics,
XenApiRecord,
XenApiVm,
} from "@/libs/xen-api";
import type { CollectionSubscription } from "@/stores/xapi-collection.store";
import type { Filter } from "@/types/filter";
import type { CollectionSubscription } from "@/types/xapi-collection";
import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
import { faFont, faHashtag, faList } from "@fortawesome/free-solid-svg-icons";
import { utcParse } from "d3-time-format";
@@ -183,15 +182,6 @@ export function parseRamUsage(
export const getFirst = <T>(value: T | T[]): T | undefined =>
Array.isArray(value) ? value[0] : value;
export function requireSubscription<T>(
subscription: T | undefined,
type: RawObjectType
): asserts subscription is T {
if (subscription === undefined) {
throw new Error(`You need to provide a ${type} subscription`);
}
}
export const isOperationsPending = (
obj: XenApiVm,
operations: string[] | string

View File

@@ -1,21 +1,28 @@
import {
isHostRunning,
requireSubscription,
sortRecordsByNameLabel,
} from "@/libs/utils";
import type { GRANULARITY } from "@/libs/xapi-stats";
import type { XenApiHostMetrics } from "@/libs/xen-api";
import {
type CollectionSubscription,
useXapiCollectionStore,
} from "@/stores/xapi-collection.store";
import { isHostRunning, sortRecordsByNameLabel } from "@/libs/utils";
import type { GRANULARITY, XapiStatsResponse } from "@/libs/xapi-stats";
import type { XenApiHost, XenApiHostMetrics } from "@/libs/xen-api";
import { useXapiCollectionStore } from "@/stores/xapi-collection.store";
import { useXenApiStore } from "@/stores/xen-api.store";
import type { CollectionSubscription } from "@/types/xapi-collection";
import { defineStore } from "pinia";
import { computed } from "vue";
import { computed, type ComputedRef } from "vue";
type SubscribeOptions = {
hostMetricsSubscription?: CollectionSubscription<XenApiHostMetrics>;
};
type MetricsSubscription = CollectionSubscription<XenApiHostMetrics>;
interface HostSubscribeOptions<M extends undefined | MetricsSubscription> {
hostMetricsSubscription?: M;
}
interface HostSubscription extends CollectionSubscription<XenApiHost> {
getStats: (
hostUuid: string,
granularity: GRANULARITY
) => Promise<XapiStatsResponse<any>>;
}
interface HostSubscriptionWithRunningHosts extends HostSubscription {
runningHosts: ComputedRef<XenApiHost[]>;
}
export const useHostStore = defineStore("host", () => {
const xenApiStore = useXenApiStore();
@@ -23,17 +30,19 @@ export const useHostStore = defineStore("host", () => {
hostCollection.setSort(sortRecordsByNameLabel);
const subscribe = ({ hostMetricsSubscription }: SubscribeOptions = {}) => {
function subscribe(
options?: HostSubscribeOptions<undefined>
): HostSubscription;
function subscribe(
options?: HostSubscribeOptions<MetricsSubscription>
): HostSubscriptionWithRunningHosts;
function subscribe({
hostMetricsSubscription,
}: HostSubscribeOptions<undefined | MetricsSubscription> = {}) {
const hostSubscription = hostCollection.subscribe();
const runningHosts = computed(() => {
requireSubscription(hostMetricsSubscription, "host_metrics");
return hostSubscription.records.value.filter((host) =>
isHostRunning(host, hostMetricsSubscription)
);
});
const getStats = (hostUuid: string, granularity: GRANULARITY) => {
const host = hostSubscription.getByUuid(hostUuid);
@@ -52,12 +61,26 @@ export const useHostStore = defineStore("host", () => {
});
};
return {
const subscription = {
...hostSubscription,
runningHosts,
getStats,
};
};
if (hostMetricsSubscription === undefined) {
return subscription;
}
const runningHosts = computed(() =>
hostSubscription.records.value.filter((host) =>
isHostRunning(host, hostMetricsSubscription)
)
);
return {
...subscription,
runningHosts,
};
}
return {
...hostCollection,

View File

@@ -1,18 +1,30 @@
import { requireSubscription, sortRecordsByNameLabel } from "@/libs/utils";
import type { GRANULARITY } from "@/libs/xapi-stats";
import { sortRecordsByNameLabel } from "@/libs/utils";
import type { GRANULARITY, XapiStatsResponse } from "@/libs/xapi-stats";
import type { XenApiHost, XenApiVm } from "@/libs/xen-api";
import {
type CollectionSubscription,
useXapiCollectionStore,
} from "@/stores/xapi-collection.store";
import { useXapiCollectionStore } from "@/stores/xapi-collection.store";
import { useXenApiStore } from "@/stores/xen-api.store";
import type { CollectionSubscription } from "@/types/xapi-collection";
import { defineStore } from "pinia";
import { computed } from "vue";
import { computed, type ComputedRef } from "vue";
type SubscribeOptions = {
hostSubscription?: CollectionSubscription<XenApiHost>;
type HostSubscription = CollectionSubscription<XenApiHost>;
type VmSubscribeOptions<H extends undefined | HostSubscription> = {
hostSubscription?: H;
};
interface VmSubscription extends CollectionSubscription<XenApiVm> {
recordsByHostRef: ComputedRef<Map<string, XenApiVm[]>>;
runningVms: ComputedRef<XenApiVm[]>;
}
interface VmSubscriptionWithGetStats extends VmSubscription {
getStats: (
id: string,
granularity: GRANULARITY
) => Promise<XapiStatsResponse<any>>;
}
export const useVmStore = defineStore("vm", () => {
const vmCollection = useXapiCollectionStore().get("VM");
@@ -22,7 +34,15 @@ export const useVmStore = defineStore("vm", () => {
vmCollection.setSort(sortRecordsByNameLabel);
const subscribe = ({ hostSubscription }: SubscribeOptions = {}) => {
function subscribe(options?: VmSubscribeOptions<undefined>): VmSubscription;
function subscribe(
options?: VmSubscribeOptions<HostSubscription>
): VmSubscriptionWithGetStats;
function subscribe({
hostSubscription,
}: VmSubscribeOptions<undefined | HostSubscription> = {}) {
const vmSubscription = vmCollection.subscribe();
const recordsByHostRef = computed(() => {
@@ -43,9 +63,17 @@ export const useVmStore = defineStore("vm", () => {
vmSubscription.records.value.filter((vm) => vm.power_state === "Running")
);
const getStats = (id: string, granularity: GRANULARITY) => {
requireSubscription(hostSubscription, "host");
const subscription = {
...vmSubscription,
recordsByHostRef,
runningVms,
};
if (hostSubscription === undefined) {
return subscription;
}
const getStats = (id: string, granularity: GRANULARITY) => {
const xenApiStore = useXenApiStore();
if (!xenApiStore.isConnected) {
@@ -72,12 +100,10 @@ export const useVmStore = defineStore("vm", () => {
};
return {
...vmSubscription,
recordsByHostRef,
...subscription,
getStats,
runningVms,
};
};
}
return {
...vmCollection,

View File

@@ -1,20 +1,14 @@
import type {
RawObjectType,
XenApiConsole,
XenApiHost,
XenApiHostMetrics,
XenApiPool,
XenApiRecord,
XenApiSr,
XenApiTask,
XenApiVm,
XenApiVmGuestMetrics,
XenApiVmMetrics,
} from "@/libs/xen-api";
import type { RawObjectType, XenApiRecord } from "@/libs/xen-api";
import { useXenApiStore } from "@/stores/xen-api.store";
import type {
CollectionSubscription,
DeferredCollectionSubscription,
RawTypeToObject,
SubscribeOptions,
} from "@/types/xapi-collection";
import { tryOnUnmounted, whenever } from "@vueuse/core";
import { defineStore } from "pinia";
import { computed, type ComputedRef, readonly, type Ref, ref } from "vue";
import { computed, readonly, ref } from "vue";
export const useXapiCollectionStore = defineStore("xapiCollection", () => {
const collections = ref(
@@ -35,18 +29,6 @@ export const useXapiCollectionStore = defineStore("xapiCollection", () => {
return { get };
});
export interface CollectionSubscription<T extends XenApiRecord> {
records: ComputedRef<T[]>;
getByOpaqueRef: (opaqueRef: string) => T | undefined;
getByUuid: (uuid: string) => T | undefined;
hasUuid: (uuid: string) => boolean;
isReady: Readonly<Ref<boolean>>;
isFetching: Readonly<Ref<boolean>>;
isReloading: ComputedRef<boolean>;
hasError: ComputedRef<boolean>;
lastError: Readonly<Ref<string | undefined>>;
}
const createXapiCollection = <T extends XenApiRecord>(type: RawObjectType) => {
const isReady = ref(false);
const isFetching = ref(false);
@@ -123,16 +105,30 @@ const createXapiCollection = <T extends XenApiRecord>(type: RawObjectType) => {
() => fetchAll()
);
const subscribe = () => {
function subscribe(
options?: SubscribeOptions<true>
): CollectionSubscription<T>;
function subscribe(
options: SubscribeOptions<false>
): DeferredCollectionSubscription<T>;
function subscribe(
options: SubscribeOptions<boolean>
): CollectionSubscription<T> | DeferredCollectionSubscription<T>;
function subscribe({ immediate = true }: SubscribeOptions<boolean> = {}) {
const id = Symbol();
subscriptions.value.add(id);
if (immediate) {
subscriptions.value.add(id);
}
tryOnUnmounted(() => {
unsubscribe(id);
});
return {
const subscription = {
records,
getByOpaqueRef,
getByUuid,
@@ -143,7 +139,17 @@ const createXapiCollection = <T extends XenApiRecord>(type: RawObjectType) => {
hasError,
lastError: readonly(lastError),
};
};
if (immediate) {
return subscription;
}
return {
...subscription,
start: () => subscriptions.value.add(id),
isStarted: computed(() => subscriptions.value.has(id)),
};
}
const unsubscribe = (id: symbol) => subscriptions.value.delete(id);
@@ -158,59 +164,3 @@ const createXapiCollection = <T extends XenApiRecord>(type: RawObjectType) => {
setSort,
};
};
type RawTypeToObject = {
Bond: never;
Certificate: never;
Cluster: never;
Cluster_host: never;
DR_task: never;
Feature: never;
GPU_group: never;
PBD: never;
PCI: never;
PGPU: never;
PIF: never;
PIF_metrics: never;
PUSB: never;
PVS_cache_storage: never;
PVS_proxy: never;
PVS_server: never;
PVS_site: never;
SDN_controller: never;
SM: never;
SR: XenApiSr;
USB_group: never;
VBD: never;
VBD_metrics: never;
VDI: never;
VGPU: never;
VGPU_type: never;
VIF: never;
VIF_metrics: never;
VLAN: never;
VM: XenApiVm;
VMPP: never;
VMSS: never;
VM_guest_metrics: XenApiVmGuestMetrics;
VM_metrics: XenApiVmMetrics;
VUSB: never;
blob: never;
console: XenApiConsole;
crashdump: never;
host: XenApiHost;
host_cpu: never;
host_crashdump: never;
host_metrics: XenApiHostMetrics;
host_patch: never;
network: never;
network_sriov: never;
pool: XenApiPool;
pool_patch: never;
pool_update: never;
role: never;
secret: never;
subject: never;
task: XenApiTask;
tunnel: never;
};

View File

@@ -0,0 +1,91 @@
import type {
XenApiConsole,
XenApiHost,
XenApiHostMetrics,
XenApiPool,
XenApiRecord,
XenApiSr,
XenApiTask,
XenApiVm,
XenApiVmGuestMetrics,
XenApiVmMetrics,
} from "@/libs/xen-api";
import type { ComputedRef, Ref } from "vue";
export interface SubscribeOptions<Immediate extends boolean> {
immediate?: Immediate;
}
export interface CollectionSubscription<T extends XenApiRecord> {
records: ComputedRef<T[]>;
getByOpaqueRef: (opaqueRef: string) => T | undefined;
getByUuid: (uuid: string) => T | undefined;
hasUuid: (uuid: string) => boolean;
isReady: Readonly<Ref<boolean>>;
isFetching: Readonly<Ref<boolean>>;
isReloading: ComputedRef<boolean>;
hasError: ComputedRef<boolean>;
lastError: Readonly<Ref<string | undefined>>;
}
export interface DeferredCollectionSubscription<T extends XenApiRecord>
extends CollectionSubscription<T> {
start: () => void;
isStarted: ComputedRef<boolean>;
}
export type RawTypeToObject = {
Bond: never;
Certificate: never;
Cluster: never;
Cluster_host: never;
DR_task: never;
Feature: never;
GPU_group: never;
PBD: never;
PCI: never;
PGPU: never;
PIF: never;
PIF_metrics: never;
PUSB: never;
PVS_cache_storage: never;
PVS_proxy: never;
PVS_server: never;
PVS_site: never;
SDN_controller: never;
SM: never;
SR: XenApiSr;
USB_group: never;
VBD: never;
VBD_metrics: never;
VDI: never;
VGPU: never;
VGPU_type: never;
VIF: never;
VIF_metrics: never;
VLAN: never;
VM: XenApiVm;
VMPP: never;
VMSS: never;
VM_guest_metrics: XenApiVmGuestMetrics;
VM_metrics: XenApiVmMetrics;
VUSB: never;
blob: never;
console: XenApiConsole;
crashdump: never;
host: XenApiHost;
host_cpu: never;
host_crashdump: never;
host_metrics: XenApiHostMetrics;
host_patch: never;
network: never;
network_sriov: never;
pool: XenApiPool;
pool_patch: never;
pool_update: never;
role: never;
secret: never;
subject: never;
task: XenApiTask;
tunnel: never;
};