Skip to content

Instantly share code, notes, and snippets.

@teemow
Created April 9, 2025 15:00
Show Gist options
  • Save teemow/0a1e819f283ca1c07cdc224a56b5e339 to your computer and use it in GitHub Desktop.
Save teemow/0a1e819f283ca1c07cdc224a56b5e339 to your computer and use it in GitHub Desktop.
Headlamp Plugin Development Specification (AI Assistant Focused)
# Headlamp Plugin Development Specification (AI Assistant Focused)
This document outlines the Headlamp plugin architecture and provides a detailed reference to the APIs, components, and hooks available for plugin development, tailored for guiding an AI coding assistant.
## 1. Plugin Architecture Overview
* **Type:** Frontend-only JavaScript/TypeScript modules running in the Headlamp UI (browser/Electron).
* **Core Library:** `@kinvolk/headlamp-plugin` (provides APIs via named exports like `K8s`, `CommonComponents`, `Router`, `Registry`, `Headlamp`, `Notification`, `Utils`, `ApiProxy`, and specific registration functions).
* **Backend Role:** Discovers plugins (folders with `main.js` + `package.json` in specified `-plugin-dir`), serves `main.js`, and signals frontend (`X-Reload` header) on changes. No backend plugin code execution.
* **UI Stack:** React, Material UI (`@mui/material`, `@mui/styles`).
* **Data Access:** Via library functions/hooks (`K8s.*`, `ApiProxy.*`).
## 2. Plugin API Reference
This section details the primary modules and functions exported by `@kinvolk/headlamp-plugin/lib`. This is the most critical section for AI-assisted development.
### 2.0 Key Exports Summary
Primary namespaces and functions exported directly from `@kinvolk/headlamp-plugin/lib`:
* **Namespaces:** `K8s` (and `k8s`), `CommonComponents`, `Router`, `Headlamp`, `Notification`, `Utils`, `ApiProxy`
* **Registration Functions:** `registerAppBarAction`, `registerAppLogo`, `registerClusterChooser`, `registerDetailsViewHeaderActionsProcessor`, `registerDetailsViewSection`, `registerPluginSettings`, `registerResourceTableColumnsProcessor`, `registerRoute`, `registerRouteFilter`, `registerSidebarEntry`, `registerSidebarEntryFilter`, `registerGetTokenFunction`, `registerHeadlampEventCallback`
* **Other Utilities:** `getHeadlampAPIHeaders`
* **Core Classes/Interfaces (often used indirectly):** `Plugin`, `Registry`
### 2.1. Registration Functions (Primary Hook Points)
These functions, mostly exported directly or via the (now less emphasized) `Registry` object, connect your plugin to the Headlamp UI:
* `registerAppBarAction(component: React.ReactNode)`: Adds a React component to the top-right application bar.
* *Example Use:* Displaying custom info, buttons.
* *Example Code:*
```typescript
import React from 'react';
import { registerAppBarAction, K8s } from '@kinvolk/headlamp-plugin/lib';
import { Typography } from '@mui/material';
function PodCounter() {
const [pods] = K8s.ResourceClasses.Pod.useList();
return (
<Typography sx={{ p: 2 }}>
Pods: {pods ? pods.length : 'Loading...'}
</Typography>
);
}
registerAppBarAction(<PodCounter />);
```
* `registerAppLogo(component: React.ReactNode | React.ComponentType<AppLogoProps>)`: Replaces the Headlamp logo in the top-left corner.
* *Example Use:* Custom branding.
* `registerClusterChooser(component: React.ReactNode | React.ComponentType<ClusterChooserProps>)`: Replaces the default cluster chooser button in the app bar.
* *Example Use:* Implementing a custom cluster selection mechanism.
* `registerDetailsViewHeaderActionsProcessor(processor: (resource: K8s.Resource, actions: DetailsViewDefaultHeaderActions) => DetailsViewDefaultHeaderActions)`: Allows modifying (add, remove, reorder) the default actions shown in the header of resource detail views (e.g., Edit, Delete buttons).
* *Input:* `resource` (the K8s resource object), `actions` (array/object of default action components).
* *Output:* The modified list of actions.
* *Example Use:* Adding a custom action button specific to a CRD.
* `registerDetailsViewSection(sectionProps: DetailsViewSectionProps)`: Appends a React component as a new section at the bottom of resource detail views.
* `sectionProps`: Object likely containing `{ resource: K8s.Resource, component: React.ReactNode }` and potentially filter criteria (e.g., `resourceKind: 'Pod'`).
* *Example Use:* Displaying related information or custom views for specific resources.
* *Example Code:*
```typescript
import React from 'react';
import {
registerDetailsViewSection,
CommonComponents,
K8s,
} from '@kinvolk/headlamp-plugin/lib';
const { SectionBox, DetailsGrid } = CommonComponents;
function MyPodInfoSection(props: { resource: K8s.Resource }) {
const { resource } = props;
if (!resource) return null;
return (
<SectionBox title="My Custom Pod Info">
<DetailsGrid
resource={resource}
entries={[{
name: 'Custom Annotation',
value: resource.metadata?.annotations?.['my-annotation'] || 'N/A',
}]}
/>
</SectionBox>
);
}
registerDetailsViewSection({
resourceKind: 'Pod', // Show only for Pods
component: MyPodInfoSection,
});
```
* `registerPluginSettings(component: React.ReactNode | React.ComponentType<PluginSettingsDetailsProps>)`: Adds a configuration UI for the plugin under Headlamp's settings section.
* *Example Use:* Allowing users to configure API keys, preferences, etc. for the plugin.
* `registerResourceTableColumnsProcessor(processor: (resourceClass: K8s.ResourceClass, columns: any[]) => any[])`: Allows modifying (add, remove, update, reorder) columns in resource list tables.
* *Input:* `resourceClass` (e.g., `K8s.ResourceClasses.Pod`), `columns` (array of column definitions).
* *Output:* The modified array of column definitions.
* *Example Use:* Adding a column showing data derived from resource fields or annotations.
* `registerRoute(route: { path: string; exact?: boolean; sidebar?: string | null; name?: string; component: (...args: any[]) => React.ReactNode })`: Registers a new view component accessible via a URL path.
* `name`: A unique name for the route, used by `Router.createRouteURL`.
* *Example Use:* Creating a completely new view/dashboard within Headlamp.
* *Example Code:*
```typescript
import React from 'react';
import { registerRoute, CommonComponents } from '@kinvolk/headlamp-plugin/lib';
import { Typography } from '@mui/material';
const { PageGrid } = CommonComponents;
function MyCustomView() {
return (
<PageGrid>
<Typography variant="h1">My Custom Plugin View</Typography>
{/* Add content here */}
</PageGrid>
);
}
registerRoute({
path: '/my-custom-view',
name: 'myCustomView',
component: MyCustomView,
sidebar: 'cluster', // Optional: Assign to a default sidebar group
});
```
* `registerRouteFilter(filter: (routes: Route[]) => Route[])`: Allows removing or modifying existing routes.
* *Example Use:* Hiding default Headlamp views.
* `registerSidebarEntry(entry: { parent?: string | null; name: string; label: string; url: string; useClusterURL?: boolean; icon?: React.ComponentType | string; sidebar?: string; order?: number; })`: Adds an entry to the left-hand sidebar navigation.
* `parent`: Name of the parent entry for nesting.
* `icon`: Can be a Material UI Icon component or potentially an icon name string.
* `name`: Unique name for this entry.
* `url`: Path registered via `registerRoute`.
* *Example Use:* Linking to a custom route created with `registerRoute`.
* *Example Code:*
```typescript
import { registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck'; // Example Icon
registerSidebarEntry({
parent: 'cluster', // Attach under the 'Cluster' main entry
name: 'myCustomViewLink',
label: 'My Custom View',
url: '/my-custom-view',
icon: PlaylistAddCheckIcon,
});
```
* `registerSidebarEntryFilter(filter: (entries: SidebarEntry[]) => SidebarEntry[])`: Allows removing or modifying existing sidebar entries.
* *Example Use:* Customizing the navigation for specific user roles (if combined with other logic).
* `registerGetTokenFunction(func: () => string | null)`: Registers a function that Headlamp can call to get an auth token.
* *Example Use:* Integrating with external authentication systems where the plugin manages the token.
* `registerHeadlampEventCallback(callback: (event: HeadlampEvent) => void)`: Allows reacting to specific events within Headlamp.
* *Example Use:* Showing a notification (`Notification.setNotificationsInStore`) when the cluster context changes.
### 2.2. Kubernetes Data Access (`K8s` and `ApiProxy` namespaces)
Provides tools to interact with the Kubernetes API.
* **`K8s.ResourceClasses`:** Namespace containing classes for specific Kubernetes resource kinds (e.g., `K8s.ResourceClasses.Pod`, `K8s.ResourceClasses.Deployment`, `K8s.ResourceClasses.Node`). Provides hooks for data fetching:
* `.useList({ namespace?: string, selector?: string, cluster?: string })`: React hook to fetch a list of resources.
* *Returns:* `[items: K8s.Resource[] | null, error: any | null]`
* *Params:* Optional namespace filtering, label selector, target cluster.
* *Example:* `const [pods, podsError] = K8s.ResourceClasses.Pod.useList({ namespace: 'my-ns' });`
* `.useGet(name: string, namespace?: string, cluster?: string)`: React hook to fetch a single resource by name.
* *Returns:* `[item: K8s.Resource | null, error: any | null]`
* *Example:* `const [myPod, myPodError] = K8s.ResourceClasses.Pod.useGet('my-pod-name', 'my-ns');`
* `.apiClass`: Access to lower-level API interaction methods (may include `.post`, `.put`, `.delete`, `.patch` for CRUD operations, though hooks are preferred for reads).
* *Example (Creating a ConfigMap):*
```typescript
import { K8s, Notification } from '@kinvolk/headlamp-plugin/lib';
async function createMyConfigMap(namespace: string, name: string, data: { [key: string]: string }) {
const configMap = K8s.ResourceClasses.ConfigMap.apiClass.newItem({
metadata: { name, namespace },
data: data,
});
try {
await K8s.ResourceClasses.ConfigMap.apiClass.post(configMap);
Notification.setNotificationsInStore(
new Notification.Notification({
message: `ConfigMap ${name} created successfully!`,
variant: 'success',
})
);
} catch (err) {
Notification.setNotificationsInStore(
new Notification.Notification({
message: `Failed to create ConfigMap ${name}: ${Utils.getErrorMessage(err)}`,
variant: 'error',
})
);
}
}
```
* *Example (Deleting a Pod):*
```typescript
import { K8s, Notification, Utils } from '@kinvolk/headlamp-plugin/lib';
async function deleteMyPod(namespace: string, name: string) {
try {
await K8s.ResourceClasses.Pod.apiClass.delete(name, namespace);
// Consider using useList hook's callback or re-fetching to update UI
Notification.setNotificationsInStore(
new Notification.Notification({
message: `Pod ${name} deleted successfully!`,
variant: 'success',
})
);
} catch (err) {
Notification.setNotificationsInStore(
new Notification.Notification({
message: `Failed to delete Pod ${name}: ${Utils.getErrorMessage(err)}`,
variant: 'error',
})
);
}
}
```
* **`K8s.cluster`:** Functions related to the currently selected cluster.
* `K8s.cluster.useClusters()`: Hook to get the list of available clusters.
* `K8s.cluster.getCurrentCluster()`: Get the name of the currently selected cluster.
* `K8s.cluster.setCurrentCluster(clusterName: string)`: Programmatically set the current cluster.
* **`K8s.Auth`:** Namespace for authorization checks.
* `K8s.Auth.useCheckAccess(rule: { resource: string; verb: string; namespace?: string; name?: string; })`: React hook to check if the user has permission for a specific action based on Kubernetes RBAC.
* *Returns:* `[allowed: boolean | null, error: any | null]`
* *Example:* `const [canEditPods, errorCheckingAccess] = K8s.Auth.useCheckAccess({ resource: 'pods', verb: 'update', namespace: 'my-ns' });`
* **`ApiProxy.request(url: string, init?: RequestInit)`:** Function to make authenticated requests to the Kubernetes API server or other Headlamp backend endpoints via Headlamp's backend proxy.
* Automatically handles authentication headers based on the current session.
* *Example (Fetching custom data):*
```typescript
import { ApiProxy, Utils, Notification } from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
function useCustomData() {
const [data, setData] = React.useState(null);
const [error, setError] = React.useState<Error | null>(null);
React.useEffect(() => {
ApiProxy.request('/apis/my-group.example.com/v1/my-custom-resources')
.then(res => {
if (!res.ok) {
throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(jsonData => setData(jsonData.items))
.catch(err => {
console.error('Failed to fetch custom data:', err);
setError(err);
Notification.setNotificationsInStore(
new Notification.Notification({
message: `Error loading custom data: ${Utils.getErrorMessage(err)}`,
variant: 'error',
})
);
});
}, []);
return { data, error };
}
```
* **`getHeadlampAPIHeaders()`:** Utility function to retrieve the necessary HTTP headers (like `Authorization`) for making direct authenticated requests, typically used with `ApiProxy.request` internally but can be used externally if needed.
### 2.3. UI Components & Styling (`CommonComponents` and Material UI)
Leverage Headlamp's existing UI elements for consistency.
* **`CommonComponents`:** Namespace containing reusable React components from Headlamp's UI library. **Crucial for maintaining UI consistency.**
* *Key Examples:* `SectionBox` (standard layout block), `SimpleTable` (data table), `ResourceLink` (link to resource details view), `DetailsGrid` (key-value pair display), `Dialog` (modal dialogs), `Button` (styled buttons), `StatusLabel` (shows status with color), `LoadingLabel` (placeholder), `PageGrid` (top-level layout grid), `EmptyContent` (placeholder for empty states), `CodeEditor` (for displaying/editing YAML/JSON).
* *Usage:* `import { SectionBox, SimpleTable } from '@kinvolk/headlamp-plugin/lib/CommonComponents';`
* *Example (SimpleTable):*
```typescript
import { CommonComponents, K8s } from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
const { SimpleTable, LoadingLabel, ResourceLink } = CommonComponents;
function MyPodListTable() {
const [pods, error] = K8s.ResourceClasses.Pod.useList();
if (error) return <Typography color="error">Error loading pods: {Utils.getErrorMessage(error)}</Typography>;
if (!pods) return <LoadingLabel />; // Or <EmptyContent> if known to be empty
return (
<SimpleTable
data={pods}
columns={[
{
label: 'Name',
getter: (item) => <ResourceLink resource={item} />,
},
{
label: 'Namespace',
getter: (item) => item.metadata.namespace,
},
{
label: 'Restarts',
getter: (item) => item.status?.containerStatuses?.[0]?.restartCount ?? 0,
cellProps: { align: 'right' },
},
// Add more columns as needed
]}
/>
);
}
```
* *Example (Dialog Confirmation):*
```typescript
import React from 'react';
import { CommonComponents } from '@kinvolk/headlamp-plugin/lib';
import { Button, DialogActions, DialogContent, DialogContentText } from '@mui/material';
const { Dialog } = CommonComponents;
function MyDeleteButton({ resourceName, onDelete }) {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const handleConfirm = () => {
onDelete();
handleClose();
};
return (
<>
<Button color="error" onClick={handleOpen}>Delete {resourceName}</Button>
<Dialog
title={`Confirm Deletion`}
open={open}
onClose={handleClose}
>
<DialogContent>
<DialogContentText>
Are you sure you want to delete {resourceName}?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleConfirm} color="error" autoFocus>
Delete
</Button>
</DialogActions>
</Dialog>
</>
);
}
```
* **Material UI (`@mui/material`, `@mui/styles`):** Headlamp's core UI library. Use these components directly.
* *Key Examples:* `Typography`, `Button`, `Box`, `Grid`, `Paper`, `List`, `ListItem`, `AppBar`, `Toolbar`, `TextField`, `Select`, `Checkbox`, `Icons` (e.g., `@mui/icons-material/Delete`).
* *Styling:* Use the `sx` prop for inline styles or `makeStyles`/`styled` from `@mui/styles` or `@mui/material/styles` for more complex styling.
* *Theme Access:* Use the `useTheme` hook from `@mui/material/styles` to access Headlamp's theme variables (colors, spacing, etc.).
* *Example (Basic Layout):*
```typescript
import React from 'react';
import { Box, Grid, Paper, Typography } from '@mui/material';
function MyComponentLayout() {
return (
<Box sx={{ flexGrow: 1, p: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12} md={8}>
<Paper sx={{ p: 2 }}>
<Typography variant="h6">Main Content Area</Typography>
{/* ... */}
</Paper>
</Grid>
<Grid item xs={12} md={4}>
<Paper sx={{ p: 2 }}>
<Typography variant="h6">Sidebar/Info Area</Typography>
{/* ... */}
</Paper>
</Grid>
</Grid>
</Box>
);
}
```
### 2.4. Routing & Navigation (`Router` namespace)
Tools for handling frontend routing and navigation within Headlamp.
* **React Router Hooks:** Headlamp likely uses React Router internally. These standard hooks are available:
* `Router.useHistory()`: Access the history object (push, replace, goBack).
* *Example (Programmatic Navigation):*
```typescript
import { Router } from '@kinvolk/headlamp-plugin/lib';
import { Button } from '@mui/material';
function GoToSettingsButton() {
const history = Router.useHistory();
const handleClick = () => {
// Assuming a route named 'pluginSettings' exists
const url = Router.createRouteURL('pluginSettings');
history.push(url);
};
return <Button onClick={handleClick}>Go to Settings</Button>;
}
```
* `Router.useLocation()`: Get the current location object (pathname, search, hash).
* `Router.useParams()`: Get URL parameters defined in the route path (e.g., `/myroute/:id`).
* *Example (Using Params in a Route Component):*
```typescript
import { Router, K8s } from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import { Typography } from '@mui/material';
// Assuming registered with path: '/myresource/:namespace/:name'
function MyResourceDetailsView() {
const params = Router.useParams<{ namespace: string; name: string }>();
const { namespace, name } = params;
const [resource, error] = K8s.ResourceClasses.MyCustomResource.useGet(name, namespace);
// ... Render resource details using name and namespace ...
return (
<Typography>Details for {name} in {namespace}</Typography>
);
}
```
* `Router.useRouteMatch()`: Get match information for the current route.
* **`Router.createRouteURL(routePath: string, params?: object, cluster?: string)`:** Utility function to construct a URL for a route registered within Headlamp, correctly handling base paths and potential cluster scoping.
* *Example:* `const url = Router.createRouteURL('myPluginRoute', { id: itemId }); history.push(url);`
* **`Router.Link`:** A component provided for creating links to internal Headlamp routes. It likely wraps React Router's `Link` and uses `createRouteURL` internally.
* *Example:* `<Router.Link routeName="myPluginRoute" params={{ id: itemId }}>Go to Item</Router.Link>`
### 2.5. Notifications (`Notification` namespace)
Display temporary messages (snackbars) to the user.
* **`Notification.Notification`:** Class used to create notification objects.
* *Constructor:* `new Notification.Notification({ message: string, variant: 'success' | 'error' | 'info' | 'warning', autoHideDuration?: number, /* other options */ })`
* **`Notification.setNotificationsInStore(notificationInstance: Notification.Notification)`:** Function to dispatch a created notification object to the Headlamp store, triggering its display.
* *Example (Success):*
```typescript
import { Notification } from '@kinvolk/headlamp-plugin/lib';
const notifySuccess = (message: string) => {
const msg = new Notification.Notification({
message: message,
variant: 'success',
autoHideDuration: 5000, // Optional: Hide after 5s
});
Notification.setNotificationsInStore(msg);
};
```
* *Example (Error):*
```typescript
import { Notification, Utils } from '@kinvolk/headlamp-plugin/lib';
const notifyError = (prefix: string, error: any) => {
const msg = new Notification.Notification({
message: `${prefix}: ${Utils.getErrorMessage(error)}`,
variant: 'error',
// Errors usually don't auto-hide
});
Notification.setNotificationsInStore(msg);
};
```
### 2.6. Headlamp Control (`Headlamp` class)
Functions to control aspects of the Headlamp application itself.
* **`Headlamp.setCluster(clusterName: string)`:** Programmatically switch the active cluster context within Headlamp.
* *Use Case:* Dynamic cluster plugins that determine the target cluster via logic rather than user selection.
* **`Headlamp.setAppMenu(menuTemplate: Array<object>)`:** Define the native application menu structure (File, Edit, View, etc.) when Headlamp is running as a desktop application (Electron).
* The `menuTemplate` follows the Electron menu template format.
### 2.7. Utilities (`Utils` namespace)
General helper functions provided by the library.
* Potential functions might include:
* Date/time formatting (`Utils.formatDate`).
* String manipulation.
* Data transformations (e.g., `Utils.getErrorMessage(error)`).
* Debounce/throttle functions.
* *Recommendation:* Explore the exported members of the `Utils` object in the library or check example plugins for specific available utilities.
### 2.8. Key Exported Types
TypeScript types for props and structures used by registration functions:
* `AppLogoProps`: Props object passed to custom logo components.
* `ClusterChooserProps`: Props object passed to custom cluster chooser components.
* `DetailsViewSectionProps`: Defines the structure for adding a details view section (includes `resource` and `component`).
* `PluginSettingsDetailsProps`: Props object passed to custom plugin settings components.
* `DefaultSidebars`: Type representing the names of built-in sidebars (e.g., 'cluster', 'workloads').
* `DetailsViewDefaultHeaderActions`: Type defining the structure of default header actions.
* `Route`: Type definition for a route object used by `registerRouteFilter`.
* `SidebarEntry`: Type definition for a sidebar entry used by `registerSidebarEntryFilter`.
* `HeadlampEvent`: Type for events used with `registerHeadlampEventCallback`.
* `K8s.Resource`: Base type for Kubernetes resource objects.
* `K8s.ResourceClass`: Represents the class/config for a Kubernetes resource type.
### 2.9. Shared Libraries (Provided by Headlamp - Do Not Bundle)
Headlamp provides these, so list them as `peerDependencies` or `devDependencies` in your `package.json`, not `dependencies`, to avoid bundling duplicates and ensure compatibility:
* `react`
* `react-dom`
* `react-redux`
* `@mui/material`
* `@mui/styles`
* `@iconify/react` (Verify Headlamp's version)
* `lodash`
* `notistack`
* `recharts`
## 3. Development Workflow Summary
1. **Scaffold:** `npx @kinvolk/headlamp-plugin create <plugin-name>`
2. **Develop:**
* Edit `src/index.tsx` (and other files).
* Import required functions/components from `@kinvolk/headlamp-plugin/lib` (e.g., `import { registerAppBarAction, K8s, CommonComponents } from '@kinvolk/headlamp-plugin/lib';`).
* Use registration functions (Section 2.1) to hook into UI.
* Use React, Material UI, and `CommonComponents` (Section 2.3) for UI.
* Use `K8s` hooks/methods (Section 2.2) for data.
* Run `npm start` for live reload.
* Run Headlamp (desktop/dev) to test.
3. **Build:** `npm run build` -> `dist/main.js`.
4. **Extract:** `npx @kinvolk/headlamp-plugin extract . <target-dir>` -> `<target-dir>/<plugin-name>/main.js + package.json`.
5. **Deploy:** Copy extracted plugin folder to Headlamp's `.plugins` directory (desktop) or configure `-plugins-dir` (server).
## 4. Key Files and Directories
* `<plugin-name>/src/index.tsx`: Main plugin code entry point.
* `<plugin-name>/package.json`: Defines metadata, scripts, and dependencies (use `peerDependencies` for shared libs in Section 2.9).
* `<plugin-name>/dist/main.js`: Bundled code loaded by Headlamp.
* `.plugins/<plugin-name>/`: Structure required by Headlamp for loading (contains `main.js` and `package.json`).
## 5. Important Considerations
* **API Exploration:** For definitive details, inspect the actual exports of `@kinvolk/headlamp-plugin/lib`, the types in `@kinvolk/headlamp-plugin/types`, and refer to official Headlamp example plugins.
* **API Stability:** Plugin API is still evolving; expect potential breaking changes between Headlamp versions. Use `npx @kinvolk/headlamp-plugin upgrade` and test thoroughly after Headlamp updates.
* **Bundle Size:** Keep external dependencies minimal. Rely on Material UI and `CommonComponents` provided by Headlamp.
* **Error Handling:** Implement robust error handling, especially around API calls (`K8s` hooks, `ApiProxy.request`). Use `try...catch` and check error return values from hooks.
* **State Management:** For complex state, consider using React Context or integrating with Redux if familiar (though `react-redux` is shared, direct access to Headlamp's store might require specific patterns or might not be intended API).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment