Skip to content

Commit 47277e6

Browse files
committed
feat/live-mode
1 parent c4dcceb commit 47277e6

10 files changed

Lines changed: 258 additions & 101 deletions

File tree

frontend/dist/assets/index-CmgSaOsV.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/dist/assets/index-DH1yPITi.js

Lines changed: 0 additions & 91 deletions
This file was deleted.

frontend/dist/assets/index-Lh7SkFHN.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

frontend/dist/assets/index-mTjSQW6I.js

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<meta http-equiv="Pragma" content="no-cache" />
88
<meta http-equiv="Expires" content="0" />
99
<title>qwui</title>
10-
<script type="module" crossorigin src="/assets/index-DH1yPITi.js"></script>
11-
<link rel="stylesheet" crossorigin href="/assets/index-Lh7SkFHN.css">
10+
<script type="module" crossorigin src="/assets/index-mTjSQW6I.js"></script>
11+
<link rel="stylesheet" crossorigin href="/assets/index-CmgSaOsV.css">
1212
</head>
1313
<body>
1414
<div id="root"></div>

frontend/dist/version.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

frontend/src/App.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ function App() {
3131
const [loading, setLoading] = useState(false)
3232
const [error, setError] = useState(null)
3333
const [abortController, setAbortController] = useState(null)
34+
const [liveMode, setLiveMode] = useState(false) // Auto-refresh mode
35+
const [liveInterval, setLiveInterval] = useState(5) // Refresh interval in seconds
3436
const [currentQuery, setCurrentQuery] = useState('*')
3537
const [filters, setFilters] = useState([])
3638
const filtersRef = useRef([]) // Ref to hold current filters for immediate access
@@ -2176,6 +2178,50 @@ function App() {
21762178
}
21772179
}
21782180

2181+
// Live mode: keep a ref to the latest refresh routine so the interval always
2182+
// re-runs the search against a freshly computed "Last 1 minute" window.
2183+
const liveRefreshRef = useRef(null)
2184+
liveRefreshRef.current = () => {
2185+
const now = Math.floor(Date.now() / 1000)
2186+
const refreshed = {
2187+
from: now - 300,
2188+
to: now,
2189+
label: 'Last 5 minutes',
2190+
fromIsNow: false,
2191+
toIsNow: true
2192+
}
2193+
setTimeRange(refreshed)
2194+
timeRangeRef.current = refreshed
2195+
setFieldAggregations({})
2196+
executeSearch(100)
2197+
}
2198+
2199+
const handleToggleLiveMode = () => {
2200+
if (liveMode) {
2201+
setLiveMode(false)
2202+
return
2203+
}
2204+
setLiveMode(true)
2205+
// Switch to the last 5 minutes and trigger an immediate refresh
2206+
const now = Math.floor(Date.now() / 1000)
2207+
handleTimeRangeChange({
2208+
from: now - 300,
2209+
to: now,
2210+
label: 'Last 5 minutes',
2211+
fromIsNow: false,
2212+
toIsNow: true
2213+
})
2214+
}
2215+
2216+
// Auto-refresh loop while live mode is active
2217+
useEffect(() => {
2218+
if (!liveMode) return
2219+
const id = setInterval(() => {
2220+
if (liveRefreshRef.current) liveRefreshRef.current()
2221+
}, liveInterval * 1000)
2222+
return () => clearInterval(id)
2223+
}, [liveMode, liveInterval])
2224+
21792225
const handleSetDefaultIndex = (indexId) => {
21802226
// This is just a callback for the FieldsSidebar
21812227
// The actual storage is handled in FieldsSidebar
@@ -2495,6 +2541,10 @@ function App() {
24952541
elapsedTimeMicros={viewMode === 'visualize' ? visualizationStats.elapsedTimeMicros : searchResults?.elapsed_time_micros}
24962542
requestTime={viewMode === 'visualize' ? visualizationStats.requestTime : requestTime}
24972543
onCancelSearch={handleCancelSearch}
2544+
liveMode={liveMode}
2545+
liveInterval={liveInterval}
2546+
onToggleLiveMode={handleToggleLiveMode}
2547+
onLiveIntervalChange={setLiveInterval}
24982548
defaultSearchFields={defaultSearchFields}
24992549
onSaveQuery={(searchBarQuery) => {
25002550
setCurrentSearchBarQuery(searchBarQuery)
@@ -2632,6 +2682,7 @@ function App() {
26322682
<ResultsTable
26332683
results={searchResults}
26342684
loading={loading}
2685+
liveMode={liveMode}
26352686
searchQuery={lastSearchQuery}
26362687
timestampField={timestampField}
26372688
hasMoreResults={hasMoreResults}

frontend/src/components/ResultsTable.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function highlightTextWithFieldTerms(text, highlightTerms, fieldName = '') {
7272
return highlightText(text, termsToUse)
7373
}
7474

75-
function ResultsTable({ results, loading, timestampField = 'timestamp', hasMoreResults = false, loadingMore = false, onLoadMore, requestTime = null, selectedColumns = [], onRemoveColumn, onFilterChange, onToggleColumn, userPreferences, darkMode = false, searchQuery = '', sortOrder = 'newest', onSortOrderChange }) {
75+
function ResultsTable({ results, loading, liveMode = false, timestampField = 'timestamp', hasMoreResults = false, loadingMore = false, onLoadMore, requestTime = null, selectedColumns = [], onRemoveColumn, onFilterChange, onToggleColumn, userPreferences, darkMode = false, searchQuery = '', sortOrder = 'newest', onSortOrderChange }) {
7676
const [expandedRows, setExpandedRows] = useState(new Set())
7777
const [sortBy, setSortBy] = useState({ field: null, direction: 'asc' })
7878
const [expandedRowTabs, setExpandedRowTabs] = useState({}) // Track active tab for each expanded row
@@ -129,7 +129,10 @@ function ResultsTable({ results, loading, timestampField = 'timestamp', hasMoreR
129129
return () => scrollContainer.removeEventListener('scroll', handleScroll)
130130
}, [hasMoreResults, loadingMore, onLoadMore])
131131

132-
if (loading) {
132+
// During a live refresh, keep the current results on screen instead of
133+
// flashing the "Searching..." placeholder. Only show it for the initial
134+
// search (or when no results are available yet).
135+
if (loading && (!liveMode || !results)) {
133136
return (
134137
<div className="results-container">
135138
<div className="loading-state">Searching...</div>

frontend/src/components/SearchBar.css

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,89 @@
313313
animation: spin 0.8s linear infinite;
314314
}
315315

316+
.live-mode-controls {
317+
display: flex;
318+
align-items: center;
319+
gap: 0.4rem;
320+
}
321+
322+
.live-mode-button {
323+
padding: 0.5rem 1rem;
324+
background: white;
325+
color: #475569;
326+
border: 1px solid #cbd5e1;
327+
border-radius: 6px;
328+
font-size: 0.9rem;
329+
font-weight: 600;
330+
cursor: pointer;
331+
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
332+
display: flex;
333+
align-items: center;
334+
gap: 0.5rem;
335+
justify-content: center;
336+
}
337+
338+
.live-mode-button:hover {
339+
border-color: #94a3b8;
340+
background: #f8fafc;
341+
}
342+
343+
.live-mode-button .live-mode-dot {
344+
width: 8px;
345+
height: 8px;
346+
border-radius: 50%;
347+
background: #94a3b8;
348+
}
349+
350+
.live-mode-button.active {
351+
background: #dc2626;
352+
border-color: #dc2626;
353+
color: white;
354+
}
355+
356+
.live-mode-button.active:hover {
357+
background: #b91c1c;
358+
border-color: #b91c1c;
359+
}
360+
361+
.live-mode-button.active .live-mode-dot {
362+
background: white;
363+
animation: live-pulse 1.2s ease-in-out infinite;
364+
}
365+
366+
@keyframes live-pulse {
367+
0%, 100% { opacity: 1; transform: scale(1); }
368+
50% { opacity: 0.4; transform: scale(0.7); }
369+
}
370+
371+
.live-interval-select {
372+
padding: 0.5rem 0.4rem;
373+
border: 1px solid #cbd5e1;
374+
border-radius: 6px;
375+
font-size: 0.85rem;
376+
font-weight: 600;
377+
color: #475569;
378+
background: white;
379+
cursor: pointer;
380+
}
381+
382+
.dark-mode .live-mode-button {
383+
background: #2d3748;
384+
border-color: #4a5568;
385+
color: #e2e8f0;
386+
}
387+
388+
.dark-mode .live-mode-button:hover {
389+
background: #374151;
390+
border-color: #6b7280;
391+
}
392+
393+
.dark-mode .live-interval-select {
394+
background: #2d3748;
395+
border-color: #4a5568;
396+
color: #e2e8f0;
397+
}
398+
316399
@keyframes spin {
317400
to {
318401
transform: rotate(360deg);

frontend/src/components/SearchBar.jsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useRef, useEffect } from 'react'
22
import './SearchBar.css'
33

4-
function SearchBar({ onSearch, loading, timeRangePicker, fields = [], selectedIndex, onFetchFieldValues, firstEventTime, lastEventTime, histogramInterval, onHistogramIntervalChange, intervalError, totalHits, elapsedTimeMicros, requestTime, onCancelSearch, defaultSearchFields = [], onSaveQuery, onLoadQuery, onShareQuery, externalQuery = '', selectedColumns = [], results = [], onExportCSV, onQueryChange, vrl, onVrlChange, vrlTime, vrlEnabled = false, filters = [], timeRange, onFiltersChange, onTimeRangeChange, onAnalyzePatterns, onRestoreHistoryState }) {
4+
function SearchBar({ onSearch, loading, timeRangePicker, fields = [], selectedIndex, onFetchFieldValues, firstEventTime, lastEventTime, histogramInterval, onHistogramIntervalChange, intervalError, totalHits, elapsedTimeMicros, requestTime, onCancelSearch, liveMode = false, liveInterval = 5, onToggleLiveMode, onLiveIntervalChange, defaultSearchFields = [], onSaveQuery, onLoadQuery, onShareQuery, externalQuery = '', selectedColumns = [], results = [], onExportCSV, onQueryChange, vrl, onVrlChange, vrlTime, vrlEnabled = false, filters = [], timeRange, onFiltersChange, onTimeRangeChange, onAnalyzePatterns, onRestoreHistoryState }) {
55
const [query, setQuery] = useState('')
66

77
// Query history management
@@ -484,6 +484,7 @@ function SearchBar({ onSearch, loading, timeRangePicker, fields = [], selectedIn
484484
type="button"
485485
className={`search-button ${loading ? 'loading' : ''} `}
486486
onClick={loading ? onCancelSearch : handleSubmit}
487+
disabled={liveMode}
487488
>
488489
{loading ? (
489490
<>
@@ -494,6 +495,29 @@ function SearchBar({ onSearch, loading, timeRangePicker, fields = [], selectedIn
494495
'Search'
495496
)}
496497
</button>
498+
<div className="live-mode-controls">
499+
<button
500+
type="button"
501+
className={`live-mode-button ${liveMode ? 'active' : ''}`}
502+
onClick={onToggleLiveMode}
503+
title={liveMode ? 'Stop live refresh' : 'Enable live mode (auto-refresh the last 5 minutes)'}
504+
>
505+
<span className="live-mode-dot"></span>
506+
Live
507+
</button>
508+
{liveMode && (
509+
<select
510+
className="live-interval-select"
511+
value={liveInterval}
512+
onChange={(e) => onLiveIntervalChange(Number(e.target.value))}
513+
title="Refresh interval"
514+
>
515+
<option value={5}>5s</option>
516+
<option value={15}>15s</option>
517+
<option value={60}>1m</option>
518+
</select>
519+
)}
520+
</div>
497521
</div>
498522

499523
{vrlEnabled && showVrlInput && (

0 commit comments

Comments
 (0)