diff --git a/Dockerfile-builder b/Dockerfile.builder similarity index 95% rename from Dockerfile-builder rename to Dockerfile.builder index 2f519342185..c021c1f2d3d 100644 --- a/Dockerfile-builder +++ b/Dockerfile.builder @@ -48,9 +48,8 @@ RUN cd /tmp && \ ln -s /usr/local/yarn/bin/yarn /usr/local/bin/yarn # Install Chrome for installer gui tests -# Use Chrome beta because v60 or higher is needed for headless mode RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \ apt-get update && \ apt-get install --no-install-recommends -y -q \ - google-chrome-beta ca-certificates + google-chrome-stable ca-certificates diff --git a/README.md b/README.md index c565b123deb..0c31ba20c7f 100644 --- a/README.md +++ b/README.md @@ -214,9 +214,9 @@ CI runs the [test-prow-e2e.sh](test-prow-e2e.sh) script, which uses the `e2e` su You can simulate an e2e run against an existing 4.0 cluster with the following commands (replace `/path/to/install-dir` with your OpenShift 4.0 install directory): ``` -$ export BRIDGE_AUTH_USERNAME=kubeadmin -$ export BRIDGE_BASE_ADDRESS="https://$(oc get route console -n openshift-console -o jsonpath='{.spec.host}')" -$ export BRIDGE_AUTH_PASSWORD=$(cat "/path/to/install-dir/auth/kubeadmin-password") +$ oc apply -f ./frontend/integration-tests/data/htpasswd-idp.yaml +$ export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')" +$ export BRIDGE_KUBEADMIN_PASSWORD=$(cat "/path/to/install-dir/auth/kubeadmin-password") $ ./test-gui.sh e2e ``` diff --git a/builder-run.sh b/builder-run.sh index 00305343bdc..3eca6be96de 100755 --- a/builder-run.sh +++ b/builder-run.sh @@ -11,7 +11,7 @@ set -e # Without env vars: # ./builder-run.sh ./my-script --my-script-arg1 --my-script-arg2 -BUILDER_IMAGE="quay.io/coreos/tectonic-console-builder:v16" +BUILDER_IMAGE="quay.io/coreos/tectonic-console-builder:v17" # forward whitelisted env variables to docker ENV_STR="" diff --git a/frontend/integration-tests/data/htpasswd-idp.yaml b/frontend/integration-tests/data/htpasswd-idp.yaml index e139bed1584..fc99d745102 100644 --- a/frontend/integration-tests/data/htpasswd-idp.yaml +++ b/frontend/integration-tests/data/htpasswd-idp.yaml @@ -17,8 +17,6 @@ metadata: spec: identityProviders: - name: test - challenge: true - login: true mappingMethod: claim type: HTPasswd htpasswd: diff --git a/frontend/integration-tests/tests/login.scenario.ts b/frontend/integration-tests/tests/login.scenario.ts index 7c485c2d674..9ae87f8965c 100644 --- a/frontend/integration-tests/tests/login.scenario.ts +++ b/frontend/integration-tests/tests/login.scenario.ts @@ -9,10 +9,10 @@ const JASMINE_EXTENDED_TIMEOUT_INTERVAL = 1000 * 60 * 3; const KUBEADMIN_IDP = 'kube:admin'; const KUBEADMIN_USERNAME = 'kubeadmin'; const { - HTPASSWD_IDP = 'test', - HTPASSWD_USERNAME = 'test', - HTPASSWD_PASSWORD = 'test', - KUBEADMIN_PASSWORD, + BRIDGE_HTPASSWD_IDP = 'test', + BRIDGE_HTPASSWD_USERNAME = 'test', + BRIDGE_HTPASSWD_PASSWORD = 'test', + BRIDGE_KUBEADMIN_PASSWORD, } = process.env; describe('Auth test', () => { @@ -21,7 +21,7 @@ describe('Auth test', () => { await browser.sleep(3000); // Wait long enough for the login redirect to complete }); - if (KUBEADMIN_PASSWORD) { + if (BRIDGE_KUBEADMIN_PASSWORD) { describe('Login test', async() => { beforeAll(() => { // Extend the default jasmine timeout interval just in case it takes a while for the htpasswd idp to be ready @@ -34,9 +34,9 @@ describe('Auth test', () => { }); it('logs in via htpasswd identity provider', async() => { - await loginView.login(HTPASSWD_IDP, HTPASSWD_USERNAME, HTPASSWD_PASSWORD); + await loginView.login(BRIDGE_HTPASSWD_IDP, BRIDGE_HTPASSWD_USERNAME, BRIDGE_HTPASSWD_PASSWORD); expect(browser.getCurrentUrl()).toContain(appHost); - expect(loginView.userDropdown.getText()).toContain(HTPASSWD_USERNAME); + expect(loginView.userDropdown.getText()).toContain(BRIDGE_HTPASSWD_USERNAME); }); it('logs out htpasswd user', async() => { @@ -46,7 +46,7 @@ describe('Auth test', () => { }); it('logs in as kubeadmin user', async() => { - await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD); + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); expect(browser.getCurrentUrl()).toContain(appHost); expect(loginView.userDropdown.getText()).toContain('kube:admin'); await browser.wait(until.presenceOf($('.co-global-notification'))); @@ -59,7 +59,7 @@ describe('Auth test', () => { expect($('.login-pf').isPresent()).toBeTruthy(); // Log back in so that remaining tests can be run - await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD); + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); expect(loginView.userDropdown.getText()).toContain('kube:admin'); }); }); diff --git a/frontend/integration-tests/tests/secrets.scenario.ts b/frontend/integration-tests/tests/secrets.scenario.ts index 1cc2fececdd..7bb15e6b236 100644 --- a/frontend/integration-tests/tests/secrets.scenario.ts +++ b/frontend/integration-tests/tests/secrets.scenario.ts @@ -5,7 +5,7 @@ import { appHost, testName, checkLogs, checkErrors, waitForCount } from '../prot import * as crudView from '../views/crud.view'; import * as secretsView from '../views/secrets.view'; -describe('Interacting with the create secret forms', () => { +xdescribe('Interacting with the create secret forms', () => { afterEach(() => { checkLogs(); diff --git a/frontend/package.json b/frontend/package.json index fe661e309b4..3d406fa4255 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,7 @@ "lint": "eslint --ext .js,.jsx,.ts,.tsx --color .", "test": "jest", "debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand", - "webdriver-update": "webdriver-manager update --versions.chrome=2.38 --gecko=false", + "webdriver-update": "webdriver-manager update --gecko=false", "test-gui-tap": "TAP=true yarn run test-gui", "test-gui-openshift": "yarn run test-suite --suite crud --params.openshift true", "test-gui": "yarn run test-suite --suite all", diff --git a/frontend/public/branding.ts b/frontend/public/branding.ts deleted file mode 100644 index 1e31d4303ae..00000000000 --- a/frontend/public/branding.ts +++ /dev/null @@ -1,17 +0,0 @@ -const getProductName = () => { - switch ((window as any).SERVER_FLAGS.branding) { - case 'openshift': - return 'OpenShift'; - case 'ocp': - return 'OpenShift Container Platform'; - case 'online': - return 'OpenShift Online'; - case 'dedicated': - return 'OpenShift Dedicated'; - case 'azure': - return 'Azure Red Hat OpenShift'; - default: - return 'OKD'; - } -}; -export const productName = getProductName(); diff --git a/frontend/public/components/about-modal.jsx b/frontend/public/components/about-modal.jsx index 8d10eeee09b..981dfb9804a 100644 --- a/frontend/public/components/about-modal.jsx +++ b/frontend/public/components/about-modal.jsx @@ -34,11 +34,9 @@ class AboutModal_ extends React.Component {

OpenShift is Red Hat's container application platform that allows developers to quickly develop, host, and scale applications in a cloud environment.

diff --git a/frontend/public/components/app.jsx b/frontend/public/components/app.jsx index 8fbb782f0f5..043818c8cde 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -7,12 +7,11 @@ import { Redirect, Route, Router, Switch } from 'react-router-dom'; import * as PropTypes from 'prop-types'; import store from '../redux'; -import { productName } from '../branding'; import { ALL_NAMESPACES_KEY } from '../const'; import { connectToFlags, featureActions, flagPending, FLAGS } from '../features'; import { analyticsSvc } from '../module/analytics'; import { GlobalNotifications } from './global-notifications'; -import { Masthead } from './masthead'; +import { getBrandingDetails, Masthead } from './masthead'; import { NamespaceBar } from './namespace'; import { Navigation } from './nav'; import { SearchPage } from './search'; @@ -162,6 +161,7 @@ class App extends React.PureComponent { render() { const { isNavOpen } = this.state; + const { productName } = getBrandingDetails(); return ( diff --git a/frontend/public/components/cluster-settings/openid-idp-form.tsx b/frontend/public/components/cluster-settings/openid-idp-form.tsx index 9171f65c53c..d064f45f15c 100644 --- a/frontend/public/components/cluster-settings/openid-idp-form.tsx +++ b/frontend/public/components/cluster-settings/openid-idp-form.tsx @@ -72,7 +72,7 @@ export class AddOpenIDPage extends PromiseComponent { namespace: 'openshift-config', }, stringData: { - ca: caFileContent, + 'ca.crt': caFileContent, }, }; @@ -81,7 +81,7 @@ export class AddOpenIDPage extends PromiseComponent { addOpenIDIDP(oauth: K8sResourceKind, clientSecretName: string, caName: string): Promise { const { name, mappingMethod, clientID, issuer, extraScopes, claimPreferredUsernames, claimNames, claimEmails } = this.state; - const openID: any = { + const idp: any = { name, type: 'OpenID', mappingMethod, @@ -101,14 +101,14 @@ export class AddOpenIDPage extends PromiseComponent { }; if (caName) { - openID.ca = { + idp.openID.ca = { name: caName, }; } const patch = _.isEmpty(oauth.spec.identityProviders) - ? { op: 'add', path: '/spec/identityProviders', value: [openID] } - : { op: 'add', path: '/spec/identityProviders/-', value: openID }; + ? { op: 'add', path: '/spec/identityProviders', value: [idp] } + : { op: 'add', path: '/spec/identityProviders/-', value: idp }; return this.handlePromise(k8sPatch(OAuthModel, oauth, [patch])); } diff --git a/frontend/public/components/deploy-image.tsx b/frontend/public/components/deploy-image.tsx index b015c986c09..dfafe08c6ec 100644 --- a/frontend/public/components/deploy-image.tsx +++ b/frontend/public/components/deploy-image.tsx @@ -66,7 +66,7 @@ export class DeployImage extends React.Component { this.setState({namespace}); @@ -435,5 +435,4 @@ export type DeployImageState = { name: string, error?: any, searchError?: string, - env?: any, }; diff --git a/frontend/public/components/events.jsx b/frontend/public/components/events.jsx index 80d00c0d57b..b66abd08cdd 100644 --- a/frontend/public/components/events.jsx +++ b/frontend/public/components/events.jsx @@ -171,6 +171,7 @@ export class EventsList extends React.Component { className="btn-group" items={categories} onChange={v => this.setState({category: v})} + selectedKey={this.state.category} title="All Categories" /> diff --git a/frontend/public/components/factory/list-page.jsx b/frontend/public/components/factory/list-page.jsx index 630262d4172..02999dc83b6 100644 --- a/frontend/public/components/factory/list-page.jsx +++ b/frontend/public/components/factory/list-page.jsx @@ -181,7 +181,7 @@ export const FireMan_ = connect(null, {filterList: k8sActions.filterList})( if (canCreate) { if (createProps.to) { createLink = - + ; } else if (createProps.items) { createLink =
@@ -189,7 +189,7 @@ export const FireMan_ = connect(null, {filterList: k8sActions.filterList})(
; } else { createLink =
- +
; } } diff --git a/frontend/public/components/masthead.jsx b/frontend/public/components/masthead.jsx index 8416cbfdd5b..b729af60147 100644 --- a/frontend/public/components/masthead.jsx +++ b/frontend/public/components/masthead.jsx @@ -12,40 +12,34 @@ import dedicatedLogoImg from '../imgs/openshift-dedicated-logo.svg'; import azureLogoImg from '../imgs/azure-red-hat-openshift-logo.svg'; export const getBrandingDetails = () => { - let logoImg, logoAlt, productTitle; + let logoImg, productName; // Webpack won't bundle these images if we don't directly reference them, hence the switch switch (window.SERVER_FLAGS.branding) { case 'openshift': logoImg = openshiftLogoImg; - logoAlt = 'OpenShift'; - productTitle = 'Red Hat OpenShift'; + productName = 'Red Hat OpenShift'; break; case 'ocp': logoImg = ocpLogoImg; - logoAlt = 'OpenShift Container Platform'; - productTitle = 'Red Hat OpenShift Container Platform'; + productName = 'Red Hat OpenShift Container Platform'; break; case 'online': logoImg = onlineLogoImg; - logoAlt = 'OpenShift Online'; - productTitle = 'Red Hat OpenShift Online'; + productName = 'Red Hat OpenShift Online'; break; case 'dedicated': logoImg = dedicatedLogoImg; - logoAlt = 'OpenShift Dedicated'; - productTitle = 'Red Hat OpenShift Dedicated'; + productName = 'Red Hat OpenShift Dedicated'; break; case 'azure': logoImg = azureLogoImg; - logoAlt = 'Azure Red Hat OpenShift'; - productTitle = 'Azure Red Hat OpenShift'; + productName = 'Azure Red Hat OpenShift'; break; default: logoImg = okdLogoImg; - logoAlt = 'OKD'; - productTitle = 'OKD'; + productName = 'OKD'; } - return { logoImg, logoAlt, productTitle }; + return { logoImg, productName }; }; export const Masthead = ({ onNavToggle }) => { @@ -62,7 +56,7 @@ export const Masthead = ({ onNavToggle }) => { return ( } + logo={} logoProps={logoProps} toolbar={} showNavToggle diff --git a/frontend/public/components/modals/taints-modal.tsx b/frontend/public/components/modals/taints-modal.tsx index 8d87ef1c210..0cc1c56b28f 100644 --- a/frontend/public/components/modals/taints-modal.tsx +++ b/frontend/public/components/modals/taints-modal.tsx @@ -13,7 +13,7 @@ class TaintsModal extends PromiseComponent { constructor(public props: TaintsModalProps) { super(props); // Add an empty row for editing if no taints exist. - this.state.taints = this.props.resource.spec.taints; + this.state.taints = this.props.resource.spec.taints || []; } _submit = (e: React.FormEvent) => { diff --git a/frontend/public/components/operator-lifecycle-manager/descriptors/spec/resource-requirements.tsx b/frontend/public/components/operator-lifecycle-manager/descriptors/spec/resource-requirements.tsx index d897aa919f9..baab98894dc 100644 --- a/frontend/public/components/operator-lifecycle-manager/descriptors/spec/resource-requirements.tsx +++ b/frontend/public/components/operator-lifecycle-manager/descriptors/spec/resource-requirements.tsx @@ -47,8 +47,8 @@ export const ResourceRequirementsModalLink: React.SFC { const modal = createModalLauncher(ResourceRequirementsModal); - const description = `Define the ${type === 'limits' ? 'resource' : 'request'} limits for this ${obj.kind} instance.`; - const title = `${obj.kind} ${type === 'limits' ? 'Resource' : 'Request'} Limits`; + const description = `Define the resource ${type} for this ${obj.kind} instance.`; + const title = `${obj.kind} Resource ${_.capitalize(type)}`; const ResourceRequirementsForm = () =>
diff --git a/frontend/public/components/operator-lifecycle-manager/subscription.tsx b/frontend/public/components/operator-lifecycle-manager/subscription.tsx index 8344511506b..e1eea83ad50 100644 --- a/frontend/public/components/operator-lifecycle-manager/subscription.tsx +++ b/frontend/public/components/operator-lifecycle-manager/subscription.tsx @@ -38,10 +38,14 @@ const menuActions = [ label: 'Remove Subscription...', callback: () => createDisableApplicationModal({k8sKill, k8sGet, k8sPatch, subscription: obj}), }), - (kind, obj) => ({ - label: `View ${ClusterServiceVersionModel.kind}...`, - href: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${_.get(obj.status, 'installedCSV')}`, - }), + (kind, obj) => { + const installedCSV = _.get(obj, 'status.installedCSV'); + return { + label: `View ${ClusterServiceVersionModel.kind}...`, + href: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${installedCSV}`, + hidden: !installedCSV, + }; + }, ]; export const SubscriptionRow: React.SFC = (props) => { @@ -62,7 +66,7 @@ export const SubscriptionRow: React.SFC = (props) => { {props.obj.spec.installPlanApproval || 'Automatic'}
- +
; }; diff --git a/frontend/public/components/overview/constants.ts b/frontend/public/components/overview/constants.ts new file mode 100644 index 00000000000..e6fa1c48560 --- /dev/null +++ b/frontend/public/components/overview/constants.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-undef */ + +// Keys for special 'group by' options +// Should not be valid label keys to avoid conflicts. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +export enum OverviewSpecialGroup { + GROUP_BY_APPLICATION = '#GROUP_BY_APPLICATION#', + GROUP_BY_RESOURCE = '#GROUP_BY_RESOURCE#', +} + +// View options for overview page +export enum OverviewViewOption { + RESOURCES = 'resources', + DASHBOARD = 'dashboard', +} diff --git a/frontend/public/components/overview/index.tsx b/frontend/public/components/overview/index.tsx index facac15cfd6..f1314ab3465 100644 --- a/frontend/public/components/overview/index.tsx +++ b/frontend/public/components/overview/index.tsx @@ -1,5 +1,4 @@ /* eslint-disable no-unused-vars, no-undef */ - import * as _ from 'lodash-es'; import * as classnames from 'classnames'; import * as fuzzy from 'fuzzysearch'; @@ -14,6 +13,7 @@ import { coFetchJSON } from '../../co-fetch'; import { getBuildNumber } from '../../module/k8s/builds'; import { prometheusTenancyBasePath } from '../graphs'; import { TextFilter } from '../factory'; +import { PodStatus } from '../pod'; import { UIActions, formatNamespacedRouteForResource } from '../../ui/ui-actions'; import { apiVersionForModel, @@ -48,31 +48,40 @@ import { import { overviewMenuActions, OverviewNamespaceDashboard } from './namespace-overview'; import { ProjectOverview } from './project-overview'; import { ResourceOverviewPage } from './resource-overview-page'; -import { PodStatus } from '../pod'; +import { OverviewViewOption, OverviewSpecialGroup } from './constants'; -enum View { - Resources = 'resources', - Dashboard = 'dashboard', -} -// The following values should not be valid label keys to avoid conflicts. -// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set -const GROUP_BY_APPLICATION = '#GROUP_BY_APPLICATION#'; -const GROUP_BY_RESOURCE = '#GROUP_BY_RESOURCE#'; -const EMPTY_GROUP_NAME = 'other resources'; +// List of container status waiting reason values that we should call out as errors in project status rows. +const CONTAINER_WAITING_STATE_ERROR_REASONS = ['CrashLoopBackOff', 'ErrImagePull', 'ImagePullBackOff']; -const DEPLOYMENT_REVISION_ANNOTATION = 'deployment.kubernetes.io/revision'; +// Annotation key for deployment config latest version const DEPLOYMENT_CONFIG_LATEST_VERSION_ANNOTATION = 'openshift.io/deployment-config.latest-version'; + +// Annotation key for deployment phase const DEPLOYMENT_PHASE_ANNOTATION = 'openshift.io/deployment.phase'; -const TRIGGERS_ANNOTATION = 'image.openshift.io/triggers'; + +// Annotaton key for deployment revision +const DEPLOYMENT_REVISION_ANNOTATION = 'deployment.kubernetes.io/revision'; + +// Display name for default overview group. +// Should not be a valid label key to avoid conflicts. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-setexport +const DEFAULT_GROUP_NAME = 'other resources'; + +// Interval at which metrics are retrieved and updated const METRICS_POLL_INTERVAL = 30 * 1000; +// Namespace prefixes that are reserved and should not have calls to action on empty state +const RESERVED_NS_PREFIXES = ['openshift-', 'kube-', 'kubernetes-']; + +// Annotation key for image triggers +const TRIGGERS_ANNOTATION = 'image.openshift.io/triggers'; + const asOverviewGroups = (keyedItems: { [name: string]: OverviewItem[] }): OverviewGroup[] => { const compareGroups = (a: OverviewGroup, b: OverviewGroup) => { - if (a.name === EMPTY_GROUP_NAME) { + if (a.name === DEFAULT_GROUP_NAME) { return 1; } - if (b.name === EMPTY_GROUP_NAME) { + if (b.name === DEFAULT_GROUP_NAME) { return -1; } return a.name.localeCompare(b.name); @@ -88,7 +97,7 @@ const asOverviewGroups = (keyedItems: { [name: string]: OverviewItem[] }): Overv const getApplication = (item: OverviewItem): string => { const labels = _.get(item, 'obj.metadata.labels') || {}; - return labels['app.kubernetes.io/part-of'] || labels['app.kubernetes.io/name'] || labels.app || EMPTY_GROUP_NAME; + return labels['app.kubernetes.io/part-of'] || labels['app.kubernetes.io/name'] || labels.app || DEFAULT_GROUP_NAME; }; const groupByApplication = (items: OverviewItem[]): OverviewGroup[] => { @@ -102,24 +111,21 @@ const groupByResource = (items: OverviewItem[]): OverviewGroup[] => { }; const groupByLabel = (items: OverviewItem[], label: string): OverviewGroup[] => { - const byLabel = _.groupBy(items, (item): string => _.get(item, ['obj', 'metadata', 'labels', label]) || EMPTY_GROUP_NAME); + const byLabel = _.groupBy(items, (item): string => _.get(item, ['obj', 'metadata', 'labels', label]) || DEFAULT_GROUP_NAME); return asOverviewGroups(byLabel); }; const groupItems = (items: OverviewItem[], selectedGroup: string): OverviewGroup[] => { switch (selectedGroup) { - case GROUP_BY_APPLICATION: + case OverviewSpecialGroup.GROUP_BY_APPLICATION: return groupByApplication(items); - case GROUP_BY_RESOURCE: + case OverviewSpecialGroup.GROUP_BY_RESOURCE: return groupByResource(items); default: return groupByLabel(items, selectedGroup); } }; -// List of container status waiting reason values that we should call out as errors in overview rows. -const CONTAINER_WAITING_STATE_ERROR_REASONS = ['CrashLoopBackOff', 'ErrImagePull', 'ImagePullBackOff']; - const getAnnotation = (obj: K8sResourceKind, annotation: string): string => { return _.get(obj, ['metadata', 'annotations', annotation]); }; @@ -293,8 +299,7 @@ const sortBuilds = (builds: K8sResourceKind[]): K8sResourceKind[] => { return builds.sort(byBuildNumber); }; -const reservedNSPrefixes = ['openshift-', 'kube-', 'kubernetes-']; -const isReservedNamespace = (ns: string) => ns === 'default' || ns === 'openshift' || reservedNSPrefixes.some(prefix => _.startsWith(ns, prefix)); +const isReservedNamespace = (ns: string) => ns === 'default' || ns === 'openshift' || RESERVED_NS_PREFIXES.some(prefix => _.startsWith(ns, prefix)); const OverviewItemReadiness: React.SFC = ({desired = 0, ready = 0, resource}) => { const href = `${resourceObjPath(resource, resource.kind)}/pods`; @@ -337,106 +342,114 @@ const OverviewEmptyState = connect(overviewEmptyStateToProps)(({activeNamespace, }); const headingStateToProps = ({UI}): OverviewHeadingPropsFromState => { - const selectedView = UI.getIn(['overview', 'selectedView']); - return { selectedView }; + const {selectedView, selectedGroup, groupOptions, filterValue} = UI.get('overview').toJS(); + return {groupOptions, selectedGroup, selectedView, filterValue}; }; const headingDispatchToProps = (dispatch): OverviewHeadingPropsFromDispatch => ({ - selectView: (view: View) => dispatch(UIActions.selectOverviewView(view)), + selectView: (view: OverviewViewOption) => dispatch(UIActions.selectOverviewView(view)), + selectGroup: (group: string) => dispatch(UIActions.updateOverviewSelectedGroup(group)), + changeFilter: (value: string) => dispatch(UIActions.updateOverviewFilterValue(value)), }); -const OverviewHeading_: React.SFC = ({disabled, firstLabel = '', groupOptions, handleFilterChange = _.noop, handleGroupChange = _.noop, selectedGroup = '', selectView, selectedView, title, project}) => ( -
- { - title && -

-
{title}
-

- } - {!_.isEmpty(project) &&
-
-