log display
This commit is contained in:
1 parent
a5bb186814
commit
795e1caca4
4 files changed
+141
-70
No files matched your search
@@ -0,0 +1,16 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
export const HeaderLogs = ({
|
||||
lines,
|
||||
isValidating,
|
||||
}: {
|
||||
lines: number;
|
||||
isValidating: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Stack direction="row">
|
||||
<Typography>{`${lines} recent lines`}</Typography>
|
||||
{isValidating && "Refreshing..."}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import { Fragment } from "react";
|
||||
|
||||
// Hooks
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
// Utils
|
||||
import type { DockerLogRow } from "@/Utils/MonitorUtils";
|
||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import type { RootState } from "@/store";
|
||||
|
||||
export const RowLog = ({ row }: { row: DockerLogRow }) => {
|
||||
const theme = useTheme();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid
|
||||
container
|
||||
spacing={{ xs: 0, md: LAYOUT.MD }}
|
||||
py={{ xs: LAYOUT.LG, md: 0 }}
|
||||
>
|
||||
<Grid size={{ xs: 12, md: "auto" }}>
|
||||
<Typography
|
||||
fontFamily="monospace"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{formatDateWithTz(row.ts, "MMM D HH:mm:ss", uiTimezone)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: "grow" }}>
|
||||
<Typography
|
||||
fontFamily={"monospace"}
|
||||
whiteSpace={{ xs: "pre-wrap", md: "nowrap" }}
|
||||
sx={{
|
||||
overflowWrap: { xs: "anywhere", md: "normal" },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component={"span"}
|
||||
color={
|
||||
row.stream === "stderr"
|
||||
? theme.palette.error.main
|
||||
: theme.palette.success.main
|
||||
}
|
||||
>
|
||||
{row.stream}
|
||||
</Box>
|
||||
: {row.text}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{row.gapBefore ? (
|
||||
<Stack
|
||||
width="100%"
|
||||
direction="row"
|
||||
alignItems={"center"}
|
||||
py={SPACING.MD}
|
||||
>
|
||||
<Typography pr={SPACING.MD}>Some log entries were skipped</Typography>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
</Stack>
|
||||
) : (
|
||||
<Divider sx={{ display: { xs: "block", md: "none" } }} />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -1,30 +1,41 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { BaseBox } from "@/Components/design-elements";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { HeaderLogs } from "@/Pages/Docker/ContainerDetails/Components/HeaderLogs";
|
||||
|
||||
// Types
|
||||
import type { DockerContainerLogsResponse } from "@/Types/Monitor";
|
||||
|
||||
// Hooks
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
Fragment,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import type { DockerLog } from "@/Types/Check";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
// Util
|
||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
||||
import { flattenDockerLogs } from "@/Utils/MonitorUtils";
|
||||
import { Box } from "@mui/material";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import type { RootState } from "@/store";
|
||||
import { RowLog } from "@/Pages/Docker/ContainerDetails/Components/RowLog";
|
||||
|
||||
interface TabLogsProps {
|
||||
monitorId?: string;
|
||||
containerName?: string;
|
||||
}
|
||||
|
||||
const SCROLL_PIN_THRESHOLD_PX = 8;
|
||||
const isScrolledToBottom = (el: HTMLElement) =>
|
||||
el.scrollHeight - el.scrollTop - el.clientHeight <= SCROLL_PIN_THRESHOLD_PX;
|
||||
|
||||
export const TabLogs = ({ monitorId, containerName }: TabLogsProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
// State
|
||||
const [olderLogs, setOlderLogs] = useState<DockerLog[]>([]);
|
||||
const [olderCursor, setOlderCursor] = useState<string | null | undefined>(undefined); // undefined -> no older requested, string -> cursor, null -> no more data
|
||||
@@ -53,76 +64,46 @@ export const TabLogs = ({ monitorId, containerName }: TabLogsProps) => {
|
||||
);
|
||||
|
||||
const rows = useMemo(() => flattenDockerLogs(head?.logs ?? []), [head?.logs]);
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
|
||||
const logBoxRef = useRef<HTMLDivElement>(null);
|
||||
const pinnedToBottom = useRef(true);
|
||||
|
||||
// Always pin to bottom when container/monitor changes
|
||||
useLayoutEffect(() => {
|
||||
pinnedToBottom.current = true;
|
||||
}, [monitorId, containerName]);
|
||||
|
||||
// New log rows
|
||||
useLayoutEffect(() => {
|
||||
const el = logBoxRef.current;
|
||||
if (!el || !pinnedToBottom.current) return;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}, [rows]);
|
||||
|
||||
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
|
||||
pinnedToBottom.current = isScrolledToBottom(event.currentTarget);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack gap={LAYOUT.MD}>
|
||||
<Stack direction="row">
|
||||
<Typography>{`${rows.length} recent lines`}</Typography>
|
||||
{isValidating && "Refreshing..."}
|
||||
</Stack>
|
||||
<HeaderLogs
|
||||
lines={rows.length}
|
||||
isValidating={isValidating}
|
||||
/>
|
||||
<BaseBox
|
||||
ref={logBoxRef}
|
||||
onScroll={handleScroll}
|
||||
minHeight={240}
|
||||
maxHeight="60vh"
|
||||
maxHeight="50vh"
|
||||
overflow={"auto"}
|
||||
padding={LAYOUT.MD}
|
||||
>
|
||||
{[...rows].reverse().map((row) => {
|
||||
{rows.map((row) => {
|
||||
return (
|
||||
<Box key={row.key}>
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateColumns={{
|
||||
xs: "1fr",
|
||||
sm: "150px 56px minmax(0, 1fr)",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
color="text.secondary"
|
||||
>
|
||||
{formatDateWithTz(row.ts, "MMM D HH:mm:ss", uiTimezone)}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
color={row.stream === "stderr" ? "error.main" : "text.secondary"}
|
||||
>
|
||||
{row.stream}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
color="text.primary"
|
||||
whiteSpace="pre-wrap"
|
||||
>
|
||||
{row.text}
|
||||
</Typography>
|
||||
</Box>
|
||||
{row.gapBefore && (
|
||||
<Stack
|
||||
role="separator"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box
|
||||
flex={1}
|
||||
borderTop={1}
|
||||
borderColor="divider"
|
||||
/>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="warning.main"
|
||||
>
|
||||
{"Some lines were skipped between checks"}
|
||||
</Typography>
|
||||
<Box
|
||||
flex={1}
|
||||
borderTop={1}
|
||||
borderColor="divider"
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
<RowLog
|
||||
key={row.key}
|
||||
row={row}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</BaseBox>
|
||||
|
||||
@@ -21,7 +21,7 @@ const TAB_MAP = {
|
||||
logs: 1,
|
||||
} as const;
|
||||
|
||||
const DockerDetailsPage = ({ open = "overview" }: DockerContainerDetailsProps) => {
|
||||
const DockerDetailsPage = ({ open = "logs" }: DockerContainerDetailsProps) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(TAB_MAP[open]);
|
||||
const [dateRange, setDateRange] = useState<DateRange>("recent");
|
||||
const { monitorId, containerName } = useParams<{
|
||||
|
||||
Reference in new issue
Block a user