@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
944 lines (939 loc) • 30.9 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
/*
* Copyright 2025 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { has } from 'lodash';
import { PluginManager } from '../components/App/pluginManager';
import { runCommand } from '../components/App/runCommand';
import { setBrandingAppLogoComponent, themeSlice } from '../components/App/themeSlice';
import { addResourceTableColumnsProcessor, } from '../components/common/Resource/resourceTableSlice';
import { SectionBox } from '../components/common/SectionBox';
import { addDetailsViewSectionsProcessor, DefaultDetailsViewSection, setDetailsViewSection, } from '../components/DetailsViewSection/detailsViewSectionSlice';
import { graphViewSlice } from '../components/resourceMap/graphViewSlice';
import { setSidebarItem, setSidebarItemFilter } from '../components/Sidebar/sidebarSlice';
import { getHeadlampAPIHeaders } from '../helpers/getHeadlampAPIHeaders';
import { addDetailsViewHeaderActionsProcessor, DefaultAppBarAction, DefaultHeaderAction, setAppBarAction, setAppBarActionsProcessor, setDetailsViewHeaderAction, } from '../redux/actionButtonsSlice';
import { clusterAction as sendClusterAction, } from '../redux/clusterActionSlice';
import { addAddClusterProvider, addClusterStatus, addDialog, addMenuItem, } from '../redux/clusterProviderSlice';
import { addEventCallback, HeadlampEventType, } from '../redux/headlampEventSlice';
import { addOverviewChartsProcessor } from '../redux/overviewChartsSlice';
import { addCustomCreateProject, addDetailsTab, addHeaderAction, addOverviewSection, setProjectDeleteButton, } from '../redux/projectsSlice';
import { setRoute, setRouteFilter } from '../redux/routesSlice';
import store from '../redux/stores/store';
import { uiSlice } from '../redux/uiSlice';
import { ConfigStore } from './configStore';
import { setPluginSettingsComponent, } from './pluginsSlice';
export const DefaultHeadlampEvents = HeadlampEventType;
export const DetailsViewDefaultHeaderActions = DefaultHeaderAction;
export default class Registry {
/**
* @deprecated Registry.registerSidebarItem is deprecated. Please use registerSidebarItem.
*/
registerSidebarItem(parentName, itemName, itemLabel, url, opts = { useClusterURL: true }) {
console.warn('Registry.registerSidebarItem is deprecated. Please use registerSidebarItem.');
const { useClusterURL = true, ...options } = opts;
store.dispatch(setSidebarItem({
name: itemName,
label: itemLabel,
url,
parent: parentName,
useClusterURL,
...options,
}));
}
/**
* @deprecated Registry.registerRoute is deprecated. Please use registerRoute.
*/
registerRoute(routeSpec) {
console.warn('Registry.registerRoute is deprecated. Please use registerRoute.');
return registerRoute(routeSpec);
}
/**
* @deprecated Registry.registerDetailsViewHeaderAction is deprecated. Please use registerDetailsViewHeaderAction.
*/
registerDetailsViewHeaderAction(actionName, actionFunc) {
console.warn('Registry.registerDetailsViewHeaderAction is deprecated. Please use registerDetailsViewHeaderAction.');
store.dispatch(setDetailsViewHeaderAction(actionFunc));
}
/**
* @deprecated Registry.registerAppBarAction is deprecated. Please use registerAppBarAction.
*/
registerAppBarAction(actionName, actionFunc) {
console.warn('Registry.registerAppBarAction is deprecated. Please use registerAppBarAction.');
return registerAppBarAction(actionFunc);
}
/**
* @deprecated Registry.registerDetailsViewSection is deprecated. Please use registerDetailsViewSection.
*
* ```tsx
*
* register.registerDetailsViewSection('biolatency', resource => {
* if (resource?.kind === 'Node') {
* return {
* title: 'Block I/O Latency',
* component: () => <CustomComponent />,
* };
* }
* return null;
* });
*
* ```
*/
registerDetailsViewSection(sectionName, sectionFunc) {
console.warn('Registry.registerDetailsViewSection is deprecated. Please use registerDetailsViewSection.');
function OurComponent({ resource }) {
const res = sectionFunc(resource);
if (res === null) {
return null;
}
return (_jsx(SectionBox, { title: sectionName, children: _jsx(res.component, { resource: resource }) }));
}
return registerDetailsViewSection(OurComponent);
}
/**
* @deprecated Registry.registerAppLogo is deprecated. Please use registerAppLogo.
*/
registerAppLogo(logo) {
console.warn('Registry.registerAppLogo is deprecated. Please use registerAppLogo.');
return registerAppLogo(logo);
}
/**
* @deprecated Registry.registerClusterChooserComponent is deprecated. Please use registerClusterChooser.
*/
registerClusterChooserComponent(component) {
console.warn('Registry.registerClusterChooserComponent is deprecated. Please use registerClusterChooser.');
return registerClusterChooser(component);
}
}
/**
* Add a Sidebar Entry to the menu (on the left side of Headlamp).
*
* @example
*
* ```tsx
* import { registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
* registerSidebarEntry({ parent: 'cluster', name: 'traces', label: 'Traces', url: '/traces' });
*
* ```
*
* @see {@link http://github.com/kinvolk/headlamp/plugins/examples/sidebar/ Sidebar Example}
*/
export function registerSidebarEntry({ parent, name, label, url, useClusterURL = true, icon, sidebar, }) {
store.dispatch(setSidebarItem({
name,
label,
url,
parent,
useClusterURL,
icon,
sidebar,
}));
}
/**
* Custom glance component for Kubernetes objects in Headlamp's graph view.
*
* @param glance - The glance object with a unique id and a React component to render.
*
* @example
*
* ```tsx
import { registerKubeObjectGlance } from '@kinvolk/headlamp-plugin/lib';
const NodeGlance = ({ node }) => {
// Check if the node represents a Kubernetes Node object
if (node.kubeObject && node.kubeObject.kind === 'Node') {
return (
<div>
<strong>Node:</strong> {node.kubeObject.metadata?.name} (CPU: {node.kubeObject.status?.capacity?.cpu || 'N/A'})
</div>
);
}
// Handle non-Kubernetes nodes with label or fallback to a default
if (node.label) {
return (
<div>
<strong>Node:</strong> {node.label}
</div>
);
}
// Return null if the node cannot be rendered by this glance
return null;
};
registerKubeObjectGlance({ id: 'node-glance', component: NodeGlance });
* ```
*/
export function registerKubeObjectGlance(glance) {
store.dispatch(graphViewSlice.actions.setGlance(glance));
}
/**
* Remove sidebar menu items.
*
* @param filterFunc - a function for filtering sidebar entries.
*
* @example
*
* ```tsx
* import { registerSidebarEntryFilter } from '@kinvolk/headlamp-plugin/lib';
*
* registerSidebarEntryFilter(entry => (entry.name === 'workloads' ? null : entry));
* ```
*/
export function registerSidebarEntryFilter(filterFunc) {
store.dispatch(setSidebarItemFilter(filterFunc));
}
/**
* Remove routes.
*
* @param filterFunc - a function for filtering routes.
*
* @example
*
* ```tsx
* import { registerRouteFilter } from '@kinvolk/headlamp-plugin/lib';
*
* registerRouteFilter(route => (route.path === '/workloads' ? null : route));
* ```
*/
export function registerRouteFilter(filterFunc) {
store.dispatch(setRouteFilter(filterFunc));
}
/**
* Add a Route for a component.
*
* @param routeSpec - details of URL, highlighted sidebar and component to use.
*
* @example
*
* ```tsx
* import { registerRoute } from '@kinvolk/headlamp-plugin/lib';
*
* // Add a route that will display the given component and select
* // the "traces" sidebar item.
* registerRoute({
* path: '/traces',
* sidebar: 'traces',
* component: () => <TraceList />
* });
* ```
*
* @see {@link https://github.com/kinvolk/headlamp/blob/main/frontend/src/lib/router.tsx Route examples}
* @see {@link http://github.com/kinvolk/headlamp/plugins/examples/sidebar/ Sidebar Example}
*
*/
export function registerRoute(routeSpec) {
store.dispatch(setRoute(routeSpec));
}
/**
* Add a component into the details view header.
*
* @param headerAction - The action (link) to put in the app bar.
*
* @example
*
* ```tsx
* import { ActionButton } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
* import { registerDetailsViewHeaderAction } from '@kinvolk/headlamp-plugin/lib';
*
* function IconAction() {
* return (
* <ActionButton
* description="Launch"
* icon="mdi:comment-quote"
* onClick={() => console.log('Hello from IconAction!')}
* />
* )
* }
*
* registerDetailsViewHeaderAction(IconAction);
* ```
*/
export function registerDetailsViewHeaderAction(headerAction) {
store.dispatch(setDetailsViewHeaderAction(headerAction));
}
/**
* Add a processor for the details view header actions. Allowing the modification of header actions.
*
* @param processor - The processor to add. Receives a resource (for which we are processing the header actions) and the current header actions and returns the new header actions. Return an empty array to remove all header actions.
*
* @example
*
* ```tsx
* import { registerDetailsViewHeaderActionsProcessor, DetailsViewDefaultHeaderActions } from '@kinvolk/headlamp-plugin/lib';
*
* // Processor that removes the default edit action.
* registerDetailsViewHeaderActionsProcessor((resource, headerActions) => {
* return headerActions.filter(action => action.name !== DetailsViewDefaultHeaderActions.EDIT);
* });
*
* More complete detail view example in plugins/examples/details-view:
* @see {@link http://github.com/kinvolk/headlamp/plugins/examples/details-view/ Detail View Example}
*
*/
export function registerDetailsViewHeaderActionsProcessor(processor) {
store.dispatch(addDetailsViewHeaderActionsProcessor(processor));
}
/**
* Add a processor for the resource table columns. Allowing the modification of what tables show.
*
* @param processor - The processor ID and function. See #TableColumnsProcessor.
*
* @example
*
* ```tsx
* import { registerResourceTableColumnsProcessor } from '@kinvolk/headlamp-plugin/lib';
*
* // Processor that adds a column to show how many init containers pods have (in the default pods' list table).
* registerResourceTableColumnsProcessor(function ageRemover({ id, columns }) {
* if (id === 'headlamp-pods') {
* columns.push({
* label: 'Init Containers',
* // return plain value to allow filtering and sorting
* getValue: (pod: Pod) => {
* return pod.spec.initContainers.length;
* }
* // (optional) customise how the cell value is rendered
* render: (pod: Pod) => <div style={{ color: "red" }}>{pod.spec.initContainers.length}</div>
* });
* }
*
* return columns;
* });
* ```
*/
export function registerResourceTableColumnsProcessor(processor) {
store.dispatch(addResourceTableColumnsProcessor(processor));
}
function isProcessor(headerAction) {
return !!(headerAction &&
(has(headerAction, 'processor') ||
(typeof headerAction === 'function' && headerAction.length === 1)));
}
/**
* Add a component into the app bar (at the top of the app).
*
* @param headerAction - The action (link) to put in the app bar.
*
* @example
*
* ```tsx
* import { registerAppBarAction } from '@kinvolk/headlamp-plugin/lib';
* import { Button } from '@mui/material';
*
* function ConsoleLogger() {
* return (
* <Button
* onClick={() => {
* console.log('Hello from ConsoleLogger!')
* }}
* >
* Print Log
* </Button>
* );
* }
*
* registerAppBarAction(ConsoleLogger);
* ```
*/
export function registerAppBarAction(headerAction) {
if (isProcessor(headerAction)) {
store.dispatch(setAppBarActionsProcessor(headerAction));
}
store.dispatch(setAppBarAction(headerAction));
}
/**
* Append a component to the details view for a given resource.
*
* @param viewSection - The section to add on different view screens.
*
* @example
*
* ```tsx
* import {
* registerDetailsViewSection,
* DetailsViewSectionProps
* } from '@kinvolk/headlamp-plugin/lib';
*
* registerDetailsViewSection(({ resource }: DetailsViewSectionProps) => {
* if (resource.kind === 'Pod') {
* return (
* <SectionBox title="A very fine section title">
* The body of our Section for {resource.kind}
* </SectionBox>
* );
* }
* return null;
* });
* ```
*/
export function registerDetailsViewSection(viewSection) {
store.dispatch(setDetailsViewSection(viewSection));
}
/**
* Add a processor for the details view sections. Allowing the modification of what sections are shown.
*
* @param processor - The processor to add. Receives a resource (for which we are processing the sections) and the current sections and returns the new sections. Return an empty array to remove all sections.
*
* @example
*
* ```tsx
* import { registerDetailsViewSectionsProcessor } from '@kinvolk/headlamp-plugin/lib';
*
* registerDetailsViewSectionsProcessor(function addTopSection( resource, sections ) {
* // Ignore if there is no resource.
* if (!resource) {
* return sections;
* }
*
* // Check if we already have added our custom section (this function may be called multiple times).
* const customSectionId = 'my-custom-section';
* if (sections.findIndex(section => section.id === customSectionId) !== -1) {
* return sections;
* }
*
* return [
* {
* id: 'my-custom-section',
* section: (
* <SectionBox title="I'm the top of the world!" />
),
* },
* ...sections,
* ];
* });
* ```
*/
export function registerDetailsViewSectionsProcessor(processor) {
store.dispatch(addDetailsViewSectionsProcessor(processor));
}
/**
* Add a logo for Headlamp to use instead of the default one.
*
* @param logo is a React Component that takes two required props
* `logoType` which is a constant string literal that accepts either
* of the two values `small` or `large` depending on whether
* the sidebar is in shrink or expanded state so that you can change your logo
* from small to large and the other optional prop is the `themeName`
* which is a string with two values 'light' and 'dark' base on which theme is selected.
*
* @example
*
* ```tsx
* import { registerAppLogo } from '@kinvolk/headlamp-plugin/lib';
*
* registerAppLogo(<p>my logo</p>)
* ```
*
* More complete logo example in plugins/examples/change-logo:
* @see {@link http://github.com/kinvolk/headlamp/plugins/examples/change-logo/ Change Logo Example}
*
*/
export function registerAppLogo(logo) {
store.dispatch(setBrandingAppLogoComponent(logo));
}
/**
* Use a custom cluster chooser button
*
* @param chooser is a React Component that takes one required props ```clickHandler``` which is the
* action handler that happens when the custom chooser button component click event occurs
*
* @example
*
* ```tsx
* import { ClusterChooserProps, registerClusterChooser } from '@kinvolk/headlamp-plugin/lib';
*
* registerClusterChooser(({ clickHandler, cluster }: ClusterChooserProps) => {
* return <button onClick={clickHandler}>my chooser Current cluster: {cluster}</button>;
* })
* ```
*
* @see {@link http://github.com/kinvolk/headlamp/plugins/examples/cluster-chooser/ Cluster Chooser example}
*
*/
export function registerClusterChooser(chooser) {
store.dispatch(uiSlice.actions.setClusterChooserButton(chooser));
}
/**
* Override headlamp setToken method
* @param override - The setToken override method to use.
*
* @example
*
* ```ts
* registerSetTokenFunction((cluster: string, token: string | null) => {
* // set token logic here
* });
* ```
*/
export function registerSetTokenFunction(override) {
store.dispatch(uiSlice.actions.setFunctionsToOverride({ setToken: override }));
}
/**
* Override headlamp getToken method
* @param override - The getToken override method to use.
*
* @example
*
* ```ts
* registerGetTokenFunction(() => {
* // set token logic here
* });
* ```
*/
export function registerGetTokenFunction(override) {
store.dispatch(uiSlice.actions.setFunctionsToOverride({ getToken: override }));
}
/**
* Add a callback for headlamp events.
* @param callback - The callback to add.
*
* @example
*
* ```ts
* import {
* DefaultHeadlampEvents,
* registerHeadlampEventCallback,
* HeadlampEvent,
* } from '@kinvolk/headlamp-plugin/lib';
*
* registerHeadlampEventCallback((event: HeadlampEvent) => {
* if (event.type === DefaultHeadlampEvents.ERROR_BOUNDARY) {
* console.error('Error:', event.data);
* } else {
* console.log(`Headlamp event of type ${event.type}: ${event.data}`)
* }
* });
* ```
*/
export function registerHeadlampEventCallback(callback) {
store.dispatch(addEventCallback(callback));
}
/**
* Register a plugin settings component.
*
* @param name - The name of the plugin.
* @param component - The component to use for the settings.
* @param displaySaveButton - Whether to display the save button.
* @returns void
*
* @example
*
* ```tsx
* import { registerPluginSettings } from '@kinvolk/headlamp-plugin/lib';
* import { TextField } from '@mui/material';
*
* function MyPluginSettingsComponent(props: PluginSettingsDetailsProps) {
* const { data, onDataChange } = props;
*
* function onChange(value: string) {
* if (onDataChange) {
* onDataChange({ works: value });
* }
* }
*
* return (
* <TextField
* value={data?.works || ''}
* onChange={e => onChange(e.target.value)}
* label="Normal Input"
* variant="outlined"
* fullWidth
* />
* );
* }
*
* const displaySaveButton = true;
* // Register a plugin settings component.
* registerPluginSettings('my-plugin', MyPluginSettingsComponent, displaySaveButton);
* ```
*
* More complete plugin settings example in plugins/examples/change-logo:
* @see {@link https://github.com/kubernetes-sigs/headlamp/tree/main/plugins/examples/change-logo Change Logo Example}
*/
export function registerPluginSettings(name, component, displaySaveButton = false) {
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) {
store.dispatch(addOverviewChartsProcessor(processor));
}
/**
* Registers a new graph source in the store.
*
* @param {GraphSource} source - The graph source to be registered.
* @example
*
* ```tsx
* const mySource = {
* id: 'my-source',
* label: 'Sample source',
* useData() {
* return {
* nodes: [{ id: 'my-node', type: 'kubeObject', data: { resource: myCustomResource } }],
* edges: []
* };
* }
* }
*
* registerMapSource(mySource);
* ```
*/
export function registerMapSource(source) {
store.dispatch(graphViewSlice.actions.addGraphSource(source));
}
/**
* Register Icon for a resource kind
*
* By default, icons are matched only by `kind`.
* Optionally, `apiGroup` can be provided to differentiate resources that share the same kind across different API groups.
*
* When `apiGroup` is provided, Headlamp will:
* 1. First try to match `${apiGroup}/${kind}`.
* 2. Fall back to `kind` if no match is found.
*
* @param kind - Resource kind
* @param {IconDefinition} definition - icon definition
* @param definition.icon - React Element of the icon
* @param definition.color - Color for the icon, optional
* @param apiGroup - Kubernetes API group, optional
*
* @example
*
* Kind only Matching
* ```tsx
* registerKindIcon("MyCustomResource", { icon: <MyIcon />, color: "#FF0000" })
* ```
*
* Match only networking service
* ```tsx
* registerKindIcon("Service", { icon: <NetworkingServiceIcon /> }, "networking.k8s.io");
* ```
*/
export function registerKindIcon(kind, definition, apiGroup) {
store.dispatch(graphViewSlice.actions.addKindIcon({ kind, definition, apiGroup }));
}
/**
* Register a new cluster action menu item.
* @param item - The item to add to the cluster action menu.
*
* @example
*
* ```tsx
* import { registerClusterProviderMenuItem } from '@kinvolk/headlamp-plugin/lib';
* import { MenuItem, ListItemText } from '@mui/material';
* registerClusterProviderMenuItem(({cluster, setOpenConfirmDialog, handleMenuClose}) => {
* const isMinikube =
* cluster.meta_data?.extensions?.context_info?.provider === 'minikube.sigs.k8s.io';
* if (!isElectron() !! !isMinikube) {
* return null;
* }
* return (
* <MenuItem
* onClick={() => {
* setOpenConfirmDialog('deleteMinikube');
* handleMenuClose();
* }}
* >
* <ListItemText>{t('translation|Delete')}</ListItemText>
* </MenuItem>
* );
* )}
* ```
*
*/
export function registerClusterProviderMenuItem(item) {
store.dispatch(addMenuItem(item));
}
/**
* Register a new cluster status component.
*
* @param item - The component to add to the cluster status.
* Item is a function/component and its props are cluster and error.
*
* @example
* ```tsx
* import { registerClusterStatus } from '@kinvolk/headlamp-plugin/lib';
* import { ClusterStatus } from './ClusterStatus';
* registerClusterStatus(({ cluster, error }) => {
* if (!isElectron() || !isMinikube(cluster)) {
* return null;
* }
* return <ClusterStatus cluster={cluster} error={error} />;
* });
* ```
*/
export function registerClusterStatus(item) {
store.dispatch(addClusterStatus(item));
}
/**
* Register a new cluster provider dialog.
*
* These dialogs are used to show actions that can be performed on a cluster.
* For example, starting, stopping, or deleting a cluster.
*
* @param item - The item to add to the cluster provider dialog.
* @param item.cluster - The cluster to show the dialog for.
* @param item.openConfirmDialog - The name of the dialog to open. Null if no dialog is open.
* @param item.setOpenConfirmDialog - The function to set the dialog to open.
* Call it with null when dialog is closed.
*
* @example
*
* ```tsx
* import { registerClusterProviderDialog } from '@kinvolk/headlamp-plugin/lib';
* import { CommandCluster } from './CommandCluster';
*
* registerClusterProviderDialog(({cluster, openConfirmDialog, setOpenConfirmDialog}) => {
*
* const isMinikube =
* cluster.meta_data?.extensions?.context_info?.provider === 'minikube.sigs.k8s.io';
* if (!isElectron() !! !isMinikube) {
* return null;
* }
*
* return (
* <CommandCluster
* initialClusterName={cluster.name}
* open={openConfirmDialog === 'startMinikube'}
* handleClose={() => setOpenConfirmDialog(null)}
* onConfirm={() => {
* setOpenConfirmDialog(null);
* }}
* command={'start'}
* finishedText={'Done! kubectl is now configured'}
* />
* );
* });
*
* ```
*
*/
export function registerClusterProviderDialog(item) {
store.dispatch(addDialog(item));
}
/**
* For adding a card to the Add Cluster page in the providers list.
* @param item - The iformation to add to the Add Cluster page.
*
* @example
*
* ```tsx
* import { useTranslation } from 'react-i18next';
* import { registerAddClusterProvider } from '@kinvolk/headlamp-plugin/lib';
* import { Card, CardHeader, CardContent, Typography, Button } from '@mui/material';
* import { MinikubeIcon } from './MinikubeIcon';
* const { t } = useTranslation();
*
* registerAddClusterProvider({
* title: 'Minikube',
* icon: MinikubeIcon,
* description:
* 'Minikube is a lightweight tool that simplifies the process of setting up a Kubernetes environment on your local PC. It provides a localStorage, single-node Kubernetes cluster that you can use for learning, development, and testing purposes.',
* url: '/create-cluster-minikube',
* });
*
* ```
*
*/
export function registerAddClusterProvider(item) {
store.dispatch(addAddClusterProvider(item));
}
/**
* Add a new theme that will be available in the settings.
* Theme name should be unique
*
* @param theme - App Theme definition
*
* @example
*
* ```ts
* registerAppTheme({
* name: "My Custom Theme",
* base: "light",
* primary: "#ff0000",
* secondary: "#333",
* })
*
*/
export function registerAppTheme(theme) {
store.dispatch(themeSlice.actions.addCustomAppTheme(theme));
}
/**
* Starts an action after a period of time giving the user an opportunity to cancel the action.
*
* @param callback - called after some time.
* @param actionOptions - options for text messages and callbacks.
*
* @example
*
* ```tsx
* clusterAction(() => runFunc(clusterName), {
* startMessage: `About to "${command}" cluster "${clusterName}"…`,
* cancelledMessage: `Cancelled "${command}" cluster "${clusterName}".`,
* successMessage: `Cluster "${command}" of "${clusterName}" begun.`,
* errorMessage: `Failed to "${command}" ${clusterName}.`,
* cancelCallback: () => {
* setActing(false);
* setRunning(false);
* handleClose();
* setOpenDialog(false);
* })
* ```
*
*/
export function clusterAction(callback, actionOptions = {}) {
store.dispatch(sendClusterAction(callback, actionOptions));
}
/**
* Registers a UI panel in the application's UI.
*
* See {@link UIPanel} for more details on Panel definition
*
* @param panel - The UI panel configuration object to be registered
* @example
* ```tsx
* registerUIPanel({
* id: 'my-panel',
* location: 'right'
* component: () => <div style={{ width: '100px', flexShrink: 0 }}>Hello world</div>,
* });
* ```
*/
export function registerUIPanel(panel) {
store.dispatch(uiSlice.actions.addUIPanel(panel));
}
/**
* Register a new way to create Headlamp 'Projects'
*
* @param customCreateProject - Definition for custom creator
*
* @example
* ```tsx
* registerCustomCreateProject({
* id: "custom-create",
* name: "Create Helm Project",
* description: "Create new project from Helm chart",
* Component: ({onBack}) => <div>
* Create project
* <input name="helm-chart-id" />
* <button>Create</button>
* <button onClick={onBack}>Back</button>
* </div>,
* })
* ```
*/
export function registerCustomCreateProject(customCreateProject) {
store.dispatch(addCustomCreateProject(customCreateProject));
}
/**
* Register a new tab in the project details view.
*
* This allows plugins to add custom tabs to the project details page,
* extending the information displayed about a project.
*
* @param projectDetailsTab - The tab configuration to register
* @param projectDetailsTab.id - Unique identifier for the tab
* @param projectDetailsTab.label - Display label for the tab
* @param projectDetailsTab.icon - Display icon for the tab
* @param projectDetailsTab.component - React component to render in the tab content
* @param projectDetailsTab.isEnabled - Optional function to determine if tab is displayed
*
* @example
* ```tsx
* registerProjectDetailsTab({
* id: 'custom-metrics',
* label: 'Metrics',
* component: ({ project }) => <ProjectMetrics project={project} />
* });
* ```
*/
export function registerProjectDetailsTab(projectDetailsTab) {
store.dispatch(addDetailsTab(projectDetailsTab));
}
/**
* Register a new section in the project overview page.
*
* This allows plugins to add custom sections to the project overview,
* providing additional information or functionality on the main project page.
*
* @param projectOverviewSection - The section configuration to register
* @param projectOverviewSection.id - Unique identifier for the section
* @param projectOverviewSection.component - React component to render in the section
*
* @example
* ```tsx
* registerProjectOverviewSection({
* id: 'resource-usage',
* component: ({ project }) => <ResourceUsageChart project={project} />
* });
* ```
*/
export function registerProjectOverviewSection(projectOverviewSection) {
store.dispatch(addOverviewSection(projectOverviewSection));
}
/**
* Override default project delete button
*
* @param projectDeleteButton.component - React component for custom delete button
* @param projectDeleteButton.isEnabled - Optional function to determine if button is enabled
*/
export function registerProjectDeleteButton(projectDeleteButton) {
store.dispatch(setProjectDeleteButton(projectDeleteButton));
}
/**
* Register a new action button in the project details header.
*
* This allows plugins to add custom action buttons next to the delete button
* in the project details page header.
*
* @param projectHeaderAction - The action configuration to register
* @param projectHeaderAction.id - Unique identifier for the action
* @param projectHeaderAction.component - React component to render as the action button
* @param projectHeaderAction.isEnabled - Optional function to determine if action is displayed
*
* @example
* ```tsx
* registerProjectHeaderAction({
* id: 'deploy-app',
* component: ({ project }) => (
* <Button onClick={() => navigate(`/deploy/${project.id}`)}>
* Deploy App
* </Button>
* )
* });
* ```
*/
export function registerProjectHeaderAction(projectHeaderAction) {
store.dispatch(addHeaderAction(projectHeaderAction));
}
export { DefaultAppBarAction, DefaultDetailsViewSection, getHeadlampAPIHeaders, runCommand, PluginManager, ConfigStore, };