chore: move source to repo root and standardize config
Phase 1 — Structural overhaul: - Move all source from headlamp-sealed-secrets/ subdirectory to repo root - Delete 23 AI-generated docs, 8 pre-built tarballs, release snapshots dir - Remove all working-directory refs from CI/release workflows - Update install-plugin.sh and typedoc.json paths Phase 2 — Config standardization: - Create .eslintrc.js and .prettierrc.js (standard Headlamp configs) - Remove inline eslintConfig/prettier from package.json (drop jsx-a11y, prettier extends) - Rewrite tsconfig.json (package name extend, add compilerOptions.types) - Create vitest.config.mts and vitest.setup.ts (standard from polaris) - Replace headlamp-plugin CLI scripts with direct tool invocation - Rewrite .gitignore with standard baseline Phase 3 — MCP & Claude settings: - Create .mcp.json with github/kubernetes/flux/playwright servers - Create .claude/settings.local.json - Remove 7 specialized agents, keep 3 meta-orchestration agents Phase 4 — Documentation: - Rewrite CLAUDE.md (remove subdirectory refs, standard format) - Add ArtifactHub badge, Architecture section, standardized install methods to README.md - Create CONTRIBUTING.md and SECURITY.md - Fix pre-existing test bugs in validators.test.ts (isValidNamespace returns boolean, not ValidationResult; error message string mismatches) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Error Boundary Components
|
||||
*
|
||||
* Provides graceful error handling for crypto and API operations
|
||||
*/
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
import { Alert, Box, Button, Typography } from '@mui/material';
|
||||
import React, { Component, ReactNode } from 'react';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
errorInfo?: React.ErrorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base error boundary component
|
||||
*/
|
||||
abstract class BaseErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('Error caught by boundary:', error, errorInfo);
|
||||
console.error('Error type:', typeof error);
|
||||
console.error('Error keys:', Object.keys(error));
|
||||
console.error('Error message:', error.message);
|
||||
console.error('Error toString:', String(error));
|
||||
this.setState({ errorInfo });
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
|
||||
if (this.props.onReset) {
|
||||
this.props.onReset();
|
||||
}
|
||||
};
|
||||
|
||||
abstract renderError(): ReactNode;
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
return this.renderError();
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error boundary for cryptographic operations
|
||||
*
|
||||
* Catches errors during encryption/decryption and provides
|
||||
* helpful context about what might have gone wrong.
|
||||
*/
|
||||
export class CryptoErrorBoundary extends BaseErrorBoundary {
|
||||
renderError() {
|
||||
return (
|
||||
<Box p={3}>
|
||||
<Alert
|
||||
severity="error"
|
||||
icon={<Icon icon="mdi:alert-circle-outline" />}
|
||||
action={
|
||||
<Button color="inherit" size="small" onClick={this.handleReset}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Cryptographic Operation Failed
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
An error occurred during encryption or decryption. This might indicate:
|
||||
</Typography>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
<li>Invalid or expired controller certificate</li>
|
||||
<li>Browser cryptography compatibility issue</li>
|
||||
<li>Malformed secret data</li>
|
||||
<li>Controller not reachable or misconfigured</li>
|
||||
</ul>
|
||||
{this.state.error && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ mt: 2, fontFamily: 'monospace', fontSize: '0.875rem' }}
|
||||
>
|
||||
{(() => {
|
||||
try {
|
||||
const msg = this.state.error.message || this.state.error.toString();
|
||||
return `Error: ${String(msg)}`;
|
||||
} catch (e) {
|
||||
return 'Error: [Unable to display error message]';
|
||||
}
|
||||
})()}
|
||||
</Typography>
|
||||
)}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error boundary for API operations
|
||||
*
|
||||
* Catches errors during Kubernetes API calls and provides
|
||||
* guidance for troubleshooting connectivity issues.
|
||||
*/
|
||||
export class ApiErrorBoundary extends BaseErrorBoundary {
|
||||
renderError() {
|
||||
return (
|
||||
<Box p={3}>
|
||||
<Alert
|
||||
severity="error"
|
||||
icon={<Icon icon="mdi:alert-circle-outline" />}
|
||||
action={
|
||||
<Button color="inherit" size="small" onClick={this.handleReset}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
API Communication Error
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
Failed to communicate with the Kubernetes API or Sealed Secrets controller.
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
Please verify:
|
||||
</Typography>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
<li>Kubernetes cluster is accessible</li>
|
||||
<li>Sealed Secrets controller is running</li>
|
||||
<li>Controller configuration is correct (name, namespace, port)</li>
|
||||
<li>Network connectivity to the cluster</li>
|
||||
</ul>
|
||||
{this.state.error && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ mt: 2, fontFamily: 'monospace', fontSize: '0.875rem' }}
|
||||
>
|
||||
{(() => {
|
||||
try {
|
||||
const msg = this.state.error.message || this.state.error.toString();
|
||||
return `Error: ${String(msg)}`;
|
||||
} catch (e) {
|
||||
return 'Error: [Unable to display error message]';
|
||||
}
|
||||
})()}
|
||||
</Typography>
|
||||
)}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic error boundary for general component errors
|
||||
*
|
||||
* Provides a fallback UI when components encounter unexpected errors.
|
||||
*/
|
||||
export class GenericErrorBoundary extends BaseErrorBoundary {
|
||||
renderError() {
|
||||
return (
|
||||
<Box p={3}>
|
||||
<Alert
|
||||
severity="error"
|
||||
icon={<Icon icon="mdi:alert-circle-outline" />}
|
||||
action={
|
||||
<Button color="inherit" size="small" onClick={this.handleReset}>
|
||||
Reload
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Something Went Wrong
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
An unexpected error occurred. Please try reloading the page or contact your administrator
|
||||
if the problem persists.
|
||||
</Typography>
|
||||
{this.state.error && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ mt: 2, fontFamily: 'monospace', fontSize: '0.875rem' }}
|
||||
>
|
||||
{(() => {
|
||||
try {
|
||||
const msg = this.state.error.message || this.state.error.toString();
|
||||
return `Error: ${String(msg)}`;
|
||||
} catch (e) {
|
||||
return 'Error: [Unable to display error message]';
|
||||
}
|
||||
})()}
|
||||
</Typography>
|
||||
)}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user