Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions frontend/src/features/log-explorer/components/LogExplorerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { Input } from '@/shared/components/ui/input'
import { TimeRangePicker, presetRange, type TimeRange } from '@/shared/components/ui/time-range-picker'
import { ResultsHeader, ResultRow, flattenDoc } from './log-results'
import { IndexPatternSelector } from './IndexPatternSelector'
import { SqlQueryEditor } from './SqlQueryEditor'
import {
logExplorerHttpService as svc,
LogExplorerHttpError,
Expand Down Expand Up @@ -442,6 +443,7 @@ export function LogExplorerView({ initial, onConfigChange }: LogExplorerViewProp
onSqlMode={(v) => setSqlMode(v)}
sqlInput={sqlInput}
onSqlInput={setSqlInput}
fields={fields}
range={range}
onRange={(r) => setRange(r)}
onRun={submit}
Expand Down Expand Up @@ -576,6 +578,7 @@ function QueryBar({
onSqlMode,
sqlInput,
onSqlInput,
fields,
range,
onRange,
onRun,
Expand All @@ -592,6 +595,7 @@ function QueryBar({
onSqlMode: (b: boolean) => void
sqlInput: string
onSqlInput: (q: string) => void
fields: IndexField[]
range: TimeRange
onRange: (r: TimeRange) => void
onRun: () => void
Expand All @@ -610,12 +614,13 @@ function QueryBar({
{/* Search input — free text or SQL */}
<div className="relative min-w-[300px] flex-1">
{sqlMode ? (
<Input
<SqlQueryEditor
value={sqlInput}
onChange={(e) => onSqlInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onRun()}
placeholder="SELECT * FROM &quot;v11-log-*&quot; ORDER BY @timestamp DESC"
className="h-9 border-0 bg-transparent font-mono text-xs shadow-none focus-visible:ring-0"
onChange={onSqlInput}
onRun={onRun}
fields={fields}
patterns={patterns}
placeholder={'SELECT * FROM "v11-log-*" ORDER BY @timestamp DESC'}
/>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useEffect, useRef } from 'react'
import { cn } from '@/shared/lib/utils'
import type { Suggestion } from '../services/autocomplete-trie.service'

interface Props {
items: Suggestion[]
activeIndex: number
position: { x: number; y: number }
onPick: (item: Suggestion) => void
onHover: (index: number) => void
}

const ROW_HEIGHT = 28
const MAX_VISIBLE = 8

export function SqlAutocompleteDropdown({
items,
activeIndex,
position,
onPick,
onHover,
}: Props) {
const listRef = useRef<HTMLUListElement>(null)

useEffect(() => {
const el = listRef.current?.children[activeIndex] as HTMLElement | undefined
el?.scrollIntoView({ block: 'nearest' })
}, [activeIndex])

if (items.length === 0) return null

return (
<ul
ref={listRef}
role="listbox"
className="fixed z-50 min-w-[220px] overflow-y-auto rounded-md border border-border bg-popover py-1 text-xs shadow-lg"
style={{
left: position.x,
top: position.y,
maxHeight: ROW_HEIGHT * MAX_VISIBLE,
}}
onMouseDown={(e) => e.preventDefault()}
>
{items.map((item, i) => (
<li
key={`${item.tag}:${item.word}`}
role="option"
aria-selected={i === activeIndex}
className={cn(
'flex cursor-pointer items-center justify-between gap-3 px-2.5 py-1 font-mono',
i === activeIndex
? 'bg-accent text-accent-foreground'
: 'text-foreground hover:bg-accent/60',
)}
onMouseEnter={() => onHover(i)}
onClick={() => onPick(item)}
>
<span className="truncate">{item.word}</span>
<span
className={cn(
'shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider',
item.tag === 'sql' && 'bg-violet-500/15 text-violet-600 dark:text-violet-300',
item.tag === 'field' && 'bg-sky-500/15 text-sky-600 dark:text-sky-300',
item.tag === 'index' && 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-300',
)}
>
{item.tag === 'sql' ? 'SQL' : item.tag === 'index' ? 'index' : 'field'}
</span>
</li>
))}
</ul>
)
}
Loading
Loading