UNPKG

@jfvilas/plugin-kwirth-log

Version:

Frontend plugin for viewing real-time Kubernetes logs in Backstage

492 lines (489 loc) 28.4 kB
import React, { useState, useRef, useEffect } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { Progress, WarningPanel } from '@backstage/core-components'; import { useApi, alertApiRef } from '@backstage/core-plugin-api'; import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR, getPodList, getContainerList } from '@jfvilas/plugin-kwirth-common'; import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react'; import { kwirthLogApiRef } from '../api/types.esm.js'; import { ESignalMessageLevel, InstanceConfigScopeEnum, InstanceMessageChannelEnum, EInstanceMessageType, EOpsCommand, accessKeySerialize, EInstanceMessageFlow, EInstanceMessageAction, EInstanceConfigView, EInstanceConfigObject } from '@jfvilas/kwirth-common'; import { Options } from './Options.esm.js'; import { ComponentNotFound, ErrorType, ClusterList, KwirthNews, ObjectSelector, StatusLog } from '@jfvilas/plugin-kwirth-frontend'; import { Box, Grid, Card, CardHeader, TextField, InputAdornment, CardContent } from '@material-ui/core'; import Divider from '@material-ui/core/Divider'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import PlayIcon from '@material-ui/icons/PlayArrow'; import PauseIcon from '@material-ui/icons/Pause'; import StopIcon from '@material-ui/icons/Stop'; import InfoIcon from '@material-ui/icons/Info'; import WarningIcon from '@material-ui/icons/Warning'; import ErrorIcon from '@material-ui/icons/Error'; import DownloadIcon from '@material-ui/icons/CloudDownload'; import KwirthLogLogo from '../assets/kwirthlog-logo.svg'; import RefreshIcon from '@material-ui/icons/Refresh'; import { VERSION } from '../version.esm.js'; const RefBox = Box; const LOG_MAX_MESSAGES = 1e3; const EntityKwirthLogContent = (props) => { const { entity } = useEntity(); const kwirthLogApi = useApi(kwirthLogApiRef); const alertApi = useApi(alertApiRef); const [validClusters, setResources] = useState([]); const [selectedClusterName, setSelectedClusterName] = useState(""); const [selectedNamespaces, setSelectedNamespaces] = useState([]); const [selectedPodNames, setSelectedPodNames] = useState([]); const [selectedContainerNames, setSelectedContainerNames] = useState([]); const [started, setStarted] = useState(false); const [stopped, setStopped] = useState(true); const paused = useRef(false); const [messages, setMessages] = useState([]); const [pendingMessages, setPendingMessages] = useState([]); const [statusMessages, setStatusMessages] = useState([]); const [websocket, setWebsocket] = useState(); const [instance, setInstance] = useState(); const kwirthLogOptionsRef = useRef({ fromStart: props.fromStart !== void 0 ? props.fromStart : false, showTimestamp: props.showTimestamp !== void 0 ? props.showTimestamp : false, showNames: props.showNames !== void 0 ? props.showNames : true, followLog: props.followLog !== void 0 ? props.followLog : true, wrapLines: props.wrapLines !== void 0 ? props.wrapLines : false }); const [showStatusDialog, setShowStatusDialog] = useState(false); const [statusLevel, setStatusLevel] = useState(ESignalMessageLevel.INFO); const preRef = useRef(null); const lastRef = useRef(null); const [backendVersion, setBackendVersion] = useState(""); const [backendInfo, setBackendInfo] = useState(); const { loading, error } = useAsync(async () => { if (backendVersion === "") setBackendVersion(await kwirthLogApi.getVersion()); if (!backendInfo) setBackendInfo(await kwirthLogApi.getInfo()); let reqScopes = [InstanceConfigScopeEnum.VIEW]; if (props.enableRestart) reqScopes.push(InstanceConfigScopeEnum.RESTART); let data = await kwirthLogApi.requestAccess(entity, InstanceMessageChannelEnum.LOG, reqScopes); setResources(data); }); const buffer = useRef(/* @__PURE__ */ new Map()); const [filter, setFilter] = useState(""); const [filterCasing, setFilterCasing] = useState(false); const [filterRegex, setFilterRegex] = useState(false); const logBoxRef = useRef(null); const [logBoxTop, setLogBoxTop] = useState(0); const adornmentSelected = { margin: 0, borderWidth: 1, borderStyle: "solid", borderColor: "gray", paddingLeft: 3, paddingRight: 3, backgroundColor: "gray", cursor: "pointer", color: "white" }; const adornmentNotSelected = { margin: 0, borderWidth: 1, borderStyle: "solid", paddingLeft: 3, paddingRight: 3, cursor: "pointer" }; useEffect(() => { if (logBoxRef.current) setLogBoxTop(logBoxRef.current.getBoundingClientRect().top); }); const clickStart = (options) => { if (!paused.current) { setStarted(true); paused.current = false; setStopped(false); startLogViewer(options); } else { setMessages((prev) => [...prev, ...pendingMessages]); setPendingMessages([]); paused.current = false; setStarted(true); } }; const onClickPause = () => { setStarted(false); paused.current = true; }; const onClickStop = () => { setStarted(false); setStopped(true); paused.current = false; stopLogViewer(); }; const onSelectCluster = (clusterName) => { if (started) onClickStop(); if (clusterName) { setSelectedClusterName(clusterName); setSelectedPodNames([]); setSelectedContainerNames([]); setStatusMessages([]); let cluster = validClusters.find((cluster2) => cluster2.name === clusterName); if (cluster && cluster.pods) { let validNamespaces = Array.from(new Set(cluster.pods.map((pod) => pod.namespace))); if (validNamespaces.length === 1) { setSelectedNamespaces(validNamespaces); let podList = getPodList(cluster.pods, validNamespaces); setSelectedPodNames(podList.map((pod) => pod.name)); setSelectedContainerNames(getContainerList(cluster.pods, validNamespaces, podList.map((pod) => pod.name), props.excludeContainers || [])); } else { setMessages([{ type: EInstanceMessageType.SIGNAL, text: "Select namespace in order to decide which pod logs to view.", namespace: "", pod: "", container: "" }]); setSelectedNamespaces([]); } } } }; const processLogMessage = (wsEvent) => { let instanceMessage = JSON.parse(wsEvent.data); switch (instanceMessage.type) { case EInstanceMessageType.DATA: let logMessage = instanceMessage; let bname = logMessage.namespace + "/" + logMessage.pod + "/" + logMessage.container; let text = logMessage.text; if (!buffer.current.has(bname)) buffer.current.set(bname, ""); if (buffer.current.get(bname)) { text = buffer.current.get(bname) + text; buffer.current.set(bname, ""); } if (!text.endsWith("\n")) { let i = text.lastIndexOf("\n"); buffer.current.set(bname, text.substring(i)); text = text.substring(0, i); } for (let line of text.split("\n")) { if (line.trim() === "") continue; let logLine = { text: line, namespace: logMessage.namespace, pod: logMessage.pod, container: logMessage.container, type: logMessage.type }; if (paused.current) { setPendingMessages((prev) => [...prev, logLine]); } else { setMessages((prev) => { while (prev.length > LOG_MAX_MESSAGES - 1) { prev.splice(0, 1); } if (kwirthLogOptionsRef.current.followLog && lastRef.current) lastRef.current.scrollIntoView({ behavior: "instant", block: "start" }); return [...prev, logLine]; }); } } break; case EInstanceMessageType.SIGNAL: if (instanceMessage.flow === EInstanceMessageFlow.RESPONSE && instanceMessage.action === EInstanceMessageAction.START) { if (instanceMessage.instance !== "") setInstance(instanceMessage.instance); else { let signalMessage = instanceMessage; if (signalMessage.text) { alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" }); } } } else { let signalMessage = instanceMessage; if (signalMessage.text) { addMessage(signalMessage.level, signalMessage.text); switch (signalMessage.level) { case ESignalMessageLevel.INFO: alertApi.post({ message: signalMessage.text, severity: "info", display: "transient" }); break; case ESignalMessageLevel.WARNING: alertApi.post({ message: signalMessage.text, severity: "warning", display: "transient" }); break; case ESignalMessageLevel.ERROR: alertApi.post({ message: signalMessage.text, severity: "error", display: "transient" }); break; default: alertApi.post({ message: signalMessage.text, severity: "success", display: "transient" }); break; } } } break; default: addMessage(ESignalMessageLevel.ERROR, "Invalid message type received: " + instanceMessage.type); alertApi.post({ message: "Invalid message type received: " + instanceMessage.type, severity: "error", display: "transient" }); break; } }; const addMessage = (level, text) => { setStatusMessages((prev) => [...prev, { level, text, type: EInstanceMessageType.SIGNAL }]); }; const websocketOnMessage = (wsEvent) => { let instanceMessage; try { instanceMessage = JSON.parse(wsEvent.data); } catch (err) { console.log(err); console.log(wsEvent.data); return; } switch (instanceMessage.channel) { case InstanceMessageChannelEnum.LOG: processLogMessage(wsEvent); break; case InstanceMessageChannelEnum.OPS: let opsMessage = instanceMessage; if (opsMessage.data?.data) addMessage(ESignalMessageLevel.WARNING, "Operations message: " + opsMessage.data.data); else addMessage(ESignalMessageLevel.WARNING, "Operations message: " + JSON.stringify(opsMessage)); break; default: addMessage(ESignalMessageLevel.ERROR, "Invalid channel in message: " + instanceMessage.channel); addMessage(ESignalMessageLevel.ERROR, "Invalid message: " + JSON.stringify(instanceMessage)); break; } }; const websocketOnOpen = (ws, options) => { let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName); if (!cluster) { addMessage(ESignalMessageLevel.ERROR, "No cluster selected"); return; } let pods = cluster.pods.filter((p2) => selectedNamespaces.includes(p2.namespace)); if (!pods) { addMessage(ESignalMessageLevel.ERROR, "No pods found"); return; } console.log(`WS connected`); let accessKey = cluster.accessKeys.get(InstanceConfigScopeEnum.VIEW); if (accessKey) { let containers = []; if (selectedContainerNames.length > 0) { for (var p of selectedPodNames) { for (var c of selectedContainerNames) { containers.push(p + "+" + c); } } } let iConfig = { channel: InstanceMessageChannelEnum.LOG, objects: EInstanceConfigObject.PODS, action: EInstanceMessageAction.START, flow: EInstanceMessageFlow.REQUEST, instance: "", accessKey: accessKeySerialize(accessKey), scope: InstanceConfigScopeEnum.VIEW, view: selectedContainerNames.length > 0 ? EInstanceConfigView.CONTAINER : EInstanceConfigView.POD, namespace: selectedNamespaces.join(","), group: "", pod: selectedPodNames.map((p2) => p2).join(","), container: containers.join(","), data: { timestamp: options.showTimestamp, previous: false, maxMessages: LOG_MAX_MESSAGES, fromStart: options.fromStart }, type: EInstanceMessageType.SIGNAL }; ws.send(JSON.stringify(iConfig)); } else { addMessage(ESignalMessageLevel.ERROR, "No accessKey for starting log streaming"); return; } }; const startLogViewer = (options) => { let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName); if (!cluster) { addMessage(ESignalMessageLevel.ERROR, "No cluster selected"); return; } setMessages([]); try { let ws = new WebSocket(cluster.url); ws.onopen = () => websocketOnOpen(ws, options); ws.onmessage = (event) => websocketOnMessage(event); ws.onclose = (event) => websocketOnClose(event); setWebsocket(ws); } catch (err) { setMessages([{ type: EInstanceMessageType.DATA, text: `Error opening log stream: ${err}`, namespace: "", pod: "", container: "" }]); } }; const websocketOnClose = (_event) => { console.log(`WS disconnected`); setStarted(false); paused.current = false; setStopped(true); }; const stopLogViewer = () => { messages.push({ type: EInstanceMessageType.DATA, text: "============================================================================================================================", namespace: "", pod: "", container: "" }); websocket?.close(); }; const onChangeLogConfig = (options) => { kwirthLogOptionsRef.current = options; if (started) { clickStart(options); } }; const onClickDownload = () => { let content = preRef.current.innerHTML.replaceAll("<pre>", "").replaceAll("</pre>", "\n"); content = content.replaceAll('<span style="color: green;">', ""); content = content.replaceAll('<span style="color: blue;">', ""); content = content.replaceAll("</span>", ""); let filename = selectedClusterName + "-" + selectedNamespaces + "-" + entity.metadata.name + ".txt"; let mimeType = "text/plain"; const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const onClickRestart = () => { var cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName); if (!cluster) { addMessage(ESignalMessageLevel.ERROR, "No cluster selected"); return; } let restartKey = cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART); if (!restartKey) { addMessage(ESignalMessageLevel.ERROR, "No access key present"); return; } if (!instance) { addMessage(ESignalMessageLevel.ERROR, "No instance has been established"); return; } let pods = cluster.pods.filter((pod) => selectedNamespaces.includes(pod.namespace)); for (let pod of pods) { let opsMessage = { msgtype: "opsmessage", action: EInstanceMessageAction.COMMAND, flow: EInstanceMessageFlow.IMMEDIATE, type: EInstanceMessageType.DATA, channel: InstanceMessageChannelEnum.OPS, instance: "", id: "1", accessKey: accessKeySerialize(restartKey), command: EOpsCommand.RESTARTPOD, namespace: pod.namespace, group: "", pod: pod.name, container: "" }; let routeMessage = { msgtype: "routemessage", accessKey: accessKeySerialize(restartKey), destChannel: InstanceMessageChannelEnum.OPS, action: EInstanceMessageAction.ROUTE, flow: EInstanceMessageFlow.IMMEDIATE, type: EInstanceMessageType.SIGNAL, channel: InstanceMessageChannelEnum.LOG, instance, data: opsMessage }; websocket?.send(JSON.stringify(routeMessage)); } }; const actionButtons = () => { let hasViewKey = false, hasRestartKey = false; let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName); if (cluster) { hasViewKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.VIEW)); hasRestartKey = Boolean(cluster.accessKeys.get(InstanceConfigScopeEnum.RESTART)); } return /* @__PURE__ */ React.createElement(React.Fragment, null, props.enableRestart && /* @__PURE__ */ React.createElement(IconButton, { title: "Restart", onClick: onClickRestart, disabled: selectedPodNames.length === 0 || !hasRestartKey || !websocket || !started }, /* @__PURE__ */ React.createElement(RefreshIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { title: "Download", onClick: onClickDownload, disabled: messages.length <= 1 }, /* @__PURE__ */ React.createElement(DownloadIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: () => clickStart(kwirthLogOptionsRef.current), title: "Play", disabled: started || !paused || selectedPodNames.length === 0 || !hasViewKey }, /* @__PURE__ */ React.createElement(PlayIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: onClickPause, title: "Pause", disabled: !(started && !paused.current && selectedPodNames.length > 0) }, /* @__PURE__ */ React.createElement(PauseIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: onClickStop, title: "Stop", disabled: stopped || selectedPodNames.length === 0 }, /* @__PURE__ */ React.createElement(StopIcon, null))); }; const statusButtons = (title) => { const show = (level) => { setShowStatusDialog(true); setStatusLevel(level); }; const prepareText = (txt) => { return txt ? txt.length > 25 ? txt.substring(0, 25) + "..." : txt : "N/A"; }; return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, prepareText(title))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { marginTop: "-8px" } }, /* @__PURE__ */ React.createElement(IconButton, { title: "info", disabled: !statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.INFO), onClick: () => show(ESignalMessageLevel.INFO) }, /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.INFO) ? "#1D63ED" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "warning", disabled: !statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.WARNING), onClick: () => show(ESignalMessageLevel.WARNING), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.WARNING) ? "orange" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "error", disabled: !statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.ERROR), onClick: () => show(ESignalMessageLevel.ERROR), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: statusMessages.some((m) => m.type === EInstanceMessageType.SIGNAL && m.level === ESignalMessageLevel.ERROR) ? "red" : "#BDBDBD" } })))); }; const statusClear = (level) => { setStatusMessages(statusMessages.filter((m) => m.level !== level)); setShowStatusDialog(false); }; const onSelectObject = (namespaces, podNames, containerNames) => { setSelectedNamespaces(namespaces); setSelectedPodNames(podNames); setSelectedContainerNames(containerNames); }; const onChangeFilter = (event) => { setFilter(event.target?.value); }; const formatLogLine = (logLine) => { if (!logLine.pod) { return /* @__PURE__ */ React.createElement(React.Fragment, null, logLine.text + "\n"); } if (props.excludeContainers && props.excludeContainers.length > 0) { if (props.excludeContainers.includes(logLine.container)) return /* @__PURE__ */ React.createElement(React.Fragment, null); } if (filter !== "") { if (filterCasing) { if (filterRegex) { try { const regex = new RegExp(filter); if (!regex.test(logLine.text) && !regex.test(logLine.pod) && !regex.test(logLine.container)) return /* @__PURE__ */ React.createElement(React.Fragment, null); } catch { return /* @__PURE__ */ React.createElement(React.Fragment, null); } } else { if (!logLine.text.includes(filter) && !logLine.pod.includes(filter) && !logLine.container.includes(filter)) return /* @__PURE__ */ React.createElement(React.Fragment, null); } } else { if (filterRegex) { try { const regex = new RegExp(filter.toLocaleLowerCase()); if (!regex.test(logLine.text.toLocaleLowerCase()) && !regex.test(logLine.pod.toLocaleLowerCase()) && !regex.test(logLine.container.toLocaleLowerCase())) return /* @__PURE__ */ React.createElement(React.Fragment, null); } catch { return /* @__PURE__ */ React.createElement(React.Fragment, null); } } else { if (!logLine.text.toLocaleLowerCase().includes(filter.toLowerCase()) && !logLine.pod.toLocaleLowerCase().includes(filter.toLocaleLowerCase()) && !logLine.container.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) return /* @__PURE__ */ React.createElement(React.Fragment, null); } } } let podPrefix = /* @__PURE__ */ React.createElement(React.Fragment, null); if (selectedPodNames.length !== 1 || kwirthLogOptionsRef.current.showNames) { podPrefix = /* @__PURE__ */ React.createElement("span", { style: { color: "green" } }, logLine.pod + " "); } let containerPrefix = /* @__PURE__ */ React.createElement(React.Fragment, null); if (selectedContainerNames.length !== 1) { containerPrefix = /* @__PURE__ */ React.createElement("span", { style: { color: "blue" } }, logLine.container + " "); } return /* @__PURE__ */ React.createElement(React.Fragment, null, podPrefix, containerPrefix, logLine.text + "\n"); }; return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/plugin-kwirth-log", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster) => sum + cluster.pods.length, 0) > 0 && /* @__PURE__ */ React.createElement(RefBox, { ref: logBoxRef, sx: { display: "flex", height: `calc(100vh - ${logBoxTop}px - 25px)` } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "200px", maxWidth: "200px" } }, /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(ClusterList, { resources: validClusters, selectedClusterName, onSelect: onSelectCluster }))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(Options, { options: kwirthLogOptionsRef.current, onChange: onChangeLogConfig, disabled: selectedContainerNames.length === 0 || started || paused.current }))), !props.hideVersion && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(KwirthNews, { latestVersions: backendInfo, backendVersion, ownVersion: VERSION }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, flex: 1, overflow: "hidden", p: 1, marginLeft: "8px" } }, !selectedClusterName && /* @__PURE__ */ React.createElement("img", { src: KwirthLogLogo, alt: "No cluster selected", style: { left: "40%", marginTop: "10%", width: "20%", position: "relative" } }), selectedClusterName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { style: { marginTop: -8, height: "100%", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement( CardHeader, { title: statusButtons(selectedClusterName), style: { marginTop: -4, marginBottom: 4, flexShrink: 0 }, action: actionButtons() } ), /* @__PURE__ */ React.createElement(Grid, { container: true, style: { alignItems: "end" } }, /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "66%" } }, /* @__PURE__ */ React.createElement(Typography, { style: { marginLeft: 14 } }, /* @__PURE__ */ React.createElement(ObjectSelector, { cluster: validClusters.find((cluster) => cluster.name === selectedClusterName), onSelect: onSelectObject, disabled: selectedClusterName === "" || started || paused.current, selectedNamespaces, selectedPodNames, selectedContainerNames, scope: InstanceConfigScopeEnum.VIEW, excludeCotainers: props.excludeContainers }))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { width: "33%", marginLeft: 0 } }, /* @__PURE__ */ React.createElement( TextField, { value: filter, onChange: onChangeFilter, label: "Filter", fullWidth: true, style: { marginBottom: 6, marginLeft: 0 }, disabled: !started, InputProps: { endAdornment: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputAdornment, { position: "start", onClick: () => started && setFilterRegex(!filterRegex), style: { margin: 0 } }, /* @__PURE__ */ React.createElement(Typography, { style: filterRegex ? adornmentSelected : adornmentNotSelected }, ".*")), /* @__PURE__ */ React.createElement(InputAdornment, { position: "start", onClick: () => started && setFilterCasing(!filterCasing), style: { margin: 0, marginLeft: 1 } }, /* @__PURE__ */ React.createElement(Typography, { style: filterCasing ? adornmentSelected : adornmentNotSelected }, "Aa"))) } } ))), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, { style: { flexGrow: 1, overflow: "hidden", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(Box, { style: { overflowY: "auto", width: "100%", flexGrow: 1, height: `calc(100vh - ${logBoxTop}px - 25px)`, overflowX: kwirthLogOptionsRef.current.wrapLines ? "hidden" : "auto" } }, /* @__PURE__ */ React.createElement("pre", { ref: preRef, style: { whiteSpace: kwirthLogOptionsRef.current.wrapLines ? "pre-wrap" : "pre", wordBreak: kwirthLogOptionsRef.current.wrapLines ? "break-word" : "normal" } }, messages.map((m) => formatLogLine(m))), /* @__PURE__ */ React.createElement("span", { ref: lastRef }))))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear })); }; export { EntityKwirthLogContent }; //# sourceMappingURL=EntityKwirthLogContent.esm.js.map