UNPKG

@stolostron/multicluster-sdk

Version:

Provides extensions and APIs that dynamic plugins can use to leverage multicluster capabilities provided by Red Hat Advanced Cluster Management.

234 lines 11.1 kB
"use strict"; /* Copyright Contributors to the Open Cluster Management project */ Object.defineProperty(exports, "__esModule", { value: true }); exports.handleWebsocketEvent = exports.stopWatch = exports.startWatch = exports.subscribe = exports.getRequestPathFromResource = void 0; exports.useGetInitialResult = useGetInitialResult; const fleetK8sWatchResourceStore_1 = require("./fleetK8sWatchResourceStore"); // Type imports const dynamic_plugin_sdk_1 = require("@openshift-console/dynamic-plugin-sdk"); const requirements_1 = require("./requirements"); const apiRequests_1 = require("./apiRequests"); const constants_1 = require("./constants"); const api_1 = require("../api"); const react_1 = require("react"); const getRequestPathFromResource = (resource, model, basePath) => { const { cluster, name, namespace, fieldSelector, selector } = resource; return (0, apiRequests_1.buildResourceURL)({ model, ns: namespace, name, cluster, queryParams: { ...(fieldSelector ? { fieldSelector: fieldSelector } : {}), labelSelector: (0, requirements_1.selectorToString)(selector || {}), }, basePath, }); }; exports.getRequestPathFromResource = getRequestPathFromResource; const getDefaultData = (resource) => { const { isList } = resource ?? {}; return isList ? [] : undefined; }; const handleError = (err, requestPath, resource) => { const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); store.setResult(requestPath, getDefaultData(resource), true, err); }; const openFleetWatchSocket = (requestPath, resource, model, basePath) => { const { cluster, name, namespace, selector, isList } = resource; const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); const cachedResult = store.getResult(requestPath); const resourceVersion = store.getResourceVersion(requestPath); try { const socket = (0, apiRequests_1.fleetWatch)(model, { ns: namespace, cluster, fieldSelector: name ? `metadata.name=${name}` : undefined, labelSelector: selector || undefined, resourceVersion: isList ? resourceVersion : undefined, allowWatchBookmarks: isList, }, basePath); store.setSocket(requestPath, socket); socket.onmessage = (event) => { try { // Handle WebSocket event - this will update the store and notify all subscribers (0, exports.handleWebsocketEvent)(event, requestPath, isList, cluster); } catch (e) { console.error('Failed to parse WebSocket message', e); } }; socket.onclose = (event) => { if (event.wasClean) { // assume data is fresh up to this point store.touchEntry(requestPath); } else { console.error('WebSocket did not close cleanly:', event); } }; socket.onerror = (err) => { console.error('WebSocket error:', err); store.setResult(requestPath, cachedResult?.data, true, err); }; } catch (err) { handleError(err, requestPath, resource); } }; const loadInitialData = async (requestPath, resource) => { const { cluster, isList } = resource; const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); try { // load initial data into the zustand store const data = await (0, dynamic_plugin_sdk_1.consoleFetchJSON)(requestPath, 'GET'); const processedData = isList ? data.items.map((i) => ({ cluster, ...i })) : { cluster, ...data }; const resourceVersion = isList ? data?.metadata?.resourceVersion : undefined; store.setResult(requestPath, processedData, true, undefined, resourceVersion); } catch (err) { handleError(err, requestPath, resource); return false; } return true; }; const monitorFleetWatchSocket = async (requestPath, resource, model, basePath) => { async function checkFleetWatchSocket(requestPath, resource, model, basePath) { const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); const entry = store.cache[requestPath]; if (entry && entry.refCount > 0) { if ((0, fleetK8sWatchResourceStore_1.isCacheEntryFresh)(entry)) { // check again when data is due to expire setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)() - (0, fleetK8sWatchResourceStore_1.getCacheEntryAge)(entry)); } else { // Socket may have disconnected; reconnect entry.socket?.close(); if (await loadInitialData(requestPath, resource)) { // only start watch if initial load succeeds openFleetWatchSocket(requestPath, resource, model, basePath); } // check again after default interval setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)()); } } } setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)()); }; function useGetInitialResult() { const isFleetAvailable = (0, api_1.useIsFleetAvailable)(); const [hubClusterName, hubClusterNameLoaded, hubClusterNameLoadedError] = (0, api_1.useHubClusterName)(); return (0, react_1.useCallback)((resource, model, basePath) => { if (resource && model && basePath) { const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath); const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); const entry = store.cache[requestPath]; if (entry && (0, fleetK8sWatchResourceStore_1.isCacheEntryValid)(entry)) { return store.getResult(requestPath); } } // Return default data and error, if any const waitingForHubClusterName = !!resource?.cluster && !hubClusterNameLoaded; const isProbablyFleetQuery = !!resource?.cluster && resource?.cluster !== hubClusterName; let loadError = undefined; if (waitingForHubClusterName) { // if we are still waiting for hub name to load, we should return any error fetching the hub name loadError = hubClusterNameLoadedError; } else if (isProbablyFleetQuery && !isFleetAvailable) { // if we need to use fleet support but it it not available, we return an error loadError = constants_1.NO_FLEET_AVAILABLE_ERROR; } return { data: getDefaultData(resource), loaded: false, loadError }; }, [isFleetAvailable, hubClusterName, hubClusterNameLoaded, hubClusterNameLoadedError]); } const subscribe = (resource, requestPath, setResult) => { return fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.subscribe((state) => state.cache[requestPath]?.result, (result) => { if (result) { setResult(result); } else { setResult({ data: getDefaultData(resource), loaded: false }); } }); }; exports.subscribe = subscribe; const startWatch = async (resource, model, basePath) => { const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath); const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); store.incrementRefCount(requestPath); // if we are the first subscriber, we are responsible for getting the initial data and watching for updates if (store.getRefCount(requestPath) === 1) { // if there is a cached value that is not expired, we can skip the initial fetch const entry = store.cache[requestPath]; if ((entry && (0, fleetK8sWatchResourceStore_1.isCacheEntryValid)(entry)) || (await loadInitialData(requestPath, resource))) { // only start watch if we have valid cached data or an initial load succeeds openFleetWatchSocket(requestPath, resource, model, basePath); } monitorFleetWatchSocket(requestPath, resource, model, basePath); } }; exports.startWatch = startWatch; const stopWatch = (resource, model, basePath) => { const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath); const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); store.decrementRefCount(requestPath); }; exports.stopWatch = stopWatch; const handleWebsocketEvent = (event, requestPath, isList, cluster) => { if (!event) { console.warn('Received undefined event', event); return; } const eventDataParsed = JSON.parse(event.data); const eventType = eventDataParsed?.type; const object = eventDataParsed?.object; if (!object) return; const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState(); if (!isList) { const currentEntry = store.getResult(requestPath); if (eventType === 'ADDED' && currentEntry?.data) return; const processedEventData = { cluster, ...object }; if (processedEventData) { // Update the store with the new data - this will notify all subscribers store.setResult(requestPath, processedEventData, true); } return; } const currentEntry = store.getResult(requestPath); const storedData = currentEntry?.data; if (!storedData) { return; } if (eventType === 'DELETED') { const updatedData = storedData.filter((i) => i.metadata?.uid !== object?.metadata?.uid); store.setResult(requestPath, updatedData, true); return; } if (eventType === 'BOOKMARK') { store.setResult(requestPath, storedData, true, undefined, object?.metadata?.resourceVersion); } if (eventType !== 'ADDED' && eventType !== 'MODIFIED') { return; } if (!object?.metadata?.uid) { console.warn('Event object does not have a metadata.uid', eventDataParsed); return; } const objectExists = storedData.some((i) => i.metadata?.uid === object?.metadata?.uid); if (objectExists && eventType === 'MODIFIED') { const updatedData = storedData.map((i) => (i.metadata?.uid === object?.metadata?.uid ? { cluster, ...object } : i)); store.setResult(requestPath, updatedData, true); return; } if (!objectExists) { const updatedData = [...storedData, { cluster, ...object }]; store.setResult(requestPath, updatedData, true); } }; exports.handleWebsocketEvent = handleWebsocketEvent; //# sourceMappingURL=fleetK8sWatchResource.js.map