diff --git a/README.md b/README.md index 34c007b7b2a2..4086fbc73fea 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ If the CA bundle of the OpenShift API server is unavailable, fetch the CA certif ``` oc get secrets -n default --field-selector type=kubernetes.io/service-account-token -o json | \ - jq '.items[0].data."service-ca.crt"' -r | python -m base64 -d > examples/ca.crt + jq '.items[0].data."ca.crt"' -r | python -m base64 -d > examples/ca.crt # Note: use "openssl base64" because the "base64" tool is different between mac and linux ``` diff --git a/auth/auth.go b/auth/auth.go index eceea80515b5..357b00bd327b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -70,6 +70,9 @@ type loginMethod interface { logout(http.ResponseWriter, *http.Request) // authenticate fetches the bearer token from the cookie of a request. authenticate(*http.Request) (*User, error) + // getKubeAdminLogoutURL returns the logout URL for the special + // kube:admin user in OpenShift + getKubeAdminLogoutURL() string } // AuthSource allows callers to switch between Tectonic and OpenShift login support. @@ -274,6 +277,11 @@ func (a *Authenticator) LogoutFunc(w http.ResponseWriter, r *http.Request) { a.loginMethod.logout(w, r) } +// GetKubeAdminLogoutURL returns the logout URL for the special kube:admin user in OpenShift +func (a *Authenticator) GetKubeAdminLogoutURL() string { + return a.loginMethod.getKubeAdminLogoutURL() +} + // ExchangeAuthCode allows callers to return a raw token response given a OAuth2 // code. This is useful for clients which need to request refresh tokens. func (a *Authenticator) ExchangeAuthCode(code string) (idToken, refreshToken string, err error) { diff --git a/auth/auth_oidc.go b/auth/auth_oidc.go index 4b5d32e51d12..ad0bb5d479f6 100644 --- a/auth/auth_oidc.go +++ b/auth/auth_oidc.go @@ -131,3 +131,7 @@ func (o *oidcAuth) authenticate(r *http.Request) (*User, error) { Token: ls.rawToken, }, nil } + +func (o *oidcAuth) getKubeAdminLogoutURL() string { + return "" +} diff --git a/auth/auth_openshift.go b/auth/auth_openshift.go index 8fe770d10aa2..57bb0e00a3bc 100644 --- a/auth/auth_openshift.go +++ b/auth/auth_openshift.go @@ -10,13 +10,16 @@ import ( "time" "golang.org/x/oauth2" + + "github.com/openshift/console/pkg/proxy" ) // openShiftAuth implements OpenShift Authentication as defined in: // https://docs.openshift.com/container-platform/3.9/architecture/additional_concepts/authentication.html type openShiftAuth struct { - cookiePath string - secureCookies bool + cookiePath string + secureCookies bool + kubeAdminLogoutURL string } type openShiftConfig struct { @@ -83,10 +86,11 @@ func newOpenShiftAuth(ctx context.Context, c *openShiftConfig) (oauth2.Endpoint, return oauth2.Endpoint{}, nil, err } + kubeAdminLogoutURL := proxy.SingleJoiningSlash(metadata.Issuer, "/logout") return oauth2.Endpoint{ AuthURL: metadata.Auth, TokenURL: metadata.Token, - }, &openShiftAuth{c.cookiePath, c.secureCookies}, nil + }, &openShiftAuth{c.cookiePath, c.secureCookies, kubeAdminLogoutURL}, nil } @@ -157,3 +161,7 @@ func (o *openShiftAuth) authenticate(r *http.Request) (*User, error) { Token: cookie.Value, }, nil } + +func (o *openShiftAuth) getKubeAdminLogoutURL() string { + return o.kubeAdminLogoutURL +} diff --git a/frontend/__tests__/components/operator-lifecycle-manager/create-crd-yaml.spec.tsx b/frontend/__tests__/components/operator-lifecycle-manager/create-crd-yaml.spec.tsx index 55d60f38aab8..e7ca08dc8693 100644 --- a/frontend/__tests__/components/operator-lifecycle-manager/create-crd-yaml.spec.tsx +++ b/frontend/__tests__/components/operator-lifecycle-manager/create-crd-yaml.spec.tsx @@ -41,6 +41,16 @@ describe(CreateCRDYAML.displayName, () => { expect(createYAML.props().template).toEqual(safeDump(testResourceInstance)); }); + it('handles invalid JSON example object on annotation', () => { + const data = _.cloneDeep(testClusterServiceVersion); + data.metadata.annotations = {'alm-examples': 'invalid === true'}; + wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data}} as any); + + const createYAML = wrapper.find(Firehose).childAt(0).dive(); + + expect(createYAML.props().template).toEqual(null); + }); + it('does not render YAML editor component if ClusterServiceVersion has not loaded yet', () => { wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: false}} as any); diff --git a/frontend/__tests__/features.spec.tsx b/frontend/__tests__/features.spec.tsx index 44425bab1f09..df08f938c8a0 100644 --- a/frontend/__tests__/features.spec.tsx +++ b/frontend/__tests__/features.spec.tsx @@ -43,6 +43,7 @@ describe('featureReducer', () => { [FLAGS.OPERATOR_LIFECYCLE_MANAGER]: true, [FLAGS.OPERATOR_HUB]: false, [FLAGS.CLUSTER_API]: false, + [FLAGS.MACHINE_CONFIG]: false, })); }); }); diff --git a/frontend/integration-tests/tests/operator-hub/operator-hub.scenario.ts b/frontend/integration-tests/tests/operator-hub/operator-hub.scenario.ts index d9edda7de4a0..db2a8292f854 100644 --- a/frontend/integration-tests/tests/operator-hub/operator-hub.scenario.ts +++ b/frontend/integration-tests/tests/operator-hub/operator-hub.scenario.ts @@ -44,10 +44,10 @@ describe('Viewing the operators in Operator Hub', () => { await catalogPageView.clickFilterCheckbox('Red Hat'); }); - it('displays "Dynatrace OneAgent" as an operator when using the filter "dy"', async() => { - await catalogPageView.filterByKeyword('dy'); + it('displays "AMQ Streams" as an operator when using the filter "stre"', async() => { + await catalogPageView.filterByKeyword('stre'); - expect(catalogPageView.catalogTileFor('Dynatrace OneAgent').isDisplayed()).toBe(true); + expect(catalogPageView.catalogTileFor('AMQ Streams').isDisplayed()).toBe(true); await catalogPageView.filterByKeyword(''); }); diff --git a/frontend/public/components/app.jsx b/frontend/public/components/app.jsx index 6dc451272175..8487a32c3fba 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -104,12 +104,22 @@ class App extends React.PureComponent { this._onNavToggle = this._onNavToggle.bind(this); this._onNavSelect = this._onNavSelect.bind(this); this._isDesktop = this._isDesktop.bind(this); + this._onResize = this._onResize.bind(this); + this.previousDesktopState = this._isDesktop(); this.state = { isNavOpen: this._isDesktop(), }; } + componentWillMount() { + window.addEventListener('resize', this._onResize); + } + + componentWillUnmount() { + window.removeEventListener('resize', this._onResize); + } + componentDidUpdate(prevProps) { const props = this.props; // Prevent infinite loop in case React Router decides to destroy & recreate the component (changing key) @@ -143,6 +153,14 @@ class App extends React.PureComponent { } } + _onResize() { + const isDesktop = this._isDesktop(); + if (this.previousDesktopState !== isDesktop) { + this.setState({isNavOpen: isDesktop}); + this.previousDesktopState = isDesktop; + } + } + render() { const { isNavOpen } = this.state; diff --git a/frontend/public/components/cluster-settings/_cluster-settings.scss b/frontend/public/components/cluster-settings/_cluster-settings.scss new file mode 100644 index 000000000000..c3fcaa9a3a43 --- /dev/null +++ b/frontend/public/components/cluster-settings/_cluster-settings.scss @@ -0,0 +1,7 @@ +.cluster-channel-modal__dropdown, +.cluster-update-modal__dropdown { + .btn-dropdown, + .dropdown-menu { + width: 100%; + } +} diff --git a/frontend/public/components/cluster-settings/cluster-operator.tsx b/frontend/public/components/cluster-settings/cluster-operator.tsx index 0a2ccdc5859e..c305952a9bb7 100644 --- a/frontend/public/components/cluster-settings/cluster-operator.tsx +++ b/frontend/public/components/cluster-settings/cluster-operator.tsx @@ -2,50 +2,31 @@ import * as React from 'react'; import * as _ from 'lodash-es'; -import { K8sResourceKind, K8sResourceKindReference, referenceForModel } from '../../module/k8s'; import { ClusterOperatorModel } from '../../models'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from '../factory'; import { + ColHead, + DetailsPage, + List, + ListHeader, + ListPage, +} from '../factory'; +import { + getClusterOperatorStatus, + getStatusAndMessage, + K8sResourceKind, + K8sResourceKindReference, + OperatorStatus, + referenceForModel, +} from '../../module/k8s'; +import { + navFactory, ResourceLink, ResourceSummary, SectionHeading, - navFactory, } from '../utils'; -enum OperatorStatus { - Available = 'Available', - Updating = 'Updating', - Failing = 'Failing', - Unknown = 'Unknown', -} - export const clusterOperatorReference: K8sResourceKindReference = referenceForModel(ClusterOperatorModel); -const getStatusAndMessage = (operator: K8sResourceKind) => { - const conditions = _.get(operator, 'status.conditions'); - const failing: any = _.find(conditions, { type: 'Failing', status: 'True' }); - if (failing) { - return { status: OperatorStatus.Failing, message: failing.message }; - } - - const progressing: any = _.find(conditions, { type: 'Progressing', status: 'True' }); - if (progressing) { - return { status: OperatorStatus.Updating, message: progressing.message }; - } - - const available: any = _.find(conditions, { type: 'Available', status: 'True' }); - if (available) { - return { status: OperatorStatus.Available, message: available.message }; - } - - return { status: OperatorStatus.Unknown, message: '' }; -}; - -export const getClusterOperatorStatus = (operator: K8sResourceKind) => { - const { status } = getStatusAndMessage(operator); - return status; -}; - const getIconClass = (status: OperatorStatus) => { return { [OperatorStatus.Available]: 'pficon pficon-ok text-success', @@ -81,7 +62,7 @@ const ClusterOperatorRow: React.SFC = ({obj}) => { {message ? _.truncate(message, { length: 256, separator: ' ' }) : '-'}
- {obj.status.version || Unknown} + {_.get(obj, 'status.version') || Unknown}
; }; diff --git a/frontend/public/components/cluster-settings/cluster-settings.tsx b/frontend/public/components/cluster-settings/cluster-settings.tsx index fc325b028a37..c8c375a05e11 100644 --- a/frontend/public/components/cluster-settings/cluster-settings.tsx +++ b/frontend/public/components/cluster-settings/cluster-settings.tsx @@ -8,12 +8,20 @@ import { Helmet } from 'react-helmet'; import { Button } from 'patternfly-react'; import { Link } from 'react-router-dom'; -import { Firehose, HorizontalNav, ResourceLink, resourcePathFromModel } from '../utils'; -import { K8sResourceKind, referenceForModel } from '../../module/k8s'; +import { ClusterVersionKind, K8sResourceKind, referenceForModel } from '../../module/k8s'; import { ClusterAutoscalerModel, ClusterVersionModel } from '../../models'; import { ClusterOperatorPage } from './cluster-operator'; -import { clusterUpdateModal } from '../modals'; +import { clusterChannelModal, clusterUpdateModal } from '../modals'; import { GlobalConfigPage } from './global-config'; +import { + EmptyBox, + Firehose, + HorizontalNav, + ResourceLink, + resourcePathFromModel, + SectionHeading, + Timestamp, +} from '../utils'; enum ClusterUpdateStatus { UpToDate = 'Up to Date', @@ -24,22 +32,27 @@ enum ClusterUpdateStatus { } const clusterAutoscalerReference = referenceForModel(ClusterAutoscalerModel); -const clusterVersionReference = referenceForModel(ClusterVersionModel); +export const clusterVersionReference = referenceForModel(ClusterVersionModel); -export const getAvailableClusterUpdates = (cv) => { +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 getCurrentClusterVersion = (cv) => { +export const getDesiredClusterVersion = (cv: ClusterVersionKind) => { return _.get(cv, 'status.desired.version'); }; - -const launchUpdateModal = (cv) => { +const launchUpdateModal = (cv: ClusterVersionKind) => { clusterUpdateModal({cv}); }; -const getClusterUpdateStatus = (cv: K8sResourceKind): ClusterUpdateStatus => { +const CurrentChannel: React.SFC = ({cv}) => ; + +const getClusterUpdateStatus = (cv: ClusterVersionKind): ClusterUpdateStatus => { const conditions = _.get(cv, 'status.conditions', []); const isFailingCondition = _.find(conditions, { type: 'Failing', status: 'True' }); if (isFailingCondition) { @@ -111,63 +124,93 @@ const UpdateStatus: React.SFC = ({cv}) => { ; }; -const CurrentVersion: React.SFC = ({cv}) => { - const currentVersion = getCurrentClusterVersion(cv); - return currentVersion ||