Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions frontend/src/components/cluster/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Event from '../../lib/k8s/event';
import Node from '../../lib/k8s/node';
import Pod from '../../lib/k8s/pod';
import { useFilterFunc } from '../../lib/util';
import { OverviewChart } from '../../redux/overviewChartsSlice';
import { useTypedSelector } from '../../redux/reducers/reducers';
import { DateLabel, Link, PageGrid, StatusLabel } from '../common';
import ResourceListView from '../common/Resource/ResourceListView';
import { SectionBox } from '../common/SectionBox';
Expand All @@ -22,34 +24,54 @@ import { ClusterGroupErrorMessage } from './ClusterGroupErrorMessage';

export default function Overview() {
const { t } = useTranslation(['translation']);

const [pods] = Pod.useList();
const [nodes] = Node.useList();

const [nodeMetrics, metricsError] = Node.useMetrics();
const chartProcessors = useTypedSelector(state => state.overviewCharts.processors);

const noMetrics = metricsError?.status === 404;
const noPermissions = metricsError?.status === 403;

// Process the default charts through any registered processors
const defaultCharts: OverviewChart[] = [
{
id: 'cpu',
component: () => (
<CpuCircularChart items={nodes} itemsMetrics={nodeMetrics} noMetrics={noMetrics} />
),
},
{
id: 'memory',
component: () => (
<MemoryCircularChart items={nodes} itemsMetrics={nodeMetrics} noMetrics={noMetrics} />
),
},
{
id: 'pods',
component: () => <PodsStatusCircleChart items={pods} />,
},
{
id: 'nodes',
component: () => <NodesStatusCircleChart items={nodes} />,
},
];
const charts = chartProcessors.reduce(
(currentCharts, p) => p.processor(currentCharts),
defaultCharts
);

return (
<PageGrid>
<SectionBox title={t('translation|Overview')} py={2} mt={[4, 0, 0]}>
{noPermissions ? (
<ClusterGroupErrorMessage errors={[metricsError]} />
) : (
<Grid container justifyContent="flex-start" alignItems="stretch" spacing={4}>
<Grid item xs sx={{ maxWidth: '300px' }}>
<CpuCircularChart items={nodes} itemsMetrics={nodeMetrics} noMetrics={noMetrics} />
</Grid>
<Grid item xs sx={{ maxWidth: '300px' }}>
<MemoryCircularChart items={nodes} itemsMetrics={nodeMetrics} noMetrics={noMetrics} />
</Grid>
<Grid item xs sx={{ maxWidth: '300px' }}>
<PodsStatusCircleChart items={pods} />
</Grid>
<Grid item xs sx={{ maxWidth: '300px' }}>
<NodesStatusCircleChart items={nodes} />
</Grid>
{charts.map(chart => (
<Grid key={chart.id} item xs sx={{ maxWidth: '300px' }}>
<chart.component />
</Grid>
))}
</Grid>
)}
</SectionBox>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/plugin/__snapshots__/pluginLib.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -16114,6 +16114,7 @@
"registerDetailsViewSectionsProcessor": [Function],
"registerGetTokenFunction": [Function],
"registerHeadlampEventCallback": [Function],
"registerOverviewChartsProcessor": [Function],
"registerPluginSettings": [Function],
"registerResourceTableColumnsProcessor": [Function],
"registerRoute": [Function],
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/plugin/registry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ScaleResourceEvent,
TerminalEvent,
} from '../redux/headlampEventSlice';
import { addOverviewChartsProcessor, OverviewChartsProcessor } from '../redux/overviewChartsSlice';
import { setRoute, setRouteFilter } from '../redux/routesSlice';
import store from '../redux/stores/store';
import {
Expand Down Expand Up @@ -97,6 +98,7 @@ export type {
EventListEvent,
PluginSettingsDetailsProps,
PluginSettingsComponentType,
OverviewChartsProcessor,
};
export const DefaultHeadlampEvents = HeadlampEventType;
export const DetailsViewDefaultHeaderActions = DefaultHeaderAction;
Expand Down Expand Up @@ -684,6 +686,31 @@ export function registerPluginSettings(
store.dispatch(setPluginSettingsComponent({ name, component, displaySaveButton }));
}

/**
* Add a processor for the overview charts section. Allowing the addition or modification of charts.
*
* @param processor - The processor to add. Returns the new charts to be displayed.
*
* @example
*
* ```tsx
* import { registerOverviewChartsProcessor } from '@kinvolk/headlamp-plugin/lib';
*
* registerOverviewChartsProcessor(function addFailedPodsChart(charts) {
* return [
* ...charts,
* {
* id: 'failed-pods',
* component: () => <FailedPodsChart />
* }
* ];
* });
* ```
*/
export function registerOverviewChartsProcessor(processor: OverviewChartsProcessor) {
store.dispatch(addOverviewChartsProcessor(processor));
}

export {
DefaultAppBarAction,
DefaultDetailsViewSection,
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/redux/overviewChartsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ReactNode } from 'react';

export interface OverviewChart {
id: string;
component: () => ReactNode;
}

export interface OverviewChartsProcessor {
id?: string;
processor: (charts: OverviewChart[]) => OverviewChart[];
}

interface OverviewChartsState {
processors: OverviewChartsProcessor[];
}

const initialState: OverviewChartsState = {
processors: [],
};

const overviewChartsSlice = createSlice({
name: 'overviewCharts',
initialState,
reducers: {
addProcessor: (state, action: PayloadAction<OverviewChartsProcessor>) => {
state.processors.push(action.payload);
},
},
});

export const { addProcessor: addOverviewChartsProcessor } = overviewChartsSlice.actions;
export default overviewChartsSlice.reducer;
2 changes: 2 additions & 0 deletions frontend/src/redux/reducers/reducers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import clusterAction from '../clusterActionSlice';
import configReducer from '../configSlice';
import filterReducer from '../filterSlice';
import eventCallbackReducer from '../headlampEventSlice';
import overviewChartsReducer from '../overviewChartsSlice';
import routesReducer from '../routesSlice';
import resourceTableReducer from './../../components/common/Resource/resourceTableSlice';
import detailsViewSectionReducer from './../../components/DetailsViewSection/detailsViewSectionSlice';
Expand All @@ -31,6 +32,7 @@ const reducers = combineReducers({
detailsViewSections: detailsViewSectionReducer,
eventCallbackReducer,
pluginConfigs: pluginConfigReducer,
overviewCharts: overviewChartsReducer,
});

export type RootState = ReturnType<typeof reducers>;
Expand Down
5 changes: 5 additions & 0 deletions plugins/examples/resource-charts/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
17 changes: 17 additions & 0 deletions plugins/examples/resource-charts/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Place your settings in this file to overwrite default and user settings.
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
33 changes: 33 additions & 0 deletions plugins/examples/resource-charts/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"problemMatcher": ["$tsc-watch", "$eslint-compact"],
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "test",
"problemMatcher": ["$tsc-watch", "$eslint-compact"],
"isBackground": true,
"presentation": {
"reveal": "always"
},
"group": {
"kind": "test",
"isDefault": true
}
}
]
}
35 changes: 35 additions & 0 deletions plugins/examples/resource-charts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Resource CircularCharts Plugin

This plugin demonstrates how to add custom resource charts to Headlamp's Overview page. Currently, it adds a Pod Failure chart that visualizes the number of failed pods in the cluster.

## Features

- Adds a "Pods Failed" chart to the Overview page
- Uses theme-based colors to highlight failed pods
- Shows percentage and count of failed pods vs total pods

## Usage

To run the plugin:

```bash
cd plugins/examples/resource-charts
npm install
npm start
```

Then visit the Overview page in Headlamp to see the Pod Failure chart in action.

## Development

The main implementation is in [src/index.tsx](src/index.tsx), which shows how to:

- Create a custom chart component using Headlamp's TileChart
- Use Kubernetes resource data with Headlamp's K8s API
- Register charts to appear in the Overview page using the charts processor

For more information on developing Headlamp plugins, please refer to:

- [Getting Started](https://headlamp.dev/docs/latest/development/plugins/)
- [API Reference](https://headlamp.dev/docs/latest/development/api/)
- [UI Component Storybook](https://headlamp.dev/docs/latest/development/frontend/#storybook)
Loading
Loading