If you plan to create your own Overview page for your OKD installation, this blog will help you get started.
The Overview page has a standardized layout consisting of three columns. The left column contains the Details and Inventory cards, the center column consists of the Status and Utilization cards, and the right column holds a single Activity card. You can of course use any other custom cards in your overview page and I will show you how in this blog.
To learn more about the design and high level concepts, you can check out this blog. This post focuses on the code.
Building Blocks of the Overview Page
Let’s take a look at components which you will be using to build your own Overview page.
const FooOverview = () => (
<Dashboard>
<DashboardGrid mainCards={[]} leftCards={[]} rightCards={[]} />
</Dashboard>
);
You probably noticed that components used for overview pages are prefixed with Dashboard
. Overview pages were originally called dashboard pages. They were later renamed to avoid confusion with monitoring dashboards, but the code hasn’t yet been updated to reflect this change.
The root Dashboard
component simply adds some CSS styling, such as a gray background and padding around the page. DashboardGrid
places the card props passed into it (mainCards
, leftCards
, rightCards
) into a grid layout, and ensures that the grid is responsive to screen size changes. To achieve this responsiveness, it measures the actual grid component width (not browser’s viewport) and will render the grid as a single column if the width is smaller than PatternFly’s defined LG breakpoint
(992px). In the single column presentation, the main cards are shown first, followed by the left and right cards.
Now, we have an overall page layout, which means it’s time for the fun part: creating content! In our case, the content is a series of cards.
Generally speaking, every card looks like this:
const FooCard = () => (
<DashboardCard>
<DashboardCardHeader>
<DashboardCardTitle>Foo</DashboardCardTitle>
<DashboardCardLink to=”/foo-location”>Foo action</DashboardCardLink>
</DashboardCardHeader>
<DashboardCardBody>
{// your content here}
</DashboardCardBody>
</DashboardCard>
);
The root DashboardCard
component is using the withFallback higher-order component (HOC), which makes sure that if something goes wrong with a particular card, the misbehaving card will be hidden instead of ruining the whole page.
At the top of each card is a DashboardCardHeader
. It has a title and optionally, some actions. Nothing fancy here.
The DashboardCardBody
is the place where you will want to show the interesting stuff. Usually, you will fetch some data from the backend (k8s, prometheus, or some other service running in the cluster) and somehow render them here.
Getting Data from the Backend
Since you will have multiple cards on your page, with all of them making some remote requests, it may happen that some cards will require the same data from the same service. If you just blindly create these requests from the cards, you will fetch the same data multiple times, for each card separately, and due to various resolution times the results won't be synchronized. Imagine a case where you, for whatever reason, want to show the data for the same prometheus metric in two cards. Every card will create its own request and every request is resolved after a different amount of time which leads to data not being the same and you also needlessly overfetched.
To avoid these issues, you will want to use the withDashboardResources HOC (we don't have a hook yet, but contributions are welcome!) which injects functions for fetching data from prometheus or any other in-cluster service and makes sure there are no duplicate requests. In order to fetch k8s resources, you will use useK8sWatchResource hook.
A Card that Actually Does Something
Let's put it all together and create a card that actually gets some data from the backend and renders it.
FooCard
will fetch data from various sources - CPU metrics from prometheus, cluster health from the healthz endpoint, and a Pod resource from k8s.
import * as React from 'react';
import { getPrometheusQueryResponse } from '@console/internal/actions/dashboards';
import { withDashboardResources } from '@console/internal/components/dashboard/with-dashboard-resources';
import { humanizePercentage } from '@console/internal/components/utils';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { PodModel } from '@console/internal/models';
import { PodKind } from '@console/internal/module/k8s';
import DashboardCard from '@console/shared/src/components/dashboard/dashboard-card/DashboardCard';
import DashboardCardHeader from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardHeader';
import DashboardCardTitle from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardTitle';
import DashboardCardLink from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardLink';
import DashboardCardBody from '@console/shared/src/components/dashboard/dashboard-card/DashboardCardBody';
import { fetchK8sHealth } from '@console/app/src/components/dashboards-page/status';
const healthzEndpoint = 'healthz';
const cpuMetric = 'cluster:cpu_usage_cores:sum';
const FooCard = withDashboardResources(({ watchPrometheus, stopWatchPrometheusQuery, prometheusResults, watchURL, stopWatchURL, urlResults }) => {
React.useEffect(() => {
watchPrometheus(cpuMetric);
watchURL(healthzEndpoint, fetchK8sHealth);
return () => {
stopWatchPrometheusQuery(cpuMetric);
stopWatchURL(healthzEndpoint);
};
});
const healthz = urlResults.getIn([healthzEndpoint, 'data']);
const healthzError = urlResults.getIn([healthzEndpoint, 'loadError']);
const [cpuResults, cpuMetricError] = getPrometheusQueryResponse(prometheusResults, cpuMetric);
const [pods, podsLoaded, podsLoadError] = useK8sWatchResource<PodKind[]>({
kind: PodModel.kind,
isList: true,
});
return (
<DashboardCard>
<DashboardCardHeader>
<DashboardCardTitle>Foo</DashboardCardTitle>
<DashboardCardLink to="/foo-location">Foo action</DashboardCardLink>
</DashboardCardHeader>
<DashboardCardBody>
<div>Cluster health: {healthzError ? 'error' : !healthz ? 'loading' : healthz}</div>
<div>CPU usage: {cpuMetricError ? 'error' : !cpuResults ? 'loading' : humanizePercentage(cpuResults.data.result[0].value[1]).string}</div>
<div>Pods count: {podsLoadError ? 'error' : !podsLoaded ? 'loading' : pods.length}</div>
</DashboardCardBody>
</DashboardCard>
);
});
export default FooCard;
The only thing that remains is to add the card to the FooOverview
page.
import * as React from 'react';
import Dashboard from '@console/shared/src/components/dashboard/Dashboard';
import DashboardGrid from '@console/shared/src/components/dashboard/DashboardGrid';
import FooCard from './FooCard';
const FooOverview = () => (
<Dashboard>
<DashboardGrid mainCards={[{ Card: FooCard }]} leftCards={[{ Card: FooCard }]} rightCards={[{ Card: FooCard }]} />
</Dashboard>
);
export default FooOverview;
In the above snippet, I've actually added the FooCard
to every column so you can verify (via the browser's network tab, for example) that the same fetch request is executed only once.
Yay! Our fancy
FooOverview
page.
Exposing the FooOverview Page
To expose your newly created FooOverview page, you will need to create a static plugin for the OpenShift Console. We have a nice readme that will help you get started in our github repository.
Every static plugin has a plugin.ts
file that exports all plugin extensions. Console provides a few extension points that you can choose to expose FooOverview
as a tab next to Cluster overview, as a page that will appear in main navigation, or as a standalone page.
To expose FooOverview
as a page, your plugin.ts
will look like this:
import { Plugin, RoutePage, HrefNavItem } from '@console/plugin-sdk';
type ConsumedExtensions = RoutePage | HrefNavItem;
const plugin: Plugin<ConsumedExtensions> = [
// expose as page & add item to navigation
{
type: 'Page/Route',
properties: {
exact: true,
path: 'foo-overview',
loader: () => import('./components/FooOverview' /* webpackChunkName: "foo-overview" */).then((m) => m.default),
},
},
{
type: 'NavItem/Href',
properties: {
id: 'fooNav',
componentProps: {
name: 'Foo Overview',
href: '/foo-overview',
},
},
},
];
export default plugin;
That's a Wrap
I hope this gives you a solid idea of how to create your own Overview page. The React components that you will be using can be found in the console-shared package.
Happy hacking!
Über den Autor
Mehr davon
Nach Thema durchsuchen
Automatisierung
Das Neueste zum Thema IT-Automatisierung für Technologien, Teams und Umgebungen
Künstliche Intelligenz
Erfahren Sie das Neueste von den Plattformen, die es Kunden ermöglichen, KI-Workloads beliebig auszuführen
Open Hybrid Cloud
Erfahren Sie, wie wir eine flexiblere Zukunft mit Hybrid Clouds schaffen.
Sicherheit
Erfahren Sie, wie wir Risiken in verschiedenen Umgebungen und Technologien reduzieren
Edge Computing
Erfahren Sie das Neueste von den Plattformen, die die Operations am Edge vereinfachen
Infrastruktur
Erfahren Sie das Neueste von der weltweit führenden Linux-Plattform für Unternehmen
Anwendungen
Entdecken Sie unsere Lösungen für komplexe Herausforderungen bei Anwendungen
Original Shows
Interessantes von den Experten, die die Technologien in Unternehmen mitgestalten
Produkte
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud-Services
- Alle Produkte anzeigen
Tools
- Training & Zertifizierung
- Eigenes Konto
- Kundensupport
- Für Entwickler
- Partner finden
- Red Hat Ecosystem Catalog
- Mehrwert von Red Hat berechnen
- Dokumentation
Testen, kaufen und verkaufen
Kommunizieren
Über Red Hat
Als weltweit größter Anbieter von Open-Source-Software-Lösungen für Unternehmen stellen wir Linux-, Cloud-, Container- und Kubernetes-Technologien bereit. Wir bieten robuste Lösungen, die es Unternehmen erleichtern, plattform- und umgebungsübergreifend zu arbeiten – vom Rechenzentrum bis zum Netzwerkrand.
Wählen Sie eine Sprache
Red Hat legal and privacy links
- Über Red Hat
- Jobs bei Red Hat
- Veranstaltungen
- Standorte
- Red Hat kontaktieren
- Red Hat Blog
- Diversität, Gleichberechtigung und Inklusion
- Cool Stuff Store
- Red Hat Summit