Fixed better File management

This commit is contained in:
frikky
2023-03-17 00:48:33 +01:00
parent 2bda021105
commit 339742b04b
+12 -770
View File
@@ -1,13 +1,12 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import { useTheme } from "@material-ui/core/styles";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import countries from "../components/Countries.jsx"; import countries from "../components/Countries.jsx";
import CodeEditor from "../components/ShuffleCodeEditor.jsx"; import CodeEditor from "../components/ShuffleCodeEditor.jsx";
import getLocalCodeData from "../components/ShuffleCodeEditor.jsx"; import getLocalCodeData from "../components/ShuffleCodeEditor.jsx";
import theme from "../theme";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import ClearIcon from '@mui/icons-material/Clear'; import ClearIcon from '@mui/icons-material/Clear';
//import ToggleButton from '@mui/material/ToggleButton'; //import ToggleButton from '@mui/material/ToggleButton';
@@ -51,7 +50,6 @@ import { Autocomplete } from "@mui/material";
import { import {
Edit as EditIcon, Edit as EditIcon,
FileCopy as FileCopyIcon, FileCopy as FileCopyIcon,
Publish as PublishIcon,
SelectAll as SelectAllIcon, SelectAll as SelectAllIcon,
OpenInNew as OpenInNewIcon, OpenInNew as OpenInNewIcon,
CloudDownload as CloudDownloadIcon, CloudDownload as CloudDownloadIcon,
@@ -77,6 +75,7 @@ import HandlePaymentNew from "../views/HandlePaymentNew.jsx";
import OrgHeader from "../components/OrgHeader.jsx"; import OrgHeader from "../components/OrgHeader.jsx";
import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx"; import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx";
import Billing from "../components/Billing.jsx"; import Billing from "../components/Billing.jsx";
import Files from "../components/Files.jsx";
import { display, style } from "@mui/system"; import { display, style } from "@mui/system";
const useStyles = makeStyles({ const useStyles = makeStyles({
@@ -126,9 +125,7 @@ const FileCategoryInput = (props) => {
const Admin = (props) => { const Admin = (props) => {
const { globalUrl, userdata, serverside } = props; const { globalUrl, userdata, serverside } = props;
var upload = "";
var to_be_copied = ""; var to_be_copied = "";
const theme = useTheme();
const classes = useStyles(); const classes = useStyles();
let navigate = useNavigate(); let navigate = useNavigate();
@@ -158,9 +155,6 @@ const Admin = (props) => {
const [environments, setEnvironments] = React.useState([]); const [environments, setEnvironments] = React.useState([]);
const [authentication, setAuthentication] = React.useState([]); const [authentication, setAuthentication] = React.useState([]);
const [schedules, setSchedules] = React.useState([]); const [schedules, setSchedules] = React.useState([]);
const [files, setFiles] = React.useState([]);
const [selectedNamespace, setSelectedNamespace] = React.useState("default");
const [fileNamespaces, setFileNamespaces] = React.useState([]);
const [selectedUser, setSelectedUser] = React.useState({}); const [selectedUser, setSelectedUser] = React.useState({});
const [newUsername, setNewUsername] = React.useState(""); const [newUsername, setNewUsername] = React.useState("");
const [newPassword, setNewPassword] = React.useState(""); const [newPassword, setNewPassword] = React.useState("");
@@ -192,7 +186,6 @@ const Admin = (props) => {
const [dealList, setDealList] = React.useState([]); const [dealList, setDealList] = React.useState([]);
const [adminTab, setAdminTab] = React.useState(1); const [adminTab, setAdminTab] = React.useState(1);
const [fileContent, setFileContent] = React.useState("");
const [billingInfo, setBillingInfo] = React.useState({}); const [billingInfo, setBillingInfo] = React.useState({});
useEffect(() => { useEffect(() => {
@@ -202,49 +195,7 @@ const Admin = (props) => {
} }
}, [isDropzone]); }, [isDropzone]);
const isCloud = const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [openFileId, setOpenFileId] = React.useState(false);
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv",]
const runUpdateText = (text) =>{
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body:text,
credentials: "include",
}).then((response) => {
if (response.status !== 200) {
console.log("Can't update file");
}
return response.json();
})
//console.log(text);
}
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
console.log('do validate')
console.log("new namespace name->",event.target.value);
fileNamespaces.push(event.target.value);
setSelectedNamespace(event.target.value);
setRenderTextBox(false);
}
if (event.key === 'Escape'){ // not working for some reasons
console.log('escape pressed')
setRenderTextBox(false);
}
}
const get2faCode = (userId) => { const get2faCode = (userId) => {
fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, { fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, {
@@ -1185,252 +1136,13 @@ const Admin = (props) => {
}); });
}; };
const handleFileUpload = (file_id, file) => {
//console.log("FILE: ", file_id, file)
fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, {
method: "POST",
credentials: "include",
body: file,
})
.then((response) => {
if (response.status !== 200 && response.status !== 201) {
console.log("Status not 200 for apps :O!");
alert.error("File was created, but failed to upload.");
return;
}
return response.json();
})
.then((responseJson) => {
//console.log("RESPONSE: ", responseJson)
//setFiles(responseJson)
})
.catch((error) => {
alert.error(error.toString());
});
};
const handleCreateFile = (filename, file) => {
var data = {
filename: filename,
org_id: selectedOrganization.id,
workflow_id: "global",
};
if (
selectedNamespace !== undefined &&
selectedNamespace !== null &&
selectedNamespace.length > 0 &&
selectedNamespace !== "default"
) {
data.namespace = selectedNamespace;
}
fetch(globalUrl + "/api/v1/files/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(data),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return;
}
return response.json();
})
.then((responseJson) => {
//console.log("RESP: ", responseJson)
if (responseJson.success === true) {
handleFileUpload(responseJson.id, file);
} else {
alert.error("Failed to upload file ", filename);
}
})
.catch((error) => {
alert.error("Failed to upload file ", filename);
console.log(error.toString());
});
};
const getFiles = () => {
fetch(globalUrl + "/api/v1/files", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson.files !== undefined && responseJson.files !== null) {
setFiles(responseJson.files);
} else {
setFiles([]);
}
console.log("NAMESPACES: ", responseJson.namespaces);
if (
responseJson.namespaces !== undefined &&
responseJson.namespaces !== null
) {
setFileNamespaces(responseJson.namespaces);
}
})
.catch((error) => {
alert.error(error.toString());
});
};
const deleteFile = (file) => {
fetch(globalUrl + "/api/v1/files/" + file.id, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file delete :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success) {
alert.info("Successfully deleted file " + file.name);
} else if (
responseJson.reason !== undefined &&
responseJson.reason !== null
) {
alert.error("Failed to delete file: " + responseJson.reason);
}
setTimeout(() => {
getFiles();
}, 1500);
console.log(responseJson);
})
.catch((error) => {
alert.error(error.toString());
});
};
const readFileData = (file) => {
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
}
return response.text();
})
.then((respdata) => {
// console.log("respdata ->", respdata);
// console.log("respdata type ->", typeof(respdata));
if (respdata.length === 0) {
alert.error("Failed getting file. Is it deleted?");
return;
}
return respdata
})
.then((responseData) => {
setFileContent(responseData);
//console.log("filecontent state ",fileContent);
})
.catch((error) => {
alert.error(error.toString());
});
};
var localData = ""; var localData = "";
// useEffect(() => { // useEffect(() => {
// console.log('confirm', fileContent); // console.log('confirm', fileContent);
// }, [fileContent]) // }, [fileContent])
const downloadFile = (file) => {
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
method: "GET",
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return "";
}
console.log("Resp: ", response)
return response.blob()
})
.then((respdata) => {
if (respdata.length === 0) {
alert.error("Failed getting file. Is it deleted?");
return;
}
var blob = new Blob([respdata], {
type: "application/octet-stream",
});
var url = URL.createObjectURL(blob);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", `${file.filename}`);
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click",
true,
true,
window,
1,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
link.dispatchEvent(event);
//return response.json()
})
.then((responseJson) => {
//console.log(responseJson)
//setSchedules(responseJson)
})
.catch((error) => {
alert.error(error.toString());
});
};
const getSchedules = () => { const getSchedules = () => {
fetch(globalUrl + "/api/v1/workflows/schedules", { fetch(globalUrl + "/api/v1/workflows/schedules", {
@@ -1620,7 +1332,6 @@ const Admin = (props) => {
getAppAuthentication(); getAppAuthentication();
} else if (newValue === 3) { } else if (newValue === 3) {
document.title = "Shuffle - admin - files"; document.title = "Shuffle - admin - files";
getFiles();
} else if (newValue === 4) { } else if (newValue === 4) {
document.title = "Shuffle - admin - schedules"; document.title = "Shuffle - admin - schedules";
getSchedules(); getSchedules();
@@ -3774,484 +3485,15 @@ const Admin = (props) => {
//setShow2faSetup(true); //setShow2faSetup(true);
}; };
const uploadFiles = (files) => {
for (var key in files) {
try {
const filename = files[key].name;
var filedata = new FormData();
filedata.append("shuffle_file", files[key]);
if (typeof files[key] === "object") {
handleCreateFile(filename, filedata);
}
/*
reader.addEventListener('load', (e) => {
var data = e.target.result;
setIsDropzone(false)
console.log(filename)
console.log(data)
console.log(files[key])
})
reader.readAsText(files[key])
*/
} catch (e) {
console.log("Error in dropzone: ", e);
}
}
setTimeout(() => {
getFiles();
}, 2500);
};
const uploadFile = (e) => {
const isDropzone =
e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
const files = isDropzone ? e.dataTransfer.files : e.target.files;
//const reader = new FileReader();
//alert.info("Starting fileupload")
uploadFiles(files);
};
const filesView = const filesView = curTab !== 3 ? null :
curTab === 3 ? ( <Files
<Dropzone globalUrl={globalUrl}
style={{ userdata={userdata}
maxWidth: window.innerWidth > 1366 ? 1366 : 1200, serverside={serverside}
margin: "auto", selectedOrganization={selectedOrganization}
padding: 20, />
}}
onDrop={uploadFile}
>
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Files</h2>
<span style={{ marginLeft: 25 }}>
Files from Workflows.{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://shuffler.io/docs/organizations#files"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
Learn more
</a>
</span>
</div>
<Button
color="primary"
variant="contained"
onClick={() => {
upload.click();
}}
>
<PublishIcon /> Upload files
</Button>
{/* <FileCategoryInput
isSet={renderTextBox} /> */}
<input
hidden
type="file"
multiple
ref={(ref) => (upload = ref)}
onChange={(event) => {
//const file = event.target.value
//const fileObject = URL.createObjectURL(actualFile)
//setFile(fileObject)
//const files = event.target.files[0]
uploadFiles(event.target.files);
}}
/>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
variant="contained"
color="primary"
onClick={() => getFiles()}
>
<CachedIcon />
</Button>
{fileNamespaces !== undefined &&
fileNamespaces !== null &&
fileNamespaces.length > 1 ? (
<FormControl style={{ minWidth: 150, maxWidth: 150 }}>
<InputLabel id="input-namespace-label">File Category</InputLabel>
<Select
labelId="input-namespace-select-label"
id="input-namespace-select-id"
style={{
color: "white",
minWidth: 150,
maxWidth: 150,
float: "right",
}}
value={selectedNamespace}
onChange={(event) => {
console.log("CHANGE NAMESPACE: ", event.target);
setSelectedNamespace(event.target.value);
}}
>
{fileNamespaces.map((data, index) => {
return (
<MenuItem
key={index}
value={data}
style={{ color: "white" }}
>
{data}
</MenuItem>
);
})}
</Select>
</FormControl>
) : null}
<div style={{display: "inline-flex", position:"relative"}}>
{renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(false);
console.log(" close clicked")
}}
>
<ClearIcon/>
</Button>
</Tooltip>
:
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(true);
}}
>
<AddIcon/>
</Button>
</Tooltip> }
{renderTextBox && <TextField
onKeyPress={(event)=>{
handleKeyDown(event);
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
placeholder="File category name"
required
margin="dense"
defaultValue={""}
autoFocus
/>}</div>
<CodeEditor
expansionModalOpen={openEditor}
setExpansionModalOpen={setOpenEditor}
setcodedata = {setFileContent}
codedata={fileContent}
isFileEditor = {true}
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
runUpdateText = {runUpdateText}
/>
<Divider
style={{
marginTop: 20,
marginBottom: 20,
backgroundColor: theme.palette.inputColor,
}}
/>
<List>
<ListItem>
<ListItemText
primary="Updated"
style={{ maxWidth: 225, minWidth: 225 }}
/>
<ListItemText
primary="Name"
style={{
maxWidth: 150,
minWidth: 150,
overflow: "hidden",
marginLeft: 10,
}}
/>
<ListItemText
primary="Workflow"
style={{ maxWidth: 100, minWidth: 100, overflow: "hidden" }}
/>
<ListItemText
primary="Md5"
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
/>
<ListItemText
primary="Status"
style={{ minWidth: 75, maxWidth: 75, marginLeft: 10 }}
/>
<ListItemText
primary="Filesize"
style={{ minWidth: 125, maxWidth: 125 }}
/>
<ListItemText primary="Actions" />
</ListItem>
{files === undefined || files === null || files.length === 0
? null
: files.map((file, index) => {
if (file.namespace === "") {
file.namespace = "default";
}
if (file.namespace !== selectedNamespace) {
return null;
}
var bgColor = "#27292d";
if (index % 2 === 0) {
bgColor = "#1f2023";
}
const filenamesplit = file.filename.split(".")
const iseditable = file.filesize < 100000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
return (
<ListItem
key={index}
style={{
backgroundColor: bgColor,
maxHeight: 100,
overflow: "hidden",
}}
>
<ListItemText
style={{
maxWidth: 225,
minWidth: 225,
overflow: "hidden",
}}
primary={new Date(file.updated_at * 1000).toISOString()}
/>
<ListItemText
style={{
maxWidth: 150,
minWidth: 150,
overflow: "hidden",
marginLeft: 10,
}}
primary={file.filename}
/>
<ListItemText
primary={
file.workflow_id === "global" ? (
<IconButton
disabled={file.workflow_id === "global"}
>
<OpenInNewIcon
style={{
color:
file.workflow_id !== "global"
? "white"
: "grey",
}}
/>
</IconButton>
) : (
<Tooltip
title={"Go to workflow"}
style={{}}
aria-label={"Download"}
>
<span>
<a
rel="noopener noreferrer"
style={{
textDecoration: "none",
color: "#f85a3e",
}}
href={`/workflows/${file.workflow_id}`}
target="_blank"
>
<IconButton
disabled={file.workflow_id === "global"}
>
<OpenInNewIcon
style={{
color:
file.workflow_id !== "global"
? "white"
: "grey",
}}
/>
</IconButton>
</a>
</span>
</Tooltip>
)
}
style={{
minWidth: 100,
maxWidth: 100,
overflow: "hidden",
}}
/>
<ListItemText
primary={file.md5_sum}
style={{
minWidth: 300,
maxWidth: 300,
overflow: "hidden",
}}
/>
<ListItemText
primary={file.status}
style={{
minWidth: 75,
maxWidth: 75,
overflow: "hidden",
marginLeft: 10,
}}
/>
<ListItemText
primary={file.filesize}
style={{
minWidth: 125,
maxWidth: 125,
overflow: "hidden",
}}
/>
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={`Edit File (${allowedFileTypes.join(", ")})`}
style={{}}
aria-label={"Edit"}
>
<span>
<IconButton
disabled={!iseditable}
style = {{padding: "6px"}}
onClick={() => {
setOpenEditor(true)
setOpenFileId(file.id)
readFileData(file)
}}
>
<EditIcon
style={{color: iseditable ? "white" : "grey",}}
/>
</IconButton>
</span>
</Tooltip>
<Tooltip
title={"Download file"}
style={{}}
aria-label={"Download"}
>
<span>
<IconButton
style = {{padding: "6px"}}
disabled={file.status !== "active"}
onClick={() => {
downloadFile(file);
}}
>
<CloudDownloadIcon
style={{
color:
file.status === "active"
? "white"
: "grey",
}}
/>
</IconButton>
</span>
</Tooltip>
<Tooltip
title={"Delete file"}
style={{}}
aria-label={"Delete"}
>
<span>
<IconButton
disabled={file.status !== "active"}
style = {{padding: "6px"}}
onClick={() => {
deleteFile(file);
}}
>
<DeleteIcon
style={{
color:
file.status === "active"
? "white"
: "grey",
}}
/>
</IconButton>
</span>
</Tooltip>
<Tooltip
title={"Copy file ID"}
style={{}}
aria-label={"copy"}
>
<IconButton
style = {{padding: "6px"}}
onClick={() => {
const elementName = "copy_element_shuffle";
var copyText =
document.getElementById(elementName);
if (
copyText !== null &&
copyText !== undefined
) {
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error(
"Can only copy over HTTPS (port 3443)"
);
return;
}
navigator.clipboard.writeText(file.id);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
alert.info(file.id + " copied to clipboard");
}
}}
>
<FileCopyIcon style={{ color: "white" }} />
</IconButton>
</Tooltip>
</span>
style={{
minWidth: 250,
maxWidth: 250,
// overflow: "hidden",
}}
/>
</ListItem>
);
})}
</List>
</div>
</Dropzone>
) : null;
const schedulesView = const schedulesView =
curTab === 4 ? ( curTab === 4 ? (
@@ -4830,7 +4072,7 @@ const Admin = (props) => {
const elementName = "copy_element_shuffle"; const elementName = "copy_element_shuffle";
const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth
const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${globalUrl}" -d ghcr.io/shuffle/shuffle-orborus:latest` const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${globalUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest`
var copyText = document.getElementById(elementName); var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) { if (copyText !== null && copyText !== undefined) {
const clipboard = navigator.clipboard; const clipboard = navigator.clipboard;