import React from "react";
import { Button } from "@/components/button.tsx";
import { Input } from "@/components/input.tsx";
import { Separator } from "@/components/separator.tsx";
import { Download, RefreshCw, Search, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import type { DockerLogOptions } from "@/types";
import { getContainerLogs, downloadContainerLogs } from "@/main-axios.ts";
interface LogViewerProps {
sessionId: string;
containerId: string;
containerName: string;
}
function AdminToggle({
on,
onToggle,
label,
}: {
on: boolean;
onToggle: () => void;
label: string;
}) {
return (
{label}
);
}
function getLogColor(line: string): string {
if (line.includes(" WARN") || line.includes(" warn"))
return "text-yellow-400/90";
if (line.includes(" ERROR") || line.includes(" error"))
return "text-destructive";
if (line.includes(" DEBUG") || line.includes(" debug"))
return "text-muted-foreground/60";
return "text-foreground/90";
}
export function LogViewer({
sessionId,
containerId,
containerName,
}: LogViewerProps): React.ReactElement {
const { t } = useTranslation();
const [rawLogs, setRawLogs] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(false);
const [isDownloading, setIsDownloading] = React.useState(false);
const [tailLines, setTailLines] = React.useState("100");
const [showTimestamps, setShowTimestamps] = React.useState(false);
const [autoRefresh, setAutoRefresh] = React.useState(false);
const [logSearch, setLogSearch] = React.useState("");
const logsEndRef = React.useRef(null);
const fetchLogs = React.useCallback(async () => {
setIsLoading(true);
try {
const options: DockerLogOptions = {
tail: tailLines === "all" ? undefined : parseInt(tailLines, 10),
timestamps: showTimestamps,
};
const data = await getContainerLogs(sessionId, containerId, options);
setRawLogs(data.logs.split("\n").filter(Boolean));
} catch (error) {
toast.error(
`Failed to fetch logs: ${error instanceof Error ? error.message : "Unknown error"}`,
);
} finally {
setIsLoading(false);
}
}, [sessionId, containerId, tailLines, showTimestamps]);
React.useEffect(() => {
fetchLogs();
}, [fetchLogs]);
React.useEffect(() => {
if (!autoRefresh) return;
const interval = setInterval(fetchLogs, 3000);
return () => clearInterval(interval);
}, [autoRefresh, fetchLogs]);
React.useEffect(() => {
if (autoRefresh && logsEndRef.current) {
logsEndRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [rawLogs, autoRefresh]);
const handleDownload = async () => {
setIsDownloading(true);
try {
const blob = await downloadContainerLogs(sessionId, containerId, {
timestamps: showTimestamps,
});
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${containerName.replace(/[^a-z0-9]/gi, "_")}_logs.txt`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
toast.success(t("docker.logsDownloaded"));
} catch (error) {
toast.error(
`Failed to download logs: ${error instanceof Error ? error.message : "Unknown error"}`,
);
} finally {
setIsDownloading(false);
}
};
const filteredLogs = logSearch
? rawLogs.filter((l) => l.toLowerCase().includes(logSearch.toLowerCase()))
: rawLogs;
return (
setAutoRefresh(!autoRefresh)}
label={t("docker.autoRefresh")}
/>
setShowTimestamps(!showTimestamps)}
label={t("docker.timestamps")}
/>
{filteredLogs.length}/{rawLogs.length} {t("docker.lines")}
{filteredLogs.length > 0 ? (
filteredLogs.map((line, i) => {
const tsEnd = line.indexOf(" ", 1);
const maybeTs = tsEnd > 10 ? line.substring(0, tsEnd) : null;
const rest = maybeTs ? line.substring(tsEnd) : line;
return (
{maybeTs && (
{maybeTs}
)}
{rest}
);
})
) : (
{logSearch
? t("docker.noLogsMatching", { query: logSearch })
: t("docker.noLogsAvailable")}
)}
);
}