diff --git a/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx b/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx new file mode 100644 index 00000000000..aba3e03edd3 --- /dev/null +++ b/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import Spy = jasmine.Spy; + +import { ConfigureUpdateStrategy, ConfigureUpdateStrategyProps } from '@console/internal/components/modals/configure-update-strategy-modal'; +import { RadioInput } from '@console/internal/components/radio'; + +describe(ConfigureUpdateStrategy.displayName, () => { + let wrapper: ShallowWrapper; + let onChangeStrategyType: Spy; + let onChangeMaxSurge: Spy; + let onChangeMaxUnavailable: Spy; + + beforeEach(() => { + onChangeStrategyType = jasmine.createSpy('onChangeStrategyType'); + onChangeMaxSurge = jasmine.createSpy('onChangeMaxSurge'); + onChangeMaxUnavailable = jasmine.createSpy('onChangeMaxUnavailable'); + + wrapper = shallow(); + }); + + it('renders two choices for different update strategy types', () => { + expect(wrapper.find(RadioInput).at(0).props().value).toEqual('RollingUpdate'); + expect(wrapper.find(RadioInput).at(1).props().value).toEqual('Recreate'); + expect(wrapper.find(RadioInput).at(1).props().checked).toBe(true); + }); + + it('is a controlled component', () => { + wrapper.find(RadioInput).at(0).dive().find('input[type="radio"]').simulate('change', {target: {value: 'RollingUpdate'}}); + wrapper.find('#input-max-unavailable').simulate('change', {target: {value: '25%'}}); + wrapper.find('#input-max-surge').simulate('change', {target: {value: '50%'}}); + + expect(onChangeStrategyType.calls.argsFor(0)[0]).toEqual('RollingUpdate'); + expect(onChangeMaxUnavailable.calls.argsFor(0)[0]).toEqual('25%'); + expect(onChangeMaxSurge.calls.argsFor(0)[0]).toEqual('50%'); + }); +}); diff --git a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/capacity-card/capacity-card.tsx b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/capacity-card/capacity-card.tsx index 0f26f835eeb..0841a2dd989 100644 --- a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/capacity-card/capacity-card.tsx +++ b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/capacity-card/capacity-card.tsx @@ -94,7 +94,7 @@ export const CapacityCard: React.FC = ({ const statUsed: React.ReactText = getLastStats(storageUsed, getInstantVectorStats); const statTotal: React.ReactText = getLastStats(storageTotal, getInstantVectorStats); const infoText = - 'Total capacity reflects the actual raw capacity of the OpenShift Container Storage cluster. Used capacity reflects provisioned capacity which factors capacity being used to store user data and overhead from ensuring redundancy and reliability of the data.'; + 'Capacity includes the capacity being used to store user data and overhead from ensuring redundancy and reliability of the data.'; return ( diff --git a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/details-card.tsx b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/details-card.tsx index 816873d90a5..567e1f5eb22 100644 --- a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/details-card.tsx +++ b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/details-card.tsx @@ -11,12 +11,13 @@ import { withDashboardResources, } from '@console/internal/components/dashboards-page/with-dashboard-resources'; import { DetailsBody } from '@console/internal/components/dashboard/details-card/details-body'; -import { FirehoseResource } from '@console/internal/components/utils/index'; +import { FirehoseResource, FirehoseResult } from '@console/internal/components/utils/index'; import { InfrastructureModel, SubscriptionModel } from '@console/internal/models/index'; import { K8sResourceKind } from '@console/internal/module/k8s/index'; import { getName } from '@console/shared/src/selectors/common'; import { referenceForModel } from '@console/internal/module/k8s/k8s'; import { CephClusterModel } from '../../../models'; +import { getOCSVersion } from '../../../selectors'; const infrastructureResource: FirehoseResource = { kind: referenceForModel(InfrastructureModel), @@ -37,8 +38,7 @@ const SubscriptionResource: FirehoseResource = { kind: referenceForModel(SubscriptionModel), namespaced: false, prop: 'subscription', - name: 'ocs-subscription', - isList: false, + isList: true, }; const DetailsCard: React.FC = ({ @@ -67,9 +67,9 @@ const DetailsCard: React.FC = ({ const cephClusterData = _.get(cephCluster, 'data') as K8sResourceKind[]; const cephClusterName = getName(_.get(cephClusterData, 0)); - const subscription = _.get(resources, 'subscription'); + const subscription = _.get(resources, 'subscription') as FirehoseResult; const subscriptionLoaded = _.get(subscription, 'loaded'); - const ocsVersion = _.get(subscription, 'data.status.currentCSV'); + const ocsVersion = getOCSVersion(subscription); return ( diff --git a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card-body.tsx b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card-body.tsx index 724be1f88c0..84b9eaa4381 100644 --- a/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card-body.tsx +++ b/frontend/packages/ceph-storage-plugin/src/components/dashboard-page/storage-dashboard/top-consumers-card/top-consumers-card-body.tsx @@ -61,7 +61,9 @@ export const TopConsumersBody: React.FC = React.memo( padding={{ top: 20, bottom: 20, left: 40, right: 20 }} containerComponent={ `${datum.y} ${maxCapacityConverted.unit}`} + labels={(datum) => + `${datum.y} ${maxCapacityConverted.unit} at ${twentyFourHourTime(datum.x)}` + } labelComponent={} /> } diff --git a/frontend/packages/ceph-storage-plugin/src/components/ocs-install/create-form.tsx b/frontend/packages/ceph-storage-plugin/src/components/ocs-install/create-form.tsx index cc2fd5c40ad..f319616bdbe 100644 --- a/frontend/packages/ceph-storage-plugin/src/components/ocs-install/create-form.tsx +++ b/frontend/packages/ceph-storage-plugin/src/components/ocs-install/create-form.tsx @@ -24,14 +24,17 @@ export const CreateOCSServiceForm: React.FC = (props)

- A minimum of 3 nodes needs to be labeled with{' '} - cluster.ocs.openshift.io/openshift-storage="" in order to create - the OCS Service. + Selected nodes will be labeled with + cluster.ocs.openshift.io/openshift-storage="" to create the OCS + Service. These nodes will also be tainted with + node.ocs.openshift.io/storage=true:NoSchedule to dedicate these nodes to + allow only OCS components to be scheduled on them. Note: Ensure you have additional + worker nodes that are not tainted to run other workloads in your OpenShift cluster.

diff --git a/frontend/packages/ceph-storage-plugin/src/constants/index.ts b/frontend/packages/ceph-storage-plugin/src/constants/index.ts index 2dda56f4022..6c95bec256f 100644 --- a/frontend/packages/ceph-storage-plugin/src/constants/index.ts +++ b/frontend/packages/ceph-storage-plugin/src/constants/index.ts @@ -11,3 +11,4 @@ export const STORAGE_CLASSES = 'Storage Classes'; export const PODS = 'Pods'; export const BY_USED = 'By Used Capacity'; export const BY_REQUESTED = 'By Requested Capacity'; +export const OCS_OPERATOR = 'ocs-operator'; diff --git a/frontend/packages/ceph-storage-plugin/src/selectors/index.ts b/frontend/packages/ceph-storage-plugin/src/selectors/index.ts index e24a1014bb4..da7a5864362 100644 --- a/frontend/packages/ceph-storage-plugin/src/selectors/index.ts +++ b/frontend/packages/ceph-storage-plugin/src/selectors/index.ts @@ -1,6 +1,8 @@ import * as _ from 'lodash'; import { Alert } from '@console/internal/components/monitoring'; import { K8sResourceKind } from '@console/internal/module/k8s'; +import { FirehoseResult } from '@console/internal/components/utils'; +import { OCS_OPERATOR } from '../constants'; const cephStorageProvisioners = ['ceph.rook.io/block', 'cephfs.csi.ceph.com', 'rbd.csi.ceph.com']; const cephStorageLabel = 'cluster.ocs.openshift.io/openshift-storage'; @@ -32,3 +34,12 @@ export const getCephSC = (scData: K8sResourceKind[]): K8sResourceKind[] => _.get(sc, 'provisioner', '').includes(provisioner), ); }); + +export const getOCSVersion = (items: FirehoseResult): string => { + const itemsData: K8sResourceKind[] = _.get(items, 'data'); + const operator: K8sResourceKind = _.find( + itemsData, + (item) => _.get(item, 'spec.name') === OCS_OPERATOR, + ); + return _.get(operator, 'status.currentCSV'); +}; diff --git a/frontend/packages/dev-console/src/components/pipelines/PipelineDetails.tsx b/frontend/packages/dev-console/src/components/pipelines/PipelineDetails.tsx index 9efb55fb537..4ced553d091 100644 --- a/frontend/packages/dev-console/src/components/pipelines/PipelineDetails.tsx +++ b/frontend/packages/dev-console/src/components/pipelines/PipelineDetails.tsx @@ -1,10 +1,14 @@ import * as React from 'react'; import { SectionHeading, ResourceSummary, ResourceLink } from '@console/internal/components/utils'; -import { referenceForModel } from '@console/internal/module/k8s'; +import { referenceForModel, K8sResourceKind } from '@console/internal/module/k8s'; import { TaskModel } from '../../models'; import { PipelineVisualization } from './PipelineVisualization'; -const PipelineDetails = ({ obj: pipeline }) => ( +interface PipelineDetailsProps { + obj: K8sResourceKind; +} + +const PipelineDetails: React.FC = ({ obj: pipeline }) => (

@@ -12,28 +16,31 @@ const PipelineDetails = ({ obj: pipeline }) => (
-
- -
- {pipeline.spec.tasks.map((task) => { - return ( - -
Name: {task.name}
-
- Ref:{' '} - -
-
- ); - })} -
-
+ {pipeline.spec && + (pipeline.spec.tasks && ( +
+ +
+ {pipeline.spec.tasks.map((task) => { + return ( + +
Name: {task.name}
+
+ Ref:{' '} + +
+
+ ); + })} +
+
+ ))}
); diff --git a/frontend/packages/kubevirt-plugin/package.json b/frontend/packages/kubevirt-plugin/package.json index da011fe7717..5f835a3a508 100644 --- a/frontend/packages/kubevirt-plugin/package.json +++ b/frontend/packages/kubevirt-plugin/package.json @@ -10,7 +10,7 @@ "@console/internal": "0.0.0-fixed", "@console/plugin-sdk": "0.0.0-fixed", "@console/shared": "0.0.0-fixed", - "kubevirt-web-ui-components": "~0.1.42" + "kubevirt-web-ui-components": "~0.1.43" }, "consolePlugin": { "entry": "src/plugin.tsx" diff --git a/frontend/packages/noobaa-storage-plugin/src/components/details-card/details-card.tsx b/frontend/packages/noobaa-storage-plugin/src/components/details-card/details-card.tsx index 65bed644be3..9c3784da187 100644 --- a/frontend/packages/noobaa-storage-plugin/src/components/details-card/details-card.tsx +++ b/frontend/packages/noobaa-storage-plugin/src/components/details-card/details-card.tsx @@ -12,9 +12,10 @@ import { DashboardItemProps, withDashboardResources, } from '@console/internal/components/dashboards-page/with-dashboard-resources'; -import { FirehoseResource, ExternalLink } from '@console/internal/components/utils'; +import { FirehoseResource, ExternalLink, FirehoseResult } from '@console/internal/components/utils'; import { InfrastructureModel, SubscriptionModel } from '@console/internal/models/index'; import { referenceForModel, K8sResourceKind } from '@console/internal/module/k8s'; +import { getOCSVersion } from '@console/ceph-storage-plugin/src/selectors'; import { getMetric } from '../../utils'; const NOOBAA_SYSTEM_NAME_QUERY = 'NooBaa_system_info'; @@ -31,8 +32,7 @@ const SubscriptionResource: FirehoseResource = { kind: referenceForModel(SubscriptionModel), namespaced: false, prop: 'subscription', - name: 'ocs-subscription', - isList: false, + isList: true, }; export const ObjectServiceDetailsCard: React.FC = ({ @@ -66,9 +66,9 @@ export const ObjectServiceDetailsCard: React.FC = ({ const infrastructureData = _.get(infrastructure, 'data') as K8sResourceKind; const infrastructurePlatform = getInfrastructurePlatform(infrastructureData); - const subscription = _.get(resources, 'subscription'); + const subscription = _.get(resources, 'subscription') as FirehoseResult; const subscriptionLoaded = _.get(subscription, 'loaded'); - const ocsVersion = _.get(subscription, 'data.status.currentCSV'); + const ocsVersion = getOCSVersion(subscription); return ( diff --git a/frontend/packages/noobaa-storage-plugin/src/components/health-card/health-card.tsx b/frontend/packages/noobaa-storage-plugin/src/components/health-card/health-card.tsx index 66c1fc08d97..c22f7108746 100644 --- a/frontend/packages/noobaa-storage-plugin/src/components/health-card/health-card.tsx +++ b/frontend/packages/noobaa-storage-plugin/src/components/health-card/health-card.tsx @@ -26,8 +26,7 @@ import { NooBaaSystemModel } from '../../models'; const noobaaSystemResource: FirehoseResource = { kind: referenceForModel(NooBaaSystemModel), - namespaced: false, - isList: false, + isList: true, prop: 'noobaa', }; @@ -36,11 +35,26 @@ const getObjectStorageHealthState = ( unhealthyBucketsResponse, poolsResponse, unhealthyPoolResponse, - noobaaSystemData, + noobaaSystem, ): ObjectStorageHealth => { + const loadError = _.get(noobaaSystem, 'loadError'); + const loaded = _.get(noobaaSystem, 'loaded'); + const noobaaSystemData = _.get(noobaaSystem, 'data[0]', null) as K8sResourceKind; + const noobaaPhase = _.get(noobaaSystemData, 'status.phase'); + + const buckets = getGaugeValue(bucketsResponse); + const unhealthyBuckets = getGaugeValue(unhealthyBucketsResponse); + const pools = getGaugeValue(poolsResponse); + const unhealthyPools = getGaugeValue(unhealthyPoolResponse); + + const result: ObjectStorageHealth = { + message: 'Object Storage is healthy', + state: HealthState.OK, + }; + if ( !( - noobaaSystemData && + (loaded || loadError) && bucketsResponse && unhealthyBucketsResponse && poolsResponse && @@ -49,17 +63,10 @@ const getObjectStorageHealthState = ( ) { return { state: HealthState.LOADING }; } - const buckets = getGaugeValue(bucketsResponse); - const unhealthyBuckets = getGaugeValue(unhealthyBucketsResponse); - const pools = getGaugeValue(poolsResponse); - const unhealthyPools = getGaugeValue(unhealthyPoolResponse); - const result: ObjectStorageHealth = { - message: 'Object Storage is healthy', - state: HealthState.OK, - }; - - let value; - if (_.isEmpty(noobaaSystemData)) { + if (loadError || !(buckets && unhealthyBuckets && pools && unhealthyPools)) { + return { message: null }; + } + if (!noobaaSystemData || noobaaPhase !== 'Ready') { result.message = 'Multi cloud gateway is not running'; result.state = HealthState.ERROR; return result; @@ -72,7 +79,7 @@ const getObjectStorageHealthState = ( } } if (!_.isNil(buckets) && !_.isNil(unhealthyBuckets)) { - value = Number(unhealthyBuckets) / Number(buckets); + const value = Number(unhealthyBuckets) / Number(buckets); if (value >= 0.5) { result.message = 'Many buckets have issues'; result.state = HealthState.ERROR; @@ -126,14 +133,14 @@ const HealthCard: React.FC = ({ 'result', ]); - const noobaaSystemData = _.get(resources.noobaa, 'data', null) as K8sResourceKind; + const noobaaSystem = _.get(resources, 'noobaa'); const objectServiceHealthState = getObjectStorageHealthState( bucketsQueryResult, unhealthyBucketsQueryResult, poolsQueryResult, unhealthyPoolsQueryResult, - noobaaSystemData, + noobaaSystem, ); const alerts = filterNooBaaAlerts(getAlerts(alertsResults)); @@ -144,10 +151,14 @@ const HealthCard: React.FC = ({ - + {objectServiceHealthState.message ? ( + + ) : ( + Unavailable + )} {alerts.length > 0 && ( @@ -171,6 +182,6 @@ const HealthCard: React.FC = ({ export default withDashboardResources(HealthCard); type ObjectStorageHealth = { - state: HealthState; + state?: HealthState; message?: string; }; diff --git a/frontend/public/components/about-modal.tsx b/frontend/public/components/about-modal.tsx index 3cab8390315..6bed9a9f07a 100644 --- a/frontend/public/components/about-modal.tsx +++ b/frontend/public/components/about-modal.tsx @@ -6,11 +6,11 @@ import { Link } from 'react-router-dom'; import { FLAGS } from '../const'; import { connectToFlags } from '../reducers/features'; import { getBrandingDetails } from './masthead'; -import { Firehose } from './utils'; +import { ExternalLink, Firehose } from './utils'; import { ClusterVersionModel } from '../models'; import { ClusterVersionKind, referenceForModel } from '../module/k8s'; import { k8sVersion } from '../module/status'; -import { hasAvailableUpdates, getK8sGitVersion, getOpenShiftVersion, getClusterID } from '../module/k8s/cluster-settings'; +import { hasAvailableUpdates, getK8sGitVersion, getOpenShiftVersion, getClusterID, getErrataLink } from '../module/k8s/cluster-settings'; const AboutModalItems: React.FC = ({closeAboutModal, cv}) => { const [kubernetesVersion, setKubernetesVersion] = React.useState(''); @@ -24,6 +24,7 @@ const AboutModalItems: React.FC = ({closeAboutModal, cv}) const clusterID = getClusterID(clusterVersion); const channel: string = _.get(cv, 'data.spec.channel'); const openshiftVersion = getOpenShiftVersion(clusterVersion); + const errataLink = getErrataLink(clusterVersion); return ( {clusterVersion && hasAvailableUpdates(clusterVersion) && Update available. View Cluster Settings} />} @@ -32,7 +33,10 @@ const AboutModalItems: React.FC = ({closeAboutModal, cv}) {openshiftVersion && ( OpenShift Version - {openshiftVersion} + +
{openshiftVersion}
+ {errataLink &&
} +
)} Kubernetes Version diff --git a/frontend/public/components/cluster-settings/cluster-settings.tsx b/frontend/public/components/cluster-settings/cluster-settings.tsx index ee3073862e4..89573e60959 100644 --- a/frontend/public/components/cluster-settings/cluster-settings.tsx +++ b/frontend/public/components/cluster-settings/cluster-settings.tsx @@ -19,15 +19,16 @@ import { ClusterVersionKind, clusterVersionReference, getAvailableClusterUpdates, + getClusterID, getClusterUpdateStatus, getClusterVersionCondition, getDesiredClusterVersion, + getErrataLink, getLastCompletedUpdate, k8sPatch, K8sResourceConditionStatus, K8sResourceKind, referenceForModel, - getClusterID, } from '../../module/k8s'; import { EmptyBox, @@ -155,6 +156,7 @@ export const CurrentVersionHeader: React.SFC = ({cv}) => { export const ClusterVersionDetailsTable: React.SFC = ({obj: cv, autoscalers}) => { const { history = [] } = cv.status; const clusterID = getClusterID(cv); + const errataLink = getErrataLink(cv); const desiredImage: string = _.get(cv, 'status.desired.image') || ''; // Split image on `@` to emphasize the digest. const imageParts = desiredImage.split('@'); @@ -222,7 +224,9 @@ export const ClusterVersionDetailsTable: React.SFC
- + + {errataLink && } + {_.isEmpty(history) ? :
diff --git a/frontend/public/components/modals/configure-update-strategy-modal.tsx b/frontend/public/components/modals/configure-update-strategy-modal.tsx index 3c7148cff65..9ed43bee8ad 100644 --- a/frontend/public/components/modals/configure-update-strategy-modal.tsx +++ b/frontend/public/components/modals/configure-update-strategy-modal.tsx @@ -53,7 +53,8 @@ export const ConfigureUpdateStrategy: React.FC = ( props.onChangeMaxUnavailable(e.target.value)} aria-describedby="input-max-unavailable-help" /> { props.replicas && of { pluralize(props.replicas, 'pod')} @@ -77,7 +78,8 @@ export const ConfigureUpdateStrategy: React.FC = ( type="text" className="pf-c-form-control" id="input-max-surge" - defaultValue={props.maxSurge as string} + value={props.maxSurge} + onChange={e => props.onChangeMaxSurge(e.target.value)} aria-describedby="input-max-surge-help" /> greater than { pluralize(props.replicas, 'pod')} diff --git a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx index 42b2298f274..51f5bea3400 100644 --- a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx +++ b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx @@ -228,7 +228,7 @@ export const ClusterServiceVersionList: React.SFC = (props) => { const title = 'Installed Operators'; const helpText =

- Installed Operators are represented by Cluster Service Versions within this namespace. For more information, see the . Or create an Operator and Cluster Service Version using the . + Installed Operators are represented by Cluster Service Versions within this namespace. For more information, see the . Or create an Operator and Cluster Service Version using the .

; return diff --git a/frontend/public/components/operator-lifecycle-manager/create-operand.tsx b/frontend/public/components/operator-lifecycle-manager/create-operand.tsx index 7b8c5af80e5..4b8a220fd44 100644 --- a/frontend/public/components/operator-lifecycle-manager/create-operand.tsx +++ b/frontend/public/components/operator-lifecycle-manager/create-operand.tsx @@ -161,7 +161,7 @@ export const CreateOperandForm: React.FC = (props) => { return {...field, capabilities, type, required, validation}; }) - .concat(fieldsForOpenAPI(props.openAPI).filter(crdField => !props.providedAPI.specDescriptors.some(d => d.path === crdField.path))) + .concat(fieldsForOpenAPI(props.openAPI).filter(crdField => props.providedAPI.specDescriptors && !props.providedAPI.specDescriptors.some(d => d.path === crdField.path))) // Associate `specDescriptors` with `fieldGroups` from OpenAPI .map((field, i, allFields) => allFields.some(f => f.capabilities.includes(SpecCapability.fieldGroup.concat(field.path.split('.')[0]) as SpecCapability.fieldGroup)) ? {...field, capabilities: [...new Set(field.capabilities).add(SpecCapability.fieldGroup.concat(field.path.split('.')[0]) as SpecCapability.fieldGroup)]} diff --git a/frontend/public/components/operator-lifecycle-manager/descriptors/spec/index.tsx b/frontend/public/components/operator-lifecycle-manager/descriptors/spec/index.tsx index afed5153b27..fd458ef55dc 100644 --- a/frontend/public/components/operator-lifecycle-manager/descriptors/spec/index.tsx +++ b/frontend/public/components/operator-lifecycle-manager/descriptors/spec/index.tsx @@ -87,6 +87,8 @@ const Secret: React.FC = (props) => { ; }; +const UpdateStrategy: React.FC = (props) =>
{_.get(props.value, 'type', 'None')}
; + const capabilityComponents = ImmutableMap>() .set(SpecCapability.podCount, PodCount) .set(SpecCapability.endpointList, Endpoints) @@ -96,7 +98,8 @@ const capabilityComponents = ImmutableMap { if (_.isEmpty(specCapability)) { diff --git a/frontend/public/module/k8s/cluster-settings.ts b/frontend/public/module/k8s/cluster-settings.ts index 86e7430cafa..cada3b31ba1 100644 --- a/frontend/public/module/k8s/cluster-settings.ts +++ b/frontend/public/module/k8s/cluster-settings.ts @@ -1,4 +1,5 @@ import * as _ from 'lodash-es'; +import * as semver from 'semver'; import { ClusterVersionModel } from '../../models'; import { referenceForModel } from './k8s'; @@ -96,6 +97,23 @@ export const getOpenShiftVersion = (cv: ClusterVersionKind): string => { return lastUpdate.state === 'Partial' ? `Updating to ${lastUpdate.version}` : lastUpdate.version; }; +// example link: https://access.redhat.com/downloads/content/290/ver=4.1/rhel---7/4.1.13/x86_64/product-errata +export const getErrataLink = (cv: ClusterVersionKind): string => { + const version: string = _.get(cv, 'status.history[0].version'); + if (!version) { + return null; + } + + const parsed = semver.parse(version); + if (!parsed) { + return null; + } + + // TODO: Determine architecture instead of assuming x86_64. + const { major, minor, patch } = parsed; + return `https://access.redhat.com/downloads/content/290/ver=${major}.${minor}/rhel---7/${major}.${minor}.${patch}/x86_64/product-errata`; +}; + export const getClusterName = (): string => window.SERVER_FLAGS.kubeAPIServerURL || null; export const getClusterID = (cv: ClusterVersionKind): string => _.get(cv, 'spec.clusterID'); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 30bd918896d..307d1cd52c5 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7965,10 +7965,10 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" -kubevirt-web-ui-components@~0.1.42: - version "0.1.42" - resolved "https://registry.yarnpkg.com/kubevirt-web-ui-components/-/kubevirt-web-ui-components-0.1.42.tgz#dc9820424208fe9c345eb95b5330edebcba7bf8f" - integrity sha512-zBZMnKm7j9HDjMeaTXXVugGliMvLzYRlt9hN2osmHchosUkf87HOBxn/4hSL6QWsAO7sOHTU7RzevnQTmXycDw== +kubevirt-web-ui-components@~0.1.43: + version "0.1.43" + resolved "https://registry.yarnpkg.com/kubevirt-web-ui-components/-/kubevirt-web-ui-components-0.1.43.tgz#c876da737718f7b8c5e47ba948905764e837df08" + integrity sha512-98E1fWaSH+OiV7kJ62RLzv/FehwiKJtPKENspSkEwyPgqguwQkl80s34IoPfIKtmcOCDH4nnTMESBLm9g/0/Ig== dependencies: "@patternfly/react-console" "1.x" babel-plugin-transform-es2015-modules-umd "6.24.1"