import { useState } from "react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
import { motion, AnimatePresence } from "framer-motion";
import { LuInfo } from "react-icons/lu";
import { Button } from "@/components/Button";
import Card, { GridCard } from "@components/Card";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import { useVersion } from "@/hooks/useVersion";
import { useDeviceStore } from "@/hooks/stores";
import notifications from "@/notifications";
import { DOWNGRADE_VERSION } from "@/ui.config";
import { GitHubIcon } from "./Icons";
interface FailSafeModeOverlayProps {
reason: string;
}
interface OverlayContentProps {
readonly children: React.ReactNode;
}
function OverlayContent({ children }: OverlayContentProps) {
return (
{children}
);
}
interface TooltipProps {
readonly children: React.ReactNode;
readonly text: string;
readonly show: boolean;
}
function Tooltip({ children, text, show }: TooltipProps) {
if (!show) {
return <>{children}>;
}
return (
);
}
export function FailSafeModeOverlay({ reason }: FailSafeModeOverlayProps) {
const { send } = useJsonRpc();
const { navigateTo } = useDeviceUiNavigation();
const { appVersion } = useVersion();
const { systemVersion } = useDeviceStore();
const [isDownloadingLogs, setIsDownloadingLogs] = useState(false);
const [hasDownloadedLogs, setHasDownloadedLogs] = useState(false);
const getReasonCopy = () => {
switch (reason) {
case "video":
return {
message:
"We've detected an issue with the video capture process. Your device is still running and accessible, but video streaming is temporarily unavailable.",
};
default:
return {
message:
"A critical process has encountered an issue. Your device is still accessible, but some functionality may be temporarily unavailable.",
};
}
};
const { message } = getReasonCopy();
const handleReportAndDownloadLogs = () => {
setIsDownloadingLogs(true);
send("getFailSafeLogs", {}, async (resp: JsonRpcResponse) => {
setIsDownloadingLogs(false);
if ("error" in resp) {
notifications.error(`Failed to get recovery logs: ${resp.error.message}`);
return;
}
// Download logs
const logContent = resp.result as string;
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `jetkvm-recovery-${reason}-${timestamp}.txt`;
const blob = new Blob([logContent], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
await new Promise(resolve => setTimeout(resolve, 1000));
a.click();
await new Promise(resolve => setTimeout(resolve, 1000));
document.body.removeChild(a);
URL.revokeObjectURL(url);
notifications.success("Crash logs downloaded successfully");
setHasDownloadedLogs(true);
// Open GitHub issue
const issueBody = `## Issue Description
The \`${reason}\` process encountered an error and failsafe mode was activated.
**Reason:** \`${reason}\`
**Timestamp:** ${new Date().toISOString()}
**App Version:** ${appVersion || "Unknown"}
**System Version:** ${systemVersion || "Unknown"}
## Logs
Please attach the recovery logs file that was downloaded to your computer:
\`${filename}\`
> [!NOTE]
> Please remove any sensitive information from the logs. The reports are public and can be viewed by anyone.
## Additional Context
[Please describe what you were doing when this occurred]`;
const issueUrl =
`https://github.com/jetkvm/kvm/issues/new?` +
`title=${encodeURIComponent(`Recovery Mode: ${reason} process issue`)}&` +
`body=${encodeURIComponent(issueBody)}`;
window.open(issueUrl, "_blank");
});
};
const handleDowngrade = () => {
navigateTo(`/settings/general/update?app=${DOWNGRADE_VERSION}`);
};
return (
Fail safe mode activated
{message}
);
}