154 lines
5.1 KiB
TypeScript
154 lines
5.1 KiB
TypeScript
import { Download } from "lucide-react"
|
||
import React from "react";
|
||
import { useObservabilityData } from "../hooks/useObservabilityData";
|
||
|
||
export const CommandAndQueryBar: React.FC = () => {
|
||
const {
|
||
logs,
|
||
traces,
|
||
metrics,
|
||
searchQuery,
|
||
setSearchQuery,
|
||
|
||
showErrorsOnly,
|
||
setShowErrorsOnly,
|
||
selectedServices,
|
||
toggleService,
|
||
resetFilters,
|
||
minDurationMs,
|
||
setMinDurationMs,
|
||
timeWindow,
|
||
setTimeWindow,
|
||
} = useObservabilityData();
|
||
|
||
const totalFilteredEvents = logs.length + traces.length + metrics.length;
|
||
|
||
const exportNDJSON = () => {
|
||
const records = [
|
||
...traces.map((t) => JSON.stringify({ type: "traces", ...t })),
|
||
...logs.map((l) => JSON.stringify({ type: "logs", ...l })),
|
||
...metrics.map((m) => JSON.stringify({ type: "metrics", ...m })),
|
||
].join("\n");
|
||
|
||
const blob = new Blob([records], { type: "application/x-ndjson" });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = `hyperscope-filtered-telemetry-${new Date().toISOString()}.ndjson`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
};
|
||
|
||
const hasActiveFilters =
|
||
Boolean(searchQuery) ||
|
||
showErrorsOnly ||
|
||
selectedServices.size > 0 ||
|
||
minDurationMs > 0 ||
|
||
timeWindow !== "all";
|
||
|
||
return (
|
||
<div className="flex flex-col gap-2.5 mb-4 font-body">
|
||
{/* Filter and Telemetry Status Bar */}
|
||
<div className="flex flex-wrap items-center justify-between gap-2.5 p-3 rounded-xl bg-ds-bg border border-ds-line">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<span className="font-mono text-[10px] text-ds-text-3 uppercase tracking-wider font-semibold shrink-0">
|
||
Active Context:
|
||
</span>
|
||
|
||
<span className="font-mono text-xs text-ds-accent font-semibold">
|
||
{totalFilteredEvents} Signals ({timeWindow === "all" ? "All Time" : `Last ${timeWindow}`})
|
||
</span>
|
||
|
||
{/* Selected Service Pills */}
|
||
{Array.from(selectedServices).map((service) => (
|
||
<span
|
||
key={service}
|
||
className="inline-flex items-center gap-1 rounded-full bg-ds-accent-bg text-ds-accent border border-ds-line px-2.5 py-0.5 font-mono text-xs font-semibold"
|
||
>
|
||
<span>service:{service}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => toggleService(service)}
|
||
className="hover:text-ds-text"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
|
||
{/* Errors Only Pill */}
|
||
{showErrorsOnly && (
|
||
<span className="inline-flex items-center gap-1 rounded-full bg-ds-critical-bg text-ds-critical px-2.5 py-0.5 font-mono text-xs font-semibold">
|
||
<span>Errors Only</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowErrorsOnly(false)}
|
||
className="hover:text-ds-text"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{/* Slow Spans Pill */}
|
||
{minDurationMs > 0 && (
|
||
<span className="inline-flex items-center gap-1 rounded-full bg-ds-warn-bg text-ds-warn border border-ds-warn/40 px-2.5 py-0.5 font-mono text-xs font-semibold">
|
||
<span>Latency >{minDurationMs}ms</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => setMinDurationMs(0)}
|
||
className="hover:text-ds-text"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{/* Search Query Pill */}
|
||
{searchQuery && (
|
||
<span className="inline-flex items-center gap-1 rounded-full bg-ds-surface-2 text-ds-text border border-ds-line px-2.5 py-0.5 font-mono text-xs">
|
||
<span>query: "{searchQuery}"</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => setSearchQuery("")}
|
||
className="hover:text-ds-text"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{hasActiveFilters && (
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
resetFilters();
|
||
setMinDurationMs(0);
|
||
setTimeWindow("all");
|
||
}}
|
||
className="text-ds-text-3 hover:text-ds-text font-mono text-xs underline ml-1"
|
||
>
|
||
Clear all
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{/* Export and Action buttons */}
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={exportNDJSON}
|
||
className="flex items-center gap-1 px-2.5 py-1 bg-ds-surface hover:bg-ds-surface-2 text-ds-text font-mono text-xs rounded-lg transition-colors border border-ds-line"
|
||
title="Export currently filtered telemetry signals as NDJSON"
|
||
>
|
||
<Download className="h-[15px] w-[15px] shrink-0" aria-hidden="true" />
|
||
<span>Export NDJSON</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CommandAndQueryBar;
|