Skip to content

Commit

Permalink
[Dashboard] Add download links to the log viewer page (#42673)
Browse files Browse the repository at this point in the history
Adding links at the log viewer list page allows logs to be downloaded much quicker.

---------

Signed-off-by: Alan Guo <[email protected]>
  • Loading branch information
alanwguo authored Jan 26, 2024
1 parent 39f758e commit 10df806
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 42 deletions.
6 changes: 1 addition & 5 deletions dashboard/client/src/pages/log/LogViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,7 @@ export const LogViewer = ({
variant="contained"
component="a"
href={downloadUrl}
download={
path.startsWith("/logs/")
? path.substring("/logs/".length)
: path
}
download={path}
>
Download log file
</Button>
Expand Down
121 changes: 84 additions & 37 deletions dashboard/client/src/pages/log/Logs.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import {
Box,
Button,
createStyles,
IconButton,
Link,
List,
ListItem,
makeStyles,
Paper,
Typography,
} from "@material-ui/core";
import React, { useState } from "react";
import { Link, Outlet, useSearchParams } from "react-router-dom";
import { grey } from "@material-ui/core/colors";
import React, { useMemo, useState } from "react";
import { RiDownload2Line } from "react-icons/ri";
import { Outlet, Link as RouterLink, useSearchParams } from "react-router-dom";
import useSWR from "swr";
import { StateApiLogViewer } from "../../common/MultiTabLogViewer";
import { SearchInput } from "../../components/SearchComponent";
import TitleCard from "../../components/TitleCard";
import { listStateApiLogs } from "../../service/log";
import { getStateApiDownloadLogUrl, listStateApiLogs } from "../../service/log";
import { getNodeList } from "../../service/node";
import { MainNavPageInfo } from "../layout/mainNavContext";

Expand Down Expand Up @@ -68,7 +74,7 @@ export const StateApiLogsListPage = () => {
{nodeId && (
<div>
<Button
component={Link}
component={RouterLink}
variant="contained"
to={backHref}
className={classes.search}
Expand Down Expand Up @@ -120,7 +126,7 @@ export const StateApiLogsNodesList = () => {
<List>
{nodes?.map(({ raylet: { nodeId }, ip }) => (
<ListItem key={nodeId}>
<Link to={`?nodeId=${nodeId}`}>
<Link component={RouterLink} to={`?nodeId=${nodeId}`}>
Node ID: {nodeId} (IP: {ip})
</Link>
</ListItem>
Expand All @@ -131,6 +137,15 @@ export const StateApiLogsNodesList = () => {
);
};

const useStateApiLogsFilesListStyles = makeStyles((theme) =>
createStyles({
iconButton: {
verticalAlign: "baseline",
color: grey[700],
},
}),
);

type StateApiLogsFilesListProps = {
nodeId: string;
folder: string | null;
Expand All @@ -142,6 +157,8 @@ export const StateApiLogsFilesList = ({
folder,
fileName,
}: StateApiLogsFilesListProps) => {
const classes = useStateApiLogsFilesListStyles();

// We want to do a partial search for file name.
const fileNameGlob = fileName ? `*${fileName}*` : undefined;
const glob = fileNameGlob
Expand All @@ -161,12 +178,51 @@ export const StateApiLogsFilesList = ({
);

const isLoading = fileGroups === undefined && error === undefined;
const files =
fileGroups !== undefined
? Object.values(fileGroups)
.flatMap((e) => e)
.sort()
: [];

const files = useMemo(
() =>
(fileGroups !== undefined
? Object.values(fileGroups)
.flatMap((e) => e)
.sort()
: []
).map((fileName) => {
const isDir = fileName.endsWith("/");
const fileNameWithoutEndingSlash = fileName.substring(
0,
fileName.length - 1,
);
const parentFolder = folder ? `${folder}/` : "";
const fileNameWithoutParent = fileName.startsWith(parentFolder)
? fileName.substring(parentFolder.length)
: fileName;

const linkPath = isDir
? `?nodeId=${encodeURIComponent(nodeId)}&folder=${encodeURIComponent(
fileNameWithoutEndingSlash,
)}`
: `viewer?nodeId=${encodeURIComponent(
nodeId,
)}&fileName=${encodeURIComponent(fileName)}`;

const downloadUrl = isDir
? undefined
: getStateApiDownloadLogUrl({
nodeId,
filename: fileName,
maxLines: -1,
});

return {
fileName,
name: fileNameWithoutParent,
linkPath,
isDir,
downloadUrl,
};
}),
[fileGroups, folder, nodeId],
);

return (
<React.Fragment>
Expand All @@ -177,34 +233,25 @@ export const StateApiLogsFilesList = ({
)}
{files && (
<List>
{files.map((fileName) => {
const isDir = fileName.endsWith("/");
const fileNameWithoutEndingSlash = fileName.substring(
0,
fileName.length - 1,
);
const parentFolder = folder ? `${folder}/` : "";
const fileNameWithoutParent = fileName.startsWith(parentFolder)
? fileName.substring(parentFolder.length)
: fileName;

{files.map(({ fileName, name, downloadUrl, linkPath }) => {
return (
<ListItem key={fileName}>
<Link
to={
isDir
? `?nodeId=${encodeURIComponent(
nodeId,
)}&folder=${encodeURIComponent(
fileNameWithoutEndingSlash,
)}`
: `viewer?nodeId=${encodeURIComponent(
nodeId,
)}&fileName=${encodeURIComponent(fileName)}`
}
>
{fileNameWithoutParent}
<Link component={RouterLink} to={linkPath}>
{name}
</Link>
{downloadUrl && (
<Box paddingLeft={0.5}>
<IconButton
component="a"
href={downloadUrl}
download={fileName}
size="small"
className={classes.iconButton}
>
<RiDownload2Line size={16} />
</IconButton>
</Box>
)}
</ListItem>
);
})}
Expand Down Expand Up @@ -244,7 +291,7 @@ export const StateApiLogViewerPage = () => {
{nodeId && (
<div>
<Button
component={Link}
component={RouterLink}
variant="contained"
to={backHref}
className={classes.search}
Expand Down

0 comments on commit 10df806

Please sign in to comment.