2cb815f921
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>
135 lines
3.0 KiB
TypeScript
135 lines
3.0 KiB
TypeScript
/**
|
|
* Headlamp Sealed Secrets Plugin
|
|
*
|
|
* A comprehensive plugin for managing Bitnami Sealed Secrets in Kubernetes.
|
|
* Provides UI for viewing, creating, and managing encrypted secrets.
|
|
*
|
|
* Features:
|
|
* - List and detail views for SealedSecrets
|
|
* - Client-side encryption using controller's public key
|
|
* - Sealing keys management
|
|
* - Secret decryption (via K8s Secret access)
|
|
* - Integration with Headlamp's Secret detail view
|
|
*
|
|
* @see https://github.com/bitnami-labs/sealed-secrets
|
|
*/
|
|
|
|
import {
|
|
registerDetailsViewSection,
|
|
registerRoute,
|
|
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';
|
|
import { SecretDetailsSection } from './components/SecretDetailsSection';
|
|
import { SettingsPage } from './components/SettingsPage';
|
|
|
|
/**
|
|
* Register sidebar navigation
|
|
*/
|
|
|
|
// Main "Sealed Secrets" entry
|
|
registerSidebarEntry({
|
|
parent: null,
|
|
name: 'sealed-secrets',
|
|
label: 'Sealed Secrets',
|
|
icon: 'mdi:lock',
|
|
url: '/sealedsecrets',
|
|
});
|
|
|
|
// "All Sealed Secrets" child entry
|
|
registerSidebarEntry({
|
|
parent: 'sealed-secrets',
|
|
name: 'sealed-secrets-list',
|
|
label: 'All Sealed Secrets',
|
|
url: '/sealedsecrets',
|
|
});
|
|
|
|
// "Sealing Keys" child entry
|
|
registerSidebarEntry({
|
|
parent: 'sealed-secrets',
|
|
name: 'sealing-keys',
|
|
label: 'Sealing Keys',
|
|
url: '/sealedsecrets/keys',
|
|
});
|
|
|
|
// "Settings" child entry
|
|
registerSidebarEntry({
|
|
parent: 'sealed-secrets',
|
|
name: 'sealed-secrets-settings',
|
|
label: 'Settings',
|
|
url: '/sealedsecrets/settings',
|
|
});
|
|
|
|
/**
|
|
* Register routes
|
|
*/
|
|
|
|
// List view
|
|
registerRoute({
|
|
path: '/sealedsecrets',
|
|
sidebar: 'sealed-secrets-list',
|
|
component: () => (
|
|
<ApiErrorBoundary>
|
|
<SealedSecretList />
|
|
</ApiErrorBoundary>
|
|
),
|
|
exact: true,
|
|
});
|
|
|
|
// Detail view
|
|
registerRoute({
|
|
path: '/sealedsecrets/:namespace/:name',
|
|
sidebar: 'sealed-secrets-list',
|
|
component: () => (
|
|
<ApiErrorBoundary>
|
|
<SealedSecretDetail />
|
|
</ApiErrorBoundary>
|
|
),
|
|
exact: true,
|
|
name: 'sealedsecret',
|
|
});
|
|
|
|
// Sealing keys view
|
|
registerRoute({
|
|
path: '/sealedsecrets/keys',
|
|
sidebar: 'sealing-keys',
|
|
component: () => (
|
|
<ApiErrorBoundary>
|
|
<SealingKeysView />
|
|
</ApiErrorBoundary>
|
|
),
|
|
exact: true,
|
|
});
|
|
|
|
// Settings page
|
|
registerRoute({
|
|
path: '/sealedsecrets/settings',
|
|
sidebar: 'sealed-secrets-settings',
|
|
component: () => (
|
|
<GenericErrorBoundary>
|
|
<SettingsPage />
|
|
</GenericErrorBoundary>
|
|
),
|
|
exact: true,
|
|
});
|
|
|
|
/**
|
|
* Register integration with Secret detail view
|
|
*
|
|
* Adds a "Sealed Secret" section to Secrets that are owned by SealedSecrets
|
|
*/
|
|
registerDetailsViewSection(({ resource }) => {
|
|
if (resource?.kind === 'Secret') {
|
|
return (
|
|
<GenericErrorBoundary>
|
|
<SecretDetailsSection resource={resource} />
|
|
</GenericErrorBoundary>
|
|
);
|
|
}
|
|
return null;
|
|
});
|