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,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user