- Add LIVE/PAUSED toggle button that auto-refreshes search results every 5s - Source environment badge from VITE_ENV_NAME env var (defaults to DEV locally, PRODUCTION in Docker) - Remove search trigger button from topnav (command palette still available via keyboard) - Rename "Transaction Explorer" to "Route Explorer" and "Active Now" to "In-Flight" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
3.4 KiB
TypeScript
79 lines
3.4 KiB
TypeScript
import { useSearchExecutions, useExecutionStats, useStatsTimeseries } from '../../api/queries/executions';
|
||
import { useExecutionSearch } from './use-execution-search';
|
||
import { StatCard } from '../../components/shared/StatCard';
|
||
import { Pagination } from '../../components/shared/Pagination';
|
||
import { SearchFilters } from './SearchFilters';
|
||
import { ResultsTable } from './ResultsTable';
|
||
import styles from './ExecutionExplorer.module.css';
|
||
|
||
export function ExecutionExplorer() {
|
||
const { toSearchRequest, offset, limit, setOffset, live, toggleLive } = useExecutionSearch();
|
||
const searchRequest = toSearchRequest();
|
||
const { data, isLoading, isFetching } = useSearchExecutions(searchRequest, live);
|
||
const { data: stats } = useExecutionStats();
|
||
const { data: timeseries } = useStatsTimeseries(
|
||
searchRequest.timeFrom ?? undefined,
|
||
searchRequest.timeTo ?? undefined,
|
||
);
|
||
|
||
const sparkTotal = timeseries?.buckets.map((b) => b.totalCount) ?? [];
|
||
const sparkFailed = timeseries?.buckets.map((b) => b.failedCount) ?? [];
|
||
const sparkAvgDuration = timeseries?.buckets.map((b) => b.avgDurationMs) ?? [];
|
||
const sparkP99 = timeseries?.buckets.map((b) => b.p99DurationMs) ?? [];
|
||
const sparkActive = timeseries?.buckets.map((b) => b.activeCount) ?? [];
|
||
|
||
const total = data?.total ?? 0;
|
||
const results = data?.data ?? [];
|
||
|
||
// Derive stats from current search results
|
||
const failedCount = results.filter((r) => r.status === 'FAILED').length;
|
||
const avgDuration = results.length > 0
|
||
? Math.round(results.reduce((sum, r) => sum + r.durationMs, 0) / results.length)
|
||
: 0;
|
||
|
||
const showFrom = total > 0 ? offset + 1 : 0;
|
||
const showTo = Math.min(offset + limit, total);
|
||
|
||
return (
|
||
<>
|
||
{/* Page Header */}
|
||
<div className={`${styles.pageHeader} animate-in`}>
|
||
<div>
|
||
<h1>Route Explorer</h1>
|
||
<div className={styles.subtitle}>Search and analyze route executions</div>
|
||
</div>
|
||
<button className={`${styles.liveToggle} ${live ? styles.liveOn : styles.liveOff}`} onClick={toggleLive}>
|
||
<span className={styles.liveDot} />
|
||
{live ? 'LIVE' : 'PAUSED'}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Stats Bar */}
|
||
<div className={styles.statsBar}>
|
||
<StatCard label="Total Matches" value={total.toLocaleString()} accent="amber" change={`from current search`} sparkData={sparkTotal} />
|
||
<StatCard label="Avg Duration" value={`${avgDuration}ms`} accent="cyan" sparkData={sparkAvgDuration} />
|
||
<StatCard label="Failed (page)" value={failedCount.toString()} accent="rose" sparkData={sparkFailed} />
|
||
<StatCard label="P99 Latency" value={stats ? `${stats.p99LatencyMs}ms` : '--'} accent="green" change="last hour" sparkData={sparkP99} />
|
||
<StatCard label="In-Flight" value={stats ? stats.activeCount.toString() : '--'} accent="blue" change="running executions" sparkData={sparkActive} />
|
||
</div>
|
||
|
||
{/* Filters */}
|
||
<SearchFilters />
|
||
|
||
{/* Results Header */}
|
||
<div className={`${styles.resultsHeader} animate-in delay-4`}>
|
||
<span className={styles.resultsCount}>
|
||
Showing <strong>{showFrom}–{showTo}</strong> of <strong>{total.toLocaleString()}</strong> results
|
||
{isFetching && !isLoading && ' · updating...'}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Results Table */}
|
||
<ResultsTable results={results} loading={isLoading} />
|
||
|
||
{/* Pagination */}
|
||
<Pagination total={total} offset={offset} limit={limit} onChange={setOffset} />
|
||
</>
|
||
);
|
||
}
|