Refactor FileList component to open a download modal instead of direct downloads; added PasswordForDownload component for secure file access.

This commit is contained in:
Atharva Ombase
2025-08-03 19:26:45 +05:30
parent 8f32c77f7e
commit 5283a2b9f1
+26 -40
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setCurrentPath } from "../store/pathSlice"; import { setCurrentPath } from "../store/pathSlice";
import PasswordForDownload from "./PasswordForDownload";
import { import {
FileText, FileText,
FileVideo, FileVideo,
@@ -20,16 +21,16 @@ import {
const API_URL = import.meta.env.VITE_API_URL; const API_URL = import.meta.env.VITE_API_URL;
const FileTable = ({ initialPath }) => { const FileList = ({ initialPath }) => {
// Read username dynamically to avoid stale null on first load
const username = localStorage.getItem("username") || ""; const username = localStorage.getItem("username") || "";
const userRoot = `/${username}`; const userRoot = `/${username}`;
// Initialize currentPath only once with the correct userRoot
const [currentPath, setCurrentPathState] = useState( const [currentPath, setCurrentPathState] = useState(
() => initialPath || userRoot () => initialPath || userRoot
); );
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [showDownloadModal, setShowDownloadModal] = useState(false);
const [downloadFilename, setDownloadFilename] = useState("");
const dispatch = useDispatch(); const dispatch = useDispatch();
const isUploading = useSelector((state) => state.upload.isUploading); const isUploading = useSelector((state) => state.upload.isUploading);
@@ -41,7 +42,6 @@ const FileTable = ({ initialPath }) => {
const getIcon = (name, type) => { const getIcon = (name, type) => {
if (type === "Folder") if (type === "Folder")
return <Folder className="text-yellow-500 w-5 h-5 mr-2" />; return <Folder className="text-yellow-500 w-5 h-5 mr-2" />;
const ext = name.split(".").pop().toLowerCase(); const ext = name.split(".").pop().toLowerCase();
switch (ext) { switch (ext) {
case "txt": case "txt":
@@ -111,7 +111,6 @@ const FileTable = ({ initialPath }) => {
: `${API_URL}/api/hdfs/deleteFolder?hdfsPath=${encodeURIComponent( : `${API_URL}/api/hdfs/deleteFolder?hdfsPath=${encodeURIComponent(
hdfsPath hdfsPath
)}`; )}`;
const resp = await fetch(endpoint, { method: "DELETE" }); const resp = await fetch(endpoint, { method: "DELETE" });
if (!resp.ok) console.error("Deletion failed:", await resp.text()); if (!resp.ok) console.error("Deletion failed:", await resp.text());
fetchFiles(); fetchFiles();
@@ -136,38 +135,15 @@ const FileTable = ({ initialPath }) => {
setCurrentPathState(parts.length === 0 ? userRoot : `/${parts.join("/")}`); setCurrentPathState(parts.length === 0 ? userRoot : `/${parts.join("/")}`);
}; };
const handleFileDownload = async (hdfsPath, name, event) => { // open modal instead of direct download
event.stopPropagation(); const openDownloadModal = (name, e) => {
try { e.stopPropagation();
const authToken = localStorage.getItem("token"); setDownloadFilename(name);
const response = await fetch( setShowDownloadModal(true);
`${API_URL}/api/hdfs/downloadFile?hdfsEncPath=${encodeURIComponent(
hdfsPath
)}&localPath=${name}&username=${username}`,
{
method: "POST",
headers: { Authorization: `Bearer ${authToken}` },
}
);
if (!response.ok) throw new Error(await response.text());
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = name;
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
fetchFiles();
} catch (error) {
console.error("Download failed:", error);
alert("Something went wrong while downloading the file.");
}
}; };
return ( return (
<>
<div className="relative overflow-x-auto rounded-2xl shadow-lg border border-blue-200"> <div className="relative overflow-x-auto rounded-2xl shadow-lg border border-blue-200">
<div className="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-semibold text-sm"> <div className="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-semibold text-sm">
<span className="truncate max-w-[80%]">Path: {currentPath}</span> <span className="truncate max-w-[80%]">Path: {currentPath}</span>
@@ -200,13 +176,14 @@ const FileTable = ({ initialPath }) => {
files.map((entry, idx) => { files.map((entry, idx) => {
const name = getName(entry); const name = getName(entry);
const type = getType(entry); const type = getType(entry);
const hdfsPath = `${currentPath}/${name}`; // const hdfsPath = `${currentPath}/${name}`;
return ( return (
<tr <tr
key={idx} key={idx}
onClick={ onClick={
type === "Folder" ? () => handleOpenFolder(name) : undefined type === "Folder"
? () => handleOpenFolder(name)
: undefined
} }
className={`even:bg-blue-50 odd:bg-white border-b border-blue-100 transition hover:bg-blue-100 ${ className={`even:bg-blue-50 odd:bg-white border-b border-blue-100 transition hover:bg-blue-100 ${
type === "Folder" ? "cursor-pointer" : "" type === "Folder" ? "cursor-pointer" : ""
@@ -219,7 +196,7 @@ const FileTable = ({ initialPath }) => {
<td className="px-6 py-4 space-x-3"> <td className="px-6 py-4 space-x-3">
{isFile(entry) && ( {isFile(entry) && (
<button <button
onClick={(e) => handleFileDownload(hdfsPath, name, e)} onClick={(e) => openDownloadModal(name, e)}
className="text-blue-600 hover:underline inline-flex items-center" className="text-blue-600 hover:underline inline-flex items-center"
> >
<Download className="w-4 h-4 mr-1" /> <Download className="w-4 h-4 mr-1" />
@@ -241,11 +218,20 @@ const FileTable = ({ initialPath }) => {
</tbody> </tbody>
</table> </table>
</div> </div>
{showDownloadModal && (
<PasswordForDownload
filename={downloadFilename}
onDownload={fetchFiles}
onClose={() => setShowDownloadModal(false)}
/>
)}
</>
); );
}; };
FileTable.propTypes = { FileList.propTypes = {
initialPath: PropTypes.string, initialPath: PropTypes.string,
}; };
export default FileTable; export default FileList;