feat: add error boundaries for graceful error handling (Phase 3.4)

Implement React Error Boundaries to catch and handle errors gracefully
without crashing the entire UI. Provides helpful recovery mechanisms.

Changes:
- Create ErrorBoundary component module
  - BaseErrorBoundary abstract class (shared logic)
  - CryptoErrorBoundary (crypto operation errors)
  - ApiErrorBoundary (API communication errors)
  - GenericErrorBoundary (general component errors)

- Error boundary features
  - Catches rendering and lifecycle errors
  - Logs errors to console for debugging
  - Displays helpful, contextual error messages
  - Provides retry/reload buttons for recovery
  - Optional custom fallback UI via props
  - Optional onReset callback for custom recovery

- Integrate error boundaries into routes
  - Wrap SealedSecretList with ApiErrorBoundary
  - Wrap SealedSecretDetail with ApiErrorBoundary
  - Wrap SealingKeysView with ApiErrorBoundary
  - Wrap SettingsPage with GenericErrorBoundary
  - Wrap SecretDetailsSection with GenericErrorBoundary

Error types and messages:
- Crypto errors: Certificate, browser compatibility, malformed data
- API errors: Cluster connection, controller config, network
- Generic errors: Unexpected errors with simple recovery message

Benefits:
- App doesn't crash completely on errors
- Users can continue using unaffected features
- Clear, actionable troubleshooting steps
- Professional error presentation
- Production-ready error handling
- Easier debugging with console logs

Build: 354.92 kB (97.76 kB gzipped), +2.47 kB (+0.7%)
Phase 3.4 complete. 10 of 14 phases done (71%).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
2026-02-11 22:10:50 -05:00
parent 2171250e99
commit 2cb815f921
4 changed files with 723 additions and 6 deletions
+26 -5
View File
@@ -20,6 +20,7 @@ import {
registerSidebarEntry,
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import { ApiErrorBoundary, GenericErrorBoundary } from './components/ErrorBoundary';
import { SealedSecretDetail } from './components/SealedSecretDetail';
import { SealedSecretList } from './components/SealedSecretList';
import { SealingKeysView } from './components/SealingKeysView';
@@ -71,7 +72,11 @@ registerSidebarEntry({
registerRoute({
path: '/sealedsecrets',
sidebar: 'sealed-secrets-list',
component: () => <SealedSecretList />,
component: () => (
<ApiErrorBoundary>
<SealedSecretList />
</ApiErrorBoundary>
),
exact: true,
});
@@ -79,7 +84,11 @@ registerRoute({
registerRoute({
path: '/sealedsecrets/:namespace/:name',
sidebar: 'sealed-secrets-list',
component: () => <SealedSecretDetail />,
component: () => (
<ApiErrorBoundary>
<SealedSecretDetail />
</ApiErrorBoundary>
),
exact: true,
name: 'sealedsecret',
});
@@ -88,7 +97,11 @@ registerRoute({
registerRoute({
path: '/sealedsecrets/keys',
sidebar: 'sealing-keys',
component: () => <SealingKeysView />,
component: () => (
<ApiErrorBoundary>
<SealingKeysView />
</ApiErrorBoundary>
),
exact: true,
});
@@ -96,7 +109,11 @@ registerRoute({
registerRoute({
path: '/sealedsecrets/settings',
sidebar: 'sealed-secrets-settings',
component: () => <SettingsPage />,
component: () => (
<GenericErrorBoundary>
<SettingsPage />
</GenericErrorBoundary>
),
exact: true,
});
@@ -107,7 +124,11 @@ registerRoute({
*/
registerDetailsViewSection(({ resource }) => {
if (resource?.kind === 'Secret') {
return <SecretDetailsSection resource={resource} />;
return (
<GenericErrorBoundary>
<SecretDetailsSection resource={resource} />
</GenericErrorBoundary>
);
}
return null;
});