Refactor FileList component to open a download modal instead of direct downloads; added PasswordForDownload component for secure file access.
This commit is contained in:
@@ -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,116 +135,103 @@ 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="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-semibold text-sm">
|
<div className="relative overflow-x-auto rounded-2xl shadow-lg border border-blue-200">
|
||||||
<span className="truncate max-w-[80%]">Path: {currentPath}</span>
|
<div className="flex items-center justify-between px-6 py-4 bg-blue-100 text-black font-semibold text-sm">
|
||||||
{currentPath !== userRoot && (
|
<span className="truncate max-w-[80%]">Path: {currentPath}</span>
|
||||||
<button
|
{currentPath !== userRoot && (
|
||||||
onClick={goBack}
|
<button
|
||||||
className="flex items-center gap-1 text-blue-600 hover:underline text-sm"
|
onClick={goBack}
|
||||||
>
|
className="flex items-center gap-1 text-blue-600 hover:underline text-sm"
|
||||||
<ArrowLeft className="w-4 h-4" />
|
>
|
||||||
Go Back
|
<ArrowLeft className="w-4 h-4" />
|
||||||
</button>
|
Go Back
|
||||||
)}
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="w-full text-sm text-left text-black">
|
||||||
|
<thead className="text-xs uppercase bg-blue-50 text-blue-800 border-b border-blue-200">
|
||||||
|
<tr>
|
||||||
|
<th className="px-6 py-3">Name</th>
|
||||||
|
<th className="px-6 py-3">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{files.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" className="px-6 py-4 text-gray-500 text-center">
|
||||||
|
No files found.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
files.map((entry, idx) => {
|
||||||
|
const name = getName(entry);
|
||||||
|
const type = getType(entry);
|
||||||
|
// const hdfsPath = `${currentPath}/${name}`;
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={idx}
|
||||||
|
onClick={
|
||||||
|
type === "Folder"
|
||||||
|
? () => handleOpenFolder(name)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
className={`even:bg-blue-50 odd:bg-white border-b border-blue-100 transition hover:bg-blue-100 ${
|
||||||
|
type === "Folder" ? "cursor-pointer" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<td className="px-6 py-4 font-medium flex items-center">
|
||||||
|
{getIcon(name, type)}
|
||||||
|
{name}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 space-x-3">
|
||||||
|
{isFile(entry) && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => openDownloadModal(name, e)}
|
||||||
|
className="text-blue-600 hover:underline inline-flex items-center"
|
||||||
|
>
|
||||||
|
<Download className="w-4 h-4 mr-1" />
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={(e) => deleteFileOrFolder(name, type, e)}
|
||||||
|
className="text-red-600 hover:underline inline-flex items-center"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 mr-1" />
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="w-full text-sm text-left text-black">
|
{showDownloadModal && (
|
||||||
<thead className="text-xs uppercase bg-blue-50 text-blue-800 border-b border-blue-200">
|
<PasswordForDownload
|
||||||
<tr>
|
filename={downloadFilename}
|
||||||
<th className="px-6 py-3">Name</th>
|
onDownload={fetchFiles}
|
||||||
<th className="px-6 py-3">Actions</th>
|
onClose={() => setShowDownloadModal(false)}
|
||||||
</tr>
|
/>
|
||||||
</thead>
|
)}
|
||||||
<tbody>
|
</>
|
||||||
{files.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan="2" className="px-6 py-4 text-gray-500 text-center">
|
|
||||||
No files found.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
files.map((entry, idx) => {
|
|
||||||
const name = getName(entry);
|
|
||||||
const type = getType(entry);
|
|
||||||
const hdfsPath = `${currentPath}/${name}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr
|
|
||||||
key={idx}
|
|
||||||
onClick={
|
|
||||||
type === "Folder" ? () => handleOpenFolder(name) : undefined
|
|
||||||
}
|
|
||||||
className={`even:bg-blue-50 odd:bg-white border-b border-blue-100 transition hover:bg-blue-100 ${
|
|
||||||
type === "Folder" ? "cursor-pointer" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<td className="px-6 py-4 font-medium flex items-center">
|
|
||||||
{getIcon(name, type)}
|
|
||||||
{name}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 space-x-3">
|
|
||||||
{isFile(entry) && (
|
|
||||||
<button
|
|
||||||
onClick={(e) => handleFileDownload(hdfsPath, name, e)}
|
|
||||||
className="text-blue-600 hover:underline inline-flex items-center"
|
|
||||||
>
|
|
||||||
<Download className="w-4 h-4 mr-1" />
|
|
||||||
Download
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={(e) => deleteFileOrFolder(name, type, e)}
|
|
||||||
className="text-red-600 hover:underline inline-flex items-center"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 mr-1" />
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
FileTable.propTypes = {
|
FileList.propTypes = {
|
||||||
initialPath: PropTypes.string,
|
initialPath: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FileTable;
|
export default FileList;
|
||||||
|
|||||||
Reference in New Issue
Block a user