@jfvilas/plugin-kwirth-log
Version:
Frontend plugin for viewing real-time Kubernetes logs in Backstage
492 lines (489 loc) • 28.4 kB
JavaScript
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