From 8f1d10756f4a3339b14ffeab9c00e41a39f283da Mon Sep 17 00:00:00 2001 From: Jonathan Kilzi Date: Thu, 27 Jul 2023 10:37:12 +0300 Subject: [PATCH] MGMT-15374: Unties common-to-ocm dependencies (#2279) * Breaks common-to-ocm dependencies * Breaks circular dependencies Moves fileSize and stringToJson to lib/common/utils.ts in order to break circular dependencies --- .../ShortCapacitySummary.tsx | 2 +- .../Hypershift/DetailsPage/NodePoolsTable.tsx | 2 +- libs/ui-lib/lib/common/api/axiosClient.ts | 56 ++++ libs/ui-lib/lib/common/api/axiosExtensions.ts | 11 + libs/ui-lib/lib/common/api/index.ts | 1 + libs/ui-lib/lib/common/api/utils.ts | 122 +++++++-- .../apis/assisted-service/ClustersAPI.ts | 253 ++++++++++++++++++ .../assisted-service/ComponentVersionsAPI.ts | 14 + .../common/apis/assisted-service/EventsAPI.ts | 57 ++++ .../common/apis/assisted-service/HostsAPI.ts | 63 +++++ .../apis/assisted-service/InfraEnvsAPI.ts | 72 +++++ .../assisted-service/ManagedDomainsAPI.ts} | 0 .../SupportedOpenshiftVersionsAPI.ts | 14 + .../components/clusterConfiguration/utils.ts | 2 +- .../clusterDetail/KubeconfigDownload.tsx | 8 +- .../ClusterWizardStepValidationsAlert.tsx | 2 +- .../clusterWizard/ReviewHostsInventory.tsx | 5 +- .../clusterWizard/ReviewValidations.tsx | 4 +- .../common/components/clusterWizard/types.ts | 2 +- .../clusterWizard/validationsInfoUtils.ts | 3 +- .../components/hosts/HostRequirements.tsx | 2 +- .../common/components/hosts/HostRowDetail.tsx | 9 +- .../lib/common/components/hosts/Hostname.tsx | 3 +- .../hosts/MassChangeHostnameModal.tsx | 2 +- .../common/components/hosts/hardwareInfo.ts | 2 +- .../common/components/hosts/tableUtils.tsx | 5 +- .../lib/common/components/hosts/utils.ts | 10 +- .../newFeatureSupportLevels/utils.ts | 3 +- .../common/components/storage/DisksTable.tsx | 4 +- .../components/ui/ClusterEventsToolbar.tsx | 5 +- .../lib/common/selectors/clusterSelectors.ts | 3 +- libs/ui-lib/lib/common/utils.ts | 28 +- .../components/ExpandedManifest.tsx | 3 +- .../NetworkConfigurationTableBase.tsx | 9 +- .../review/ReviewPreflightChecks.tsx | 2 +- .../staticIp/data/fromInfraEnv.ts | 3 +- .../ocm/components/clusterDetail/utils.tsx | 2 +- .../clusterWizard/wizardTransition.ts | 3 +- .../ocm/components/hosts/HardwareStatus.tsx | 2 +- .../hosts/HostRequirementsContent.tsx | 3 +- libs/ui-lib/lib/ocm/components/hosts/utils.ts | 4 +- .../ocm/services/apis/ManagedDomainsAPI.ts | 14 + libs/ui-lib/lib/ocm/services/apis/types.ts | 7 - 43 files changed, 743 insertions(+), 78 deletions(-) create mode 100644 libs/ui-lib/lib/common/api/axiosClient.ts create mode 100644 libs/ui-lib/lib/common/api/axiosExtensions.ts create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/ClustersAPI.ts create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/ComponentVersionsAPI.ts create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/EventsAPI.ts create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/HostsAPI.ts create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/InfraEnvsAPI.ts rename libs/ui-lib/lib/{ocm/services/apis/ManagedDomainsAPI.tsx => common/apis/assisted-service/ManagedDomainsAPI.ts} (100%) create mode 100644 libs/ui-lib/lib/common/apis/assisted-service/SupportedOpenshiftVersionsAPI.ts create mode 100644 libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.ts diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ShortCapacitySummary.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ShortCapacitySummary.tsx index 4110007152..a0a8338e2e 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ShortCapacitySummary.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ShortCapacitySummary.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { fileSize } from '../../../common'; import { AgentK8sResource } from '../../types'; import { TFunction } from 'i18next'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; +import { fileSize } from '../../../common/utils'; export const getTotalCompute = (selectedAgents: AgentK8sResource[], t: TFunction) => { const totals = selectedAgents.reduce( diff --git a/libs/ui-lib/lib/cim/components/Hypershift/DetailsPage/NodePoolsTable.tsx b/libs/ui-lib/lib/cim/components/Hypershift/DetailsPage/NodePoolsTable.tsx index 8a4632f84e..056d147f05 100644 --- a/libs/ui-lib/lib/cim/components/Hypershift/DetailsPage/NodePoolsTable.tsx +++ b/libs/ui-lib/lib/cim/components/Hypershift/DetailsPage/NodePoolsTable.tsx @@ -25,11 +25,11 @@ import AddNodePoolModal from '../modals/AddNodePoolModal'; import { AgentMachineK8sResource, HostedClusterK8sResource, NodePoolK8sResource } from '../types'; import RemoveNodePoolModal from '../modals/RemoveNodePoolModal'; import NodePoolStatus from './NodePoolStatus'; -import { fileSize } from '../../../../common'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { getNodepoolAgents } from '../utils'; import './NodePoolsTable.css'; +import { fileSize } from '../../../../common/utils'; type NodePoolsTableProps = { nodePools: NodePoolK8sResource[]; diff --git a/libs/ui-lib/lib/common/api/axiosClient.ts b/libs/ui-lib/lib/common/api/axiosClient.ts new file mode 100644 index 0000000000..ee3d4212b7 --- /dev/null +++ b/libs/ui-lib/lib/common/api/axiosClient.ts @@ -0,0 +1,56 @@ +import axios, { AxiosInstance } from 'axios'; +import applyCaseMiddleware from 'axios-case-converter'; +import { camelCase } from 'camel-case'; + +// conforms basePath in swagger.json +export const BASE_PATH = '/api/assisted-install'; + +// Prevent axios converter to change object keys from '4.7-fc2' to '4_7Fc2' +const axiosCaseConverterOptions = { + caseFunctions: { + camel: (input: string) => + camelCase(input, { + stripRegexp: /[^A-Z0-9.-]+/gi, + }), + }, +}; + +const getDefaultClient = (withoutConverter = false) => { + const client = axios.create(); + client.interceptors.request.use((cfg) => ({ + ...cfg, + url: `${process.env.AIUI_APP_API_ROOT || ''}${cfg.url || ''}`, + })); + if (withoutConverter) { + return client; + } else { + return applyCaseMiddleware(client, axiosCaseConverterOptions); + } +}; + +let client: AxiosInstance = getDefaultClient(); +let clientWithoutConverter: AxiosInstance = getDefaultClient(true); +let ocmClient: AxiosInstance | null; +let isInOcm = false; + +const aiInterceptor = (client: AxiosInstance) => { + client.interceptors.request.use((cfg) => ({ + ...cfg, + url: `${BASE_PATH}${cfg.url || ''}`, + })); + return client; +}; + +const getOcmClient = () => ocmClient; + +export const setAuthInterceptor = (authInterceptor: (client: AxiosInstance) => AxiosInstance) => { + ocmClient = authInterceptor(axios.create()); + isInOcm = true; + client = applyCaseMiddleware( + aiInterceptor(authInterceptor(axios.create())), + axiosCaseConverterOptions, + ); + clientWithoutConverter = aiInterceptor(authInterceptor(axios.create())); +}; + +export { client, getOcmClient, isInOcm, clientWithoutConverter }; diff --git a/libs/ui-lib/lib/common/api/axiosExtensions.ts b/libs/ui-lib/lib/common/api/axiosExtensions.ts new file mode 100644 index 0000000000..3a0cb65e8a --- /dev/null +++ b/libs/ui-lib/lib/common/api/axiosExtensions.ts @@ -0,0 +1,11 @@ +import { AxiosError } from 'axios'; +import { hasProp, isNonNullObject } from '../types/typescriptExtensions'; + +// Implementation from Axios.isAxiosError v0.29.2, which is not available in OCM's version 0.17.x +export function isAxiosError( + payload: unknown, +): payload is AxiosError { + return ( + isNonNullObject(payload) && hasProp(payload, 'isAxiosError') && payload.isAxiosError === true + ); +} diff --git a/libs/ui-lib/lib/common/api/index.ts b/libs/ui-lib/lib/common/api/index.ts index 6d5a6ef495..b2cb512213 100644 --- a/libs/ui-lib/lib/common/api/index.ts +++ b/libs/ui-lib/lib/common/api/index.ts @@ -1,2 +1,3 @@ +export * from './axiosClient'; export * from './types'; export * from './utils'; diff --git a/libs/ui-lib/lib/common/api/utils.ts b/libs/ui-lib/lib/common/api/utils.ts index 88102d8dea..f3e0c90df8 100644 --- a/libs/ui-lib/lib/common/api/utils.ts +++ b/libs/ui-lib/lib/common/api/utils.ts @@ -1,22 +1,114 @@ -import camelCase from 'lodash-es/camelCase.js'; - -export const stringToJSON = (jsonString: string | undefined): T | undefined => { - let jsObject: T | undefined; - if (jsonString) { - try { - const camelCased = jsonString.replace( - /"([\w-]+)":/g, - (_match, offset: string) => `"${camelCase(offset)}":`, - ); - jsObject = JSON.parse(camelCased) as T; - } catch (e) { - // console.error('Failed to parse api string', e, jsonString); +import Axios, { AxiosError } from 'axios'; +import * as Sentry from '@sentry/browser'; +import pick from 'lodash-es/pick.js'; +import { Error as APIError, InfraError } from './types'; +import { getErrorMessage } from '../utils'; +import { isAxiosError } from './axiosExtensions'; +import { isInOcm } from './axiosClient'; + +export const FETCH_ABORTED_ERROR_CODE = 'ERR_CANCELED'; +export const FETCH_CONNECTIVITY_ERROR_CODE = 'CONNECTIVITY_ERROR'; +export const SERVER_ERROR_CODE = 'SERVER_ERROR'; +export const PAGE_RELOAD_ERROR = 'ECONNABORTED'; + +type OnError = (arg0: unknown) => void; + +export const handleApiError = (error: unknown, onError?: OnError): void => { + if (Axios.isCancel(error)) { + captureException(error, 'Request canceled', Sentry.Severity.Info); + } else if (isApiError(error)) { + const config = error.config || { url: '', method: '' }; + let message = `URL: ${JSON.stringify(config.url, null, 1)}\n`; + message += `Method: ${JSON.stringify(config.method, null, 1)}\n`; + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + message += `Status: ${error.response.status}\n`; + message += `Response: ${JSON.stringify( + pick(error.response.data, ['code', 'message', 'reason']), + null, + 1, + )}\n`; + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + message += `Status Code: ${JSON.stringify( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + error.request.__sentry_xhr__.status_code, + null, + 1, + )}`; } + captureException(error, message); } else { - // console.info('Empty api string received.'); + captureException(error); } + if (onError) return onError(error); +}; + +export const getApiErrorMessage = (error: unknown): string => { + if (isApiError(error)) { + return error.response?.data?.message || error.response?.data.reason || error.message; + } + return getErrorMessage(error); +}; - return jsObject; +export const isUnknownServerError = (error: Error): boolean => { + return getApiErrorCode(error) === SERVER_ERROR_CODE; +}; + +export const getApiErrorCode = (error: Error | AxiosError): string | number => { + if (!isAxiosError(error)) { + return FETCH_CONNECTIVITY_ERROR_CODE; + } + // Aborted request + if (error.code === FETCH_ABORTED_ERROR_CODE) { + return FETCH_ABORTED_ERROR_CODE; + } + + // Page is reloading and some request has been interrupted + if (error.code === PAGE_RELOAD_ERROR) { + return ''; + } + + const responseStatus = error.response?.status || 0; + + // Error status + if (responseStatus >= 500 && responseStatus < 600) { + return SERVER_ERROR_CODE; + } + if (responseStatus >= 400 && responseStatus < 500) { + return responseStatus; + } + // A generic connectivity issue + return FETCH_CONNECTIVITY_ERROR_CODE; +}; + +export type APIErrorMixin = InfraError & APIError; +export type AIAxiosErrorType = AxiosError; + +export const isApiError = (error: unknown): error is AIAxiosErrorType => { + if (!isAxiosError(error)) { + return false; + } + return typeof error.response?.data === 'object'; +}; + +export const captureException = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + error: any, + message?: string, + severity: Sentry.Severity = Sentry.Severity.Error, +) => { + if (isInOcm) { + message && Sentry.captureMessage(message, severity); + Sentry.captureException(error); + } else { + // severity === Sentry.Severity.Error + // ? console.error(message, error) + // : console.warn(message, error); + } }; export const removeProtocolFromURL = (url = '') => url.replace(/^(http|https):\/\//, ''); diff --git a/libs/ui-lib/lib/common/apis/assisted-service/ClustersAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/ClustersAPI.ts new file mode 100644 index 0000000000..efa76c0562 --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/ClustersAPI.ts @@ -0,0 +1,253 @@ +import { client } from '../../api/axiosClient'; +import { + Cluster, + ClusterCreateParams, + ClusterDefaultConfig, + V2ClusterUpdateParams, + Credentials, + Host, + ImportClusterParams, + PlatformType, + PreflightHardwareRequirements, + PresignedUrl, + ListManifests, + CreateManifestParams, + UpdateManifestParams, + Manifest, + LogsType, +} from '../../api/types'; +import { AxiosResponse } from 'axios'; + +let _getRequestAbortController = new AbortController(); + +export type ClustersAPIGetPresignedOptions = { + clusterId: string; + fileName: 'logs' | 'kubeconfig' | 'kubeconfig-noingress'; + hostId?: string; + logsType?: LogsType; +}; + +const ClustersAPI = { + abortLastGetRequest() { + _getRequestAbortController.abort(); + /** + * The AbortController.signal can only be aborted once per instance. + * Therefore in order for other requests to be also abortable we need + * to create a new instance when this event occurs + */ + _getRequestAbortController = new AbortController(); + }, + + makeBaseURI(clusterId?: Cluster['id']) { + return `/v2/clusters/${clusterId ? clusterId : ''}`; + }, + + makeDownloadsBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeBaseURI(clusterId)}/downloads`; + }, + + makeLogsBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeBaseURI(clusterId)}/logs`; + }, + + makeDownloadsCredentialsBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeDownloadsBaseURI(clusterId)}/credentials`; + }, + + makeDownloadsCredentialsPresignedBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeDownloadsBaseURI(clusterId)}/credentials-presigned`; + }, + + makeDownloadsFilesPresignedBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeDownloadsBaseURI(clusterId)}/files-presigned`; + }, + + makeSupportedPlatformsBaseURI(clusterId: Cluster['id']) { + return `${ClustersAPI.makeBaseURI(clusterId)}/supported-platforms`; + }, + + makeActionsBaseURI(clusterId: string) { + return `${ClustersAPI.makeBaseURI(clusterId)}/actions`; + }, + + downloadClusterCredentials(clusterId: Cluster['id'], fileName: string) { + return client.get( + `${ClustersAPI.makeDownloadsCredentialsBaseURI(clusterId)}?file_name=${fileName}`, + { + responseType: 'blob', + headers: { + Accept: 'application/octet-stream', + }, + }, + ); + }, + + getPresignedForClusterCredentials({ + clusterId, + fileName, + hostId, + logsType, + }: ClustersAPIGetPresignedOptions) { + const queryParams = `${logsType ? `&logs_type=${logsType}` : ''}${ + hostId ? `&host_id=${hostId}` : '' + }`; + return client.get( + `${ClustersAPI.makeDownloadsCredentialsPresignedBaseURI( + clusterId, + )}?file_name=${fileName}${queryParams}`, + ); + }, + + getPresignedForClusterFiles({ + clusterId, + fileName, + hostId, + logsType, + }: ClustersAPIGetPresignedOptions) { + const queryParams = `${logsType ? `&logs_type=${logsType}` : ''}${ + hostId ? `&host_id=${hostId}` : '' + }`; + return client.get( + `${ClustersAPI.makeDownloadsFilesPresignedBaseURI( + clusterId, + )}?file_name=${fileName}${queryParams}`, + ); + }, + + list() { + return client.get(`${ClustersAPI.makeBaseURI()}`, { + signal: _getRequestAbortController.signal, + }); + }, + + get(clusterId: Cluster['id']) { + return client.get(`${ClustersAPI.makeBaseURI(clusterId)}`, { + signal: _getRequestAbortController.signal, + }); + }, + + getSupportedPlatforms(clusterId: Cluster['id']) { + return client.get(`${ClustersAPI.makeSupportedPlatformsBaseURI(clusterId)}`); + }, + + getPreflightRequirements(clusterId: Cluster['id']) { + return client.get( + `${ClustersAPI.makeBaseURI(clusterId)}/preflight-requirements`, + ); + }, + + getDefaultConfig() { + return client.get(`${ClustersAPI.makeBaseURI()}/default-config`); + }, + + deregister(clusterId: Cluster['id']) { + return client.delete(`${ClustersAPI.makeBaseURI(clusterId)}`); + }, + + register(params: ClusterCreateParams) { + return client.post, ClusterCreateParams>( + `${ClustersAPI.makeBaseURI()}`, + params, + ); + }, + + update(clusterId: Cluster['id'], params: V2ClusterUpdateParams) { + return client.patch, V2ClusterUpdateParams>( + `${ClustersAPI.makeBaseURI(clusterId)}`, + params, + ); + }, + + install(clusterId: Cluster['id']) { + return client.post, never>( + `${ClustersAPI.makeActionsBaseURI(clusterId)}/install`, + ); + }, + + cancel(clusterId: Cluster['id']) { + return client.post, never>( + `${ClustersAPI.makeActionsBaseURI(clusterId)}/cancel`, + ); + }, + + reset(clusterId: Cluster['id']) { + return client.post, never>( + `${ClustersAPI.makeActionsBaseURI(clusterId)}/reset`, + ); + }, + + registerAddHosts(params: ImportClusterParams) { + return client.post, ImportClusterParams>( + `${ClustersAPI.makeBaseURI()}import`, + params, + ); + }, + + allowAddHosts(clusterId: Cluster['id']) { + return client.post, never>( + `${ClustersAPI.makeActionsBaseURI(clusterId)}/allow-add-workers`, + ); + }, + + downloadLogs(clusterId: Cluster['id'], hostId?: Host['id']) { + const queryParams = `logs_type=${!hostId ? 'all' : `host&host_id=${hostId}`}`; + return client.get(`${ClustersAPI.makeLogsBaseURI(clusterId)}?${queryParams}`, { + responseType: 'blob', + headers: { + Accept: 'application/octet-stream', + }, + }); + }, + + getCredentials(clusterId: Cluster['id']) { + return client.get(`${ClustersAPI.makeBaseURI(clusterId)}/credentials`); + }, + + listByOpenshiftId(openshiftId: Cluster['openshiftClusterId']) { + return client.get( + `${ClustersAPI.makeBaseURI()}?openshift_cluster_id=${openshiftId || ''}`, + ); + }, + + listBySubscriptionIds(subscriptionIds: Cluster['amsSubscriptionId'][]) { + return client.get( + `${ClustersAPI.makeBaseURI()}?ams_subscription_ids=${subscriptionIds.toString()}`, + ); + }, + + getManifests(clusterId: Cluster['id']) { + return client.get(`${ClustersAPI.makeBaseURI(clusterId)}/manifests`); + }, + createCustomManifest(clusterId: Cluster['id'], params: CreateManifestParams) { + return client.post, CreateManifestParams>( + `${ClustersAPI.makeBaseURI(clusterId)}/manifests`, + params, + ); + }, + removeCustomManifest(clusterId: Cluster['id'], folderName: string, fileName: string) { + return client.delete( + `${ClustersAPI.makeBaseURI(clusterId)}/manifests?folder=${folderName}&file_name=${fileName}`, + ); + }, + getManifestContent(clusterId: Cluster['id'], folderName: string, fileName: string) { + return client.get( + `${ClustersAPI.makeBaseURI( + clusterId, + )}/manifests/files?folder=${folderName}&file_name=${fileName}`, + { + responseType: 'blob', + headers: { + Accept: 'application/octet-stream', + }, + }, + ); + }, + updateCustomManifest(clusterId: Cluster['id'], params: UpdateManifestParams) { + return client.patch, UpdateManifestParams>( + `${ClustersAPI.makeBaseURI(clusterId)}/manifests`, + params, + ); + }, +}; + +export default ClustersAPI; diff --git a/libs/ui-lib/lib/common/apis/assisted-service/ComponentVersionsAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/ComponentVersionsAPI.ts new file mode 100644 index 0000000000..e4a54d6804 --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/ComponentVersionsAPI.ts @@ -0,0 +1,14 @@ +import { client } from '../../api'; +import { ListVersions } from '../../../common'; + +const ComponentVersionsAPI = { + makeBaseURI() { + return `/v2/component-versions`; + }, + + list() { + return client.get(`${ComponentVersionsAPI.makeBaseURI()}`); + }, +}; + +export default ComponentVersionsAPI; diff --git a/libs/ui-lib/lib/common/apis/assisted-service/EventsAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/EventsAPI.ts new file mode 100644 index 0000000000..b30b80f38e --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/EventsAPI.ts @@ -0,0 +1,57 @@ +import { EventList, V2Events } from '../../../common'; +import { client } from '../../api'; + +let _getRequestAbortController = new AbortController(); + +const EventsAPI = { + makeBaseURI() { + return '/v2/events'; + }, + + abort() { + _getRequestAbortController.abort(); + /* + * The AbortController.signal can only be aborted once per instance. + * Therefore in order for other requests to be also abortable we need + * to create a new instance when this event occurs + */ + _getRequestAbortController = new AbortController(); + }, + + createParams({ + clusterId, + hostIds, + infraEnvId, + severities, + deletedHosts = true, + clusterLevel = true, + message, + limit = 10, + offset = 0, + }: V2Events) { + let params = '?'; + params += 'order=descending&'; + + params += clusterId ? `cluster_id=${clusterId}&` : ''; + params += hostIds?.length ? `host_ids=${hostIds.join(',')}&` : ''; + params += infraEnvId ? `infra_env_id=${infraEnvId}&` : ''; + + params += severities?.length ? `severities=${severities?.join(',')}&` : ''; + params += deletedHosts ? `deleted_hosts=${deletedHosts.toString()}&` : ''; + params += clusterLevel ? `cluster_level=${clusterLevel.toString()}&` : ''; + params += message ? `message=${encodeURIComponent(message)}&` : ''; + + params += `limit=${limit}&`; + params += `offset=${offset}`; + + return params; + }, + + list(options: V2Events) { + return client.get(`${EventsAPI.makeBaseURI()}${EventsAPI.createParams(options)}`, { + signal: _getRequestAbortController.signal, + }); + }, +}; + +export default EventsAPI; diff --git a/libs/ui-lib/lib/common/apis/assisted-service/HostsAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/HostsAPI.ts new file mode 100644 index 0000000000..864531f2aa --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/HostsAPI.ts @@ -0,0 +1,63 @@ +import { Host, HostUpdateParams, InfraEnv } from '../../../common'; +import { client } from '../../api'; +import { AxiosResponse } from 'axios'; +import InfraEnvsAPI from './InfraEnvsAPI'; + +let _getRequestAbortController = new AbortController(); + +const HostsAPI = { + abortLastGetRequest() { + _getRequestAbortController.abort(); + /** + * The AbortController.signal can only be aborted once per instance. + * Therefore in order for other requests to be also abortable we need + * to create a new instance when this event occurs + */ + _getRequestAbortController = new AbortController(); + }, + + makeBaseURI(infraEnvId: InfraEnv['id'], hostId?: Host['id']) { + return `${InfraEnvsAPI.makeBaseURI(infraEnvId)}/hosts/${hostId ? hostId : ''}`; + }, + + makeActionsBaseURI(infraEnvId: InfraEnv['id'], hostId: Host['id']) { + return `${HostsAPI.makeBaseURI(infraEnvId, hostId)}/actions`; + }, + + list(infraEnvId: InfraEnv['id']) { + return client.get(`${HostsAPI.makeBaseURI(infraEnvId)}`, { + signal: _getRequestAbortController.signal, + }); + }, + + get(infraEnvId: InfraEnv['id'], hostId: Host['id']) { + return client.get(`${HostsAPI.makeBaseURI(infraEnvId, hostId)}`, { + signal: _getRequestAbortController.signal, + }); + }, + + update(infraEnvId: InfraEnv['id'], hostId: Host['id'], params: HostUpdateParams) { + return client.patch, HostUpdateParams>( + `${HostsAPI.makeBaseURI(infraEnvId, hostId)}`, + params, + ); + }, + + deregister(infraEnvId: InfraEnv['id'], hostId: Host['id']) { + return client.delete(`${HostsAPI.makeBaseURI(infraEnvId, hostId)}`); + }, + + reset(infraEnvId: InfraEnv['id'], hostId: Host['id']) { + return client.post, never>( + `${HostsAPI.makeActionsBaseURI(infraEnvId, hostId)}/reset`, + ); + }, + + installHost(infraEnvId: InfraEnv['id'], hostId: Host['id']) { + return client.post, never>( + `${HostsAPI.makeActionsBaseURI(infraEnvId, hostId)}/install`, + ); + }, +}; + +export default HostsAPI; diff --git a/libs/ui-lib/lib/common/apis/assisted-service/InfraEnvsAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/InfraEnvsAPI.ts new file mode 100644 index 0000000000..a7370e03ad --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/InfraEnvsAPI.ts @@ -0,0 +1,72 @@ +import { client } from '../../api'; +import { + InfraEnv, + InfraEnvCreateParams, + PresignedUrl, + InfraEnvUpdateParams, +} from '../../../common'; +import { AxiosResponse } from 'axios'; + +let _getRequestAbortController = new AbortController(); + +const InfraEnvsAPI = { + abortLastGetRequest() { + _getRequestAbortController.abort(); + /* + * The AbortController.signal can only be aborted once per instance. + * Therefore in order for other requests to be also abortable we need + * to create a new instance when this event occurs + */ + _getRequestAbortController = new AbortController(); + }, + + makeBaseURI(infraEnvId?: InfraEnv['id']) { + return `/v2/infra-envs/${infraEnvId ? infraEnvId : ''}`; + }, + + /** + * Retrieves the list of infra-envs. + * @param clusterId If provided, returns only infra-envs which directly reference the given clusterId. + */ + list(clusterId = '') { + const query = clusterId && `?${new URLSearchParams({ ['cluster_id']: clusterId }).toString()}`; + return client.get(`${InfraEnvsAPI.makeBaseURI()}${query}`, { + signal: _getRequestAbortController.signal, + }); + }, + + get(infraEnvId: InfraEnv['id']) { + return client.get(`${InfraEnvsAPI.makeBaseURI(infraEnvId)}`, { + signal: _getRequestAbortController.signal, + }); + }, + + update(infraEnvId: InfraEnv['id'], params: InfraEnvUpdateParams) { + return client.patch, InfraEnvUpdateParams>( + `${InfraEnvsAPI.makeBaseURI(infraEnvId)}`, + params, + ); + }, + + register(params: InfraEnvCreateParams) { + return client.post, InfraEnvCreateParams>( + `${InfraEnvsAPI.makeBaseURI()}`, + params, + ); + }, + + deregister(infraEnvId: InfraEnv['id']) { + return client.delete(`${InfraEnvsAPI.makeBaseURI(infraEnvId)}`); + }, + + getImageUrl(infraEnvId: InfraEnv['id']) { + return client.get(`${InfraEnvsAPI.makeBaseURI(infraEnvId)}/downloads/image-url`); + }, + getIpxeImageUrl(infraEnvId: InfraEnv['id']) { + return client.get( + `${InfraEnvsAPI.makeBaseURI(infraEnvId)}/downloads/files-presigned?file_name=ipxe-script`, + ); + }, +}; + +export default InfraEnvsAPI; diff --git a/libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.tsx b/libs/ui-lib/lib/common/apis/assisted-service/ManagedDomainsAPI.ts similarity index 100% rename from libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.tsx rename to libs/ui-lib/lib/common/apis/assisted-service/ManagedDomainsAPI.ts diff --git a/libs/ui-lib/lib/common/apis/assisted-service/SupportedOpenshiftVersionsAPI.ts b/libs/ui-lib/lib/common/apis/assisted-service/SupportedOpenshiftVersionsAPI.ts new file mode 100644 index 0000000000..c4f6f9176c --- /dev/null +++ b/libs/ui-lib/lib/common/apis/assisted-service/SupportedOpenshiftVersionsAPI.ts @@ -0,0 +1,14 @@ +import { client } from '../../api/axiosClient'; +import { OpenshiftVersion } from '../../../common'; + +const SupportedOpenshiftVersionsAPI = { + makeBaseURI() { + return `/v2/openshift-versions`; + }, + + list() { + return client.get(`${SupportedOpenshiftVersionsAPI.makeBaseURI()}`); + }, +}; + +export default SupportedOpenshiftVersionsAPI; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/utils.ts b/libs/ui-lib/lib/common/components/clusterConfiguration/utils.ts index 22b7e6b50f..ac86f84c0e 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/utils.ts +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/utils.ts @@ -8,7 +8,6 @@ import { Inventory, MachineNetwork, ServiceNetwork, - stringToJSON, } from '../../api'; import { NETWORK_TYPE_OVN, NETWORK_TYPE_SDN, NO_SUBNET_SET } from '../../config'; import { @@ -31,6 +30,7 @@ import { WithRequired, } from '../../types'; import { getHostname } from '../hosts/utils'; +import { stringToJSON } from '../../utils'; type VersionConfig = WithRequired, 'openshiftVersion'> & { cpuArchitecture?: ClusterCpuArchitecture; diff --git a/libs/ui-lib/lib/common/components/clusterDetail/KubeconfigDownload.tsx b/libs/ui-lib/lib/common/components/clusterDetail/KubeconfigDownload.tsx index a8adc7cebc..e85d0e5464 100644 --- a/libs/ui-lib/lib/common/components/clusterDetail/KubeconfigDownload.tsx +++ b/libs/ui-lib/lib/common/components/clusterDetail/KubeconfigDownload.tsx @@ -4,11 +4,9 @@ import { Button, ButtonVariant } from '@patternfly/react-core'; import { canDownloadKubeconfig } from '../hosts/utils'; import { useAlerts } from '../AlertsContextProvider'; import { Cluster } from '../../api/types'; -/* eslint-disable no-restricted-imports */ -import { isInOcm } from '../../../ocm/api/axiosClient'; -import { getApiErrorMessage, handleApiError } from '../../../ocm/api/utils'; -import ClustersAPI from '../../../ocm/services/apis/ClustersAPI'; -/* eslint-enable no-restricted-imports */ +import { isInOcm } from '../../api/axiosClient'; +import { getApiErrorMessage, handleApiError } from '../../api/utils'; +import ClustersAPI from '../../apis/assisted-service/ClustersAPI'; import { AxiosResponseHeaders } from 'axios'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import { TFunction } from 'i18next'; diff --git a/libs/ui-lib/lib/common/components/clusterWizard/ClusterWizardStepValidationsAlert.tsx b/libs/ui-lib/lib/common/components/clusterWizard/ClusterWizardStepValidationsAlert.tsx index 19a534529c..9cc5cf7550 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/ClusterWizardStepValidationsAlert.tsx +++ b/libs/ui-lib/lib/common/components/clusterWizard/ClusterWizardStepValidationsAlert.tsx @@ -19,7 +19,7 @@ import { getWizardStepClusterValidationsInfo, } from './validationsInfoUtils'; import { useTranslation } from '../../hooks/use-translation-wrapper'; -import { stringToJSON } from '../../api'; +import { stringToJSON } from '../../utils'; type ClusterWizardStepValidationsAlertProps = { currentStepId: ClusterWizardStepsType; diff --git a/libs/ui-lib/lib/common/components/clusterWizard/ReviewHostsInventory.tsx b/libs/ui-lib/lib/common/components/clusterWizard/ReviewHostsInventory.tsx index 643549a4df..725531c76d 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/ReviewHostsInventory.tsx +++ b/libs/ui-lib/lib/common/components/clusterWizard/ReviewHostsInventory.tsx @@ -1,8 +1,9 @@ import { Table, TableBody, TableVariant } from '@patternfly/react-table'; import * as React from 'react'; -import { stringToJSON, Inventory, Host } from '../../api'; -import { getEnabledHosts, fileSize } from '../hosts'; +import type { Inventory, Host } from '../../api'; +import { getEnabledHosts } from '../hosts'; import { getSimpleHardwareInfo } from '../hosts/hardwareInfo'; +import { fileSize, stringToJSON } from '../../utils'; const ReviewHostsInventory = ({ hosts = [] }: { hosts?: Host[] }) => { const rows = React.useMemo(() => { diff --git a/libs/ui-lib/lib/common/components/clusterWizard/ReviewValidations.tsx b/libs/ui-lib/lib/common/components/clusterWizard/ReviewValidations.tsx index 2e9866bb07..d4221b56bc 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/ReviewValidations.tsx +++ b/libs/ui-lib/lib/common/components/clusterWizard/ReviewValidations.tsx @@ -19,7 +19,7 @@ import { ValidationsInfo as HostValidationsInfo, Validation as HostValidation, } from '../../types/hosts'; -import { ClusterValidationId, HostValidationId, stringToJSON } from '../../api'; +import type { ClusterValidationId, HostValidationId } from '../../api'; import { clusterValidationLabels, hostValidationLabels } from '../../config'; import { getEnabledHosts } from '../hosts'; import { findValidationFixStep } from './validationsInfoUtils'; @@ -31,7 +31,7 @@ import { } from './types'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import { Trans } from 'react-i18next'; -import { getKeys } from '../../utils'; +import { getKeys, stringToJSON } from '../../utils'; const AllValidationsPassed = () => { const { t } = useTranslation(); diff --git a/libs/ui-lib/lib/common/components/clusterWizard/types.ts b/libs/ui-lib/lib/common/components/clusterWizard/types.ts index e071a7f7b3..76e3c90385 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/types.ts +++ b/libs/ui-lib/lib/common/components/clusterWizard/types.ts @@ -1,4 +1,4 @@ -import { DiskEncryption, Host, Cluster, PlatformType } from '../../api'; +import type { DiskEncryption, Host, Cluster, PlatformType } from '../../api'; import { ValidationGroup as ClusterValidationGroup, diff --git a/libs/ui-lib/lib/common/components/clusterWizard/validationsInfoUtils.ts b/libs/ui-lib/lib/common/components/clusterWizard/validationsInfoUtils.ts index a394872164..02a9ae7880 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/validationsInfoUtils.ts +++ b/libs/ui-lib/lib/common/components/clusterWizard/validationsInfoUtils.ts @@ -2,7 +2,7 @@ import lodashValues from 'lodash-es/values.js'; import lodashKeys from 'lodash-es/keys.js'; import reduce from 'lodash-es/reduce.js'; -import { Cluster, ClusterValidationId, Host, HostValidationId, stringToJSON } from '../../api'; +import type { Cluster, ClusterValidationId, Host, HostValidationId } from '../../api'; import { ClusterWizardStepStatusDeterminationObject, ValidationGroup as ClusterValidationGroup, @@ -16,6 +16,7 @@ import { ValidationsInfo, ValidationsInfo as HostValidationsInfo, } from '../../../common/types/hosts'; +import { stringToJSON } from '../../utils'; export type WizardStepValidationMap = { cluster: { diff --git a/libs/ui-lib/lib/common/components/hosts/HostRequirements.tsx b/libs/ui-lib/lib/common/components/hosts/HostRequirements.tsx index 3eb3741713..3fb12835b4 100644 --- a/libs/ui-lib/lib/common/components/hosts/HostRequirements.tsx +++ b/libs/ui-lib/lib/common/components/hosts/HostRequirements.tsx @@ -1,8 +1,8 @@ import { List, ListItem, TextContent } from '@patternfly/react-core'; import * as React from 'react'; -import { fileSize } from './utils'; import ExternalLink from '../ui/ExternalLink'; import { useTranslation } from '../../hooks/use-translation-wrapper'; +import { fileSize } from '../../utils'; type HWRequirements = { cpuCores?: number; diff --git a/libs/ui-lib/lib/common/components/hosts/HostRowDetail.tsx b/libs/ui-lib/lib/common/components/hosts/HostRowDetail.tsx index 029459560e..69ec7aa26a 100644 --- a/libs/ui-lib/lib/common/components/hosts/HostRowDetail.tsx +++ b/libs/ui-lib/lib/common/components/hosts/HostRowDetail.tsx @@ -11,18 +11,19 @@ import { } from '@patternfly/react-table'; import { ExtraParamsType } from '@patternfly/react-table/dist/js/components/Table/base'; import { DetailItem, DetailList, DetailListProps } from '../ui'; -import { Disk, Host, Interface, stringToJSON } from '../../api'; -import { ValidationsInfo } from '../../types/hosts'; -import { WithTestID } from '../../types'; +import type { Disk, Host, Interface } from '../../api'; +import type { ValidationsInfo } from '../../types/hosts'; +import type { WithTestID } from '../../types/index'; import { DASH } from '../constants'; import { getHostRowHardwareInfo } from './hardwareInfo'; import { getHardwareTypeText, getInventory } from './utils'; -import { ValidationInfoActionProps } from './HostValidationGroups'; +import type { ValidationInfoActionProps } from './HostValidationGroups'; import NtpValidationStatus from './NtpValidationStatus'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import SectionTitle from '../ui/SectionTitle'; import { OnDiskRoleType } from './DiskRole'; import StorageDetail from '../storage/StorageDetail'; +import { stringToJSON } from '../../utils'; type HostDetailProps = { host: Host; diff --git a/libs/ui-lib/lib/common/components/hosts/Hostname.tsx b/libs/ui-lib/lib/common/components/hosts/Hostname.tsx index dce2166e1d..f7c09c2388 100644 --- a/libs/ui-lib/lib/common/components/hosts/Hostname.tsx +++ b/libs/ui-lib/lib/common/components/hosts/Hostname.tsx @@ -2,10 +2,11 @@ import React from 'react'; import { Button, ButtonVariant, Flex, FlexItem, Popover } from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { global_warning_color_100 as warningColor } from '@patternfly/react-tokens'; -import { Host, Inventory, stringToJSON } from '../../api'; +import type { Host, Inventory } from '../../api'; import { getHostname } from './utils'; import { DASH } from '../constants'; import { useTranslation } from '../../hooks/use-translation-wrapper'; +import { stringToJSON } from '../../utils'; type HostnameProps = { host: Host; diff --git a/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx b/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx index 1a0b5b693a..46dd61eb29 100644 --- a/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx +++ b/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx @@ -33,7 +33,7 @@ import { Host } from '../../api'; import { getHostname as getHostnameUtils, getInventory } from './utils'; import { ActionCheck } from './types'; import { useTranslation } from '../../hooks/use-translation-wrapper'; -import { getApiErrorMessage } from '../../../ocm/api'; // eslint-disable-line no-restricted-imports +import { getApiErrorMessage } from '../../api'; // eslint-disable-line no-restricted-imports import './MassChangeHostnameModal.css'; diff --git a/libs/ui-lib/lib/common/components/hosts/hardwareInfo.ts b/libs/ui-lib/lib/common/components/hosts/hardwareInfo.ts index 7e4e90c787..d0fe4182b9 100644 --- a/libs/ui-lib/lib/common/components/hosts/hardwareInfo.ts +++ b/libs/ui-lib/lib/common/components/hosts/hardwareInfo.ts @@ -2,7 +2,7 @@ import Humanize from 'humanize-plus'; import { Disk, Inventory } from '../../api'; import { DASH, OpticalDiskDriveType } from '../constants'; import { HumanizedSortable } from '../ui/table/utils'; -import { fileSize } from './utils'; +import { fileSize } from '../../utils'; export type HostRowHardwareInfo = { serialNumber: string; diff --git a/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx b/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx index 9e03570c97..7b6aa4154c 100644 --- a/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx +++ b/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx @@ -1,8 +1,8 @@ import { breakWord, expandable, sortable } from '@patternfly/react-table'; import * as React from 'react'; import { Address4, Address6 } from 'ip-address'; -import { Cluster, Host, HostUpdateParams, Interface, stringToJSON } from '../../api'; -import { ValidationsInfo as HostValidationsInfo } from '../../types/hosts'; +import type { Cluster, Host, HostUpdateParams, Interface } from '../../api'; +import type { ValidationsInfo as HostValidationsInfo } from '../../types/hosts'; import { getSubnet } from '../clusterConfiguration'; import { DASH } from '../constants'; import { getDateTimeCell } from '../ui'; @@ -24,6 +24,7 @@ import { import { selectMachineNetworkCIDR } from '../../selectors/clusterSelectors'; import { hostStatus } from './status'; import { TFunction } from 'i18next'; +import { stringToJSON } from '../../utils'; export const getSelectedNic = (nics: Interface[], currentSubnet: Address4 | Address6) => { return nics.find((nic) => { diff --git a/libs/ui-lib/lib/common/components/hosts/utils.ts b/libs/ui-lib/lib/common/components/hosts/utils.ts index 57db9534c8..6164c4ebe0 100644 --- a/libs/ui-lib/lib/common/components/hosts/utils.ts +++ b/libs/ui-lib/lib/common/components/hosts/utils.ts @@ -1,7 +1,7 @@ -import filesize from 'filesize.js'; import Fuse from 'fuse.js'; import { TFunction } from 'i18next'; -import { Host, Cluster, Inventory, stringToJSON } from '../../api'; +import type { Host, Cluster, Inventory } from '../../api/types'; +import { stringToJSON } from '../../utils'; import { hostRoles, TIME_ZERO } from '../../config'; import { DASH } from '../constants'; import { @@ -185,12 +185,6 @@ export const getHardwareTypeText = (inventory: Inventory, t: TFunction) => { return hardwareTypeText; }; -export const fileSize: typeof filesize = (...args) => - filesize - .call(null, ...args) - .toUpperCase() - .replace(/I/, 'i'); - export const getInventory = (host: Host) => { const { inventory: inventoryString = '' } = host; return stringToJSON(inventoryString) || {}; diff --git a/libs/ui-lib/lib/common/components/newFeatureSupportLevels/utils.ts b/libs/ui-lib/lib/common/components/newFeatureSupportLevels/utils.ts index 174814c5fc..d92bfa6f7e 100644 --- a/libs/ui-lib/lib/common/components/newFeatureSupportLevels/utils.ts +++ b/libs/ui-lib/lib/common/components/newFeatureSupportLevels/utils.ts @@ -1,10 +1,11 @@ -import { Cluster, stringToJSON, SupportLevel } from '../../api'; +import { Cluster, SupportLevel } from '../../api'; import { ClusterFeatureUsage, FeatureId, FeatureIdToSupportLevel } from '../../types'; import { NewFeatureSupportLevelData, NewFeatureSupportLevelMap, } from './NewFeatureSupportLevelContext'; import { TFunction } from 'i18next'; +import { stringToJSON } from '../../utils'; export const getLimitedFeatureSupportLevels = ( cluster: Cluster, diff --git a/libs/ui-lib/lib/common/components/storage/DisksTable.tsx b/libs/ui-lib/lib/common/components/storage/DisksTable.tsx index 91430d46f5..a0abb9758c 100644 --- a/libs/ui-lib/lib/common/components/storage/DisksTable.tsx +++ b/libs/ui-lib/lib/common/components/storage/DisksTable.tsx @@ -10,7 +10,8 @@ import { IRow, } from '@patternfly/react-table'; import { ExtraParamsType } from '@patternfly/react-table/dist/js/components/Table/base'; -import { Disk, fileSize, Host, WithTestID } from '../../index'; +import type { Disk, Host } from '../../api/types'; +import type { WithTestID } from '../../types/index'; import DiskRole, { OnDiskRoleType } from '../hosts/DiskRole'; import DiskLimitations from '../hosts/DiskLimitations'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; @@ -19,6 +20,7 @@ import FormatDiskCheckbox, { DiskFormattingType, isInDiskSkipFormattingList, } from '../hosts/FormatDiskCheckbox'; +import { fileSize } from '../../utils'; interface DisksTableProps extends WithTestID { canEditDisks?: (host: Host) => boolean; diff --git a/libs/ui-lib/lib/common/components/ui/ClusterEventsToolbar.tsx b/libs/ui-lib/lib/common/components/ui/ClusterEventsToolbar.tsx index a47be1653a..c803ed3c38 100644 --- a/libs/ui-lib/lib/common/components/ui/ClusterEventsToolbar.tsx +++ b/libs/ui-lib/lib/common/components/ui/ClusterEventsToolbar.tsx @@ -20,12 +20,13 @@ import { TextInputTypes, } from '@patternfly/react-core'; import { SearchIcon, FilterIcon } from '@patternfly/react-icons'; -import { ClusterEventsFiltersType } from '../../types'; -import { Cluster, Event, Host, Inventory, stringToJSON } from '../../api'; +import type { ClusterEventsFiltersType } from '../../types'; +import type { Cluster, Event, Host, Inventory } from '../../api'; import { EVENT_SEVERITIES } from '../../config'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import { isSelectEventChecked } from './utils'; import { TFunction } from 'i18next'; +import { stringToJSON } from '../../utils'; export type SeverityCountsType = { [severity in Event['severity']]: number }; diff --git a/libs/ui-lib/lib/common/selectors/clusterSelectors.ts b/libs/ui-lib/lib/common/selectors/clusterSelectors.ts index ef6fa2f2b8..40f2fd3c1e 100644 --- a/libs/ui-lib/lib/common/selectors/clusterSelectors.ts +++ b/libs/ui-lib/lib/common/selectors/clusterSelectors.ts @@ -1,7 +1,8 @@ import head from 'lodash-es/head.js'; import { SupportedPlatformIntegrations, ValidationsInfo } from '../types'; -import { Cluster, Ip, stringToJSON } from '../api'; +import { Cluster, Ip } from '../api'; import { ExposedOperatorName } from '../config'; +import { stringToJSON } from '../utils'; export const selectMachineNetworkCIDR = ({ machineNetworks, diff --git a/libs/ui-lib/lib/common/utils.ts b/libs/ui-lib/lib/common/utils.ts index 0d5c3c59f5..b4be5dd261 100644 --- a/libs/ui-lib/lib/common/utils.ts +++ b/libs/ui-lib/lib/common/utils.ts @@ -1,7 +1,8 @@ +import filesize from 'filesize.js'; +import camelCase from 'lodash-es/camelCase.js'; import isString from 'lodash-es/isString.js'; import { load } from 'js-yaml'; import { MAX_FILE_SIZE_BYTES, MAX_FILE_SIZE_OFFSET_FACTOR } from './configurations'; -import { fileSize } from './components/hosts/utils'; export const FILENAME_REGEX = /^[^\/]*\.(yaml|yml|json)$/; export const FILE_TYPE_MESSAGE = 'Unsupported file type. Please provide a valid YAML file.'; @@ -52,6 +53,12 @@ export const validateFileSize = (value: string): boolean => { return contentFile.size <= MAX_FILE_SIZE_BYTES * MAX_FILE_SIZE_OFFSET_FACTOR; }; +export const fileSize: typeof filesize = (...args) => + filesize + .call(null, ...args) + .toUpperCase() + .replace(/I/, 'i'); + export const getMaxFileSizeMessage = `File size is too big. The file size must be less than ${fileSize( MAX_FILE_SIZE_BYTES, 0, @@ -61,3 +68,22 @@ export const getMaxFileSizeMessage = `File size is too big. The file size must b export const validateFileName = (fileName: string) => { return new RegExp(FILENAME_REGEX).test(fileName || ''); }; + +export const stringToJSON = (jsonString: string | undefined): T | undefined => { + let jsObject: T | undefined; + if (jsonString) { + try { + const camelCased = jsonString.replace( + /"([\w-]+)":/g, + (_match, offset: string) => `"${camelCase(offset)}":`, + ); + jsObject = JSON.parse(camelCased) as T; + } catch (e) { + // console.error('Failed to parse api string', e, jsonString); + } + } else { + // console.info('Empty api string received.'); + } + + return jsObject; +}; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.tsx index 9a4debad2d..57a41812ba 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.tsx @@ -6,8 +6,9 @@ import { OcmInputField, OcmCodeField } from '../../../ui/OcmFormFields'; import { CustomManifestValues } from '../data/dataTypes'; import { FolderDropdown } from './FolderDropdown'; import { CustomManifestComponentProps } from './propTypes'; -import { PopoverIcon, fileSize } from '../../../../../common'; +import { PopoverIcon } from '../../../../../common'; import { MAX_FILE_SIZE_BYTES } from '../../../../../common/configurations'; +import { fileSize } from '../../../../../common/utils'; const getDownloadFileName = (manifestIdx: number, value: CustomManifestValues) => { return value.folder && value.filename diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx index e330334815..0403287fcd 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx @@ -1,12 +1,6 @@ import * as React from 'react'; import { sortable } from '@patternfly/react-table'; -import { - Cluster, - Host, - HostsTableActions, - selectSchedulableMasters, - stringToJSON, -} from '../../../../common'; +import { Cluster, Host, HostsTableActions, selectSchedulableMasters } from '../../../../common'; import NetworkingStatus from '../../hosts/NetworkingStatus'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { @@ -24,6 +18,7 @@ import { HostDetail } from '../../../../common/components/hosts/HostRowDetail'; import HostsTable from '../../../../common/components/hosts/HostsTable'; import { ValidationsInfo } from '../../../../common/types/hosts'; import { useClusterWizardContext } from '../../clusterWizard/ClusterWizardContext'; +import { stringToJSON } from '../../../../common/utils'; export const networkingStatusColumn = ( onEditHostname?: HostsTableActions['onEditHost'], diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.tsx index 46d14d7ca4..1a2f43ed21 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.tsx @@ -27,7 +27,6 @@ import { DetailItem, DetailList, HostsValidations, - stringToJSON, } from '../../../../common'; import { useClusterWizardContext } from '../../clusterWizard/ClusterWizardContext'; import { useOpenshiftVersions } from '../../../hooks'; @@ -47,6 +46,7 @@ import { useTranslation } from '../../../../common/hooks/use-translation-wrapper import { ValidationsInfo as ClusterValidationsInfo } from '../../../../common/types/clusters'; import { ValidationsInfo as HostValidationsInfo } from '../../../../common/types/hosts'; import { useNewFeatureSupportLevel } from '../../../../common/components/newFeatureSupportLevels'; +import { stringToJSON } from '../../../../common/utils'; const PreflightChecksDetailExpanded = ({ cluster }: { cluster: Cluster }) => { const clusterWizardContext = useClusterWizardContext(); diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/fromInfraEnv.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/fromInfraEnv.ts index df28288f5d..76e3822440 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/fromInfraEnv.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/fromInfraEnv.ts @@ -1,4 +1,4 @@ -import { HostStaticNetworkConfig, InfraEnv, stringToJSON } from '../../../../../common'; +import { HostStaticNetworkConfig, InfraEnv } from '../../../../../common'; import { FORM_VIEW_PREFIX, getProtocolType, getYamlComments } from './nmstateYaml'; import { StaticIpInfo, @@ -11,6 +11,7 @@ import { import { isDummyYaml } from './dummyData'; import { formDataFromInfraEnvField } from './formDataFromInfraEnvField'; import { getEmptyFormViewHost } from './emptyData'; +import { stringToJSON } from '../../../../../common/utils'; export const getStaticNetworkConfig = ( infraEnv: InfraEnv, diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/utils.tsx b/libs/ui-lib/lib/ocm/components/clusterDetail/utils.tsx index 17df354cdc..75e01b9a47 100644 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/utils.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterDetail/utils.tsx @@ -6,12 +6,12 @@ import { Host, HostRole, Inventory, - stringToJSON, AlertsContextType, hostStatusOrder, } from '../../../common'; import { ClustersAPI } from '../../services/apis'; import { ClustersService } from '../../services'; +import { stringToJSON } from '../../../common/utils'; export const downloadClusterInstallationLogs = async ( addAlert: AlertsContextType['addAlert'], diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts index e6c7d76e6a..6d55d9dc4c 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/wizardTransition.ts @@ -5,7 +5,6 @@ import { getWizardStepClusterStatus, Host, HostValidationId, - stringToJSON, WizardStepsValidationMap, WizardStepValidationMap, } from '../../../common'; @@ -15,7 +14,7 @@ import { ValidationsInfo as HostValidationsInfo, Validation as HostValidation, } from '../../../common/types/hosts'; -import { getKeys } from '../../../common/utils'; +import { getKeys, stringToJSON } from '../../../common/utils'; export type ClusterWizardStepsType = | 'cluster-details' diff --git a/libs/ui-lib/lib/ocm/components/hosts/HardwareStatus.tsx b/libs/ui-lib/lib/ocm/components/hosts/HardwareStatus.tsx index 2c06a443b9..008b213a8a 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/HardwareStatus.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/HardwareStatus.tsx @@ -8,12 +8,12 @@ import { HostsTableActions, hostStatus, HostStatus, - stringToJSON, } from '../../../common'; import { TableRow } from '../../../common/components/hosts/AITable'; import { ValidationsInfo } from '../../../common/types/hosts'; import { wizardStepsValidationsMap } from '../clusterWizard/wizardTransition'; import { AdditionalNTPSourcesDialogToggle } from './AdditionaNTPSourceDialogToggle'; +import { stringToJSON } from '../../../common/utils'; type HardwareStatusProps = { host: Host; diff --git a/libs/ui-lib/lib/ocm/components/hosts/HostRequirementsContent.tsx b/libs/ui-lib/lib/ocm/components/hosts/HostRequirementsContent.tsx index 8a7bbd9766..c37e8de276 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/HostRequirementsContent.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/HostRequirementsContent.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { List, ListItem, Text, TextContent } from '@patternfly/react-core'; -import { Cluster, ErrorState, ExternalLink, LoadingState, fileSize } from '../../../common'; +import { Cluster, ErrorState, ExternalLink, LoadingState } from '../../../common'; import { useClusterPreflightRequirements } from '../../hooks'; +import { fileSize } from '../../../common/utils'; const DISK_WRITE_SPEED_LINK = 'https://access.redhat.com/solutions/4885641'; diff --git a/libs/ui-lib/lib/ocm/components/hosts/utils.ts b/libs/ui-lib/lib/ocm/components/hosts/utils.ts index 840e9017a5..f0055c0bc3 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/utils.ts +++ b/libs/ui-lib/lib/ocm/components/hosts/utils.ts @@ -1,10 +1,9 @@ import { saveAs } from 'file-saver'; -import { +import type { AlertsContextType, Cluster, V2ClusterUpdateParams, Host, - stringToJSON, Inventory, } from '../../../common'; @@ -12,6 +11,7 @@ import { isInOcm, handleApiError, getApiErrorMessage } from '../../api'; import { updateCluster } from '../../reducers/clusters'; import { ClustersService } from '../../services'; import ClustersAPI from '../../services/apis/ClustersAPI'; +import { stringToJSON } from '../../../common/utils'; export const downloadHostInstallationLogs = async ( addAlert: AlertsContextType['addAlert'], diff --git a/libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.ts b/libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.ts new file mode 100644 index 0000000000..8dfed046e7 --- /dev/null +++ b/libs/ui-lib/lib/ocm/services/apis/ManagedDomainsAPI.ts @@ -0,0 +1,14 @@ +import { client } from '../../api/axiosClient'; +import { ManagedDomain } from '../../../common'; + +const ManagedDomainsAPI = { + makeBaseURI() { + return `/v2/domains`; + }, + + list() { + return client.get(ManagedDomainsAPI.makeBaseURI()); + }, +}; + +export default ManagedDomainsAPI; diff --git a/libs/ui-lib/lib/ocm/services/apis/types.ts b/libs/ui-lib/lib/ocm/services/apis/types.ts index 394dcf34be..4cef0ad362 100644 --- a/libs/ui-lib/lib/ocm/services/apis/types.ts +++ b/libs/ui-lib/lib/ocm/services/apis/types.ts @@ -6,10 +6,3 @@ export type ClustersAPIGetPresignedOptions = { hostId?: string; logsType?: LogsType; }; - -export type EventsAPIListOptions = { - clusterId?: string; - hostId?: string; - infraEnvId?: string; - categories?: string[]; -};