Add React UI with Execution Explorer, auth, and standalone deployment
- Scaffold Vite + React + TypeScript frontend in ui/ with full design system (dark/light themes) matching the HTML mockups - Implement Execution Explorer page: search filters, results table with expandable processor tree and exchange detail sidebar, pagination - Add UI authentication: UiAuthController (login/refresh endpoints), JWT filter handles ui: subject prefix, CORS configuration - Shared components: StatusPill, DurationBar, StatCard, AppBadge, FilterChip, Pagination — all using CSS Modules with design tokens - API client layer: openapi-fetch with auth middleware, TanStack Query hooks for search/detail/snapshot queries, Zustand for state - Standalone deployment: Nginx Dockerfile, K8s Deployment + ConfigMap + NodePort (30080), runtime config.js for API base URL - Embedded mode: maven-resources-plugin copies ui/dist into JAR static resources, SPA forward controller for client-side routing - CI/CD: UI build step, Docker build/push for server-ui image, K8s deploy step for UI, UI credential secrets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
70
ui/src/pages/executions/ProcessorTree.tsx
Normal file
70
ui/src/pages/executions/ProcessorTree.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useExecutionDetail } from '../../api/queries/executions';
|
||||
import type { ProcessorNode as ProcessorNodeType } from '../../api/schema';
|
||||
import styles from './ProcessorTree.module.css';
|
||||
|
||||
const ICON_MAP: Record<string, { label: string; className: string }> = {
|
||||
from: { label: 'EP', className: styles.iconEndpoint },
|
||||
to: { label: 'EP', className: styles.iconEndpoint },
|
||||
toD: { label: 'EP', className: styles.iconEndpoint },
|
||||
choice: { label: 'CB', className: styles.iconEip },
|
||||
when: { label: 'CB', className: styles.iconEip },
|
||||
otherwise: { label: 'CB', className: styles.iconEip },
|
||||
split: { label: 'CB', className: styles.iconEip },
|
||||
aggregate: { label: 'CB', className: styles.iconEip },
|
||||
filter: { label: 'CB', className: styles.iconEip },
|
||||
multicast: { label: 'CB', className: styles.iconEip },
|
||||
recipientList: { label: 'CB', className: styles.iconEip },
|
||||
routingSlip: { label: 'CB', className: styles.iconEip },
|
||||
dynamicRouter: { label: 'CB', className: styles.iconEip },
|
||||
exception: { label: '!!', className: styles.iconError },
|
||||
onException: { label: '!!', className: styles.iconError },
|
||||
};
|
||||
|
||||
function getIcon(type: string, status: string) {
|
||||
if (status === 'FAILED') return { label: '!!', className: styles.iconError };
|
||||
const key = type.toLowerCase();
|
||||
return ICON_MAP[key] ?? { label: 'PR', className: styles.iconProcessor };
|
||||
}
|
||||
|
||||
export function ProcessorTree({ executionId }: { executionId: string }) {
|
||||
const { data, isLoading } = useExecutionDetail(executionId);
|
||||
|
||||
if (isLoading) return <div className={styles.tree}><div className={styles.loading}>Loading processor tree...</div></div>;
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.tree}>
|
||||
<h4 className={styles.title}>Processor Execution Tree</h4>
|
||||
{data.processors.map((proc) => (
|
||||
<ProcessorNodeView key={proc.index} node={proc} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProcessorNodeView({ node }: { node: ProcessorNodeType }) {
|
||||
const icon = getIcon(node.processorType, node.status);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.procNode}>
|
||||
<div className={styles.procConnector} />
|
||||
<div className={`${styles.procIcon} ${icon.className}`}>{icon.label}</div>
|
||||
<div className={styles.procInfo}>
|
||||
<div className={styles.procType}>{node.processorType}</div>
|
||||
{node.uri && <div className={styles.procUri}>{node.uri}</div>}
|
||||
</div>
|
||||
<div className={styles.procTiming}>
|
||||
<span className={styles.procDuration}>{node.duration}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
{node.children.length > 0 && (
|
||||
<div className={styles.nested}>
|
||||
{node.children.map((child) => (
|
||||
<ProcessorNodeView key={child.index} node={child} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user