Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 76 additions & 71 deletions frontend/public/components/cluster-settings/cluster-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO (jon) - Remove mock code from this file once cluster update feature is complete

/* eslint-disable no-unused-vars, no-undef */

import * as React from 'react';
Expand All @@ -8,11 +6,26 @@ import { Helmet } from 'react-helmet';
import { Button } from 'patternfly-react';
import { Link } from 'react-router-dom';

import { ClusterVersionKind, K8sResourceKind, referenceForModel } from '../../module/k8s';
import { ClusterAutoscalerModel, ClusterVersionModel } from '../../models';
import { ClusterOperatorPage } from './cluster-operator';
import { clusterChannelModal, clusterUpdateModal } from '../modals';
import { GlobalConfigPage } from './global-config';
import { ClusterAutoscalerModel } from '../../models';
import {
ClusterUpdateStatus,
ClusterVersionCondition,
ClusterVersionConditionType,
ClusterVersionKind,
clusterVersionReference,
getAvailableClusterUpdates,
getClusterUpdateStatus,
getClusterVersionCondition,
getDesiredClusterVersion,
isProgressing,
K8sResourceConditionStatus,
K8sResourceKind,
referenceForModel,
updateFailing,
} from '../../module/k8s';
import {
EmptyBox,
Firehose,
Expand All @@ -23,56 +36,12 @@ import {
Timestamp,
} from '../utils';

enum ClusterUpdateStatus {
UpToDate = 'Up to Date',
UpdatesAvailable = 'Updates Available',
Updating = 'Updating',
Failing = 'Failing',
ErrorRetrieving = 'Error Retrieving',
}

const clusterAutoscalerReference = referenceForModel(ClusterAutoscalerModel);
export const clusterVersionReference = referenceForModel(ClusterVersionModel);

export const getAvailableClusterChannels = () => ({'nightly-4.0': 'nightly-4.0', 'pre-release-4.0': 'pre-release-4.0', 'stable-4.0': 'stable-4.0'});

export const getAvailableClusterUpdates = (cv: ClusterVersionKind) => {
return _.get(cv, 'status.availableUpdates');
};

export const getDesiredClusterVersion = (cv: ClusterVersionKind) => {
return _.get(cv, 'status.desired.version');
};

const launchUpdateModal = (cv: ClusterVersionKind) => {
clusterUpdateModal({cv});
};

const CurrentChannel: React.SFC<CurrentChannelProps> = ({cv}) => <button className="btn btn-link co-m-modal-link" onClick={() => (clusterChannelModal({cv}))}>
{cv.spec.channel || '-'}
</button>;

const getClusterUpdateStatus = (cv: ClusterVersionKind): ClusterUpdateStatus => {
const conditions = _.get(cv, 'status.conditions', []);
const isFailingCondition = _.find(conditions, { type: 'Failing', status: 'True' });
if (isFailingCondition) {
return ClusterUpdateStatus.Failing;
}

const retrievedUpdatesFailedCondition = _.find(conditions, { type: 'RetrievedUpdates', status: 'False' });
if (retrievedUpdatesFailedCondition) {
return ClusterUpdateStatus.ErrorRetrieving;
}

const isProgressingCondition = _.find(conditions, { type: 'Progressing', status: 'True' });
if (isProgressingCondition) {
return ClusterUpdateStatus.Updating;
}

const updates = _.get(cv, 'status.availableUpdates');
return _.isEmpty(updates) ? ClusterUpdateStatus.UpToDate : ClusterUpdateStatus.UpdatesAvailable;
};

const getIconClass = (status: ClusterUpdateStatus) => {
return {
[ClusterUpdateStatus.UpToDate]: 'pficon pficon-ok',
Expand All @@ -83,34 +52,52 @@ const getIconClass = (status: ClusterUpdateStatus) => {
}[status];
};

const FailedConditionAlert = ({message, condition}) => <div className="alert alert-danger">
<i className="pficon pficon-error-circle-o" aria-hidden="true" /> <strong>{message}</strong> {condition.message}
const UpdateFailingAlert: React.SFC<UpdateFailingAlertProps> = ({condition, updatesAvailable=false}) => <div className="alert alert-danger">
<i className="pficon pficon-error-circle-o" aria-hidden="true" />
<strong>Update is failing.</strong>
&nbsp;
{condition.message && `${condition.message}. `}
View <Link to="/settings/cluster/clusteroperators?orderBy=desc&sortBy=Status">Cluster Operators</Link> for more details
{updatesAvailable && ' or check other available updates to try another version'}.
</div>;

const RetrieveUpdatesFailedAlert: React.SFC<RetrieveUpdatesFailedAlertProps> = ({condition}) => <div className="alert alert-danger">
<i className="pficon pficon-error-circle-o" aria-hidden="true" />
<strong>Could not retrieve updates.</strong>
{condition.message && ` ${condition.message}.`}
</div>;

const UpdateInProgressAlert = () => <div className="alert alert-info">
const UpdateInProgressAlert: React.SFC<UpdateInProgressAlertProps> = ({condition}) => <div className="alert alert-info">
<i className="pficon pficon-info" aria-hidden={true} />
Cluster update in progress.
<strong>Cluster update in progress.</strong>
&nbsp;
<Link to="/settings/cluster/clusteroperators">
View detailed progress.
</Link>
{condition.message && `${condition.message}. `}
View <Link to="/settings/cluster/clusteroperators?orderBy=desc&sortBy=Status">Cluster Operators</Link> for more details.
</div>;

const UpdatesAvailableAlert = ({cv}) => <div className="alert alert-info">
<i className="pficon pficon-info" aria-hidden={true} />
Cluster update is available.
<Button bsStyle="link" className="co-m-modal-link" onClick={()=> (launchUpdateModal(cv))}>
Update Now
</Button>
</div>;
const UpdatesAvailableAlert: React.SFC<UpdatesAvailableAlertProps> = ({cv}) => {
const currentlyUpdating = isProgressing(cv) || updateFailing(cv);
const titleText = currentlyUpdating ? 'Other updates are available.' : 'Cluster update is available.';
const buttonText = currentlyUpdating ? 'Update to a different version.' : 'Update now.';
return <div className="alert alert-info">
<i className="pficon pficon-info" aria-hidden={true} />
<strong>
{titleText}
</strong>
<Button bsStyle="link" className="co-m-modal-link" onClick={()=> (clusterUpdateModal({cv}))}>
{buttonText}
</Button>
</div>;
};


const UpdateStatus: React.SFC<UpdateStatusProps> = ({cv}) => {
const status = getClusterUpdateStatus(cv);
const iconClass = getIconClass(status);
return <React.Fragment>
{
status === ClusterUpdateStatus.UpdatesAvailable
? <Button bsStyle="link" className="co-m-modal-link" onClick={() => (launchUpdateModal(cv))}>
? <Button bsStyle="link" className="co-m-modal-link" onClick={() => (clusterUpdateModal({cv}))}>
<i className={iconClass} aria-hidden={true}></i>
&nbsp;
{status}
Expand All @@ -132,18 +119,19 @@ const DesiredVersion: React.SFC<DesiredVersionProps> = ({cv}) => {
};

const ClusterVersionDetailsTable: React.SFC<ClusterVersionDetailsTableProps> = ({obj: cv, autoscalers}) => {
const { history = [], conditions = [] } = cv.status;
const status = getClusterUpdateStatus(cv);
const retrievedUpdatesFailedCondition = _.find(conditions, { type: 'RetrievedUpdates', status: 'False' });
const isFailingCondition = _.find(conditions, { type: 'Failing', status: 'True' });
const { history = [] } = cv.status;
const retrievedUpdatesFailedCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.RetrievedUpdates, K8sResourceConditionStatus.False);
const isFailingCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.Failing, K8sResourceConditionStatus.True);
const updatingCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.Progressing, K8sResourceConditionStatus.True);
const updatesAvailable = !_.isEmpty(getAvailableClusterUpdates(cv));

return <React.Fragment>
<div className="co-m-pane__body">
<div className="co-m-pane__body-group">
{ status === ClusterUpdateStatus.Updating && <UpdateInProgressAlert /> }
{ status === ClusterUpdateStatus.UpdatesAvailable && <UpdatesAvailableAlert cv={cv} /> }
{ isFailingCondition && <FailedConditionAlert message="Update is failing." condition={isFailingCondition} /> }
{ retrievedUpdatesFailedCondition && <FailedConditionAlert message="Could not retrieve updates." condition={retrievedUpdatesFailedCondition} /> }
{ isFailingCondition && <UpdateFailingAlert condition={isFailingCondition} updatesAvailable={updatesAvailable} /> }
{ retrievedUpdatesFailedCondition && <RetrieveUpdatesFailedAlert condition={retrievedUpdatesFailedCondition} /> }
{ !isFailingCondition && updatingCondition && <UpdateInProgressAlert condition={updatingCondition} /> }
{ updatesAvailable && <UpdatesAvailableAlert cv={cv} /> }
<div className="co-detail-table">
<div className="co-detail-table__row row">
<div className="co-detail-table__section">
Expand Down Expand Up @@ -269,3 +257,20 @@ type ClusterVersionDetailsTableProps = {
type ClusterSettingsPageProps = {
match: any;
};

type UpdateFailingAlertProps = {
condition: ClusterVersionCondition;
updatesAvailable: boolean;
};

type RetrieveUpdatesFailedAlertProps = {
condition: ClusterVersionCondition;
};

type UpdateInProgressAlertProps = {
condition: ClusterVersionCondition;
};

type UpdatesAvailableAlertProps = {
cv: ClusterVersionKind;
};
2 changes: 1 addition & 1 deletion frontend/public/components/masthead-toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { authSvc } from '../module/auth';
import { history, Firehose } from './utils';
import { openshiftHelpBase } from './utils/documentation';
import { AboutModal } from './about-modal';
import { getAvailableClusterUpdates, clusterVersionReference } from './cluster-settings/cluster-settings';
import { getAvailableClusterUpdates, clusterVersionReference } from '../module/k8s/cluster-settings';

const UpdatesAvailableButton = ({obj, onClick}) => {
const updatesAvailable = !_.isEmpty(getAvailableClusterUpdates(obj.data));
Expand Down
18 changes: 14 additions & 4 deletions frontend/public/components/modals/cluster-channel-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@

import * as React from 'react';

import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, ModalComponentProps } from '../factory/modal';
import { Dropdown, PromiseComponent } from '../utils';
import { k8sPatch, K8sResourceKind } from '../../module/k8s';
import { ClusterVersionModel } from '../../models';
import { getAvailableClusterChannels } from '../cluster-settings/cluster-settings';
import { Dropdown, PromiseComponent } from '../utils';
import {
createModalLauncher,
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
} from '../factory/modal';
import {
getAvailableClusterChannels,
k8sPatch,
K8sResourceKind,
} from '../../module/k8s';

class ClusterChannelModal extends PromiseComponent {
readonly state: ClusterChannelModalState;
Expand Down Expand Up @@ -48,6 +57,7 @@ class ClusterChannelModal extends PromiseComponent {
items={availableChannels}
onChange={this._change}
selectedKey={cv.spec.channel}
title="Select Channel"
/>
</div>
</ModalBody>
Expand Down
25 changes: 17 additions & 8 deletions frontend/public/components/modals/cluster-update-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
import * as _ from 'lodash-es';
import * as React from 'react';

import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, ModalComponentProps } from '../factory/modal';
import { Dropdown, PromiseComponent, ExternalLink } from '../utils';
import { ClusterVersionKind, k8sPatch } from '../../module/k8s';
import { ClusterVersionModel } from '../../models';
import { getAvailableClusterUpdates, getDesiredClusterVersion } from '../cluster-settings/cluster-settings';
import { Dropdown, PromiseComponent } from '../utils';
import {
ClusterVersionKind,
getAvailableClusterUpdates,
getDesiredClusterVersion,
k8sPatch,
} from '../../module/k8s';
import {
createModalLauncher,
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
} from '../factory/modal';

class ClusterUpdateModal extends PromiseComponent {
readonly state: ClusterUpdateModalState;
Expand Down Expand Up @@ -42,10 +52,9 @@ class ClusterUpdateModal extends PromiseComponent {
return <form onSubmit={this._submit} name="form" className="modal-content">
<ModalTitle>Update Cluster</ModalTitle>
<ModalBody>
<p>
For more detailed documentation on specific versions, &nbsp;
<ExternalLink href="https://github.com/openshift/origin/releases" text="view release notes." />
</p>
{/* <p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we need to resolve this TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not for beta2. The link that we had here was just not useful, so I removed it until we have something real to put there.

// TODO: Determine what content goes here.
</p> */}
<div className="form-group">
<label>Current Version</label>
<p>{currentVersion}</p>
Expand Down
66 changes: 66 additions & 0 deletions frontend/public/module/k8s/cluster-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable no-unused-vars, no-undef */
import * as _ from 'lodash-es';

import { ClusterVersionModel } from '../../models';
import { referenceForModel } from './k8s';
import { ClusterVersionKind, ClusterUpdate, ClusterVersionConditionType, K8sResourceConditionStatus, ClusterVersionCondition } from '.';

export enum ClusterUpdateStatus {
UpToDate = 'Up to Date',
UpdatesAvailable = 'Updates Available',
Updating = 'Updating',
Failing = 'Failing',
ErrorRetrieving = 'Error Retrieving',
}

export const clusterVersionReference = referenceForModel(ClusterVersionModel);

export const getAvailableClusterUpdates = (cv: ClusterVersionKind): ClusterUpdate[] => {
return _.get(cv, 'status.availableUpdates', []);
};

export const getAvailableClusterChannels = () => ({'nightly-4.0': 'nightly-4.0', 'pre-release-4.0': 'pre-release-4.0', 'stable-4.0': 'stable-4.0'});

export const getDesiredClusterVersion = (cv: ClusterVersionKind): string => {
return _.get(cv, 'status.desired.version');
};

export const getClusterVersionCondition = (cv: ClusterVersionKind, type: ClusterVersionConditionType, status: K8sResourceConditionStatus = undefined): ClusterVersionCondition => {
const conditions: ClusterVersionCondition[] = _.get(cv, 'status.conditions');
if (status) {
return _.find(conditions, {type, status});
}
return _.find(conditions, {type});
};

export const isProgressing = (cv: ClusterVersionKind): boolean => {
return !_.isEmpty(getClusterVersionCondition(cv, ClusterVersionConditionType.Progressing, K8sResourceConditionStatus.True));
};

export const failedToRetrieveUpdates = (cv: ClusterVersionKind): boolean => {
return !_.isEmpty(getClusterVersionCondition(cv, ClusterVersionConditionType.RetrievedUpdates, K8sResourceConditionStatus.False));
};

export const updateFailing = (cv: ClusterVersionKind): boolean => {
return !_.isEmpty(getClusterVersionCondition(cv, ClusterVersionConditionType.Failing, K8sResourceConditionStatus.True));
};

export const hasAvailableUpdates = (cv: ClusterVersionKind): boolean => {
return !_.isEmpty(getAvailableClusterUpdates(cv));
};

export const getClusterUpdateStatus = (cv: ClusterVersionKind): ClusterUpdateStatus => {
if (updateFailing(cv)) {
return ClusterUpdateStatus.Failing;
}

if (failedToRetrieveUpdates(cv)) {
return ClusterUpdateStatus.ErrorRetrieving;
}

if (isProgressing(cv)) {
return ClusterUpdateStatus.Updating;
}

return hasAvailableUpdates(cv) ? ClusterUpdateStatus.UpdatesAvailable : ClusterUpdateStatus.UpToDate;
};
Loading