main.jsx – vollständiger nativer Log-Explorer
EuiProvider und Borealis ermöglichen eine gesteuerte Ereignistabelle mit Filtern nach Schweregrad und Suche sowie einem lokalen Zustand für gespeicherte Ereignisse.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
EuiProvider,
EuiPanel,
EuiTitle,
EuiText,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiFieldSearch,
EuiButtonGroup,
EuiButtonEmpty,
EuiBasicTable,
EuiHealth,
EuiStat,
EuiSwitch,
EuiCallOut,
useEuiTheme,
} from "@elastic/eui";
const logs = [
{
id: 1,
time: "09:42:18",
service: "gateway",
message: "Request completed",
level: "Info",
duration: 42,
},
{
id: 2,
time: "09:42:21",
service: "worker",
message: "Retry queued after timeout",
level: "Warning",
duration: 1200,
},
{
id: 3,
time: "09:42:24",
service: "gateway",
message: "Upstream connection refused",
level: "Error",
duration: 800,
},
{
id: 4,
time: "09:42:29",
service: "indexer",
message: "Batch indexed",
level: "Info",
duration: 180,
},
{
id: 5,
time: "09:42:32",
service: "worker",
message: "Job completed",
level: "Info",
duration: 94,
},
{
id: 6,
time: "09:42:36",
service: "gateway",
message: "Upstream connection restored",
level: "Info",
duration: 51,
},
];
function Screen({ dark, setDark }) {
const { euiTheme } = useEuiTheme();
const [query, setQuery] = useState(""),
[level, setLevel] = useState("all"),
[saved, setSaved] = useState([]);
const visible = logs.filter(
(x) =>
(level === "all" || x.level === level) &&
(x.service + " " + x.message).toLowerCase().includes(query.toLowerCase()),
);
const colors = {
Info: euiTheme.colors.primary,
Warning: euiTheme.colors.warning,
Error: euiTheme.colors.danger,
};
const columns = [
{ field: "time", name: "Time", width: "105px" },
{ field: "service", name: "Service", width: "105px" },
{
field: "message",
name: "Event",
render: (value, item) => (
<span>
{value}
{saved.includes(item.id) ? " · Saved locally" : ""}
</span>
),
},
{
field: "level",
name: "Level",
width: "110px",
render: (value) => <EuiHealth color={colors[value]}>{value}</EuiHealth>,
},
{
name: "Action",
width: "110px",
render: (item) => (
<EuiButtonEmpty
size="xs"
onClick={() =>
setSaved((v) =>
v.includes(item.id)
? v.filter((id) => id !== item.id)
: [...v, item.id],
)
}
>
{saved.includes(item.id) ? "Unsave" : "Save event"}
</EuiButtonEmpty>
),
},
];
return (
<main
style={{
padding: 24,
background: euiTheme.colors.backgroundBasePlain,
color: euiTheme.colors.textParagraph,
}}
>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiText size="xs">
<p>ELASTIC UI · LOG EXPLORER</p>
</EuiText>
<EuiTitle size="m">
<h1>Investigate gateway events</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label="Dark mode"
checked={dark}
onChange={(e) => setDark(e.target.checked)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="l" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<EuiStat title="6" description="Sample events" titleSize="m" />
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
title="1"
description="Error event"
titleSize="m"
titleColor="danger"
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
title={String(saved.length)}
description="Saved in this preview"
titleSize="m"
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="l" />
<EuiPanel>
<EuiTitle size="xs">
<h2>Event stream</h2>
</EuiTitle>
<EuiText size="s">
<p>09:42:18–09:42:36 · fictional data · no live telemetry</p>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFieldSearch
fullWidth
aria-label="Search log events"
placeholder="Search service or message"
value={query}
onChange={(e) => setQuery(e.target.value)}
incremental
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend="Filter severity"
options={[
{ id: "all", label: "All" },
{ id: "Warning", label: "Warning" },
{ id: "Error", label: "Error" },
]}
idSelected={level}
onChange={(id) => setLevel(id)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiText size="xs">
<p aria-live="polite">{visible.length} matching events</p>
</EuiText>
<div style={{ overflowX: "auto", maxWidth: "100%" }}>
<div style={{ minWidth: 640 }}>
<EuiBasicTable
responsiveBreakpoint={false}
items={visible}
itemId="id"
columns={columns}
tableCaption="Fictional application events"
noItemsMessage="No events match the current filters."
/>
</div>
</div>
{!visible.length && (
<EuiButtonEmpty
onClick={() => {
setLevel("all");
setQuery("");
}}
>
Clear filters
</EuiButtonEmpty>
)}
</EuiPanel>
<EuiSpacer size="l" />
<EuiCallOut size="s" title="Local investigation workspace">
Saving an event only changes this preview. No query is sent to
Elasticsearch and no incident is created.
</EuiCallOut>
<EuiSpacer size="m" />
<EuiText size="xs">
<p>
Native Elastic UI 123.0.0 · Borealis 8.1.0 theme · controlled filters
and table actions.
</p>
</EuiText>
</main>
);
}
function App() {
const [dark, setDark] = useState(false);
return (
<EuiProvider colorMode={dark ? "dark" : "light"}>
<Screen dark={dark} setDark={setDark} />
</EuiProvider>
);
}
createRoot(document.getElementById("root")).render(<App />);
- Als src/main.jsx im React-Grundgerüst speichern. In diesem Einstiegspunkt wird StrictMode bewusst nicht aktiviert.
- Die begrenzte Tabellenbreite von 640px ist eine Anwendungsentscheidung unter CSS; alle visuellen Komponenten stammen von EUI.


