Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions gcs/src/components/fla/DownloadLogModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import {
Button,
Group,
LoadingOverlay,
Modal,
Progress,
ScrollArea,
Text,
} from "@mantine/core"
import { useEffect, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import { showErrorNotification } from "../../helpers/notification"
import {
emitListLogFiles,
emitReadFile,
resetFiles,
selectFiles,
selectIsReadingFile,
selectLoadingListFiles,
selectLogPath,
selectReadFileProgress,
} from "../../redux/slices/ftpSlice"
import { readableBytes } from "./utils"

export default function DownloadLogModal({ opened, onClose }) {
const dispatch = useDispatch()
const files = useSelector(selectFiles)
const loadingListFiles = useSelector(selectLoadingListFiles)
const isReadingFile = useSelector(selectIsReadingFile)
const readFileProgress = useSelector(selectReadFileProgress)
const logPath = useSelector(selectLogPath)

const [selectedLog, setSelectedLog] = useState(null)
const [hasFetched, setHasFetched] = useState(false)

useEffect(() => {
// Fetch log files when modal opens (only once per opening)
if (opened && !hasFetched) {
dispatch(emitListLogFiles())
setHasFetched(true)
}
}, [opened, hasFetched, dispatch])

useEffect(() => {
// Reset files and fetch state when modal closes
if (!opened) {
dispatch(resetFiles())
setSelectedLog(null)
setHasFetched(false)
}
}, [opened, dispatch])

async function handleLogClick(log) {
// First, ask user where to save the file
try {
const options = {
title: "Save log file",
defaultPath: log.name,
filters: [
{ name: "Log Files", extensions: ["bin", "log"] },
{ name: "All Files", extensions: ["*"] },
],
}

const result = await window.ipcRenderer.invoke(
"app:get-save-file-path",
options,
)

if (!result.canceled && result.filePath) {
// Now download the file with the save path
setSelectedLog(log)
dispatch(emitReadFile({ path: log.path, savePath: result.filePath }))
}
} catch (error) {
showErrorNotification(`Error selecting save location: ${error.message}`)
}
}

function handleRefresh() {
dispatch(resetFiles())
setHasFetched(false)
dispatch(emitListLogFiles())
setHasFetched(true)
Comment thread
1Blademaster marked this conversation as resolved.
}

return (
<Modal
opened={opened}
onClose={onClose}
title="Download Log from Drone"
size="lg"
centered
closeOnEscape={!isReadingFile}
closeOnClickOutside={!isReadingFile}
withCloseButton={!isReadingFile}
>
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center">
<div className="flex flex-col">
<Text size="sm" c="dimmed">
{files.length > 0
? `Found ${files.length} log file${files.length !== 1 ? "s" : ""}`
: loadingListFiles
? "Searching for logs..."
: "No log files found"}
</Text>
{logPath && files.length > 0 && (
<Text size="xs" c="dimmed">
Location: {logPath}
</Text>
)}
</div>
<Button
size="xs"
onClick={handleRefresh}
disabled={loadingListFiles || isReadingFile}
loading={loadingListFiles}
>
Refresh
</Button>
</div>

{isReadingFile && readFileProgress && (
<div className="flex flex-col gap-2 p-3 bg-falcongrey-900/20 rounded">
<div className="flex justify-between items-center">
<Text size="sm">Downloading: {selectedLog?.name}</Text>
</div>
<Text size="xs" c="dimmed">
{readFileProgress.bytes_downloaded.toLocaleString()} /{" "}
{readFileProgress.total_bytes.toLocaleString()} bytes
</Text>
<Progress
value={readFileProgress.percentage}
size="lg"
animated
color="blue"
/>
<Text size="xs" c="dimmed" ta="center">
{readFileProgress.percentage}% complete
</Text>
</div>
)}

<div className="relative">
<LoadingOverlay
visible={loadingListFiles}
zIndex={1000}
overlayProps={{ blur: 2 }}
/>

<ScrollArea.Autosize mah={400} offsetScrollbars>
{files.length > 0 ? (
<div className="flex flex-col gap-1">
{files.map((log, idx) => (
<div
key={idx}
className={`flex items-center gap-3 p-1 rounded cursor-pointer transition-colors ${
selectedLog?.path === log.path
? "bg-falcongrey-700/30"
: "hover:bg-falcongrey-600"
} ${isReadingFile ? "opacity-50 pointer-events-none" : ""}`}
onClick={() => handleLogClick(log)}
>
<div className="flex-1">
<Text size="sm">{log.name}</Text>
<Group gap={8}>
<Text size="xs" c="dimmed">
{readableBytes(log.size_b)}
</Text>
</Group>
</div>
</div>
))}
</div>
) : (
!loadingListFiles && (
<div className="text-center py-8 text-gray-400">
<Text size="sm">No log files found</Text>
</div>
)
)}
</ScrollArea.Autosize>
</div>
</div>
</Modal>
)
}
19 changes: 18 additions & 1 deletion gcs/src/components/fla/SelectFlightLog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,32 @@ import {
Progress,
ScrollArea,
} from "@mantine/core"
import { useDisclosure } from "@mantine/hooks"
import moment from "moment"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useDispatch } from "react-redux"
import { useDispatch, useSelector } from "react-redux"
import {
showErrorNotification,
showSuccessNotification,
} from "../../helpers/notification.js"
import { selectConnectedToDrone } from "../../redux/slices/droneConnectionSlice.js"
import { setFile } from "../../redux/slices/logAnalyserSlice.js"
import DownloadLogModal from "./DownloadLogModal.jsx"
import { readableBytes } from "./utils"

/**
* Initial FLA screen for selecting or uploading a flight log file.
*/
export default function SelectFlightLog({ getLogSummary }) {
const dispatch = useDispatch()
const connected = useSelector(selectConnectedToDrone)
const [recentFgcsLogs, setRecentFgcsLogs] = useState(null)
const [loadingFile, setLoadingFile] = useState(false)
const [loadingFileProgress, setLoadingFileProgress] = useState(0)
const [
downloadModalOpened,
{ open: openDownloadModal, close: closeDownloadModal },
] = useDisclosure(false)

async function getFgcsLogs() {
setRecentFgcsLogs(await window.ipcRenderer.invoke("fla:get-recent-logs"))
Expand Down Expand Up @@ -133,6 +141,11 @@ export default function SelectFlightLog({ getLogSummary }) {
<Button onClick={selectFile} loading={loadingFile}>
Analyse a log
</Button>
{connected && (
<Button onClick={openDownloadModal} color="blue" variant="filled">
Download from Drone
</Button>
)}
<Button
disabled={logsExist}
color="red"
Expand Down Expand Up @@ -172,6 +185,10 @@ export default function SelectFlightLog({ getLogSummary }) {
color="green"
/>
)}
<DownloadLogModal
opened={downloadModalOpened}
onClose={closeDownloadModal}
/>
</div>
)
}
9 changes: 9 additions & 0 deletions gcs/src/redux/middleware/emitters.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from "../slices/droneConnectionSlice"
import {
emitListFiles,
emitListLogFiles,
emitReadFile,
setIsReadingFile,
setLoadingListFiles,
Expand Down Expand Up @@ -389,11 +390,19 @@ export function handleEmitters(socket, store, action) {
store.dispatch(setLoadingListFiles(true))
},
},
{
emitter: emitListLogFiles,
callback: () => {
socket.socket.emit("list_log_files")
store.dispatch(setLoadingListFiles(true))
},
},
{
emitter: emitReadFile,
callback: () => {
socket.socket.emit("read_file", {
path: action.payload.path,
save_path: action.payload.savePath,
})
store.dispatch(setIsReadingFile(true))
store.dispatch(setReadFileProgress(null)) // Reset progress when starting new download
Expand Down
25 changes: 25 additions & 0 deletions gcs/src/redux/middleware/socketMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
resetFiles,
setIsReadingFile,
setLoadingListFiles,
setLogPath,
setReadFileData,
setReadFileProgress,
} from "../slices/ftpSlice.js"
Expand Down Expand Up @@ -181,6 +182,7 @@ const ConfigSpecificSocketEvents = Object.freeze({

const FtpSpecificSocketEvents = Object.freeze({
onListFilesResult: "list_files_result",
onListLogFilesResult: "list_log_files_result",
onReadFileResult: "read_file_result",
onReadFileProgress: "read_file_progress",
})
Expand Down Expand Up @@ -1099,6 +1101,29 @@ const socketMiddleware = (store) => {
}
})

socket.socket.on(
FtpSpecificSocketEvents.onListLogFilesResult,
(msg) => {
store.dispatch(setLoadingListFiles(false))
if (msg.success) {
const data = msg.data || {}
const files = data.files || []
const logPath = data.log_path || null

store.dispatch(addFiles(files))
store.dispatch(setLogPath(logPath))

if (files.length === 0) {
showErrorNotification(
msg.message || "No log files found on drone",
)
}
} else {
showErrorNotification(msg.message)
}
},
)

socket.socket.on(FtpSpecificSocketEvents.onReadFileResult, (msg) => {
if (msg.success) {
showSuccessNotification(msg.message)
Expand Down
Loading