From 2dabcecf73829dfb645d71e532f45b7ee29ff995 Mon Sep 17 00:00:00 2001 From: Patrick Riley Date: Mon, 15 Apr 2019 15:36:55 -0400 Subject: [PATCH] feature: add patternfly4 react-table --- .../clusterserviceversion-resource.spec.tsx | 77 +-- .../clusterserviceversion.spec.tsx | 60 +- .../install-plan.spec.tsx | 84 +-- .../subscription.spec.tsx | 88 +-- .../tests/monitoring.scenario.ts | 22 +- frontend/integration-tests/views/crud.view.ts | 2 +- .../views/monitoring.view.ts | 2 +- frontend/package.json | 4 +- frontend/public/components/RBAC/bindings.jsx | 96 ++- frontend/public/components/RBAC/role.jsx | 133 ++-- frontend/public/components/alert-manager.tsx | 92 ++- frontend/public/components/build-config.tsx | 95 ++- frontend/public/components/build.tsx | 95 ++- frontend/public/components/chargeback.tsx | 103 ++- .../components/cluster-service-broker.tsx | 97 ++- .../components/cluster-service-class.tsx | 79 ++- .../components/cluster-service-plan.tsx | 77 ++- .../cluster-settings/cluster-operator.tsx | 89 ++- frontend/public/components/configmap.jsx | 86 ++- frontend/public/components/cron-job.jsx | 100 ++- .../components/custom-resource-definition.tsx | 118 ++-- frontend/public/components/daemon-set.jsx | 109 ++-- .../public/components/default-resource.jsx | 87 ++- .../public/components/deployment-config.tsx | 37 +- frontend/public/components/deployment.tsx | 49 +- frontend/public/components/factory/index.tsx | 1 + frontend/public/components/factory/table.tsx | 594 ++++++++++++++++++ frontend/public/components/hpa.tsx | 128 ++-- frontend/public/components/image-stream.tsx | 93 ++- frontend/public/components/ingress.jsx | 89 ++- frontend/public/components/job.jsx | 89 ++- frontend/public/components/limit-range.tsx | 87 ++- .../public/components/machine-autoscaler.tsx | 116 ++-- .../public/components/machine-config-pool.tsx | 117 ++-- frontend/public/components/machine-config.tsx | 122 ++-- .../public/components/machine-deployment.tsx | 96 ++- frontend/public/components/machine-set.tsx | 93 ++- frontend/public/components/machine.tsx | 112 ++-- frontend/public/components/monitoring.tsx | 189 ++++-- frontend/public/components/namespace.jsx | 158 +++-- frontend/public/components/network-policy.jsx | 87 ++- frontend/public/components/node.tsx | 104 +-- .../_operator-lifecycle-manager.scss | 3 +- .../clusterserviceversion-resource.tsx | 131 ++-- .../clusterserviceversion.tsx | 111 ++-- .../install-plan.tsx | 134 ++-- .../subscription.tsx | 122 ++-- .../components/persistent-volume-claim.jsx | 103 ++- .../public/components/persistent-volume.jsx | 116 ++-- frontend/public/components/pod.tsx | 117 ++-- frontend/public/components/prometheus.jsx | 96 ++- frontend/public/components/replicaset.jsx | 26 +- .../components/replication-controller.jsx | 27 +- frontend/public/components/resource-quota.jsx | 61 +- frontend/public/components/routes.tsx | 110 +++- frontend/public/components/secret.jsx | 87 ++- .../public/components/service-account.jsx | 78 ++- .../public/components/service-binding.tsx | 111 ++-- .../public/components/service-instance.tsx | 123 ++-- .../public/components/service-monitor.jsx | 91 ++- frontend/public/components/service.jsx | 97 ++- frontend/public/components/stateful-set.jsx | 26 +- frontend/public/components/storage-class.tsx | 89 ++- .../public/components/template-instance.tsx | 89 ++- frontend/public/components/utils/dropdown.jsx | 2 +- frontend/public/components/utils/kebab.tsx | 44 +- frontend/public/components/workload-table.tsx | 92 +++ frontend/public/module/k8s/index.ts | 5 + frontend/public/style.scss | 2 + frontend/public/style/_base.scss | 8 +- frontend/public/style/_common.scss | 11 +- frontend/public/style/_overrides.scss | 434 ++++++++++++- frontend/public/style/_vars.scss | 3 +- frontend/public/vendor.scss | 9 +- frontend/yarn.lock | 526 ++++++++++++---- 75 files changed, 5202 insertions(+), 2038 deletions(-) create mode 100644 frontend/public/components/factory/table.tsx create mode 100644 frontend/public/components/workload-table.tsx diff --git a/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion-resource.spec.tsx b/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion-resource.spec.tsx index 0b84a155cba6..4e9dfe47a3cf 100644 --- a/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion-resource.spec.tsx +++ b/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion-resource.spec.tsx @@ -2,70 +2,28 @@ import * as React from 'react'; import { match as RouterMatch } from 'react-router-dom'; import { shallow, ShallowWrapper } from 'enzyme'; import * as _ from 'lodash-es'; - -import { ClusterServiceVersionResourceList, ClusterServiceVersionResourceListProps, ProvidedAPIsPage, ProvidedAPIsPageProps, ClusterServiceVersionResourceHeaderProps, ClusterServiceVersionResourceRowProps, ClusterServiceVersionResourceHeader, ClusterServiceVersionResourceRow, ClusterServiceVersionResourceDetails, ClusterServiceVersionResourcesDetailsPageProps, ClusterServiceVersionResourcesDetailsProps, ClusterServiceVersionResourcesDetailsPage, ClusterServiceVersionResourceLink, ProvidedAPIPage, ProvidedAPIPageProps } from '../../../public/components/operator-lifecycle-manager/clusterserviceversion-resource'; +import { ClusterServiceVersionResourceList, ClusterServiceVersionResourceListProps, ProvidedAPIsPage, ProvidedAPIsPageProps, CSVRTableRowProps, CSVRTableHeader, CSVRTableRow, ClusterServiceVersionResourceDetails, ClusterServiceVersionResourcesDetailsPageProps, ClusterServiceVersionResourcesDetailsProps, ClusterServiceVersionResourcesDetailsPage, ClusterServiceVersionResourceLink, ProvidedAPIPage, ProvidedAPIPageProps } from '../../../public/components/operator-lifecycle-manager/clusterserviceversion-resource'; import { Resources } from '../../../public/components/operator-lifecycle-manager/k8s-resource'; import { ClusterServiceVersionResourceKind, referenceForProvidedAPI } from '../../../public/components/operator-lifecycle-manager'; import { StatusDescriptor } from '../../../public/components/operator-lifecycle-manager/descriptors/status'; import { SpecDescriptor } from '../../../public/components/operator-lifecycle-manager/descriptors/spec'; import { testCRD, testResourceInstance, testClusterServiceVersion, testOwnedResourceInstance, testModel } from '../../../__mocks__/k8sResourcesMocks'; -import { List, ColHead, ListHeader, DetailsPage, MultiListPage, ListPage } from '../../../public/components/factory'; +import { Table, DetailsPage, MultiListPage, ListPage } from '../../../public/components/factory'; import { Timestamp, LabelList, StatusBox, ResourceKebab } from '../../../public/components/utils'; import { referenceFor, K8sKind, referenceForModel } from '../../../public/module/k8s'; import { ClusterServiceVersionModel } from '../../../public/models'; -describe(ClusterServiceVersionResourceHeader.displayName, () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders column header for resource name', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(0); - - expect(colHeader.props().sortField).toEqual('metadata.name'); - expect(colHeader.childAt(0).text()).toEqual('Name'); - }); - - it('renders column header for resource labels', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(1); - - expect(colHeader.props().sortField).toEqual('metadata.labels'); - expect(colHeader.childAt(0).text()).toEqual('Labels'); - }); - - it('renders column header for resource type', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(2); - - expect(colHeader.props().sortField).toEqual('kind'); - expect(colHeader.childAt(0).text()).toEqual('Type'); - }); - - it('renders column header for resource status', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(3); - - expect(colHeader.childAt(0).text()).toEqual('Status'); - }); - - it('renders column header for resource version', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(4); - - expect(colHeader.childAt(0).text()).toEqual('Version'); - }); - - it('renders column header for last updated timestamp', () => { - const colHeader = wrapper.find(ListHeader).find(ColHead).at(5); - - expect(colHeader.childAt(0).text()).toEqual('Last Updated'); +describe(CSVRTableHeader.displayName, () => { + it('returns column header definition for resource', () => { + expect(Array.isArray(CSVRTableHeader())); }); }); -describe(ClusterServiceVersionResourceRow.displayName, () => { - let wrapper: ShallowWrapper; +describe(CSVRTableRow.displayName, () => { + let wrapper: ShallowWrapper; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow(); }); it('renders column for resource name', () => { @@ -95,13 +53,13 @@ describe(ClusterServiceVersionResourceRow.displayName, () => { it('renders column for resource type', () => { const col = wrapper.childAt(2); - expect(col.text()).toEqual(testResourceInstance.kind); + expect(col.shallow().text()).toEqual(testResourceInstance.kind); }); it('renders column for resource status', () => { const col = wrapper.childAt(3); - expect(col.text()).toEqual('Unknown'); + expect(col.shallow().text()).toEqual('Unknown'); }); it('renders column for resource status if unknown', () => { @@ -110,13 +68,13 @@ describe(ClusterServiceVersionResourceRow.displayName, () => { wrapper.setProps({obj}); const col = wrapper.childAt(3); - expect(col.text()).toEqual('Unknown'); + expect(col.shallow().text()).toEqual('Unknown'); }); it('renders column for resource version', () => { const col = wrapper.childAt(4); - expect(col.text()).toEqual(testResourceInstance.spec.version || 'Unknown'); + expect(col.shallow().text()).toEqual(testResourceInstance.spec.version || 'Unknown'); }); it('renders column for last updated timestamp', () => { @@ -136,12 +94,11 @@ describe(ClusterServiceVersionResourceList.displayName, () => { wrapper = shallow(); }); - it('renders a `List` of the custom resource instances of the given kind', () => { - const list: ShallowWrapper = wrapper.find(List); - - expect(Object.keys(wrapper.props()).reduce((k, prop) => list.prop(prop) === wrapper.prop(prop), false)).toBe(true); - expect(list.props().Header).toEqual(ClusterServiceVersionResourceHeader); - expect(list.props().Row).toEqual(ClusterServiceVersionResourceRow); + it('renders a `Table` of the custom resource instances of the given kind', () => { + const table: ShallowWrapper = wrapper.find(Table); + expect(Object.keys(wrapper.props()).reduce((k, prop) => table.prop(prop) === wrapper.prop(prop), false)).toBe(true); + expect(table.props().Header).toEqual(CSVRTableHeader); + expect(table.props().Row).toEqual(CSVRTableRow); }); }); diff --git a/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion.spec.tsx b/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion.spec.tsx index 6dad34f13815..3bb5022b3589 100644 --- a/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion.spec.tsx +++ b/frontend/__tests__/components/operator-lifecycle-manager/clusterserviceversion.spec.tsx @@ -3,9 +3,9 @@ import { shallow, ShallowWrapper, mount, ReactWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; import * as _ from 'lodash-es'; -import { ClusterServiceVersionsDetailsPage, ClusterServiceVersionsDetailsPageProps, ClusterServiceVersionDetails, ClusterServiceVersionDetailsProps, ClusterServiceVersionsPage, ClusterServiceVersionsPageProps, ClusterServiceVersionList, ClusterServiceVersionHeader, ClusterServiceVersionRow, ClusterServiceVersionRowProps, CRDCard, CRDCardRow } from '../../../public/components/operator-lifecycle-manager/clusterserviceversion'; +import { ClusterServiceVersionsDetailsPage, ClusterServiceVersionsDetailsPageProps, ClusterServiceVersionDetails, ClusterServiceVersionDetailsProps, ClusterServiceVersionsPage, ClusterServiceVersionsPageProps, ClusterServiceVersionList, ClusterServiceVersionTableHeader, ClusterServiceVersionTableRow, ClusterServiceVersionTableRowProps, CRDCard, CRDCardRow } from '../../../public/components/operator-lifecycle-manager/clusterserviceversion'; import { ClusterServiceVersionKind, ClusterServiceVersionLogo, ClusterServiceVersionLogoProps, referenceForProvidedAPI, CSVConditionReason } from '../../../public/components/operator-lifecycle-manager'; -import { DetailsPage, ListPage, ListHeader, ColHead, List, ListInnerProps } from '../../../public/components/factory'; +import { DetailsPage, ListPage, TableInnerProps, Table, TableRow } from '../../../public/components/factory'; import { testClusterServiceVersion } from '../../../__mocks__/k8sResourcesMocks'; import { Timestamp, MsgBox, ResourceLink, ResourceKebab, ErrorBoundary, LoadingBox, ScrollToTopOnMount, SectionHeading } from '../../../public/components/utils'; import { referenceForModel } from '../../../public/module/k8s'; @@ -13,50 +13,27 @@ import { ClusterServiceVersionModel } from '../../../public/models'; import * as operatorLogo from '../../../public/imgs/operator.svg'; -describe(ClusterServiceVersionHeader.displayName, () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders `ListHeader`', () => { - expect(wrapper.find(ListHeader).exists()).toBe(true); - }); - - it('renders column header for app name and logo', () => { - expect(wrapper.find(ColHead).at(0).childAt(0).text()).toEqual('Name'); - expect(wrapper.find(ColHead).at(0).props().sortField).toEqual('metadata.name'); - }); - - it('renders column header for app namespace', () => { - expect(wrapper.find(ColHead).at(1).childAt(0).text()).toEqual('Namespace'); - }); - - it('renders column header for Operator deployment', () => { - expect(wrapper.find(ColHead).at(2).childAt(0).text()).toEqual('Deployment'); - }); - - it('renders column header for app status', () => { - expect(wrapper.find(ColHead).at(3).childAt(0).text()).toEqual('Status'); +describe(ClusterServiceVersionTableHeader.displayName, () => { + it('returns column header definition for cluster service version table header', () => { + expect(Array.isArray(ClusterServiceVersionTableHeader())); }); }); -describe(ClusterServiceVersionRow.displayName, () => { - let wrapper: ShallowWrapper; +describe(ClusterServiceVersionTableRow.displayName, () => { + let wrapper: ShallowWrapper; beforeEach(() => { - wrapper = shallow().childAt(0).shallow(); + wrapper = shallow().childAt(0).shallow(); }); it('renders a component wrapped in an `ErrorBoundary', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(ErrorBoundary).exists()).toBe(true); }); it('renders `ResourceKebab` with actions', () => { - const col = wrapper.find('.row'); + const col = wrapper.find(TableRow); expect(col.find(ResourceKebab).props().resource).toEqual(testClusterServiceVersion); expect(col.find(ResourceKebab).props().kind).toEqual(referenceForModel(ClusterServiceVersionModel)); @@ -64,14 +41,14 @@ describe(ClusterServiceVersionRow.displayName, () => { }); it('renders clickable column for app logo and name', () => { - const col = wrapper.find('.row').childAt(0); + const col = wrapper.find(TableRow).childAt(0); expect(col.find(Link).props().to).toEqual(`/k8s/ns/${testClusterServiceVersion.metadata.namespace}/${ClusterServiceVersionModel.plural}/${testClusterServiceVersion.metadata.name}`); expect(col.find(Link).find(ClusterServiceVersionLogo).exists()).toBe(true); }); it('renders column for app namespace link', () => { - const link = wrapper.find('.row').childAt(1).find(ResourceLink); + const link = wrapper.find(TableRow).childAt(1).find(ResourceLink); expect(link.props().kind).toEqual('Namespace'); expect(link.props().title).toEqual(testClusterServiceVersion.metadata.namespace); @@ -79,27 +56,27 @@ describe(ClusterServiceVersionRow.displayName, () => { }); it('renders column with link to Operator deployment', () => { - const col = wrapper.find('.row').childAt(2); + const col = wrapper.find(TableRow).childAt(2); expect(col.find(ResourceLink).props().kind).toEqual('Deployment'); expect(col.find(ResourceLink).props().name).toEqual(testClusterServiceVersion.spec.install.spec.deployments[0].name); }); it('renders column for app status', () => { - const col = wrapper.find('.row').childAt(3); + const col = wrapper.find(TableRow).childAt(3); expect(col.childAt(0).text()).toEqual(CSVConditionReason.CSVReasonInstallSuccessful); }); it('renders "disabling" status if CSV has `deletionTimestamp`', () => { wrapper = wrapper.setProps({obj: _.cloneDeepWith(testClusterServiceVersion, (v, k) => k === 'metadata' ? {...v, deletionTimestamp: Date.now()} : undefined)}); - const col = wrapper.find('.row').childAt(3); + const col = wrapper.find(TableRow).childAt(3); expect(col.childAt(0).text()).toEqual('Disabling'); }); it('renders column with each CRD provided by the Operator', () => { - const col = wrapper.find('.row').childAt(4); + const col = wrapper.find(TableRow).childAt(4); testClusterServiceVersion.spec.customresourcedefinitions.owned.forEach((desc, i) => { expect(col.find(Link).at(i).props().title).toEqual(desc.name); expect(col.find(Link).at(i).props().to).toEqual(`/k8s/ns/default/clusterserviceversions/testapp/${referenceForProvidedAPI(desc)}`); @@ -138,9 +115,8 @@ describe(ClusterServiceVersionList.displayName, () => { it('renders `List` with correct props', () => { const wrapper = shallow(); - - expect(wrapper.find(List).props().Row).toEqual(ClusterServiceVersionRow); - expect(wrapper.find(List).props().Header).toEqual(ClusterServiceVersionHeader); + expect(wrapper.find(Table).props().Row).toEqual(ClusterServiceVersionTableRow); + expect(wrapper.find(Table).props().Header).toEqual(ClusterServiceVersionTableHeader); }); }); diff --git a/frontend/__tests__/components/operator-lifecycle-manager/install-plan.spec.tsx b/frontend/__tests__/components/operator-lifecycle-manager/install-plan.spec.tsx index 655a0ab244d8..6b52b037bbd9 100644 --- a/frontend/__tests__/components/operator-lifecycle-manager/install-plan.spec.tsx +++ b/frontend/__tests__/components/operator-lifecycle-manager/install-plan.spec.tsx @@ -4,92 +4,66 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; import Spy = jasmine.Spy; -import { InstallPlanHeader, InstallPlanHeaderProps, InstallPlanRow, InstallPlanRowProps, InstallPlansList, InstallPlansListProps, InstallPlansPage, InstallPlansPageProps, InstallPlanDetailsPage, InstallPlanPreview, InstallPlanPreviewProps, InstallPlanPreviewState, InstallPlanDetailsPageProps, InstallPlanDetails, InstallPlanDetailsProps } from '../../../public/components/operator-lifecycle-manager/install-plan'; +import { InstallPlanTableHeader, InstallPlanTableRow, InstallPlanTableRowProps, InstallPlansList, InstallPlansListProps, InstallPlansPage, InstallPlansPageProps, InstallPlanDetailsPage, InstallPlanPreview, InstallPlanPreviewProps, InstallPlanPreviewState, InstallPlanDetailsPageProps, InstallPlanDetails, InstallPlanDetailsProps } from '../../../public/components/operator-lifecycle-manager/install-plan'; import { InstallPlanKind, InstallPlanApproval, referenceForStepResource } from '../../../public/components/operator-lifecycle-manager'; -import { ListHeader, ColHead, ResourceRow, List, MultiListPage, DetailsPage } from '../../../public/components/factory'; +import { Table, TableRow, MultiListPage, DetailsPage } from '../../../public/components/factory'; import { ResourceKebab, ResourceLink, ResourceIcon, Kebab, MsgBox } from '../../../public/components/utils'; import { testInstallPlan } from '../../../__mocks__/k8sResourcesMocks'; import { InstallPlanModel, ClusterServiceVersionModel, OperatorGroupModel, CustomResourceDefinitionModel } from '../../../public/models'; import * as k8s from '../../../public/module/k8s'; import * as modals from '../../../public/components/modals'; - -describe(InstallPlanHeader.displayName, () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders column header for install plan name', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(0).props().sortField).toEqual('metadata.name'); - expect(wrapper.find(ListHeader).find(ColHead).at(0).childAt(0).text()).toEqual('Name'); - }); - - it('renders column header for install plan namespace', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(1).props().sortField).toEqual('metadata.namespace'); - expect(wrapper.find(ListHeader).find(ColHead).at(1).childAt(0).text()).toEqual('Namespace'); - }); - - it('renders column header for install plan components', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(2).childAt(0).text()).toEqual('Components'); - }); - - it('renders column header for parent subscription(s)', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(3).childAt(0).text()).toEqual('Subscriptions'); - }); - - it('renders column header for install plan status', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(4).props().sortField).toEqual('status.phase'); - expect(wrapper.find(ListHeader).find(ColHead).at(4).childAt(0).text()).toEqual('Status'); +describe(InstallPlanTableHeader.displayName, () => { + it('returns column header definition for install plans', () => { + expect(Array.isArray(InstallPlanTableHeader())); }); }); -describe(InstallPlanRow.displayName, () => { - let wrapper: ShallowWrapper; +describe(InstallPlanTableRow.displayName, () => { + let wrapper: ShallowWrapper; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow(); }); it('renders resource kebab for performing common actions', () => { - expect(wrapper.find(ResourceRow).find(ResourceKebab).props().actions).toEqual(Kebab.factory.common); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions).toEqual(Kebab.factory.common); }); it('renders column for install plan name', () => { - expect(wrapper.find(ResourceRow).childAt(0).find(ResourceLink).props().kind).toEqual(k8s.referenceForModel(InstallPlanModel)); - expect(wrapper.find(ResourceRow).childAt(0).find(ResourceLink).props().namespace).toEqual(testInstallPlan.metadata.namespace); - expect(wrapper.find(ResourceRow).childAt(0).find(ResourceLink).props().name).toEqual(testInstallPlan.metadata.name); - expect(wrapper.find(ResourceRow).childAt(0).find(ResourceLink).props().title).toEqual(testInstallPlan.metadata.uid); + expect(wrapper.find(TableRow).childAt(0).find(ResourceLink).props().kind).toEqual(k8s.referenceForModel(InstallPlanModel)); + expect(wrapper.find(TableRow).childAt(0).find(ResourceLink).props().namespace).toEqual(testInstallPlan.metadata.namespace); + expect(wrapper.find(TableRow).childAt(0).find(ResourceLink).props().name).toEqual(testInstallPlan.metadata.name); + expect(wrapper.find(TableRow).childAt(0).find(ResourceLink).props().title).toEqual(testInstallPlan.metadata.uid); }); it('renders column for install plan namespace', () => { - expect(wrapper.find(ResourceRow).childAt(1).find(ResourceLink).props().kind).toEqual('Namespace'); - expect(wrapper.find(ResourceRow).childAt(1).find(ResourceLink).props().title).toEqual(testInstallPlan.metadata.namespace); - expect(wrapper.find(ResourceRow).childAt(1).find(ResourceLink).props().displayName).toEqual(testInstallPlan.metadata.namespace); + expect(wrapper.find(TableRow).childAt(1).find(ResourceLink).props().kind).toEqual('Namespace'); + expect(wrapper.find(TableRow).childAt(1).find(ResourceLink).props().title).toEqual(testInstallPlan.metadata.namespace); + expect(wrapper.find(TableRow).childAt(1).find(ResourceLink).props().displayName).toEqual(testInstallPlan.metadata.namespace); }); it('render column for install plan components list', () => { - expect(wrapper.find(ResourceRow).childAt(2).find(ResourceLink).props().kind).toEqual(k8s.referenceForModel(ClusterServiceVersionModel)); - expect(wrapper.find(ResourceRow).childAt(2).find(ResourceLink).props().name).toEqual(testInstallPlan.spec.clusterServiceVersionNames.toString()); - expect(wrapper.find(ResourceRow).childAt(2).find(ResourceLink).props().namespace).toEqual(testInstallPlan.metadata.namespace); + expect(wrapper.find(TableRow).childAt(2).find(ResourceLink).props().kind).toEqual(k8s.referenceForModel(ClusterServiceVersionModel)); + expect(wrapper.find(TableRow).childAt(2).find(ResourceLink).props().name).toEqual(testInstallPlan.spec.clusterServiceVersionNames.toString()); + expect(wrapper.find(TableRow).childAt(2).find(ResourceLink).props().namespace).toEqual(testInstallPlan.metadata.namespace); }); it('renders column for parent subscription(s) determined by `metadata.ownerReferences`', () => { - expect(wrapper.find(ResourceRow).childAt(3).find(ResourceLink).length).toEqual(1); + expect(wrapper.find(TableRow).childAt(3).find(ResourceLink).length).toEqual(1); }); it('renders column for install plan status', () => { - expect(wrapper.find(ResourceRow).childAt(4).text()).toEqual(testInstallPlan.status.phase); + expect(wrapper.find(TableRow).childAt(4).shallow().text()).toEqual(testInstallPlan.status.phase); }); it('renders column with fallback status if `status.phase` is undefined', () => { const obj = {..._.cloneDeep(testInstallPlan), status: null}; wrapper = wrapper.setProps({obj}); - expect(wrapper.find(ResourceRow).childAt(4).text()).toEqual('Unknown'); - expect(wrapper.find(ResourceRow).childAt(2).find(ResourceIcon).length).toEqual(1); - expect(wrapper.find(ResourceRow).childAt(2).find(ResourceIcon).at(0).props().kind).toEqual(k8s.referenceForModel(ClusterServiceVersionModel)); + expect(wrapper.find(TableRow).childAt(4).shallow().text()).toEqual('Unknown'); + expect(wrapper.find(TableRow).childAt(2).find(ResourceIcon).length).toEqual(1); + expect(wrapper.find(TableRow).childAt(2).find(ResourceIcon).at(0).props().kind).toEqual(k8s.referenceForModel(ClusterServiceVersionModel)); }); }); @@ -100,13 +74,13 @@ describe(InstallPlansList.displayName, () => { wrapper = shallow(); }); - it('renders a `List` component with the correct props', () => { - expect(wrapper.find(List).props().Header).toEqual(InstallPlanHeader); - expect(wrapper.find(List).props().Row).toEqual(InstallPlanRow); + it('renders a `Table` component with the correct props', () => { + expect(wrapper.find(Table).props().Header).toEqual(InstallPlanTableHeader); + expect(wrapper.find(Table).props().Row).toEqual(InstallPlanTableRow); }); - it('passes custom empty message for list', () => { - const EmptyMsg = wrapper.find(List).props().EmptyMsg; + it('passes custom empty message for table', () => { + const EmptyMsg = wrapper.find(Table).props().EmptyMsg; const msgWrapper = shallow(); expect(msgWrapper.find(MsgBox).props().title).toEqual('No Install Plans Found'); diff --git a/frontend/__tests__/components/operator-lifecycle-manager/subscription.spec.tsx b/frontend/__tests__/components/operator-lifecycle-manager/subscription.spec.tsx index 15d8abc98f6a..d35f7b7cc0d5 100644 --- a/frontend/__tests__/components/operator-lifecycle-manager/subscription.spec.tsx +++ b/frontend/__tests__/components/operator-lifecycle-manager/subscription.spec.tsx @@ -2,109 +2,85 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import * as _ from 'lodash'; -import { SubscriptionHeader, SubscriptionHeaderProps, SubscriptionRow, SubscriptionRowProps, SubscriptionsList, SubscriptionsListProps, SubscriptionsPage, SubscriptionsPageProps, SubscriptionDetails, SubscriptionDetailsPage, SubscriptionDetailsProps, SubscriptionUpdates, SubscriptionUpdatesProps, SubscriptionUpdatesState } from '../../../public/components/operator-lifecycle-manager/subscription'; +import { SubscriptionTableHeader, SubscriptionTableRow, SubscriptionTableRowProps, SubscriptionsList, SubscriptionsListProps, SubscriptionsPage, SubscriptionsPageProps, SubscriptionDetails, SubscriptionDetailsPage, SubscriptionDetailsProps, SubscriptionUpdates, SubscriptionUpdatesProps, SubscriptionUpdatesState } from '../../../public/components/operator-lifecycle-manager/subscription'; import { SubscriptionKind, SubscriptionState } from '../../../public/components/operator-lifecycle-manager'; import { referenceForModel } from '../../../public/module/k8s'; import { SubscriptionModel, ClusterServiceVersionModel, PackageManifestModel, OperatorGroupModel, InstallPlanModel } from '../../../public/models'; -import { ListHeader, ColHead, List, MultiListPage, DetailsPage } from '../../../public/components/factory'; +import { Table, TableRow, MultiListPage, DetailsPage } from '../../../public/components/factory'; import { ResourceKebab, ResourceLink, Kebab } from '../../../public/components/utils'; import { testSubscription, testClusterServiceVersion, testPackageManifest } from '../../../__mocks__/k8sResourcesMocks'; -describe(SubscriptionHeader.displayName, () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders column header for subscription name', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(0).props().sortField).toEqual('metadata.name'); - expect(wrapper.find(ListHeader).find(ColHead).at(0).childAt(0).text()).toEqual('Name'); - }); - - it('renders column header for namespace name', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(1).props().sortField).toEqual('metadata.namespace'); - expect(wrapper.find(ListHeader).find(ColHead).at(1).childAt(0).text()).toEqual('Namespace'); - }); - - it('renders column header for status', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(2).childAt(0).text()).toEqual('Status'); - }); - - it('renders column header for channel', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(3).childAt(0).text()).toEqual('Channel'); - }); - - it('renders column header for approval strategy', () => { - expect(wrapper.find(ListHeader).find(ColHead).at(4).childAt(0).text()).toEqual('Approval Strategy'); +describe(SubscriptionTableHeader.displayName, () => { + it('returns column header definition for subscription table header', () => { + expect(Array.isArray(SubscriptionTableHeader())); }); }); -describe(SubscriptionRow.displayName, () => { - let wrapper: ShallowWrapper; +describe(SubscriptionTableRow.displayName, () => { + let wrapper: ShallowWrapper; let subscription: SubscriptionKind; beforeEach(() => { subscription = {..._.cloneDeep(testSubscription), status: {installedCSV: 'testapp.v1.0.0'}}; - wrapper = shallow(); + wrapper = shallow(); }); it('renders column for subscription name', () => { - expect(wrapper.find('.co-resource-list__item').childAt(0).find(ResourceLink).props().name).toEqual(subscription.metadata.name); - expect(wrapper.find('.co-resource-list__item').childAt(0).find(ResourceLink).props().title).toEqual(subscription.metadata.name); - expect(wrapper.find('.co-resource-list__item').childAt(0).find(ResourceLink).props().namespace).toEqual(subscription.metadata.namespace); - expect(wrapper.find('.co-resource-list__item').childAt(0).find(ResourceLink).props().kind).toEqual(referenceForModel(SubscriptionModel)); + expect(wrapper.find(TableRow).childAt(0).shallow().find(ResourceLink).props().name).toEqual(subscription.metadata.name); + expect(wrapper.find(TableRow).childAt(0).shallow().find(ResourceLink).props().title).toEqual(subscription.metadata.name); + expect(wrapper.find(TableRow).childAt(0).shallow().find(ResourceLink).props().namespace).toEqual(subscription.metadata.namespace); + expect(wrapper.find(TableRow).childAt(0).shallow().find(ResourceLink).props().kind).toEqual(referenceForModel(SubscriptionModel)); }); it('renders actions kebab', () => { const menuArgs = [ClusterServiceVersionModel, subscription]; - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().kind).toEqual(referenceForModel(SubscriptionModel)); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().resource).toEqual(subscription); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().actions[0]).toEqual(Kebab.factory.Edit); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().actions[1](...menuArgs).label).toEqual('Remove Subscription...'); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().actions[1](...menuArgs).callback).toBeDefined(); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().actions[2](...menuArgs).label).toEqual(`View ${ClusterServiceVersionModel.kind}...`); - expect(wrapper.find('.co-resource-list__item').find(ResourceKebab).props().actions[2](...menuArgs).href).toEqual(`/k8s/ns/default/${ClusterServiceVersionModel.plural}/testapp.v1.0.0`); + expect(wrapper.find(TableRow).find(ResourceKebab).props().kind).toEqual(referenceForModel(SubscriptionModel)); + expect(wrapper.find(TableRow).find(ResourceKebab).props().resource).toEqual(subscription); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions[0]).toEqual(Kebab.factory.Edit); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions[1](...menuArgs).label).toEqual('Remove Subscription...'); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions[1](...menuArgs).callback).toBeDefined(); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions[2](...menuArgs).label).toEqual(`View ${ClusterServiceVersionModel.kind}...`); + expect(wrapper.find(TableRow).find(ResourceKebab).props().actions[2](...menuArgs).href).toEqual(`/k8s/ns/default/${ClusterServiceVersionModel.plural}/testapp.v1.0.0`); }); it('renders column for namespace name', () => { - expect(wrapper.find('.co-resource-list__item').childAt(1).find(ResourceLink).props().name).toEqual(subscription.metadata.namespace); - expect(wrapper.find('.co-resource-list__item').childAt(1).find(ResourceLink).props().title).toEqual(subscription.metadata.namespace); - expect(wrapper.find('.co-resource-list__item').childAt(1).find(ResourceLink).props().displayName).toEqual(subscription.metadata.namespace); - expect(wrapper.find('.co-resource-list__item').childAt(1).find(ResourceLink).props().kind).toEqual('Namespace'); + expect(wrapper.find(TableRow).childAt(1).shallow().find(ResourceLink).props().name).toEqual(subscription.metadata.namespace); + expect(wrapper.find(TableRow).childAt(1).shallow().find(ResourceLink).props().title).toEqual(subscription.metadata.namespace); + expect(wrapper.find(TableRow).childAt(1).shallow().find(ResourceLink).props().displayName).toEqual(subscription.metadata.namespace); + expect(wrapper.find(TableRow).childAt(1).shallow().find(ResourceLink).props().kind).toEqual('Namespace'); }); it('renders column for subscription state when update available', () => { subscription.status.state = SubscriptionState.SubscriptionStateUpgradeAvailable; wrapper = wrapper.setProps({obj: subscription}); - expect(wrapper.find('.co-resource-list__item').childAt(2).text()).toContain('Upgrade available'); + expect(wrapper.find(TableRow).childAt(2).shallow().text()).toContain('Upgrade available'); }); it('renders column for subscription state when unknown state', () => { - expect(wrapper.find('.co-resource-list__item').childAt(2).text()).toEqual('Unknown'); + expect(wrapper.find(TableRow).childAt(2).shallow().text()).toEqual('Unknown'); }); it('renders column for subscription state when update in progress', () => { subscription.status.state = SubscriptionState.SubscriptionStateUpgradePending; wrapper = wrapper.setProps({obj: subscription}); - expect(wrapper.find('.co-resource-list__item').childAt(2).text()).toContain('Upgrading'); + expect(wrapper.find(TableRow).childAt(2).shallow().text()).toContain('Upgrading'); }); it('renders column for subscription state when no updates available', () => { subscription.status.state = SubscriptionState.SubscriptionStateAtLatest; wrapper = wrapper.setProps({obj: subscription}); - expect(wrapper.find('.co-resource-list__item').childAt(2).text()).toContain('Up to date'); + expect(wrapper.find(TableRow).childAt(2).shallow().text()).toContain('Up to date'); }); it('renders column for current subscription channel', () => { - expect(wrapper.find('.co-resource-list__item').childAt(3).text()).toEqual(subscription.spec.channel); + expect(wrapper.find(TableRow).childAt(3).shallow().text()).toEqual(subscription.spec.channel); }); it('renders column for approval strategy', () => { - expect(wrapper.find('.co-resource-list__item').childAt(4).text()).toEqual('Automatic'); + expect(wrapper.find(TableRow).childAt(4).shallow().text()).toEqual('Automatic'); }); }); @@ -115,9 +91,9 @@ describe(SubscriptionsList.displayName, () => { wrapper = shallow(); }); - it('renders a `List` component with correct props', () => { - expect(wrapper.find(List).props().Header).toEqual(SubscriptionHeader); - expect(wrapper.find(List).props().Row).toEqual(SubscriptionRow); + it('renders a `Table` component with correct props', () => { + expect(wrapper.find(Table).props().Header).toEqual(SubscriptionTableHeader); + expect(wrapper.find(Table).props().Row).toEqual(SubscriptionTableRow); }); }); diff --git a/frontend/integration-tests/tests/monitoring.scenario.ts b/frontend/integration-tests/tests/monitoring.scenario.ts index 269741310682..2f8169ecf8c4 100644 --- a/frontend/integration-tests/tests/monitoring.scenario.ts +++ b/frontend/integration-tests/tests/monitoring.scenario.ts @@ -35,13 +35,13 @@ describe('Monitoring: Alerts', () => { it('filters Alerts by name', async() => { await monitoringView.wait(until.elementToBeClickable(crudView.nameFilter)); await crudView.nameFilter.sendKeys(testAlertName); - expect(monitoringView.firstListLink.getText()).toContain(testAlertName); + expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain(testAlertName); }); it('displays Alert details page', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLink)); - expect(monitoringView.firstListLink.getText()).toContain(testAlertName); - await monitoringView.firstListLink.click(); + await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('alert-resource-link'))); + expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain(testAlertName); + await monitoringView.firstListLinkById('alert-resource-link').click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingAlertIcon)); testDetailsPage('Alert Overview', testAlertName); }); @@ -81,11 +81,11 @@ describe('Monitoring: Alerts', () => { }); it('shows the newly created Silence in the Silenced By list', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLink)); - expect(monitoringView.firstListLink.getText()).toContain(testAlertName); + await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link'))); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); // Click the link to navigate back to the Silence details page - await monitoringView.firstListLink.click(); + await monitoringView.firstListLinkById('silence-resource-link').click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingSilenceIcon)); testDetailsPage('Silence Overview', testAlertName); }); @@ -134,13 +134,13 @@ describe('Monitoring: Silences', () => { await crudView.isLoaded(); await monitoringView.wait(until.elementToBeClickable(crudView.nameFilter)); await crudView.nameFilter.sendKeys(testAlertName); - expect(monitoringView.firstListLink.getText()).toContain(testAlertName); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); }); it('displays Silence details page', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLink)); - expect(monitoringView.firstListLink.getText()).toContain(testAlertName); - await monitoringView.firstListLink.click(); + await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link'))); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); + await monitoringView.firstListLinkById('silence-resource-link').click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingSilenceIcon)); testDetailsPage('Silence Overview', testAlertName); }); diff --git a/frontend/integration-tests/views/crud.view.ts b/frontend/integration-tests/views/crud.view.ts index e7d4b0604715..f5b939595aca 100644 --- a/frontend/integration-tests/views/crud.view.ts +++ b/frontend/integration-tests/views/crud.view.ts @@ -21,7 +21,7 @@ export const untilNoLoadersPresent = waitForNone($$('.co-m-loader')); export const isLoaded = () => browser.wait(until.and(untilNoLoadersPresent, untilLoadingBoxLoaded)).then(() => browser.sleep(1000)); export const resourceRowsPresent = () => browser.wait(until.presenceOf($('.co-m-resource-icon + a')), 10000); -export const resourceRows = $$('.co-resource-list__item'); +export const resourceRows = $$('.ReactVirtualized__VirtualGrid__innerScrollContainer tr'); export const resourceRowNamesAndNs = $$('.co-m-resource-icon + a'); export const rowForName = (name: string) => resourceRows.filter((row) => row.$$('.co-m-resource-icon + a').first().getText().then(text => text === name)).first(); export const rowForOperator = (name: string) => resourceRows.filter((row) => row.$('.co-clusterserviceversion-logo__name__clusterserviceversion').getText().then(text => text === name)).first(); diff --git a/frontend/integration-tests/views/monitoring.view.ts b/frontend/integration-tests/views/monitoring.view.ts index 4a585cf44b24..cf0e373d70d0 100644 --- a/frontend/integration-tests/views/monitoring.view.ts +++ b/frontend/integration-tests/views/monitoring.view.ts @@ -4,7 +4,7 @@ export const wait = async(condition) => await browser.wait(condition, 15000); // List pages export const listPageHeading = $('.co-m-pane__heading'); -export const firstListLink = $$('.co-resource-list__item a.co-resource-item__resource-name').first(); +export const firstListLinkById = (id: string) => $(`[data-test-id=${id}]`); export const createButton = $('.co-m-pane__filter-bar-group button'); // Details pages diff --git a/frontend/package.json b/frontend/package.json index 631247dbc9fa..873c154b5c31 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,9 @@ "dependencies": { "@patternfly/patternfly": "2.8.2", "@patternfly/react-charts": "3.6.6", - "@patternfly/react-core": "3.33.1", + "@patternfly/react-core": "3.38.1", + "@patternfly/react-table": "2.11.1", + "@patternfly/react-virtualized-extension": "1.0.5", "brace": "0.11.x", "classnames": "2.x", "core-js": "2.x", diff --git a/frontend/public/components/RBAC/bindings.jsx b/frontend/public/components/RBAC/bindings.jsx index 1c852ff32754..e7baff8df923 100644 --- a/frontend/public/components/RBAC/bindings.jsx +++ b/frontend/public/components/RBAC/bindings.jsx @@ -3,11 +3,12 @@ import * as React from 'react'; import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { ClusterRoleBindingModel } from '../../models'; import { getQN, k8sCreate, k8sPatch, referenceFor } from '../../module/k8s'; import * as UIActions from '../../actions/ui'; -import { ColHead, List, ListHeader, MultiListPage, ResourceRow } from '../factory'; +import { MultiListPage, Table, TableRow, TableData } from '../factory'; import { RadioGroup } from '../radio'; import { confirmModal } from '../modals'; import { @@ -107,13 +108,43 @@ const menuActions = ({subjectIndex, subjects}, startImpersonate) => { return actions; }; -const Header = props => - Name - Role Ref - Subject Kind - Subject Name - Namespace -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + Kebab.columnClass, +]; + +const RoleBindingsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Role Ref', sortField: 'roleRef.name', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Subject Kind', sortField: 'subject.kind', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Subject Name', sortField: 'subject.name', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +RoleBindingsTableHeader.displayName = 'RoleBindingsTableHeader'; export const BindingName = ({binding}) => { ; @@ -131,30 +162,35 @@ export const RoleLink = ({binding}) => { return ; }; -const Row = ({obj: binding}) => -
- -
-
- -
-
- {binding.subject.kind} -
-
- {binding.subject.name} -
-
- {binding.metadata.namespace ? : 'all'} -
-
- -
-
; +const RoleBindingsTableRow = ({obj: binding, index, key, style}) => { + return ( + + + + + + + + + {binding.subject.kind} + + + {binding.subject.name} + + + {binding.metadata.namespace ? : 'all'} + + + + + + ); +}; +RoleBindingsTableRow.displayName = 'RoleBindingsTableRow'; const EmptyMsg = () => ; -export const BindingsList = props => ; +export const BindingsList = props => ; export const bindingType = binding => { if (!binding) { diff --git a/frontend/public/components/RBAC/role.jsx b/frontend/public/components/RBAC/role.jsx index cfd75157986f..5a7c7aec52a8 100644 --- a/frontend/public/components/RBAC/role.jsx +++ b/frontend/public/components/RBAC/role.jsx @@ -2,11 +2,12 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import * as fuzzy from 'fuzzysearch'; // import { Link } from 'react-router-dom'; - import { RoleModel } from '../../models'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { flatten as bindingsFlatten } from './bindings'; import { BindingName, BindingsList, RulesList } from './index'; -import { ColHead, DetailsPage, List, ListHeader, MultiListPage, ResourceRow, TextFilter } from '../factory'; +import { DetailsPage, MultiListPage, TextFilter, Table, TableRow, TableData } from '../factory'; import { Kebab, SectionHeading, MsgBox, navFactory, ResourceKebab, ResourceLink, Timestamp } from '../utils'; export const isSystemRole = role => _.startsWith(role.metadata.name, 'system:'); @@ -29,22 +30,44 @@ const menuActions = [ Kebab.factory.Delete, ]; -const Header = props => - Name - Namespace -; - -const Row = ({obj: role}) =>
-
- -
-
- {role.metadata.namespace ? : 'all'} -
-
- -
-
; +const roleColumnClasses = [ + classNames('pf-m-6-col-on-sm'), + classNames('pf-m-6-col-on-sm'), + Kebab.columnClass, +]; + + +const RolesTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: roleColumnClasses[0]}, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: roleColumnClasses[1]}, + }, + { title: '', props: { className: roleColumnClasses[2]}}, + ]; +}; +RolesTableHeader.displayName = 'RolesTableHeader'; + +const RolesTableRow = ({obj: role, index, key, style}) => { + return ( + + + + + + {role.metadata.namespace ? : 'all'} + + + + + + ); +}; +RolesTableRow.displayName = 'RolesTableRow'; class Details extends React.Component { constructor(props) { @@ -107,29 +130,55 @@ class Details extends React.Component { } } -const BindingHeader = props => - Name - Subject Kind - Subject Name - Namespace -; - -const BindingRow = ({obj: binding}) => -
- -
-
- {binding.subject.kind} -
-
- {binding.subject.name} -
-
- {binding.namespace || 'all'} -
-
; - -const BindingsListComponent = props => ; +const bindingsColumnClasses = [ + classNames('pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-sm'), + classNames('pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-sm'), +]; + +const BindingsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: bindingsColumnClasses[0]}, + }, + { + title: 'Subject Kind', sortField: 'subject.kind', transforms: [sortable], + props: { className: bindingsColumnClasses[1]}, + }, + { + title: 'Subject Name', sortField: 'subject.name', transforms: [sortable], + props: { className: bindingsColumnClasses[2]}, + }, + { title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: bindingsColumnClasses[3]}, + }, + ]; +}; +BindingsTableHeader.displayName = 'BindingsTableHeader'; + +const BindingsTableRow = ({obj: binding, index, key, style}) => { + return ( + + + + + + {binding.subject.kind} + + + {binding.subject.name} + + + {binding.namespace || 'all'} + + + ); +}; +BindingsTableRow.displayName = 'BindingsTableRow'; + +const BindingsListComponent = props => ; export const BindingsForRolePage = (props) => { const {match: {params: {name, ns}}, obj:{kind}} = props; @@ -159,7 +208,7 @@ export const ClusterRolesDetailsPage = RolesDetailsPage; const EmptyMsg = () => ; -const RolesList = props => ; +const RolesList = props =>
; export const roleType = role => { if (!role) { diff --git a/frontend/public/components/alert-manager.tsx b/frontend/public/components/alert-manager.tsx index 6762548b7dee..421cd7cac7f6 100644 --- a/frontend/public/components/alert-manager.tsx +++ b/frontend/public/components/alert-manager.tsx @@ -1,8 +1,9 @@ import * as _ from 'lodash-es'; import * as React from 'react'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { referenceForModel, K8sResourceKind } from '../module/k8s'; -import { ColHead, List, ListHeader, ListPage, ResourceRow, DetailsPage } from './factory'; +import { ListPage, DetailsPage, Table, TableRow, TableData } from './factory'; import { SectionHeading, LabelList, navFactory, ResourceLink, Selector, Firehose, LoadingInline, pluralize } from './utils'; import { configureReplicaCountModal } from './modals'; import { AlertmanagerModel } from '../models'; @@ -83,37 +84,72 @@ export const AlertManagersListContainer = props => ; -const AlertManagerRow = ({obj: alertManager}) => { +const tableColumnClasses = [ + classNames('pf-m-2-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-3-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), +]; + +const AlertManagerTableRow: React.FC = ({obj: alertManager, index, key, style}) => { const {metadata, spec} = alertManager; + return ( + + + + + + + + + + + + {spec.version} + + + + + + ); +}; +AlertManagerTableRow.displayName = 'AlertManagerTableRow'; +type AlertManagerTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; - return -
- -
-
- -
-
- -
-
{spec.version}
-
- -
-
; +const AlertManagerTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Version', sortField: 'spec.version', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Node Selector', sortField: 'spec.nodeSelector', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + ]; }; +AlertManagerTableHeader.displayName = 'AlertManagerTableHeader'; -const AlertManagerHeader = props => - Name - Namespace - Labels - Version - - Node Selector - -; +export const AlertManagersList = props =>
; -export const AlertManagersList = props => ; export const AlertManagersPage = props => ; type DetailsProps = { diff --git a/frontend/public/components/build-config.tsx b/frontend/public/components/build-config.tsx index 19b0b2d0a943..f601bf84640e 100644 --- a/frontend/public/components/build-config.tsx +++ b/frontend/public/components/build-config.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import * as _ from 'lodash-es'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { K8sResourceKind, K8sResourceKindReference, referenceFor } from '../module/k8s'; import { startBuild } from '../module/k8s/builds'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { errorModal } from './modals'; import { BuildHooks, @@ -86,30 +87,67 @@ export const BuildConfigsDetailsPage: React.SFC = pages={pages} />; BuildConfigsDetailsPage.displayName = 'BuildConfigsDetailsPage'; -const BuildConfigsHeader = props => - Name - Namespace - Labels - Created -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; -const BuildConfigsRow: React.SFC = ({obj}) =>
-
- -
-
- -
-
- -
-
- { fromNow(obj.metadata.creationTimestamp) } -
-
- -
-
; +const BuildConfigsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; +}; +BuildConfigsTableHeader.displayName = 'BuildConfigsTableHeader'; + +const BuildConfigsTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + { fromNow(obj.metadata.creationTimestamp) } + + + + + + ); +}; +BuildConfigsTableRow.displayName = 'BuildConfigsTableRow'; +type BuildConfigsTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; const buildStrategy = (buildConfig: K8sResourceKind): BuildStrategyType => buildConfig.spec.strategy.type; @@ -124,7 +162,8 @@ const filters = [{ })), }]; -export const BuildConfigsList: React.SFC = props => ; +export const BuildConfigsList: React.SFC = props =>
; + BuildConfigsList.displayName = 'BuildConfigsList'; export const BuildConfigsPage: React.SFC = props => @@ -138,10 +177,6 @@ export const BuildConfigsPage: React.SFC = props => rowFilters={filters} />; BuildConfigsPage.displayName = 'BuildConfigsListPage'; -export type BuildConfigsRowProps = { - obj: any; -}; - export type BuildConfigsDetailsProps = { obj: any; }; diff --git a/frontend/public/components/build.tsx b/frontend/public/components/build.tsx index ab4927272f6b..2d58bb7c0762 100644 --- a/frontend/public/components/build.tsx +++ b/frontend/public/components/build.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { Link } from 'react-router-dom'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { K8sResourceKindReference, referenceFor, K8sResourceKind, k8sPatch, K8sKind } from '../module/k8s'; import { cloneBuild, formatBuildDuration, getBuildNumber } from '../module/k8s/builds'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { errorModal, confirmModal } from './modals'; import { AsyncComponent, @@ -250,32 +251,70 @@ export const BuildsDetailsPage: React.SFC = props => pages={pages} />; BuildsDetailsPage.displayName = 'BuildsDetailsPage'; -const BuildsHeader = props => - Name - Namespace - Status - Created -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const BuildsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Status', sortField: 'status.phase', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; +}; +BuildsTableHeader.displayName = 'BuildsTableHeader'; + +const BuildsTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + {fromNow(obj.metadata.creationTimestamp)} + + + + + + ); +}; +BuildsTableRow.displayName = 'BuildsTableRow'; +type BuildsTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; -const BuildsRow: React.SFC = ({ obj }) =>
-
- -
-
- -
-
- -
-
- {fromNow(obj.metadata.creationTimestamp)} -
-
- -
-
; +export const BuildsList: React.SFC = props =>
; -export const BuildsList: React.SFC = props => ; BuildsList.displayName = 'BuildsList'; export const buildPhase = build => build.status.phase; @@ -302,10 +341,6 @@ export const BuildsPage: React.SFC = props => />; BuildsPage.displayName = 'BuildsListPage'; -export type BuildsRowProps = { - obj: any, -}; - export type BuildsDetailsProps = { obj: any, }; diff --git a/frontend/public/components/chargeback.tsx b/frontend/public/components/chargeback.tsx index dfb1e7005cad..76bb81e0134e 100644 --- a/frontend/public/components/chargeback.tsx +++ b/frontend/public/components/chargeback.tsx @@ -1,11 +1,11 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import * as classNames from 'classnames'; - +import { sortable } from '@patternfly/react-table'; import { connectToFlags, flagPending } from '../reducers/features'; import { FLAGS } from '../const'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; import { Conditions } from './conditions'; +import { ColHead, DetailsPage, List, ListHeader, ListPage, Table, TableRow, TableData } from './factory'; import { getQueryArgument, setQueryArgument } from './utils/router'; import { coFetchJSON } from '../co-fetch'; import { ChargebackReportModel } from '../models'; @@ -17,6 +17,7 @@ import { } from './utils/status-box'; import { GroupVersionKind, + K8sResourceKind, modelFor, referenceForModel, } from '../module/k8s'; @@ -55,28 +56,76 @@ const ChargebackNavBar: React.SFC<{match: {url: string}}> = props =>
; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-sm'), + classNames('pf-m-3-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-2-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-2-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; -const ReportsHeader = props => - Name - Namespace - Report Query - Reporting Start - Reporting End -; - -const ReportsRow: React.SFC = ({obj}) => { - return
-
- -
-
-
-
-
-
- -
-
; +const ReportsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Report Query', props: { className: tableColumnClasses[2] }, + }, + { + title: 'Reporting Start', sortField: 'spec.reportingStart', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Reporting End', sortField: 'spec.reportingEnd', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +ReportsTableHeader.displayName = 'ReportsTableHeader'; + +const ReportsTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + {_.get(obj, ['status', 'phase'])} + + + + + + + + + + + + ); +}; +ReportsTableRow.displayName = 'ReportsTableRow'; +type ReportsTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; }; class ReportsDetails extends React.Component { @@ -279,7 +328,8 @@ const reportsPages = [ ]; const EmptyMsg = () => ; -export const ReportsList: React.SFC = props => ; + +export const ReportsList: React.SFC = props =>
; const ReportsPage_: React.SFC = props => { if (flagPending(props.flags[FLAGS.CHARGEBACK])) { @@ -387,10 +437,6 @@ export const ReportGenerationQueriesDetailsPage: React.SFC; }; -export type ReportsRowProps = { - obj: any, -}; - export type ReportsDetailsProps = { obj: any, }; @@ -443,7 +489,6 @@ export type ReportGenerationQueriesDetailsPageProps = { match: any, }; -ReportsRow.displayName = 'ReportsRow'; ReportsList.displayName = 'ReportsList'; ReportsPage.displayName = 'ReportsPage'; ReportsDetailsPage.displayName = 'ReportsDetailsPage'; diff --git a/frontend/public/components/cluster-service-broker.tsx b/frontend/public/components/cluster-service-broker.tsx index 3863be3581d9..b089bce3fcec 100644 --- a/frontend/public/components/cluster-service-broker.tsx +++ b/frontend/public/components/cluster-service-broker.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, SectionHeading, detailsPage, navFactory, ResourceLink, ResourceKebab, ResourceSummary, StatusWithIcon, Timestamp, ExternalLink } from './utils'; import { K8sResourceKind, referenceForModel } from '../module/k8s'; import { ClusterServiceBrokerModel } from '../models'; @@ -8,30 +10,67 @@ import { ClusterServiceClassPage } from './cluster-service-class'; const menuActions = Kebab.factory.common; -const ClusterServiceBrokerHeader: React.SFC = props => - Name - Status - Relist Behavior - Last Retrieved -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; -const ClusterServiceBrokerListRow: React.SFC = ({obj: serviceBroker}) => -
- -
-
- -
-
- {serviceBroker.spec.relistBehavior} -
-
- -
-
- -
-
; +const ClusterServiceBrokerTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Status', sortFunc: 'serviceCatalogStatus', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Relist Behavior', sortField: 'spec.relistBehavior', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Last Retrieved', sortField: 'status.lastCatalogRetrievalTime', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; +}; +ClusterServiceBrokerTableHeader.displayName = 'ClusterServiceBrokerTableHeader'; + +const ClusterServiceBrokerTableRow: React.FC = ({obj: serviceBroker, index, key, style}) => { + return ( + + + + + + + + + {serviceBroker.spec.relistBehavior} + + + + + + + + + ); +}; +ClusterServiceBrokerTableRow.displayName = 'ClusterServiceBrokerTableRow'; +type ClusterServiceBrokerTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; const ClusterServiceBrokerDetails: React.SFC = ({obj: serviceBroker}) => { return @@ -81,7 +120,7 @@ export const ClusterServiceBrokerDetailsPage: React.SFC; -export const ClusterServiceBrokerList: React.SFC = props => ; +export const ClusterServiceBrokerList: React.SFC = props =>
; export const ClusterServiceBrokerPage: React.SFC = props => showTitle={false} />; -export type ClusterServiceBrokerRowProps = { - obj: K8sResourceKind -}; - -export type ClusterServiceBrokerHeaderProps = { - obj: K8sResourceKind -}; - export type ClusterServiceBrokerPageProps = { obj: K8sResourceKind }; diff --git a/frontend/public/components/cluster-service-class.tsx b/frontend/public/components/cluster-service-class.tsx index 04a586f452c0..9949ab64bab6 100644 --- a/frontend/public/components/cluster-service-class.tsx +++ b/frontend/public/components/cluster-service-class.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import * as _ from 'lodash-es'; - -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { history, SectionHeading, detailsPage, navFactory, ResourceSummary, resourcePathFromModel, ResourceLink } from './utils'; import { viewYamlComponent } from './utils/horizontal-nav'; import { ClusterServiceClassModel, ClusterServiceBrokerModel } from '../models'; @@ -27,27 +28,55 @@ const actionButtons = [ createInstance, ]; -const ClusterServiceClassHeader: React.SFC = props => - Display Name - External Name - Broker -; +const tableColumnClasses = [ + classNames('pf-m-6-col-on-md', 'pf-m-12-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), +]; + +const ClusterServiceClassTableHeader = () => { + return [ + { + title: 'Display Name', sortFunc: 'serviceClassDisplayName', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'External Name', sortField: 'spec.externalName', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Broker', sortField: 'spec.clusterServiceBrokerName', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + ]; +}; +ClusterServiceClassTableHeader.displayName = 'ClusterServiceClassTableHeader'; -const ClusterServiceClassListRow: React.SFC = ({obj: serviceClass}) => { +const ClusterServiceClassTableRow: React.FC = ({obj: serviceClass, index, key, style}) => { const path = resourcePathFromModel(ClusterServiceClassModel, serviceClass.metadata.name); - return -
- - {serviceClassDisplayName(serviceClass)} -
-
- {serviceClass.spec.externalName} -
-
- -
-
; + return ( + + + + {serviceClassDisplayName(serviceClass)} + + + {serviceClass.spec.externalName} + + + + + + ); }; +ClusterServiceClassTableRow.displayName = 'ClusterServiceClassTableRow'; +type ClusterServiceClassTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; + const ClusterServiceClassDetails: React.SFC = ({obj: serviceClass}) =>
@@ -79,7 +108,7 @@ export const ClusterServiceClassDetailsPage: React.SFC)]} />; -export const ClusterServiceClassList: React.SFC = props => ; +export const ClusterServiceClassList: React.SFC = props =>
; export const ClusterServiceClassPage: React.SFC = props => = canCreate={false} />; -export type ClusterServiceClassRowProps = { - obj: K8sResourceKind -}; - -export type ClusterServiceClassHeaderProps = { - obj: K8sResourceKind -}; - export type ClusterServiceClassPageProps = { showTitle?: boolean, fieldSelector?: string diff --git a/frontend/public/components/cluster-service-plan.tsx b/frontend/public/components/cluster-service-plan.tsx index 4a943c1232ec..d32fb3f4b864 100644 --- a/frontend/public/components/cluster-service-plan.tsx +++ b/frontend/public/components/cluster-service-plan.tsx @@ -1,28 +1,58 @@ import * as React from 'react'; - -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { SectionHeading, detailsPage, navFactory, ResourceLink, ResourceSummary } from './utils'; import { K8sResourceKind, referenceForModel, servicePlanDisplayName } from '../module/k8s'; import { ClusterServicePlanModel, ClusterServiceBrokerModel, ClusterServiceClassModel } from '../models'; import { viewYamlComponent } from './utils/horizontal-nav'; -const ClusterServicePlanHeader: React.SFC = props => - Name - External Name - Broker -; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), +]; + +const ClusterServicePlanTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'External Name', sortField: 'spec.externalName', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Broker', sortField: 'spec.clusterServiceBrokerName', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + ]; +}; +ClusterServicePlanTableHeader.displayName = 'ClusterServicePlanTableHeader'; -const ClusterServicePlanListRow: React.SFC = ({obj: servicePlan}) => -
- -
-
- {servicePlan.spec.externalName} -
-
- -
-
; +const ClusterServicePlanTableRow: React.FC = ({obj: servicePlan, index, key, style}) => { + return ( + + + + + + {servicePlan.spec.externalName} + + + + + + ); +}; +ClusterServicePlanTableRow.displayName = 'ClusterServicePlanTableRow'; +type ClusterServicePlanTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; const ClusterServicePlanDetails: React.SFC = ({obj: servicePlan}) => { return
@@ -58,7 +88,8 @@ export const ClusterServicePlanDetailsPage: React.SFC; -export const ClusterServicePlanList: React.SFC = props => ; + +export const ClusterServicePlanList: React.SFC = props =>
; export const ClusterServicePlanPage: React.SFC = props => = pr canCreate={false} />; -export type ClusterServicePlanRowProps = { - obj: K8sResourceKind -}; - -export type ClusterServicePlanHeaderProps = { - obj: K8sResourceKind -}; - export type ClusterServicePlanPageProps = { showTitle?: boolean, fieldSelector?: string diff --git a/frontend/public/components/cluster-settings/cluster-operator.tsx b/frontend/public/components/cluster-settings/cluster-operator.tsx index 7d9bab00ec49..91bc2dbb327f 100644 --- a/frontend/public/components/cluster-settings/cluster-operator.tsx +++ b/frontend/public/components/cluster-settings/cluster-operator.tsx @@ -1,14 +1,15 @@ import * as React from 'react'; import * as _ from 'lodash-es'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { ClusterOperatorModel } from '../../models'; import { StartGuide } from '../start-guide'; import { - ColHead, DetailsPage, - List, - ListHeader, ListPage, + Table, + TableRow, + TableData, } from '../factory'; import { Conditions } from '../conditions'; import { @@ -24,6 +25,7 @@ import { import { navFactory, EmptyBox, + Kebab, ResourceLink, ResourceSummary, SectionHeading, @@ -46,33 +48,64 @@ const OperatorStatusIconAndLabel: React.SFC = ( return
+ ); +}; +TableData.displayName = 'TableData'; +export type TableDataProps = { + className?: string; +} + +const TableWrapper: React.SFC = ({virtualize, ariaLabel, ariaRowCount, ...props}) => { + return virtualize ? ( +
+ ) : ( + + ); +}; +export type TableWrapperProps = { + virtualize: boolean; + ariaLabel: string; + ariaRowCount: number | undefined; +} + +const VirtualBody: React.SFC = (props) => { + const { customData, Row, height, isScrolling, onChildScroll, data, columns, scrollTop, width } = props; + + const cellMeasurementCache = new CellMeasurerCache({ + fixedWidth: true, + minHeight: 44, + keyMapper: rowIndex => { + const uid = _.get(props.data[rowIndex], 'metadata.uid', rowIndex); + return `${uid}-${props.expand ? 'expanded' : 'collapsed'}`; + }, + }); + + const rowRenderer = ({index, isScrolling: scrolling, isVisible, key, style, parent}) => { + const rowArgs = {obj: data[index], index, columns, isScrolling: scrolling, key, style, customData}; + const row = (Row as RowFunction)(rowArgs as RowFunctionArgs); + + // do not render non visible elements (this excludes overscan) + if (!isVisible){ + return null; + } + return {row}; + }; + + return ( + + ); +}; + +export type RowFunctionArgs = {obj: object, index: number, columns: [], isScrolling: boolean, key: string, style: object, customData?: object}; +export type RowFunction = (RowFunctionArgs) => JSX.Element; + +export type VirtualBodyProps = { + customData?: object; + Row: RowFunction | React.ComponentClass | React.ComponentType; + height: number; + isScrolling: boolean; + onChildScroll: (...args) => any; + data: any[]; + columns: any[]; + scrollTop: number; + width: number; + expand: boolean; +} + +type TableOwnProps = { + customData?: object; + data?: any[]; + defaultSortFunc?: string; + defaultSortField?: string; + filters?: {[key: string]: any}; + Header: (...args) => any[]; + loadError?: string | Object; + Row?: RowFunction | React.ComponentClass | React.ComponentType; + Rows?: (...args)=> any[]; + 'aria-label': string; + virtualize?: boolean; + EmptyMsg?: React.ComponentType<{}>; + loaded?: boolean; + reduxID?: string; + reduxIDs?: string[]; +} + +type TablePropsFromState = {}; + +type TablePropsFromDispatch = {}; + +type TableOptionProps = { + UI: any; +} + +export const Table = connect( + stateToProps, + {sortList: UIActions.sortList}, + null, + {areStatesEqual: ({UI: next}, {UI: prev}) => next.get('listSorts') === prev.get('listSorts')} +)( + class TableInner extends React.Component { + static propTypes = { + customData: PropTypes.object, + data: PropTypes.array, + EmptyMsg: PropTypes.func, + expand: PropTypes.bool, + fieldSelector: PropTypes.string, + filters: filterPropType, + Header: PropTypes.func.isRequired, + Row: PropTypes.func, + Rows: PropTypes.func, + loaded: PropTypes.bool, + loadError: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + mock: PropTypes.bool, + namespace: PropTypes.string, + reduxID: PropTypes.string, + reduxIDs: PropTypes.array, + selector: PropTypes.object, + staticFilters: PropTypes.array, + virtualize: PropTypes.bool, + currentSortField: PropTypes.string, + currentSortFunc: PropTypes.string, + currentSortOrder: PropTypes.any, + defaultSortField: PropTypes.string, + defaultSortFunc: PropTypes.string, + label: PropTypes.string, + listId: PropTypes.string, + sortList: PropTypes.func, + onSelect: PropTypes.func, + }; + _columnShift: number; + + constructor(props){ + super(props); + const componentProps: any = _.pick(props, ['data', 'filters', 'selected', 'match', 'kindObj']); + const columns = props.Header(componentProps); + //sort by first column + this.state = { + columns, + sortBy: {}, + }; + this._applySort = this._applySort.bind(this); + this._onSort = this._onSort.bind(this); + this._handleResize = this._handleResize.bind(this); + this._columnShift = props.onSelect ? 1 : 0; //shift indexes by 1 if select provided + } + + componentDidMount(){ + const {columns} = this.state; + const sp = new URLSearchParams(window.location.search); + const columnIndex = _.findIndex(columns, {title: sp.get('sortBy')}); + + if (columnIndex > -1){ + const sortOrder = sp.get('orderBy') || SortByDirection.asc; + const column = columns[columnIndex]; + this._applySort(column.sortField, column.sortFunc, sortOrder, column.title); + this.setState({ + sortBy: { + index: columnIndex + this._columnShift, + direction: sortOrder, + }, + }); + } + + // re-render after resize + window.addEventListener('resize', this._handleResize); + } + + componentWillUnmount(){ + window.removeEventListener('resize', this._handleResize); + } + + _handleResize() { + this.forceUpdate(); + } + + _applySort(sortField, sortFunc, direction, columnTitle){ + const {sortList, listId, currentSortFunc} = this.props; + const applySort = _.partial(sortList, listId); + applySort(sortField, sortFunc || currentSortFunc, direction, columnTitle); + } + + _onSort(_event, index, direction){ + const sortColumn = this.state.columns[index - this._columnShift]; + this._applySort(sortColumn.sortField, sortColumn.sortFunc, direction, sortColumn.title); + this.setState({ + sortBy: { + index, + direction, + }, + }); + } + + render() { + const {Rows, Row, expand, label, mock, onSelect, selectedResourcesForKind, 'aria-label': ariaLabel, virtualize = true, customData} = this.props; + const {sortBy, columns} = this.state; + const componentProps: any = _.pick(this.props, ['data', 'filters', 'selected', 'match', 'kindObj']); + const ariaRowCount = componentProps.data && componentProps.data.length; + + const children = mock ? : ( + + + + {!virtualize && ( + + )} + + {virtualize && ( + + {({height, isScrolling, registerChild, onChildScroll, scrollTop}) => ( + + {({width}) => ( +
+ +
+ )} +
+ )} +
+ )} +
+ ); + return
+ { mock ? children : {children} } +
; + } + }); + + +export type TableInnerProps = { + 'aria-label': string; + customData?: object; + currentSortField?: string; + currentSortFunc?: string; + currentSortOrder?: any; + data?: any[]; + defaultSortField?: string; + defaultSortFunc?: string; + EmptyMsg?: React.ComponentType<{}>; + expand?: boolean; + fieldSelector?: string; + filters?: {[name: string]: any}; + Header: (...args) => any[]; + label?: string; + listId?: string; + loaded?: boolean; + loadError?: string | Object; + mock?: boolean; + namespace?: string; + reduxID?: string; + reduxIDs?: string[]; + Row?: RowFunction | React.ComponentClass | React.ComponentType; + Rows?: (...args)=> any[]; + selector?: Object; + sortList?: (listId: string, field: string, func: any, orderBy: string, column: string) => any; + selectedResourcesForKind?: string[]; + onSelect?: (event: React.MouseEvent, isSelected: boolean, rowIndex: number, rowData: IRowData, extraData: IExtraData) => void; + staticFilters?: any[]; + virtualize?: boolean; +}; + +export type TableInnerState = { + columns?: any[]; + sortBy: object; +}; diff --git a/frontend/public/components/hpa.tsx b/frontend/public/components/hpa.tsx index 2fcb3b5dda8b..6f7158c23f71 100644 --- a/frontend/public/components/hpa.tsx +++ b/frontend/public/components/hpa.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import * as _ from 'lodash-es'; - -import { K8sResourceKindReference } from '../module/k8s'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import { K8sResourceKind, K8sResourceKindReference } from '../module/k8s'; import { Conditions } from './conditions'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, SectionHeading, LabelList, navFactory, ResourceKebab, ResourceLink, ResourceSummary, Timestamp } from './utils'; import { ResourceEventStream } from './events'; @@ -170,40 +171,87 @@ export const HorizontalPodAutoscalersDetailsPage: React.SFC; HorizontalPodAutoscalersDetailsPage.displayName = 'HorizontalPodAutoscalersDetailsPage'; -const HorizontalPodAutoscalersHeader = props => - Name - Namespace - Labels - Scale Target - Min Pods - Max Pods -; - -const HorizontalPodAutoscalersRow: React.SFC = ({obj}) =>
-
- -
-
- -
-
- -
-
- -
-
- {obj.spec.minReplicas} -
-
- {obj.spec.maxReplicas} -
-
- -
-
; +const tableColumnClasses = [ + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + Kebab.columnClass, +]; -const HorizontalPodAutoscalersList: React.SFC = props => ; +const kind = 'HorizontalPodAutoscaler'; + +const HorizontalPodAutoscalersTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Scale Target', sortField: 'spec.scaleTargetRef.name', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Min Pods', sortField: 'spec.minReplicas', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + { + title: 'Max Pods', sortField: 'spec.maxReplicas', transforms: [sortable], + props: { className: tableColumnClasses[5] }, + }, + { + title: '', props: { className: tableColumnClasses[6] }, + }, + ]; +}; +HorizontalPodAutoscalersTableHeader.displayName = 'HorizontalPodAutoscalersTableHeader'; + +const HorizontalPodAutoscalersTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + + + + {obj.spec.minReplicas} + + + {obj.spec.maxReplicas} + + + + + + ); +}; +HorizontalPodAutoscalersTableRow.displayName = 'HorizontalPodAutoscalersTableRow'; +type HorizontalPodAutoscalersTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; + +const HorizontalPodAutoscalersList: React.SFC = props => ; HorizontalPodAutoscalersList.displayName = 'HorizontalPodAutoscalersList'; export const HorizontalPodAutoscalersPage: React.SFC = props => @@ -215,10 +263,6 @@ export const HorizontalPodAutoscalersPage: React.SFC; HorizontalPodAutoscalersPage.displayName = 'HorizontalPodAutoscalersListPage'; -export type HorizontalPodAutoscalersRowProps = { - obj: any, -}; - export type HorizontalPodAutoscalersDetailsProps = { obj: any, }; @@ -233,11 +277,11 @@ export type HorizontalPodAutoscalersDetailsPageProps = { match: any, }; -export type MetricsTableProps = { +type MetricsTableProps = { obj: any, }; -export type MetricsRowProps = { +type MetricsRowProps = { type: any, current: any, target: any, diff --git a/frontend/public/components/image-stream.tsx b/frontend/public/components/image-stream.tsx index 3ed8b490cb33..1ac6753cf0b0 100644 --- a/frontend/public/components/image-stream.tsx +++ b/frontend/public/components/image-stream.tsx @@ -1,11 +1,13 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import * as semver from 'semver'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { Popover } from '@patternfly/react-core'; import { QuestionCircleIcon } from '@patternfly/react-icons'; import { K8sResourceKind, K8sResourceKindReference } from '../module/k8s'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { CopyToClipboard, ExternalLink, Kebab, SectionHeading, LabelList, navFactory, ResourceKebab, ResourceLink, ResourceSummary, history, Timestamp } from './utils'; import { fromNow } from './utils/datetime'; @@ -195,32 +197,69 @@ export const ImageStreamsDetailsPage: React.SFC = pages={pages} />; ImageStreamsDetailsPage.displayName = 'ImageStreamsDetailsPage'; -const ImageStreamsHeader = props => - Name - Namespace - Labels - Created -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const ImageStreamsTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; +}; +ImageStreamsTableHeader.displayName = 'ImageStreamsTableHeader'; -const ImageStreamsRow: React.SFC = ({obj}) =>
-
- -
-
- -
-
- -
-
- { fromNow(obj.metadata.creationTimestamp) } -
-
- -
-
; +const ImageStreamsTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + {fromNow(obj.metadata.creationTimestamp)} + + + + + + ); +}; +ImageStreamsTableRow.displayName = 'ImageStreamsTableRow'; +type ImageStreamsTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; -export const ImageStreamsList: React.SFC = props => ; +export const ImageStreamsList: React.SFC = props =>
; ImageStreamsList.displayName = 'ImageStreamsList'; export const buildPhase = build => build.status.phase; @@ -246,10 +285,6 @@ export type ImageStreamManipulationHelpProps = { tag?: string }; -export type ImageStreamsRowProps = { - obj: K8sResourceKind; -}; - export type ImageStreamsDetailsProps = { obj: K8sResourceKind; }; diff --git a/frontend/public/components/ingress.jsx b/frontend/public/components/ingress.jsx index 99368a1510bf..45db13925df5 100644 --- a/frontend/public/components/ingress.jsx +++ b/frontend/public/components/ingress.jsx @@ -1,7 +1,8 @@ import * as _ from 'lodash-es'; import * as React from 'react'; - -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, SectionHeading, LabelList, ResourceKebab, ResourceIcon, detailsPage, EmptyBox, navFactory, ResourceLink, ResourceSummary } from './utils'; const menuActions = Kebab.factory.common; @@ -32,29 +33,64 @@ const getTLSCert = (ingress) => { ; }; -const IngressListHeader = props => - Name - Namespace - Labels - Hosts -; - -const IngressListRow = ({obj: ingress}) => -
- -
-
- -
-
- -
-
{getHosts(ingress)}
-
- -
-
; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; + +const kind = 'Ingress'; + +const IngressTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Hosts', sortFunc: 'ingressValidHosts', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; +}; +IngressTableHeader.displayName = 'IngressTableHeader'; + +const IngressTableRow = ({obj: ingress, index, key, style}) => { + return ( + + + + + + + + + + + + {getHosts(ingress)} + + + + + + ); +}; +IngressTableRow.displayName = 'IngressTableRow'; const RulesHeader = () =>
Host
@@ -139,7 +175,8 @@ const IngressesDetailsPage = props => ; -const IngressesList = props => ; +const IngressesList = props =>
; + const IngressesPage = props => ; export {IngressesList, IngressesPage, IngressesDetailsPage}; diff --git a/frontend/public/components/job.jsx b/frontend/public/components/job.jsx index 1540191bf8dd..18f795240fcf 100644 --- a/frontend/public/components/job.jsx +++ b/frontend/public/components/job.jsx @@ -1,8 +1,9 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { getJobTypeAndCompletions } from '../module/k8s'; -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { configureJobParallelismModal } from './modals'; import { Kebab, ContainerTable, SectionHeading, LabelList, ResourceKebab, ResourceLink, ResourceSummary, Timestamp, navFactory, StatusIconAndText } from './utils'; import { ResourceEventStream } from './events'; @@ -23,41 +24,72 @@ const ModifyJobParallelism = (kind, obj) => ({ }); const menuActions = [ModifyJobParallelism, ...Kebab.factory.common]; -const JobHeader = props => - Name - Namespace - Labels - Completions - Type -; +const kind = 'Job'; + +const tableColumnClasses = [ + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-xl', 'pf-m-2-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + Kebab.columnClass, +]; + +const JobTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Completions', sortFunc: 'jobCompletions', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Type', sortFunc: 'jobType', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +JobTableHeader.displayName = 'JobTableHeader'; -const JobRow = ({obj: job}) => { +const JobTableRow = ({obj: job, index, key, style}) => { const {type, completions} = getJobTypeAndCompletions(job); return ( - -
- -
-
+ + + + + -
-
- -
-
+ + + + + {job.status.succeeded || 0} of {completions} -
-
- {type} -
-
+ + {type} + -
-
+ + ); }; +JobTableRow.displayName = 'JobTableRow'; const Details = ({obj: job}) =>
@@ -104,6 +136,7 @@ const JobsDetailsPage = props => ; -const JobsList = props => ; +const JobsList = props =>
; + const JobsPage = props => ; export {JobsList, JobsPage, JobsDetailsPage}; diff --git a/frontend/public/components/limit-range.tsx b/frontend/public/components/limit-range.tsx index 2c54b8d94c74..bf826535f111 100644 --- a/frontend/public/components/limit-range.tsx +++ b/frontend/public/components/limit-range.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import * as _ from 'lodash-es'; - -import {K8sResourceKindReference} from '../module/k8s'; -import {ColHead, DetailsPage, List, ListHeader, ListPage} from './factory'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; +import {K8sResourceKindReference, K8sResourceKind} from '../module/k8s'; +import {DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import {Kebab, navFactory, SectionHeading, ResourceKebab, ResourceLink, ResourceSummary, Timestamp} from './utils'; const { common } = Kebab.factory; @@ -10,35 +11,61 @@ const menuActions = [...common]; const LimitRangeReference: K8sResourceKindReference = 'LimitRange'; -const LimitRangeRow: React.SFC = ({obj}) => -
-
- -
-
- -
-
- -
-
- -
-
; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; -const LimitRangeHeader: React.SFC = props => - - Name - Namespace - Created - ; +const LimitRangeTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + + + + ); +}; +LimitRangeTableRow.displayName = 'LimitRangeTableRow'; +type LimitRangeTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; -export const LimitRangeList: React.SFC = props => - ; +const LimitRangeTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: '', props: { className: tableColumnClasses[3] }, + }, + ]; +}; +LimitRangeTableHeader.displayName = 'LimitRangeTableHeader'; + +export const LimitRangeList: React.SFC = props =>
; export const LimitRangeListPage: React.SFC = props => = return ; }; -const MachineAutoscalerHeader: React.FC = props => - Name - Namespace - Scale Target - Min - Max -; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-1-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-1-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; -const MachineAutoscalerRow: React.FC = ({obj}) =>
-
- -
-
- -
-
- -
-
- {_.get(obj, 'spec.minReplicas') || '-'} -
-
- {_.get(obj, 'spec.maxReplicas') || '-'} -
-
- -
-
; +const MachineAutoscalerTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Scale Target', sortField: 'spec.scaleTargetRef.name', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Min', sortField: 'spec.minReplicas', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: 'Max', sortField: 'spec.maxReplicas', transforms: [sortable], + props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +MachineAutoscalerTableHeader.displayName = 'MachineAutoscalerTableHeader'; -const MachineAutoscalerList: React.FC = props => - ; +const MachineAutoscalerTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + + + {_.get(obj, 'spec.minReplicas') || '-'} + + + {_.get(obj, 'spec.maxReplicas') || '-'} + + + + + + ); +}; +MachineAutoscalerTableRow.displayName = 'MachineAutoscalerTableRow'; +type MachineAutoscalerTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; + +const MachineAutoscalerList: React.FC = props =>
; const MachineAutoscalerDetails: React.FC = ({obj}) => { return @@ -104,10 +146,6 @@ export const MachineAutoscalerDetailsPage: React.FC; -type MachineAutoscalerRowProps = { - obj: K8sResourceKind; -}; - type MachineAutoscalerPageProps = { showTitle?: boolean; namespace?: string; diff --git a/frontend/public/components/machine-config-pool.tsx b/frontend/public/components/machine-config-pool.tsx index b876de6c20eb..0911a3ac5037 100644 --- a/frontend/public/components/machine-config-pool.tsx +++ b/frontend/public/components/machine-config-pool.tsx @@ -1,6 +1,7 @@ import * as _ from 'lodash-es'; import * as React from 'react'; - +import { sortable } from '@patternfly/react-table'; +import * as classNames from 'classnames'; import { Conditions } from './conditions'; import { errorModal } from './modals'; import { Tooltip } from './utils/tooltip'; @@ -13,11 +14,11 @@ import { referenceForModel, } from '../module/k8s'; import { - ColHead, DetailsPage, - List, - ListHeader, ListPage, + Table, + TableRow, + TableData, } from './factory'; import { Kebab, @@ -197,43 +198,79 @@ export const MachineConfigPoolDetailsPage: React.SFC = props => ( /> ); -const MachineConfigPoolHeader: React.SFC = props => - Name - Configuration - Updated - Updating - Degraded -; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-xl', 'pf-m-6-col-on-sm'), + classNames('pf-m-5-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-1-col-on-xl', 'pf-m-2-col-on-md', 'pf-m-3-col-on-sm'), + classNames('pf-m-1-col-on-xl', 'pf-m-2-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-1-col-on-xl', 'pf-m-2-col-on-md', 'pf-m-3-col-on-sm'), + Kebab.columnClass, +]; -const MachineConfigPoolRow: React.SFC = ({obj}) =>
-
- -
-
- {_.get(obj, 'status.configuration.name') ? : '-'} -
-
- {getConditionStatus(obj, MachineConfigPoolConditionType.Updated)} -
-
- {getConditionStatus(obj, MachineConfigPoolConditionType.Updating)} -
-
- {getConditionStatus(obj, MachineConfigPoolConditionType.Degraded)} -
+const MachineConfigPoolTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Configuration', sortField: 'status.configuration.name', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Updated', props: { className: tableColumnClasses[2] }, + }, + { + title: 'Updating', props: { className: tableColumnClasses[3] }, + }, + { + title: 'Degraded', props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +MachineConfigPoolTableHeader.displayName = 'MachineConfigPoolTableHeader'; -
- -
-
; +const MachineConfigPoolTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + {_.get(obj, 'status.configuration.name') ? : '-'} + + + {getConditionStatus(obj, MachineConfigPoolConditionType.Updated)} + + + {getConditionStatus(obj, MachineConfigPoolConditionType.Updating)} + + + {getConditionStatus(obj, MachineConfigPoolConditionType.Degraded)} + + + + + + ); +}; +MachineConfigPoolTableRow.displayName = 'MachineConfigPoolTableRow'; +type MachineConfigPoolTableRowProps = { + obj: MachineConfigPoolKind; + index: number; + key?: string; + style: object; +}; -const MachineConfigPoolList: React.SFC = props => ( - -); +const MachineConfigPoolList: React.SFC = props =>
; export const MachineConfigPoolPage: React.SFC = props => ( = props => { return ; }; -const MachineConfigHeader: React.SFC = props => - Name - Generated By Controller - Ignition Version - OS Image URL - Created -; +const tableColumnClasses = [ + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-6-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-2-col-on-lg', 'pf-m-2-col-on-md', 'pf-m-6-col-on-sm'), + Kebab.columnClass, +]; -const MachineConfigRow: React.SFC = ({obj}) =>
-
- -
-
- { _.get(obj, ['metadata', 'annotations', 'machineconfiguration.openshift.io/generated-by-controller-version'], '-')} -
-
- {_.get(obj, 'spec.config.ignition.version') || '-'} -
-
- {_.get(obj, 'spec.osImageURL') || '-'} -
-
- {fromNow(obj.metadata.creationTimestamp)} -
-
- -
-
; +const MachineConfigTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Generated By Controller', + sortField: 'metadata.annotations[\'machineconfiguration.openshift.io/generated-by-controller-version\']', + transforms: [sortable], props: { className: tableColumnClasses[1] }, + }, + { + title: 'Ignition Version', sortField: 'spec.config.ignition.version', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'OS Image URL', sortField: 'spec.osImageURL', + transforms: [sortable], props: { className: tableColumnClasses[3] }, + }, + { + title: 'Created', sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +MachineConfigTableHeader.displayName = 'MachineConfigTableHeader'; -const MachineConfigList: React.SFC = props => ( - -); +const MachineConfigTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + { _.get(obj, ['metadata', 'annotations', 'machineconfiguration.openshift.io/generated-by-controller-version'], '-')} + + + {_.get(obj, 'spec.config.ignition.version') || '-'} + + + {_.get(obj, 'spec.osImageURL') || '-'} + + + {fromNow(obj.metadata.creationTimestamp)} + + + + + + ); +}; +MachineConfigTableRow.displayName = 'MachineConfigTableRow'; +type MachineConfigTableRowProps = { + obj: MachineConfigKind; + index: number; + key?: string; + style: object; +}; + +const MachineConfigList: React.SFC = props =>
; export const MachineConfigPage: React.SFC = ({canCreate = true, ...rest}) => ( = ({canCreate = true, ...rest}) = /> ); -type MachineConfigRowProps = { - obj: MachineConfigKind; -}; - type MachineConfigDetailsProps = { obj: MachineConfigKind; }; diff --git a/frontend/public/components/machine-deployment.tsx b/frontend/public/components/machine-deployment.tsx index 3022157d3eea..73aab84463c6 100644 --- a/frontend/public/components/machine-deployment.tsx +++ b/frontend/public/components/machine-deployment.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { Link } from 'react-router-dom'; - +import { sortable } from '@patternfly/react-table'; +import * as classNames from 'classnames'; import { MachineModel, MachineDeploymentModel } from '../models'; import { MachineDeploymentKind, referenceForModel } from '../module/k8s'; import { getMachineRole } from './machine'; @@ -13,7 +14,7 @@ import { MachineCounts, MachineTabPage, } from './machine-set'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, ResourceKebab, @@ -32,28 +33,61 @@ const menuActions = [editCountAction, ...common]; const machineReference = referenceForModel(MachineModel); const machineDeploymentReference = referenceForModel(MachineDeploymentModel); -const MachineDeploymentHeader: React.SFC = props => - Name - Namespace - Machines -; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const MachineDeploymentTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Machines', sortField: 'status.replicas', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: '', props: { className: tableColumnClasses[3] }, + }, + ]; +}; +MachineDeploymentTableHeader.displayName = 'MachineDeploymentTableHeader'; -const MachineDeploymentRow: React.SFC = ({obj}: {obj: MachineDeploymentKind}) =>
-
- -
-
- -
-
- - {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines - -
-
- -
-
; +const MachineDeploymentTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines + + + + + + + ); +}; +MachineDeploymentTableRow.displayName = 'MachineDeploymentTableRow'; +type MachineDeploymentTableRowProps = { + obj: MachineDeploymentKind; + index: number; + key?: string; + style: object; +}; const MachineDeploymentDetails: React.SFC = ({obj}) => { const machineRole = getMachineRole(obj); @@ -110,12 +144,12 @@ const MachineDeploymentDetails: React.SFC = ({obj ; }; -export const MachineDeploymentList: React.SFC = props => - ; +export const MachineDeploymentList: React.SFC = props =>
; export const MachineDeploymentPage: React.SFC = props => ; -export type MachineDeploymentRowProps = { - obj: MachineDeploymentKind; -}; - export type MachineDeploymentDetailsProps = { obj: MachineDeploymentKind; }; diff --git a/frontend/public/components/machine-set.tsx b/frontend/public/components/machine-set.tsx index 7dc7b0202caa..4dd43a207ff5 100644 --- a/frontend/public/components/machine-set.tsx +++ b/frontend/public/components/machine-set.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { Link } from 'react-router-dom'; - +import { sortable } from '@patternfly/react-table'; +import * as classNames from 'classnames'; import { MachineAutoscalerModel, MachineModel, MachineSetModel } from '../models'; import { K8sKind, MachineDeploymentKind, MachineSetKind, referenceForModel } from '../module/k8s'; import { getMachineRole, MachinePage } from './machine'; import { configureMachineAutoscalerModal, configureReplicaCountModal } from './modals'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, KebabAction, @@ -67,28 +68,61 @@ const getReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.ge export const getReadyReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'status.readyReplicas', 0); export const getAvailableReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'status.availableReplicas', 0); -const MachineSetHeader: React.SFC = props => - Name - Namespace - Machines -; - -const MachineSetRow: React.SFC = ({obj}: {obj: MachineSetKind}) =>
-
- -
-
- -
-
- - {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines - -
-
- -
-
; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const MachineSetTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Machines', sortField: 'status.replicas', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: '', props: { className: tableColumnClasses[3] }, + }, + ]; +}; +MachineSetTableHeader.displayName = 'MachineSetTableHeader'; + +const MachineSetTableRow: React.FC = ({obj, index, key, style}) => { + return ( + + + + + + + + + + {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines + + + + + + + ); +}; +MachineSetTableRow.displayName = 'MachineSetTableRow'; +type MachineSetTableRowProps = { + obj: MachineSetKind; + index: number; + key?: string; + style: object; +}; export const MachineCounts: React.SFC = ({resourceKind, resource}: {resourceKind: K8sKind, resource: MachineSetKind | MachineDeploymentKind}) => { const editReplicas = (event) => { @@ -185,12 +219,7 @@ const MachineSetDetails: React.SFC = ({obj}) => { ; }; -export const MachineSetList: React.SFC = props => - ; +export const MachineSetList: React.SFC = props =>
; export const MachineSetPage: React.SFC = props => = prop ]} />; -export type MachineSetRowProps = { - obj: MachineSetKind; -}; - export type MachineCountsProps = { resourceKind: K8sKind; resource: MachineSetKind | MachineDeploymentKind; diff --git a/frontend/public/components/machine.tsx b/frontend/public/components/machine.tsx index e3a00115febf..91a0662039f1 100644 --- a/frontend/public/components/machine.tsx +++ b/frontend/public/components/machine.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import * as _ from 'lodash-es'; - +import { sortable } from '@patternfly/react-table'; +import * as classNames from 'classnames'; import { MachineModel } from '../models'; import { MachineDeploymentKind, MachineKind, MachineSetKind, referenceForModel } from '../module/k8s'; import { Conditions } from './conditions'; import { NodeIPList } from './node'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, NodeLink, @@ -26,38 +27,76 @@ export const getMachineRole = (obj: MachineKind | MachineSetKind | MachineDeploy const getNodeName = (obj) => _.get(obj, 'status.nodeRef.name'); -const MachineHeader = props => - Name - Namespace - Node - Region - Availability Zone -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-3-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + Kebab.columnClass, +]; + +const MachineTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Node', sortField: 'status.nodeRef.name', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Region', sortField: 'spec.providerSpec.value.placement.region', + transforms: [sortable], props: { className: tableColumnClasses[3] }, + }, + { + title: 'Availability Zone', sortField: 'spec.providerSpec.value.placement.availabilityZone', + transforms: [sortable], props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +MachineTableHeader.displayName = 'MachineTableHeader'; -const MachineRow: React.SFC = ({obj}: {obj: MachineKind}) => { +const MachineTableRow: React.FC = ({obj, index, key, style}) => { const { availabilityZone, region } = getAWSPlacement(obj); const nodeName = getNodeName(obj); - - return
-
- -
-
- -
-
- {nodeName ? : '-'} -
-
- {region || '-'} -
-
- {availabilityZone || '-'} -
-
- -
-
; + return ( + + + + + + + + + {nodeName ? : '-'} + + + {region || '-'} + + + {availabilityZone || '-'} + + + + + + ); +}; +MachineTableRow.displayName = 'MachineTableRow'; +type MachineTableRowProps = { + obj: MachineKind; + index: number; + key: string; + style: object; }; const MachineDetails: React.SFC = ({obj}: {obj: MachineKind}) => { @@ -95,12 +134,7 @@ const MachineDetails: React.SFC = ({obj}: {obj: MachineKind ; }; -export const MachineList: React.SFC = props => - ; +export const MachineList: React.SFC = props =>
; export const MachinePage: React.SFC = props => = props => pages={[navFactory.details(MachineDetails), navFactory.editYaml()]} />; -export type MachineRowProps = { - obj: MachineKind; -}; - export type MachineDetailsProps = { obj: MachineKind; }; diff --git a/frontend/public/components/monitoring.tsx b/frontend/public/components/monitoring.tsx index 225894c9c0a1..47fa242ebfd2 100644 --- a/frontend/public/components/monitoring.tsx +++ b/frontend/public/components/monitoring.tsx @@ -5,13 +5,19 @@ import * as React from 'react'; import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; import { Link, Redirect, Route, Switch } from 'react-router-dom'; - +import { sortable } from '@patternfly/react-table'; import { coFetchJSON } from '../co-fetch'; import * as k8sActions from '../actions/k8s'; -import * as UIActions from '../actions/ui'; import { alertState, AlertStates, connectToURLs, MonitoringRoutes, silenceState, SilenceStates } from '../reducers/monitoring'; import store from '../redux'; -import { ColHead, List, ListHeader, ResourceRow, TextFilter } from './factory'; +import * as UIActions from '../actions/ui'; +import { + ResourceRow, + TextFilter, + Table, + TableRow, + TableData, +} from './factory'; import { QueryBrowser } from './graphs'; import { graphColors } from './graphs/query-browser'; import { getPrometheusExpressionBrowserURL } from './graphs/prometheus-graph'; @@ -276,7 +282,7 @@ const AlertsDetailsPage = withFallback(connect(alertStateToProps)((props: Alerts
- {_.get(rule, 'name')} + {_.get(rule, 'name')}
@@ -508,34 +514,66 @@ const SilencesDetailsPage = withFallback(connect(silenceParamToProps)((props: Si ; })); -const AlertRow = ({obj}) => { - const {annotations = {}, labels = {}} = obj; - const state = alertState(obj); +const tableAlertClasses = [ + classNames('pf-m-7-col-on-md', 'pf-m-8-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; - return -
-
- - {labels.alertname} -
-
{annotations.description || annotations.message}
-
-
- - -
-
{_.startCase(_.get(labels, 'severity')) || '-'}
-
- -
-
; +const AlertTableRow: React.FC = ({obj, index, key, style}) => { + const {annotations = {}, labels} = obj; + const state = alertState(obj); + return ( + + +
+ + {labels && labels.alertname} +
+
{annotations.description || annotations.message}
+
+ + + + + + {_.startCase(_.get(labels, 'severity')) || '-'} + + + + +
+ ); +}; +AlertTableRow.displayName = 'AlertTableRow'; +type AlertTableRowProps = { + obj: Alert; + index: number; + key?: string; + style: object; }; -const AlertHeader = props => - Name - State - Severity -; +const AlertTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableAlertClasses[0]}, + }, + { + title: 'State', sortFunc: 'alertStateOrder', transforms: [sortable], + props: { className: tableAlertClasses[1]}, + }, + { + title: 'Severity', sortField: 'labels.severity', transforms: [sortable], + props: { className: tableAlertClasses[2]}, + }, + { title: '', + props: { className: tableAlertClasses[3]}, + }, + ]; +}; +AlertTableHeader.displayName = 'AlertTableHeader'; const AlertsPageDescription = () =>

Alerts help notify you when certain conditions in your environment are met. @@ -648,7 +686,8 @@ const MonitoringListPage = connect(filtersToProps)(class InnerMonitoringListPage

- + virtualize />
@@ -666,21 +705,49 @@ const MonitoringListPage = connect(filtersToProps)(class InnerMonitoringListPage const AlertsPage_ = props => ; const AlertsPage = withFallback(connect(alertsToProps)(AlertsPage_)); -const SilenceHeader = props => - Name - State - Firing Alerts -; +// const SilenceHeader = props => +// Name +// State +// Firing Alerts +// ; + +const tableSilenceClasses = [ + classNames('pf-m-7-col-on-md', 'pf-m-8-col-on-sm'), + classNames('pf-m-3-col-on-md', 'pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const SilenceTableHeader = () => { + return [ + { + title: 'Name', sortField: 'name', transforms: [sortable], + props: { className: tableSilenceClasses[0]}, + }, + { + title: 'State', sortFunc: 'silenceStateOrder', transforms: [sortable], + props: { className: tableSilenceClasses[1]}, + }, + { + title: 'Firing Alerts', sortField: 'firingAlerts.length', transforms: [sortable], + props: { className: tableSilenceClasses[2]}, + }, + { title: '', + props: { className: tableSilenceClasses[3]}, + }, + ]; +}; +SilenceTableHeader.displayName = 'SilenceTableHeader'; const SilenceRow = ({obj}) => { const state = silenceState(obj); @@ -689,7 +756,7 @@ const SilenceRow = ({obj}) => {
- {obj.name} + {obj.name}
@@ -708,6 +775,43 @@ const SilenceRow = ({obj}) => { ; }; +const SilenceTableRow: React.FC = ({obj, index, key, style}) => { + const state = silenceState(obj); + return ( + + +
+ + {obj.name} +
+
+ +
+
+ + + {state === SilenceStates.Pending && } + {state === SilenceStates.Active && } + {state === SilenceStates.Expired && } + + + {obj.firingAlerts.length} + + + + +
+ ); +}; +SilenceTableRow.displayName = 'SilenceTableRow'; +export type SilenceTableRowProps = { + obj: Silence; + index: number; + key?: string; + style: object; +}; + + const SilencesPageDescription = () =>

Silences temporarily mute alerts based on a set of conditions that you define. Notifications are not sent for alerts that meet the given conditions.

; @@ -731,12 +835,12 @@ const SilencesPage_ = props => ; const SilencesPage = withFallback(connect(silencesToProps)(SilencesPage_)); @@ -1184,7 +1288,8 @@ export type ListPageProps = { CreateButton: React.ComponentType<{}>; data: Rule[] | Silence[]; filters: {[key: string]: any}; - Header: React.ComponentType; + // Header: React.ComponentType; + Header: (...args) => any[]; itemCount: number; kindPlural: string; loaded: boolean; diff --git a/frontend/public/components/namespace.jsx b/frontend/public/components/namespace.jsx index e9e45dcd8b15..179f1c90caca 100644 --- a/frontend/public/components/namespace.jsx +++ b/frontend/public/components/namespace.jsx @@ -1,5 +1,7 @@ import * as _ from 'lodash-es'; import * as React from 'react'; +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { connect } from 'react-redux'; import { Tooltip } from './utils/tooltip'; import { Link } from 'react-router-dom'; @@ -8,7 +10,7 @@ import * as fuzzy from 'fuzzysearch'; import { NamespaceModel, ProjectModel, SecretModel } from '../models'; import { k8sGet } from '../module/k8s'; import * as UIActions from '../actions/ui'; -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { ActionsMenu, Kebab, Dropdown, Firehose, LabelList, LoadingInline, navFactory, ResourceKebab, SectionHeading, ResourceIcon, ResourceLink, ResourceSummary, MsgBox, StatusIconAndText, ExternalLink, humanizeCpuCores, humanizeDecimalBytes } from './utils'; import { createNamespaceModal, createProjectModal, deleteNamespaceModal, configureNamespacePullSecretModal } from './modals'; import { RoleBindingsPage } from './RBAC'; @@ -45,64 +47,122 @@ export const deleteModal = (kind, ns) => { const nsMenuActions = [Kebab.factory.ModifyLabels, Kebab.factory.ModifyAnnotations, Kebab.factory.Edit, deleteModal]; -const NamespaceHeader = props => - Name - Status - Labels -; +const namespacesColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const NamespacesTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: namespacesColumnClasses[0]}, + }, + { + title: 'Status', sortField: 'status.phase', transforms: [sortable], + props: { className: namespacesColumnClasses[1]}, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: namespacesColumnClasses[2]}, + }, + { title: '', + props: { className: namespacesColumnClasses[3]}, + }, + ]; +}; +NamespacesTableHeader.displayName = 'NamespacesTableHeader'; + +const NamespacesTableRow = ({obj: ns, index, key, style}) => { + return ( + + + + + + + + + + + + + + + ); +}; +NamespacesTableRow.displayName = 'NamespacesTableRow'; -const NamespaceRow = ({obj: ns}) => -
- -
-
- -
-
- -
-
- -
-
; +export const NamespacesList = props =>
; -export const NamespacesList = props => ; export const NamespacesPage = props => createNamespaceModal({blocking: true})} />; const projectMenuActions = [Kebab.factory.Edit, deleteModal]; -const ProjectHeader = props => - Name - Status - Requester - Labels -; +const projectColumnClasses = [ + classNames('pf-m-3-col-on-lg', 'pf-m-6-col-on-md', 'pf-m-8-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-4-col-on-sm'), + classNames('pf-m-3-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; + +const ProjectTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: projectColumnClasses[0]}, + }, + { + title: 'Status', sortField: 'status.phase', transforms: [sortable], + props: { className: projectColumnClasses[1]}, + }, + { + title: 'Requester', sortField: 'metadata.annotations.[\'openshift.io/requester\']', transforms: [sortable], + props: { className: projectColumnClasses[2]}, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: projectColumnClasses[3]}, + }, + { title: '', + props: { className: projectColumnClasses[4]}, + }, + ]; +}; +ProjectTableHeader.displayName = 'ProjectTableHeader'; -const ProjectRow = ({obj: project}) => { + +const ProjectTableRow = ({obj: project, index, key, style}) => { const name = project.metadata.name; const displayName = getDisplayName(project); const requester = getRequester(project); - return -
- - - {project.metadata.name} - -
-
- -
-
- {requester || No requester} -
-
- -
-
- -
-
; + return ( + + + + + {project.metadata.name} + + + + + + + {requester || No requester} + + + + + + + + + ); }; +ProjectTableRow.displayName = 'ProjectTableRow'; const ProjectList_ = props => { const ProjectEmptyMessageDetail = @@ -117,7 +177,7 @@ const ProjectList_ = props => {

; const ProjectEmptyMessage = () => ; - return ; + return
; }; export const ProjectList = connect(createProjectMessageStateToProps)(ProjectList_); diff --git a/frontend/public/components/network-policy.jsx b/frontend/public/components/network-policy.jsx index d40b2589b5ab..6e9f590a3b0f 100644 --- a/frontend/public/components/network-policy.jsx +++ b/frontend/public/components/network-policy.jsx @@ -1,50 +1,79 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Link } from 'react-router-dom'; - +import { sortable } from '@patternfly/react-table'; +import * as classNames from 'classnames'; import { connectToFlags } from '../reducers/features'; import { FLAGS } from '../const'; -import { ColHead, DetailsPage, List, ListHeader, ListPage } from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, navFactory, ResourceKebab, SectionHeading, ResourceLink, ResourceSummary, Selector, ExternalLink } from './utils'; const { common } = Kebab.factory; const menuActions = [...common]; -const Header = props => - Name - Namespace - Pod Selector -; +const tableColumnClasses = [ + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + Kebab.columnClass, +]; + +const NetworkPolicyTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Pod Selector', sortField: 'spec.podSelector', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: '', props: { className: tableColumnClasses[3] }, + }, + ]; +}; +NetworkPolicyTableHeader.displayName = 'NetworkPolicyTableHeader'; const kind = 'NetworkPolicy'; -const Row = ({obj: np}) =>
-
- -
-
- -
-
- { - _.isEmpty(np.spec.podSelector) ? - {`All pods within ${np.metadata.namespace}`} : - - } -
-
- -
-
; +const NetworkPolicyTableRow = ({obj: np, index, key, style}) => { + return ( + + + + + + + + + { + _.isEmpty(np.spec.podSelector) ? + {`All pods within ${np.metadata.namespace}`} : + + } + + + + + + ); +}; +NetworkPolicyTableRow.displayName = 'NetworkPolicyTableRow'; + +const NetworkPoliciesList = props =>
; -const NetworkPoliciesList = props => ; export const NetworkPoliciesPage = props => ; const IngressHeader = () =>
-
target pods
-
from
-
to ports
+
Target Pods
+
From
+
To Ports
; const IngressRow = ({ingress, namespace, podSelector}) => { diff --git a/frontend/public/components/node.tsx b/frontend/public/components/node.tsx index eeeeb7affe71..12e5dcb7cb38 100644 --- a/frontend/public/components/node.tsx +++ b/frontend/public/components/node.tsx @@ -1,9 +1,10 @@ import * as _ from 'lodash-es'; import * as React from 'react'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { getNodeRoles, nodeStatus, makeNodeSchedulable, K8sResourceKind, referenceForModel } from '../module/k8s'; import { ResourceEventStream } from './events'; -import { ColHead, DetailsPage, List, ListHeader, ListPage, ResourceRow } from './factory'; +import { Table, TableRow, TableData, DetailsPage, ListPage } from './factory'; import { configureUnschedulableModal } from './modals'; import { PodsPage } from './pod'; import { Kebab, navFactory, LabelList, ResourceKebab, SectionHeading, ResourceLink, Timestamp, units, cloudProviderNames, cloudProviderID, pluralize, StatusIconAndText, humanizeDecimalBytes, humanizeCpuCores } from './utils'; @@ -61,52 +62,73 @@ export const NodeIPList = ({ips, expand = false}) =>
)} ; -const Header = props => { - if (!props.data) { - return null; - } - return - Name - Status - Role - Machine - ; +const tableColumnClasses = [ + classNames('pf-m-5-col-on-lg', 'pf-m-5-col-on-md', 'pf-m-8-col-on-sm'), + classNames('pf-m-2-col-on-lg', 'pf-m-3-col-on-md', 'pf-m-4-col-on-sm'), + classNames('pf-m-2-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; + +const NodeTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Status', sortFunc: 'nodeReadiness', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Role', sortFunc: 'nodeRoles', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Machine', sortField: 'metadata.annotations[\'machine.openshift.io/machine\']', transforms: [sortable], + props: { className: tableColumnClasses[3] }, + }, + { + title: '', props: { className: tableColumnClasses[4] }, + }, + ]; }; +NodeTableHeader.displayName = 'NodeTableHeader'; const NodeStatus = ({node}) => ; -const NodeRow = ({obj: node, expand}) => { +const NodeTableRow: React.FC = ({obj: node, index, key, style}) => { const machine = getMachine(node); const roles = getNodeRoles(node).sort(); - - return -
- -
-
- -
-
- {roles.length ? roles.join(', ') : '-'} -
-
- {machine && } -
- {expand &&
-
- -
-
- -
-
} -
- -
-
; + return ( + + + + + + + + + {roles.length ? roles.join(', ') : '-'} + + + {machine && } + + + + + + ); +}; +NodeTableRow.displayName = 'NodeTableRow'; +type NodeTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; }; -const NodesList = props => ; +const NodesList = props =>
; const filters = [{ type: 'node-status', @@ -117,7 +139,7 @@ const filters = [{ {id: 'Not Ready', title: 'Not Ready'}, ], }]; -export const NodesPage = props => ; +export const NodesPage = props => ; const NodeGraphs = requirePrometheus(({node}) => { const nodeIp = _.find<{type: string, address: string}>(node.status.addresses, {type: 'InternalIP'}); diff --git a/frontend/public/components/operator-lifecycle-manager/_operator-lifecycle-manager.scss b/frontend/public/components/operator-lifecycle-manager/_operator-lifecycle-manager.scss index 71d4c45f7a7a..f1b437f76b73 100644 --- a/frontend/public/components/operator-lifecycle-manager/_operator-lifecycle-manager.scss +++ b/frontend/public/components/operator-lifecycle-manager/_operator-lifecycle-manager.scss @@ -104,7 +104,8 @@ align-items: center; } -.co-package-row__actions { +.co-package-row__actions, +.pf-c-table.pf-c-virtualized .co-package-row__actions { align-items: center; display: flex; justify-content: space-between; diff --git a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion-resource.tsx b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion-resource.tsx index 2992a0589f18..7376af46e170 100644 --- a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion-resource.tsx +++ b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion-resource.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import { Link, match } from 'react-router-dom'; import * as _ from 'lodash-es'; import { connect } from 'react-redux'; - +import * as classNames from 'classnames'; +import { sortable } from '@patternfly/react-table'; import { ClusterServiceVersionResourceKind, ClusterServiceVersionKind, referenceForProvidedAPI } from './index'; import { StatusDescriptor } from './descriptors/status'; import { SpecDescriptor } from './descriptors/spec'; @@ -10,8 +11,8 @@ import { SpecDescriptor } from './descriptors/spec'; import { StatusCapability, Descriptor } from './descriptors/types'; import { Resources } from './k8s-resource'; import { ErrorPage404 } from '../error'; -import { List, MultiListPage, ListPage, ListHeader, ColHead, DetailsPage } from '../factory'; -import { ResourceSummary, StatusBox, navFactory, Timestamp, LabelList, ResourceIcon, MsgBox, ResourceKebab, KebabAction, LoadingBox, StatusIconAndText } from '../utils'; +import { MultiListPage, ListPage, DetailsPage, Table, TableRow, TableData } from '../factory'; +import { ResourceSummary, StatusBox, navFactory, Timestamp, LabelList, ResourceIcon, MsgBox, ResourceKebab, Kebab, KebabAction, LoadingBox, StatusIconAndText } from '../utils'; import { connectToModel } from '../../kinds'; import { kindForReference, K8sResourceKind, OwnerReference, K8sKind, referenceFor, GroupVersionKind, referenceForModel } from '../../module/k8s'; import { ClusterServiceVersionModel } from '../../models'; @@ -49,14 +50,45 @@ const actions = [ }), ] as KebabAction[]; -export const ClusterServiceVersionResourceHeader: React.SFC = (props) => - Name - Labels - Type - Status - Version - Last Updated -; +const tableColumnClasses = [ + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + Kebab.columnClass, +]; + +export const CSVRTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, + { + title: 'Type', sortField: 'kind', transforms: [sortable], + props: { className: tableColumnClasses[2] }, + }, + { + title: 'Status', props: { className: tableColumnClasses[3] }, + }, + { + title: 'Version', props: { className: tableColumnClasses[4] }, + }, + { + title: 'Last Updated', props: { className: tableColumnClasses[5] }, + }, + { + title: '', props: { className: tableColumnClasses[6] }, + }, + ]; +}; +CSVRTableHeader.displayName = 'CSVRTableHeader'; export const ClusterServiceVersionResourceLink: React.SFC = (props) => { const {namespace, name} = props.obj.metadata; @@ -67,49 +99,56 @@ export const ClusterServiceVersionResourceLink: React.SFC; }; -export const ClusterServiceVersionResourceRow: React.SFC = (props) => { - const {obj} = props; +export const CSVRTableRow: React.FC = ({obj, index, key, style}) => { const status = _.get(obj.status, 'phase'); - - return
-
- -
-
- -
-
- {obj.kind} -
-
- {_.isEmpty(status) ? -
Unknown
: - - } -
-
- {_.get(obj.spec, 'version') ||
Unknown
} -
-
- -
-
- -
-
; + return ( + + + + + + + + + {obj.kind} + + + {_.isEmpty(status) ? +
Unknown
: + + } +
+ + {_.get(obj.spec, 'version') ||
Unknown
} +
+ + + + + + +
+ ); +}; +CSVRTableRow.displayName = 'CSVRTableRow'; +export type CSVRTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; }; export const ClusterServiceVersionResourceList: React.SFC = (props) => { const ensureKind = (data: K8sResourceKind[]) => data.map(obj => ({kind: obj.kind || props.kinds[0], ...obj})); const EmptyMsg = () => ; - return ; + aria-label="Cluster Operators" + Header={CSVRTableHeader} + Row={CSVRTableRow} + virtualize />; }; const inFlightStateToProps = ({k8s}) => ({inFlight: k8s.getIn(['RESOURCES', 'inFlight'])}); @@ -343,8 +382,6 @@ export type ClusterServiceVersionResourceLinkProps = { // TODO(alecmerdler): Find Webpack loader/plugin to add `displayName` to React components automagically ClusterServiceVersionResourceList.displayName = 'ClusterServiceVersionResourceList'; -ClusterServiceVersionResourceHeader.displayName = 'ClusterServiceVersionResourceHeader'; -ClusterServiceVersionResourceRow.displayName = 'ClusterServiceVersionResourceRow'; ClusterServiceVersionResourceDetails.displayName = 'ClusterServiceVersionResourceDetails'; ClusterServiceVersionResourceList.displayName = 'ClusterServiceVersionResourceList'; ClusterServiceVersionResourceLink.displayName = 'ClusterServiceVersionResourceLink'; diff --git a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx index d6542e6c92f0..d72657d044c0 100644 --- a/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx +++ b/frontend/public/components/operator-lifecycle-manager/clusterserviceversion.tsx @@ -4,9 +4,9 @@ import * as _ from 'lodash-es'; import { connect } from 'react-redux'; import { Alert } from 'patternfly-react'; import * as classNames from 'classnames'; - +import { sortable } from '@patternfly/react-table'; import { ProvidedAPIsPage, ProvidedAPIPage } from './clusterserviceversion-resource'; -import { DetailsPage, ListHeader, ColHead, List, ListPage } from '../factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from '../factory'; import { withFallback } from '../utils/error-boundary'; import { referenceForModel, referenceFor, GroupVersionKind, K8sKind } from '../../module/k8s'; import { ClusterServiceVersionModel } from '../../models'; @@ -40,55 +40,85 @@ import { } from '../utils'; import { operatorGroupFor, operatorNamespaceFor } from './operator-group'; -export const ClusterServiceVersionHeader: React.SFC = () => - Name - Namespace - Deployment - Status - Provided APIs -; +const tableColumnClasses = [ + classNames('pf-m-3-col-on-xl', 'pf-m-4-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-2-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-6-col-on-sm'), + classNames('pf-m-2-col-on-xl', 'pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-2-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-4-col-on-md', 'pf-m-hidden', 'pf-m-visible-on-md'), + classNames('pf-m-3-col-on-xl', 'pf-m-3-col-on-lg', 'pf-m-hidden', 'pf-m-visible-on-lg'), + Kebab.columnClass, +]; + +export const ClusterServiceVersionTableHeader = () => { + return [ + { + title: 'Name', sortField: 'metadata.name', transforms: [sortable], + props: { className: tableColumnClasses[0] }, + }, + { + title: 'Namespace', props: { className: tableColumnClasses[1] }, + }, + { + title: 'Deployment', props: { className: tableColumnClasses[2] }, + }, + { + title: 'Status', props: { className: tableColumnClasses[3] }, + }, + { + title: 'Provided APIs', props: { className: tableColumnClasses[4] }, + }, + { + title: '', props: { className: tableColumnClasses[5] }, + }, + ]; +}; +ClusterServiceVersionTableHeader.displayName = 'ClusterServiceVersionTableHeader'; const menuActions = [Kebab.factory.Edit]; -export const ClusterServiceVersionRow = withFallback(({obj}) => { +export const ClusterServiceVersionTableRow = withFallback(({obj, index, key, style}) => { const route = `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${obj.metadata.name}`; const statusString = _.get(obj, 'status.reason', ClusterServiceVersionPhase.CSVPhaseUnknown); const showSuccessIcon = statusString === 'Copied' || statusString === 'InstallSucceeded'; const installStatus = obj.status && obj.status.phase !== ClusterServiceVersionPhase.CSVPhaseFailed - ? {showSuccessIcon &&