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:
77
ui/src/pages/executions/use-execution-search.ts
Normal file
77
ui/src/pages/executions/use-execution-search.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { create } from 'zustand';
|
||||
import type { SearchRequest } from '../../api/schema';
|
||||
|
||||
interface ExecutionSearchState {
|
||||
status: string[];
|
||||
timeFrom: string;
|
||||
timeTo: string;
|
||||
durationMin: number | null;
|
||||
durationMax: number | null;
|
||||
text: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
|
||||
setStatus: (statuses: string[]) => void;
|
||||
toggleStatus: (s: string) => void;
|
||||
setTimeFrom: (v: string) => void;
|
||||
setTimeTo: (v: string) => void;
|
||||
setDurationMin: (v: number | null) => void;
|
||||
setDurationMax: (v: number | null) => void;
|
||||
setText: (v: string) => void;
|
||||
setOffset: (v: number) => void;
|
||||
clearAll: () => void;
|
||||
toSearchRequest: () => SearchRequest;
|
||||
}
|
||||
|
||||
export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
||||
status: ['COMPLETED', 'FAILED'],
|
||||
timeFrom: '',
|
||||
timeTo: '',
|
||||
durationMin: null,
|
||||
durationMax: null,
|
||||
text: '',
|
||||
offset: 0,
|
||||
limit: 25,
|
||||
|
||||
setStatus: (statuses) => set({ status: statuses, offset: 0 }),
|
||||
toggleStatus: (s) =>
|
||||
set((state) => ({
|
||||
status: state.status.includes(s)
|
||||
? state.status.filter((x) => x !== s)
|
||||
: [...state.status, s],
|
||||
offset: 0,
|
||||
})),
|
||||
setTimeFrom: (v) => set({ timeFrom: v, offset: 0 }),
|
||||
setTimeTo: (v) => set({ timeTo: v, offset: 0 }),
|
||||
setDurationMin: (v) => set({ durationMin: v, offset: 0 }),
|
||||
setDurationMax: (v) => set({ durationMax: v, offset: 0 }),
|
||||
setText: (v) => set({ text: v, offset: 0 }),
|
||||
setOffset: (v) => set({ offset: v }),
|
||||
clearAll: () =>
|
||||
set({
|
||||
status: ['COMPLETED', 'FAILED', 'RUNNING'],
|
||||
timeFrom: '',
|
||||
timeTo: '',
|
||||
durationMin: null,
|
||||
durationMax: null,
|
||||
text: '',
|
||||
offset: 0,
|
||||
}),
|
||||
|
||||
toSearchRequest: (): SearchRequest => {
|
||||
const s = get();
|
||||
const statusStr = s.status.length > 0 && s.status.length < 3
|
||||
? s.status.join(',')
|
||||
: undefined;
|
||||
return {
|
||||
status: statusStr ?? undefined,
|
||||
timeFrom: s.timeFrom ? new Date(s.timeFrom).toISOString() : undefined,
|
||||
timeTo: s.timeTo ? new Date(s.timeTo).toISOString() : undefined,
|
||||
durationMin: s.durationMin,
|
||||
durationMax: s.durationMax,
|
||||
text: s.text || undefined,
|
||||
offset: s.offset,
|
||||
limit: s.limit,
|
||||
};
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user