Add Cmd+K command palette for searching executions and agents
All checks were successful
CI / build (push) Successful in 59s
CI / docker (push) Successful in 56s
CI / deploy (push) Successful in 26s

Backend: add routeId, agentId, processorType filter fields to SearchRequest
and ClickHouseSearchEngine. Expand global text search to match route_id and
agent_id columns.

Frontend: new command palette component (portal overlay, Zustand store,
TanStack Query search hook with 300ms debounce, filter chip parsing,
keyboard navigation, scope tabs). Search bar in SearchFilters and TopNav
now open the palette. Selecting a result writes filters to the execution
search store to drive the results table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-13 16:28:16 +01:00
parent 6f415cb017
commit 64b03a4e2f
20 changed files with 1348 additions and 67 deletions

View File

@@ -0,0 +1,72 @@
import { useRef, useEffect } from 'react';
import { useCommandPalette } from './use-command-palette';
import { parseFilterPrefix, checkTrailingFilter } from './utils';
import styles from './CommandPalette.module.css';
export function PaletteInput() {
const { query, filters, setQuery, addFilter, removeLastFilter, removeFilter } =
useCommandPalette();
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
function handleChange(value: string) {
// Check if user typed a filter prefix like "status:failed "
const parsed = parseFilterPrefix(value);
if (parsed) {
addFilter(parsed.filter);
setQuery(parsed.remaining);
return;
}
const trailing = checkTrailingFilter(value);
if (trailing) {
addFilter(trailing);
setQuery('');
return;
}
setQuery(value);
}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === 'Backspace' && query === '' && filters.length > 0) {
e.preventDefault();
removeLastFilter();
}
}
return (
<div className={styles.inputWrap}>
<svg className={styles.searchIcon} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
{filters.length > 0 && (
<div className={styles.chipList}>
{filters.map((f, i) => (
<span key={f.key} className={styles.chip}>
<span className={styles.chipKey}>{f.key}:</span>
{f.value}
<button className={styles.chipRemove} onClick={() => removeFilter(i)}>
&times;
</button>
</span>
))}
</div>
)}
<input
ref={inputRef}
className={styles.input}
type="text"
value={query}
onChange={(e) => handleChange(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={filters.length > 0 ? 'Refine search...' : 'Search executions, agents...'}
/>
<div className={styles.inputHint}>
<kbd className={styles.kbd}>esc</kbd> close
</div>
</div>
);
}