Skip to content

Commit

Permalink
Implement download full logs for service logs
Browse files Browse the repository at this point in the history
  • Loading branch information
Dartoxian committed Dec 5, 2023
1 parent cd4263b commit b93b22b
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 17 deletions.
18 changes: 15 additions & 3 deletions enclave-manager/web/src/components/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button, ButtonProps, IconButton, IconButtonProps } from "@chakra-ui/react";
import { FiDownload } from "react-icons/fi";
import { isDefined } from "../utils";
import streamsaver from "streamsaver";
import { isAsyncIterable, isDefined, stripAnsi } from "../utils";
import { saveTextAsFile } from "../utils/download";

type DownloadButtonProps<IsIconButton extends boolean> = (IsIconButton extends true ? IconButtonProps : ButtonProps) & {
valueToDownload?: (() => string) | string | null;
valueToDownload?: (() => string) | (() => AsyncIterable<string>) | string | null;
fileName: string;
text?: IsIconButton extends true ? string : never;
isIconButton?: IsIconButton;
Expand All @@ -17,9 +18,20 @@ export const DownloadButton = <IsIconButton extends boolean>({
isIconButton,
...buttonProps
}: DownloadButtonProps<IsIconButton>) => {
const handleDownloadClick = () => {
const handleDownloadClick = async () => {
if (isDefined(valueToDownload)) {
const v = typeof valueToDownload === "string" ? valueToDownload : valueToDownload();

if (isAsyncIterable(v)) {
const writableStream = streamsaver.createWriteStream(fileName);
const writer = writableStream.getWriter();

for await (const part of v) {
await writer.write(new TextEncoder().encode(`${stripAnsi(part)}\n`));
}
await writer.close();
return;
}
saveTextAsFile(v, fileName);
}
};
Expand Down
26 changes: 16 additions & 10 deletions enclave-manager/web/src/components/enclaves/logs/LogViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type LogViewerProps = {
ProgressWidget?: ReactElement;
logsFileName?: string;
searchEnabled?: boolean;
copyLogsEnabled?: boolean;
onGetAllLogs?: () => AsyncIterable<string>;
};

type SearchBaseState = {
Expand Down Expand Up @@ -69,6 +71,8 @@ export const LogViewer = ({
ProgressWidget,
logsFileName,
searchEnabled,
copyLogsEnabled,
onGetAllLogs,
}: LogViewerProps) => {
const virtuosoRef = useRef<VirtuosoHandle>(null);
const [logLines, setLogLines] = useState(propsLogLines);
Expand Down Expand Up @@ -179,17 +183,19 @@ export const LogViewer = ({
</FormLabel>
</FormControl>
<ButtonGroup>
<CopyButton
contentName={"logs"}
valueToCopy={getLogsValue}
size={"sm"}
isDisabled={logLines.length === 0}
isIconButton
aria-label={"Copy logs"}
color={"gray.100"}
/>
{copyLogsEnabled && (
<CopyButton
contentName={"logs"}
valueToCopy={getLogsValue}
size={"sm"}
isDisabled={logLines.length === 0}
isIconButton
aria-label={"Copy logs"}
color={"gray.100"}
/>
)}
<DownloadButton
valueToDownload={getLogsValue}
valueToDownload={onGetAllLogs || getLogsValue}
size={"sm"}
fileName={logsFileName || `logs.txt`}
isDisabled={logLines.length === 0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const DownloadFileArtifactButton = ({ file, enclave }: DownloadFileButton

const handleDownloadClick = async () => {
setIsLoading(true);
// todo: get tgz download instead
const fileParts = await kurtosisClient.downloadFilesArtifact(enclave, file);
const writableStream = streamsaver.createWriteStream(`${enclave.name}--${file.fileName}.tgz`);
const writer = writableStream.getWriter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export const EnclaveLogs = () => {
<LogViewer
logLines={logLines}
progressPercent={progressPercent}
copyLogsEnabled
ProgressWidget={
<Flex justifyContent={"space-between"} alignItems={"center"} width={"100%"}>
<ProgressSummary progress={progress} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Timestamp } from "@bufbuild/protobuf";
import { ServiceInfo } from "enclave-manager-sdk/build/api_container_service_pb";
import { DateTime } from "luxon";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useKurtosisClient } from "../../../../../client/enclaveManager/KurtosisClientContext";
import { LogViewer } from "../../../../../components/enclaves/logs/LogViewer";
import { LogLineMessage } from "../../../../../components/enclaves/logs/types";
import { isDefined } from "../../../../../utils";
import { assertDefined, isDefined } from "../../../../../utils";
import { EnclaveFullInfo } from "../../../types";

const serviceLogLineToLogLineMessage = (lines: string[], timestamp?: Timestamp): LogLineMessage[] => {
Expand Down Expand Up @@ -42,6 +42,27 @@ export const ServiceLogs = ({ enclave, service }: ServiceLogsProps) => {
const kurtosisClient = useKurtosisClient();
const [logLines, setLogLines] = useState<LogLineMessage[]>([]);

const handleGetAllLogs = useCallback(
async function* () {
const abortController = new AbortController();
const logs = await kurtosisClient.getServiceLogs(abortController, enclave, [service], false, 0, true);
for await (const lineGroup of logs) {
const lineGroupForService = lineGroup.serviceLogsByServiceUuid[service.serviceUuid];
assertDefined(
lineGroupForService,
`Log line response included a line group withouth service ${
service.serviceUuid
}: ${lineGroup.toJsonString()}`,
);
const parsedLogLines = serviceLogLineToLogLineMessage(lineGroupForService.line, lineGroupForService.timestamp);
for (const parsedLine of parsedLogLines) {
yield parsedLine.message || "";
}
}
},
[enclave, service],
);

useEffect(() => {
let canceled = false;
const abortController = new AbortController();
Expand Down Expand Up @@ -75,5 +96,5 @@ export const ServiceLogs = ({ enclave, service }: ServiceLogsProps) => {
}, [enclave, service, kurtosisClient]);

const logsFileName = `${enclave.name}--${service.name}-logs.txt`;
return <LogViewer logLines={logLines} logsFileName={logsFileName} searchEnabled />;
return <LogViewer logLines={logLines} logsFileName={logsFileName} searchEnabled onGetAllLogs={handleGetAllLogs} />;
};

0 comments on commit b93b22b

Please sign in to comment.