diff --git a/frontend/packages/console-demo-plugin/src/dashboards/foo-card.tsx b/frontend/packages/console-demo-plugin/src/dashboards/foo-card.tsx new file mode 100644 index 00000000000..7d8fe44c3fe --- /dev/null +++ b/frontend/packages/console-demo-plugin/src/dashboards/foo-card.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { + DashboardCard, + DashboardCardTitle, + DashboardCardBody, + DashboardCardHeader, +} from '@console/internal/components/dashboard/dashboard-card'; + +export const FooCard: React.FC<{}> = () => ( + + + Foo Card + + +
foo content
+
+
+); diff --git a/frontend/packages/console-demo-plugin/src/plugin.tsx b/frontend/packages/console-demo-plugin/src/plugin.tsx index f7dd18acc7d..0892aa266a2 100644 --- a/frontend/packages/console-demo-plugin/src/plugin.tsx +++ b/frontend/packages/console-demo-plugin/src/plugin.tsx @@ -14,11 +14,14 @@ import { RoutePage, DashboardsOverviewHealthPrometheusSubsystem, DashboardsOverviewHealthURLSubsystem, + DashboardsCard, + DashboardsTab, } from '@console/plugin-sdk'; // TODO(vojtech): internal code needed by plugins should be moved to console-shared package import { PodModel } from '@console/internal/models'; import { FLAGS } from '@console/internal/const'; +import { GridPosition } from '@console/internal/components/dashboard/grid'; import { FooBarModel } from './models'; import { yamlTemplates } from './yaml-templates'; @@ -37,7 +40,9 @@ type ConsumedExtensions = | YAMLTemplate | RoutePage | DashboardsOverviewHealthPrometheusSubsystem - | DashboardsOverviewHealthURLSubsystem; + | DashboardsOverviewHealthURLSubsystem + | DashboardsTab + | DashboardsCard; const plugin: Plugin = [ { @@ -167,6 +172,22 @@ const plugin: Plugin = [ render: () =>

Test Page

, }, }, + { + type: 'Dashboards/Tab', + properties: { + id: 'foo-tab', + title: 'Foo', + }, + }, + { + type: 'Dashboards/Card', + properties: { + tab: 'foo-tab', + position: GridPosition.MAIN, + loader: () => + import('./dashboards/foo-card' /* webpackChunkName: "demo-card" */).then((m) => m.FooCard), + }, + }, ]; export default plugin; diff --git a/frontend/packages/console-plugin-sdk/src/registry.ts b/frontend/packages/console-plugin-sdk/src/registry.ts index 50d376b8b82..81348b68f10 100644 --- a/frontend/packages/console-plugin-sdk/src/registry.ts +++ b/frontend/packages/console-plugin-sdk/src/registry.ts @@ -12,6 +12,8 @@ import { isYAMLTemplate, isRoutePage, isDashboardsOverviewHealthSubsystem, + isDashboardsCard, + isDashboardsTab, } from './typings'; /** @@ -59,4 +61,12 @@ export class ExtensionRegistry { public getDashboardsOverviewHealthSubsystems() { return this.extensions.filter(isDashboardsOverviewHealthSubsystem); } + + public getDashboardsTabs() { + return this.extensions.filter(isDashboardsTab); + } + + public getDashboardsCards() { + return this.extensions.filter(isDashboardsCard); + } } diff --git a/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts b/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts index 4dba964e9e3..c1a8b8b9611 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts @@ -1,5 +1,8 @@ import { SubsystemHealth } from '@console/internal/components/dashboards-page/overview-dashboard/health-card'; +import { GridPosition } from '@console/internal/components/dashboard/grid'; + import { Extension } from './extension'; +import { LazyLoader } from './types'; namespace ExtensionProperties { interface DashboardsOverviewHealthSubsystem { @@ -31,6 +34,25 @@ namespace ExtensionProperties { /** The Prometheus query */ query: string; } + + export interface DashboardsTab { + /** The tab's ID which will be used as part of href within dashboards page */ + id: string; + + /** The tab title */ + title: string; + } + + export interface DashboardsCard { + /** The tab's ID where this card should be rendered */ + tab: string; + + /** The card position in the tab */ + position: GridPosition; + + /** Loader for the corresponding dashboard card component. */ + loader: LazyLoader; + } } export interface DashboardsOverviewHealthURLSubsystem @@ -60,3 +82,17 @@ export const isDashboardsOverviewHealthSubsystem = ( e: Extension, ): e is DashboardsOverviewHealthSubsystem => isDashboardsOverviewHealthURLSubsystem(e) || isDashboardsOverviewHealthPrometheusSubsystem(e); + +export interface DashboardsTab extends Extension { + type: 'Dashboards/Tab'; +} + +export const isDashboardsTab = (e: Extension): e is DashboardsTab => + e.type === 'Dashboards/Tab'; + +export interface DashboardsCard extends Extension { + type: 'Dashboards/Card'; +} + +export const isDashboardsCard = (e: Extension): e is DashboardsCard => + e.type === 'Dashboards/Card'; diff --git a/frontend/packages/console-plugin-sdk/src/typings/pages.ts b/frontend/packages/console-plugin-sdk/src/typings/pages.ts index 8adcbb0522c..de88694ed41 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/pages.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/pages.ts @@ -1,9 +1,7 @@ -import * as React from 'react'; import { RouteProps, RouteComponentProps } from 'react-router-dom'; import { K8sKind, K8sResourceKindReference } from '@console/internal/module/k8s'; import { Extension } from './extension'; - -type LazyLoader = () => Promise>>; +import { LazyLoader } from './types'; namespace ExtensionProperties { export interface ResourcePage { diff --git a/frontend/packages/console-plugin-sdk/src/typings/types.ts b/frontend/packages/console-plugin-sdk/src/typings/types.ts new file mode 100644 index 00000000000..81742885303 --- /dev/null +++ b/frontend/packages/console-plugin-sdk/src/typings/types.ts @@ -0,0 +1 @@ +export type LazyLoader = () => Promise>>; diff --git a/frontend/public/components/app-contents.tsx b/frontend/public/components/app-contents.tsx index bf9894383c3..1b5020b4832 100644 --- a/frontend/public/components/app-contents.tsx +++ b/frontend/public/components/app-contents.tsx @@ -118,7 +118,7 @@ const AppContents = withRouter(React.memo(() => ( - import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then(m => m.DashboardsPage)} /> + import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then(m => m.DashboardsPage)} /> import('./cluster-overview' /* webpackChunkName: "cluster-overview" */).then(m => m.ClusterOverviewPage)} /> diff --git a/frontend/public/components/dashboard/dashboard-card/card-body.tsx b/frontend/public/components/dashboard/dashboard-card/card-body.tsx index d03170c4316..50f0aa8f05c 100644 --- a/frontend/public/components/dashboard/dashboard-card/card-body.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card-body.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { CardBody, CardBodyProps } from '@patternfly/react-core'; -import { LoadingInline } from '../../utils'; +import { LoadingInline } from '../../utils/status-box'; export const DashboardCardBody: React.FC = React.memo(({ isLoading, classname, children, ...props }) => ( diff --git a/frontend/public/components/dashboard/grid.tsx b/frontend/public/components/dashboard/grid.tsx index f73c3afc441..30a6f7ced4d 100644 --- a/frontend/public/components/dashboard/grid.tsx +++ b/frontend/public/components/dashboard/grid.tsx @@ -1,35 +1,58 @@ import * as React from 'react'; import { Grid, GridItem } from '@patternfly/react-core'; -import { useRefWidth } from '../utils'; +import { useRefWidth } from '../utils/ref-width-hook'; export const MEDIA_QUERY_LG = 992; -export const DashboardGrid: React.FC = ({ mainCards, leftCards, rightCards }) => { +export enum GridPosition { + MAIN = 'MAIN', + LEFT = 'LEFT', + RIGHT = 'RIGHT', +} + +const mapCardsToGrid = (cards: React.ComponentType[], keyPrefix: string): React.ReactNode[] => + cards.map((Card, index) => ( + + )); + +export const DashboardGrid: React.FC = ({ mainCards, leftCards = [], rightCards = [] }) => { const [containerRef, width] = useRefWidth(); const grid = width <= MEDIA_QUERY_LG ? ( - {mainCards} + + {mapCardsToGrid(mainCards, 'main')} + - {leftCards} + + {mapCardsToGrid(leftCards, 'left')} + - {rightCards} + + {mapCardsToGrid(rightCards, 'right')} + ) : ( - {leftCards} + + {mapCardsToGrid(leftCards, 'left')} + - {mainCards} + + {mapCardsToGrid(mainCards, 'main')} + - {rightCards} + + {mapCardsToGrid(rightCards, 'right')} + ); @@ -38,7 +61,7 @@ export const DashboardGrid: React.FC = ({ mainCards, leftCar }; type DashboardGridProps = { - mainCards: React.ReactNode, - leftCards?: React.ReactNode, - rightCards?: React.ReactNode, + mainCards: React.ComponentType[], + leftCards?: React.ComponentType[], + rightCards?: React.ComponentType[], }; diff --git a/frontend/public/components/dashboards-page/dashboards.tsx b/frontend/public/components/dashboards-page/dashboards.tsx index bb0379180a9..4dc53912bc1 100644 --- a/frontend/public/components/dashboards-page/dashboards.tsx +++ b/frontend/public/components/dashboards-page/dashboards.tsx @@ -2,18 +2,46 @@ import * as React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { connect } from 'react-redux'; +import * as plugins from '../../plugins'; import { OverviewDashboard } from './overview-dashboard/overview-dashboard'; -import { HorizontalNav, PageHeading, LoadingBox } from '../utils'; +import { HorizontalNav, PageHeading, LoadingBox, Page, AsyncComponent } from '../utils'; +import { Dashboard } from '../dashboard/dashboard'; +import { DashboardGrid, GridPosition } from '../dashboard/grid'; +import { DashboardsCard } from '@console/plugin-sdk'; -const tabs = [ +const getCardsOnPosition = (cards: DashboardsCard[], position: GridPosition): React.ComponentType[] => + cards.filter(c => c.properties.position === position).map(c => () => ); + +const getPluginTabPages = (): Page[] => { + const cards = plugins.registry.getDashboardsCards(); + return plugins.registry.getDashboardsTabs().map(tab => { + const tabCards = cards.filter(c => c.properties.tab === tab.properties.id); + return { + href: tab.properties.id, + name: tab.properties.title, + component: () => ( + + + + ), + }; + }); +}; + +const tabs: Page[] = [ { href: '', name: 'Overview', component: OverviewDashboard, }, + ...getPluginTabPages(), ]; -const _DashboardsPage: React.FC = ({ match, kindsInFlight }) => { +const DashboardsPage_: React.FC = ({ match, kindsInFlight }) => { return kindsInFlight ? : ( @@ -28,7 +56,7 @@ const mapStateToProps = ({k8s}) => ({ kindsInFlight: k8s.getIn(['RESOURCES', 'inFlight']), }); -export const DashboardsPage = connect(mapStateToProps)(_DashboardsPage); +export const DashboardsPage = connect(mapStateToProps)(DashboardsPage_); type DashboardsPageProps = RouteComponentProps & { kindsInFlight: boolean; diff --git a/frontend/public/components/dashboards-page/overview-dashboard/overview-dashboard.tsx b/frontend/public/components/dashboards-page/overview-dashboard/overview-dashboard.tsx index e62acf723a5..a912359af4a 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/overview-dashboard.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/overview-dashboard.tsx @@ -5,13 +5,8 @@ import { HealthCard } from './health-card'; import { DetailsCard } from './details-card'; export const OverviewDashboard: React.FC<{}> = () => { - const mainCards = [ - , - ]; - - const leftCards = [ - , - ]; + const mainCards = [HealthCard]; + const leftCards = [DetailsCard]; return ( diff --git a/frontend/public/components/utils/horizontal-nav.tsx b/frontend/public/components/utils/horizontal-nav.tsx index 8ff063fa165..113877db532 100644 --- a/frontend/public/components/utils/horizontal-nav.tsx +++ b/frontend/public/components/utils/horizontal-nav.tsx @@ -26,7 +26,7 @@ class PodsComponent extends React.PureComponent { } } -type Page = { +export type Page = { href: string; name: string; component?: React.ComponentType;