diff --git a/backend/go-app/files.go b/backend/go-app/files.go index 0779a08c..419d834d 100644 --- a/backend/go-app/files.go +++ b/backend/go-app/files.go @@ -135,7 +135,7 @@ func handleGetFiles(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("Got %d files for org %s", len(files), user.ActiveOrg.Id) + log.Printf("[INFO] Got %d files for org %s", len(files), user.ActiveOrg.Id) newBody, err := json.Marshal(files) if err != nil { log.Printf("[ERROR] Failed marshaling files: %s", err) @@ -716,38 +716,45 @@ func handleCreateFile(resp http.ResponseWriter, request *http.Request) { return } - // Try to get the org and workflow in case they don't exist - workflow, err := getWorkflow(ctx, curfile.WorkflowId) - if err != nil { - log.Printf("[ERROR] Workflow %s doesn't exist.", curfile.WorkflowId) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Error with workflow id or org id"}`)) - return - } - - _, err = getOrg(ctx, curfile.OrgId) - if err != nil { - log.Printf("[ERROR] Org %s doesn't exist.", curfile.OrgId) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Error with workflow id or org id"}`)) - return - } - - if workflow.ExecutingOrg.Id != curfile.OrgId { - found := false - for _, curorg := range workflow.Org { - if curorg.Id == curfile.OrgId { - found = true - break - } - } - - if !found { - log.Printf("[ERROR] Org %s doesn't have access to %s.", curfile.OrgId, curfile.WorkflowId) + var workflow *Workflow + if curfile.WorkflowId == "global" { + // PS: Not a security issue. + // Files are global anyway, but the workflow_id is used to identify origin + log.Printf("[INFO] Uploading filename %s for org %s as global file.", curfile.Filename, curfile.OrgId) + } else { + // Try to get the org and workflow in case they don't exist + workflow, err = getWorkflow(ctx, curfile.WorkflowId) + if err != nil { + log.Printf("[ERROR] Workflow %s doesn't exist.", curfile.WorkflowId) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Error with workflow id or org id"}`)) return } + + _, err = getOrg(ctx, curfile.OrgId) + if err != nil { + log.Printf("[ERROR] Org %s doesn't exist.", curfile.OrgId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Error with workflow id or org id"}`)) + return + } + + if workflow.ExecutingOrg.Id != curfile.OrgId { + found := false + for _, curorg := range workflow.Org { + if curorg.Id == curfile.OrgId { + found = true + break + } + } + + if !found { + log.Printf("[ERROR] Org %s doesn't have access to %s.", curfile.OrgId, curfile.WorkflowId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Error with workflow id or org id"}`)) + return + } + } } if strings.Contains(curfile.Filename, "/") || strings.Contains(curfile.Filename, `"`) || strings.Contains(curfile.Filename, "..") || strings.Contains(curfile.Filename, "~") { @@ -776,24 +783,26 @@ func handleCreateFile(resp http.ResponseWriter, request *http.Request) { downloadPath := fmt.Sprintf("%s/%s", folderPath, fileId) duplicateWorkflows := []string{} - for _, trigger := range workflow.Triggers { - if trigger.AppName == "Shuffle Workflow" && trigger.TriggerType == "SUBFLOW" { - for _, parameter := range trigger.Parameters { - if parameter.Name == "workflow" && len(parameter.Value) > 0 { + if curfile.WorkflowId != "global" { + for _, trigger := range workflow.Triggers { + if trigger.AppName == "Shuffle Workflow" && trigger.TriggerType == "SUBFLOW" { + for _, parameter := range trigger.Parameters { + if parameter.Name == "workflow" && len(parameter.Value) > 0 { - found := false - for _, workflow := range duplicateWorkflows { - if workflow == parameter.Value { - found = true - break + found := false + for _, workflow := range duplicateWorkflows { + if workflow == parameter.Value { + found = true + break + } } - } - if !found { - duplicateWorkflows = append(duplicateWorkflows, parameter.Value) - } + if !found { + duplicateWorkflows = append(duplicateWorkflows, parameter.Value) + } - break + break + } } } } diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 45d3a1e0..1143a696 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -219,16 +219,6 @@ const Header = props => { Settings - {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : - { - event.preventDefault() - handleClose() - }}> - - Admin - - - } { event.preventDefault() handleClose() @@ -237,8 +227,15 @@ const Header = props => { Logout + {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : + + + + } - + const loginTextMobile = !isLoggedIn ?
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 305db169..735814cf 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { makeStyles } from '@material-ui/styles'; import {Link} from 'react-router-dom'; @@ -25,6 +25,7 @@ import IconButton from '@material-ui/core/IconButton'; import Avatar from '@material-ui/core/Avatar'; import Zoom from '@material-ui/core/Zoom'; import { useAlert } from "react-alert"; +import Dropzone from '../components/Dropzone'; import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; @@ -33,6 +34,8 @@ import OrgHeader from '../components/OrgHeader' import CircularProgress from '@material-ui/core/CircularProgress'; import EditIcon from '@material-ui/icons/Edit'; +import FileCopyIcon from '@material-ui/icons/FileCopy'; +import PublishIcon from '@material-ui/icons/Publish'; import SelectAllIcon from '@material-ui/icons/SelectAll'; import OpenInNewIcon from '@material-ui/icons/OpenInNew'; import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; @@ -62,6 +65,7 @@ const Admin = (props) => { const { globalUrl, userdata } = props; var upload = "" + var to_be_copied = "" const theme = useTheme(); const classes = useStyles(); const [firstRequest, setFirstRequest] = React.useState(true); @@ -93,6 +97,14 @@ const Admin = (props) => { const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false) const [authenticationFields, setAuthenticationFields] = React.useState([]) const [showArchived, setShowArchived] = React.useState(false) + const [isDropzone, setIsDropzone] = React.useState(false); + + useEffect(() => { + if (isDropzone) { + //redirectOpenApi(); + setIsDropzone(false); + } + }, [isDropzone]); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" const getApps = () => { @@ -647,6 +659,67 @@ 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) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + //console.log("RESPONSE: ", responseJson) + //setFiles(responseJson) + }) + .catch(error => { + //alert.error(error.toString()) + }); + } + + const handleCreateFile = (filename, file) => { + const data = { + "filename": filename, + "org_id": selectedOrganization.id, + "workflow_id": "global", + } + + 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) { + handleFileUpload(responseJson.id, file) + } else { + alert.error("Failed to upload file ", filename) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + const getFiles = () => { fetch(globalUrl + "/api/v1/files", { method: 'GET', @@ -1778,12 +1851,72 @@ const Admin = (props) => {
: null + 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) + } + } + + getFiles() + } + + 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 = curTab === 5 ? + 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>

Files

Files from Workflows. Learn more
+ + 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) + }} /> + @@ -1814,6 +1947,9 @@ const Admin = (props) => { + {files === undefined || files === null ? null : files.map((file, index) => { var bgColor = "#27292d" @@ -1833,13 +1969,19 @@ const Admin = (props) => { /> - - - + {file.workflow_id === "global" ? + + - - + : + + + + + + + + } style={{minWidth: 100, maxWidth: 100, overflow: "hidden"}} /> { - { + { downloadFile(file) }}> - + style={{minWidth: 75, maxWidth: 75, overflow: "hidden"}} @@ -1878,11 +2020,31 @@ const Admin = (props) => { */} + { + const elementName = "copy_element_shuffle" + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + 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") + } + }}> + + + /> ) })}
+
: null const schedulesView = curTab === 4 ? @@ -2445,6 +2607,11 @@ const Admin = (props) => { {editUserModal} {editAuthenticationModal} {data} + ) }