From 8246f3d16f67f7a2ea4c970f1f3d8a9d314518c4 Mon Sep 17 00:00:00 2001 From: Jeff Ohrstrom Date: Thu, 7 Dec 2023 09:50:57 -0500 Subject: [PATCH] add test for DOS encoded files (#3228) Add test for DOS encoded files and convert these files from DOS to Unix format. --- .github/workflows/tests.yml | 3 + .../app/javascript/files/clip_board.js | 362 +++++++++--------- .../app/javascript/files/sweet_alert.js | 182 ++++----- .../app/javascript/files/uppy_ops.js | 362 +++++++++--------- .../_ganglia_graphs_table.html.erb | 28 +- .../app/views/files/_copy_move_popup.html.erb | 44 +-- .../_default_label_error_messages.html.erb | 46 +-- .../app/views/files/_favorites.html.erb | 42 +- .../views/files/_file_action_menu.html.erb | 42 +- 9 files changed, 557 insertions(+), 554 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c154308fa7..1abeb1d63d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,9 @@ jobs: - name: Run ShellCheck run: bundle exec rake test:shellcheck + - name: Check file encodings + run: bundle exec rake test:unix + - name: Run Zeitwerk check run: | cd apps/dashboard diff --git a/apps/dashboard/app/javascript/files/clip_board.js b/apps/dashboard/app/javascript/files/clip_board.js index 45249d9047..d8e230ccab 100755 --- a/apps/dashboard/app/javascript/files/clip_board.js +++ b/apps/dashboard/app/javascript/files/clip_board.js @@ -1,181 +1,181 @@ -import ClipboardJS from 'clipboard' -import Handlebars from 'handlebars'; -import {CONTENTID} from './data_table.js'; -import {EVENTNAME as SWAL_EVENTNAME} from './sweet_alert.js'; -import {EVENTNAME as FILEOPS_EVENTNAME} from './file_ops.js'; -import { csrfToken } from '../config.js'; - -export {EVENTNAME}; - -const EVENTNAME = { - clearClipboard: 'clearClipboard', - updateClipboard: 'updateClipboard', - updateClipboardView: 'updateClipboardView', -} - -jQuery(function () { - - var clipBoard = new ClipBoard(); - - $("#copy-move-btn").on("click", function () { - let table = $(CONTENTID).DataTable(); - let selection = table.rows({ selected: true }).data(); - - const eventData = { - selection: selection - }; - - $(CONTENTID).trigger(EVENTNAME.updateClipboard, eventData); - - }); - - - $(CONTENTID).on('success', function (e) { - $(e.trigger).tooltip({ title: 'Copied path to clipboard!', trigger: 'manual', placement: 'bottom' }).tooltip('show'); - setTimeout(() => $(e.trigger).tooltip('hide'), 2000); - e.clearSelection(); - }); - - $(CONTENTID).on('error', function (e) { - e.clearSelection(); - }); - - $(CONTENTID).on(EVENTNAME.clearClipboard, function (e, options) { - clipBoard.clearClipboard(); - clipBoard.updateViewForClipboard(); - }); - - $(CONTENTID).on(EVENTNAME.updateClipboard, function (e, options) { - if (options.selection.length == 0) { - const eventData = { - 'title': 'Select a file, files, or directory to copy or move.', - 'message': 'You have selected none.', - }; - - $(CONTENTID).trigger(SWAL_EVENTNAME.showError, eventData); - $(CONTENTID).trigger(EVENTNAME.clearClipbaord, eventData); - - } else { - clipBoard.updateClipboardFromSelection(options.selection); - clipBoard.updateViewForClipboard(); - } - }); - - $(CONTENTID).on(EVENTNAME.updateClipboardView, function (e, options) { - clipBoard.updateViewForClipboard(); - }); - - -}); - -class ClipBoard { - _clipBoard = null; - - constructor() { - this._clipBoard = new ClipboardJS('#copy-path'); - this.updateViewForClipboard(); - } - - getClipBoard() { - return this._clipBoard; - } - - clearClipboard() { - localStorage.removeItem('filesClipboard'); - } - - updateClipboardFromSelection(selection) { - - if (selection.length == 0) { - this.clearClipboard(); - } else { - let clipboardData = { - from: history.state.currentDirectory, - from_fs: history.state.currentFilesystem, - files: selection.toArray().map((f) => { - return { directory: f.type == 'd', name: f.name }; - }) - }; - - localStorage.setItem('filesClipboard', JSON.stringify(clipboardData)); - } - } - - - updateViewForClipboard() { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || '{}'), - template_str = $('#clipboard-template').html(), - template = Handlebars.compile(template_str); - - $('#clipboard').html(template(clipboard)); - - $('#clipboard-clear').on("click", () => { - this.clearClipboard(); - this.updateViewForClipboard(); - }); - - $('#clipboard-move-to-dir').on("click", () => { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); - if (clipboard) { - clipboard.to = history.state.currentDirectory; - clipboard.to_fs = history.state.currentFilesystem; - - if (clipboard.from == clipboard.to) { - // No files are changed, so we just have to clear and update the clipboard - this.clearClipboard(); - this.updateViewForClipboard(); - } - else { - let files = {}; - clipboard.files.forEach((f) => { - files[`${clipboard.from}/${f.name}`] = `${history.state.currentDirectory}/${f.name}` - }); - - const eventData = { - 'files': files, - 'token': csrfToken(), - 'from_fs': clipboard.from_fs, - 'to_fs': clipboard.to_fs, - }; - - $(CONTENTID).trigger(FILEOPS_EVENTNAME.moveFile, eventData); - } - } - else { - console.error('files clipboard is empty'); - } - }); - - - $('#clipboard-copy-to-dir').on("click", () => { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); - - if (clipboard) { - clipboard.to = history.state.currentDirectory; - clipboard.to_fs = history.state.currentFilesystem; - - // files is a hashmap with keys of file current path and value as the corresponding files desired path - let files = {}; - - clipboard.files.forEach((f) => { - files[`${clipboard.from}/${f.name}`] = `${clipboard.to}/${f.name}`; - }); - - const eventData = { - 'files': files, - 'token': csrfToken(), - 'from_fs': clipboard.from_fs, - 'to_fs': clipboard.to_fs, - }; - - $(CONTENTID).trigger(FILEOPS_EVENTNAME.copyFile, eventData); - } - else { - console.error('files clipboard is empty'); - } - }); - - } - - -} +import ClipboardJS from 'clipboard' +import Handlebars from 'handlebars'; +import {CONTENTID} from './data_table.js'; +import {EVENTNAME as SWAL_EVENTNAME} from './sweet_alert.js'; +import {EVENTNAME as FILEOPS_EVENTNAME} from './file_ops.js'; +import { csrfToken } from '../config.js'; + +export {EVENTNAME}; + +const EVENTNAME = { + clearClipboard: 'clearClipboard', + updateClipboard: 'updateClipboard', + updateClipboardView: 'updateClipboardView', +} + +jQuery(function () { + + var clipBoard = new ClipBoard(); + + $("#copy-move-btn").on("click", function () { + let table = $(CONTENTID).DataTable(); + let selection = table.rows({ selected: true }).data(); + + const eventData = { + selection: selection + }; + + $(CONTENTID).trigger(EVENTNAME.updateClipboard, eventData); + + }); + + + $(CONTENTID).on('success', function (e) { + $(e.trigger).tooltip({ title: 'Copied path to clipboard!', trigger: 'manual', placement: 'bottom' }).tooltip('show'); + setTimeout(() => $(e.trigger).tooltip('hide'), 2000); + e.clearSelection(); + }); + + $(CONTENTID).on('error', function (e) { + e.clearSelection(); + }); + + $(CONTENTID).on(EVENTNAME.clearClipboard, function (e, options) { + clipBoard.clearClipboard(); + clipBoard.updateViewForClipboard(); + }); + + $(CONTENTID).on(EVENTNAME.updateClipboard, function (e, options) { + if (options.selection.length == 0) { + const eventData = { + 'title': 'Select a file, files, or directory to copy or move.', + 'message': 'You have selected none.', + }; + + $(CONTENTID).trigger(SWAL_EVENTNAME.showError, eventData); + $(CONTENTID).trigger(EVENTNAME.clearClipbaord, eventData); + + } else { + clipBoard.updateClipboardFromSelection(options.selection); + clipBoard.updateViewForClipboard(); + } + }); + + $(CONTENTID).on(EVENTNAME.updateClipboardView, function (e, options) { + clipBoard.updateViewForClipboard(); + }); + + +}); + +class ClipBoard { + _clipBoard = null; + + constructor() { + this._clipBoard = new ClipboardJS('#copy-path'); + this.updateViewForClipboard(); + } + + getClipBoard() { + return this._clipBoard; + } + + clearClipboard() { + localStorage.removeItem('filesClipboard'); + } + + updateClipboardFromSelection(selection) { + + if (selection.length == 0) { + this.clearClipboard(); + } else { + let clipboardData = { + from: history.state.currentDirectory, + from_fs: history.state.currentFilesystem, + files: selection.toArray().map((f) => { + return { directory: f.type == 'd', name: f.name }; + }) + }; + + localStorage.setItem('filesClipboard', JSON.stringify(clipboardData)); + } + } + + + updateViewForClipboard() { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || '{}'), + template_str = $('#clipboard-template').html(), + template = Handlebars.compile(template_str); + + $('#clipboard').html(template(clipboard)); + + $('#clipboard-clear').on("click", () => { + this.clearClipboard(); + this.updateViewForClipboard(); + }); + + $('#clipboard-move-to-dir').on("click", () => { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); + if (clipboard) { + clipboard.to = history.state.currentDirectory; + clipboard.to_fs = history.state.currentFilesystem; + + if (clipboard.from == clipboard.to) { + // No files are changed, so we just have to clear and update the clipboard + this.clearClipboard(); + this.updateViewForClipboard(); + } + else { + let files = {}; + clipboard.files.forEach((f) => { + files[`${clipboard.from}/${f.name}`] = `${history.state.currentDirectory}/${f.name}` + }); + + const eventData = { + 'files': files, + 'token': csrfToken(), + 'from_fs': clipboard.from_fs, + 'to_fs': clipboard.to_fs, + }; + + $(CONTENTID).trigger(FILEOPS_EVENTNAME.moveFile, eventData); + } + } + else { + console.error('files clipboard is empty'); + } + }); + + + $('#clipboard-copy-to-dir').on("click", () => { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); + + if (clipboard) { + clipboard.to = history.state.currentDirectory; + clipboard.to_fs = history.state.currentFilesystem; + + // files is a hashmap with keys of file current path and value as the corresponding files desired path + let files = {}; + + clipboard.files.forEach((f) => { + files[`${clipboard.from}/${f.name}`] = `${clipboard.to}/${f.name}`; + }); + + const eventData = { + 'files': files, + 'token': csrfToken(), + 'from_fs': clipboard.from_fs, + 'to_fs': clipboard.to_fs, + }; + + $(CONTENTID).trigger(FILEOPS_EVENTNAME.copyFile, eventData); + } + else { + console.error('files clipboard is empty'); + } + }); + + } + + +} diff --git a/apps/dashboard/app/javascript/files/sweet_alert.js b/apps/dashboard/app/javascript/files/sweet_alert.js index 04aebb5144..b739e1a4d5 100644 --- a/apps/dashboard/app/javascript/files/sweet_alert.js +++ b/apps/dashboard/app/javascript/files/sweet_alert.js @@ -1,91 +1,91 @@ -import Swal from 'sweetalert2' -import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; - -export {EVENTNAME}; - -const EVENTNAME = { - showError: 'showError', - showInput: 'showInput', - showLoading: 'showLoading', - showPrompt: 'showPrompt', - closeSwal: 'closeSwal', -} - -let sweetAlert = null; - -jQuery(function() { - sweetAlert = new SweetAlert(); - $(CONTENTID).on(EVENTNAME.showError, function(e,options) { - sweetAlert.alertError(options.title, options.message); - }); - - $(CONTENTID).on(EVENTNAME.showPrompt, function(e,options) { - sweetAlert.alertError(options.title, options.message); - }); - - $(CONTENTID).on(EVENTNAME.showInput, function(e,options) { - sweetAlert.input(options); - }); - - $(CONTENTID).on(EVENTNAME.showLoading, function(e,options) { - sweetAlert.loading(options.message); - }); - - $(CONTENTID).on(EVENTNAME.closeSwal, function() { - sweetAlert.close(); - }); - -}); - -class SweetAlert { - - constructor() { - this.setMixin(); - } - - input(options) { - Swal.fire(options.inputOptions) - .then (function(result){ - if(result.isConfirmed) { - const eventData = { - result: result, - files: options.files ? options.files : null - }; - - $(CONTENTID).trigger(options.action, eventData); - } else { - $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable); - } - }); - } - - setMixin() { - Swal.mixIn = ({ - showClass: { - popup: 'swal2-noanimation', - backdrop: 'swal2-noanimation' - }, - hideClass: { - popup: '', - backdrop: '' - } - }); - } - - alertError(error_title, error_message) { - Swal.fire(error_title, error_message, 'error'); - } - - async loading(title) { - Swal.fire({ - title: title, - allowOutsideClick: false, - showConfirmButton: false, - willOpen: () => { Swal.showLoading() } - }); - } - - close() { - Swal.close(); - } -} +import Swal from 'sweetalert2' +import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; + +export {EVENTNAME}; + +const EVENTNAME = { + showError: 'showError', + showInput: 'showInput', + showLoading: 'showLoading', + showPrompt: 'showPrompt', + closeSwal: 'closeSwal', +} + +let sweetAlert = null; + +jQuery(function() { + sweetAlert = new SweetAlert(); + $(CONTENTID).on(EVENTNAME.showError, function(e,options) { + sweetAlert.alertError(options.title, options.message); + }); + + $(CONTENTID).on(EVENTNAME.showPrompt, function(e,options) { + sweetAlert.alertError(options.title, options.message); + }); + + $(CONTENTID).on(EVENTNAME.showInput, function(e,options) { + sweetAlert.input(options); + }); + + $(CONTENTID).on(EVENTNAME.showLoading, function(e,options) { + sweetAlert.loading(options.message); + }); + + $(CONTENTID).on(EVENTNAME.closeSwal, function() { + sweetAlert.close(); + }); + +}); + +class SweetAlert { + + constructor() { + this.setMixin(); + } + + input(options) { + Swal.fire(options.inputOptions) + .then (function(result){ + if(result.isConfirmed) { + const eventData = { + result: result, + files: options.files ? options.files : null + }; + + $(CONTENTID).trigger(options.action, eventData); + } else { + $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable); + } + }); + } + + setMixin() { + Swal.mixIn = ({ + showClass: { + popup: 'swal2-noanimation', + backdrop: 'swal2-noanimation' + }, + hideClass: { + popup: '', + backdrop: '' + } + }); + } + + alertError(error_title, error_message) { + Swal.fire(error_title, error_message, 'error'); + } + + async loading(title) { + Swal.fire({ + title: title, + allowOutsideClick: false, + showConfirmButton: false, + willOpen: () => { Swal.showLoading() } + }); + } + + close() { + Swal.close(); + } +} diff --git a/apps/dashboard/app/javascript/files/uppy_ops.js b/apps/dashboard/app/javascript/files/uppy_ops.js index c9ec671c7c..bd5c1053a2 100755 --- a/apps/dashboard/app/javascript/files/uppy_ops.js +++ b/apps/dashboard/app/javascript/files/uppy_ops.js @@ -1,181 +1,181 @@ -import { Uppy, BasePlugin } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import XHRUpload from '@uppy/xhr-upload' -import _ from 'lodash'; -import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; -import { maxFileSize, csrfToken, uppyLocale } from '../config.js'; - -let uppy = null; - -jQuery(function() { - - class EmptyDirCreator extends BasePlugin { - constructor (uppy, opts){ - super(uppy, opts) - this.id = this.opts.id || 'EmptyDirUploaderCatcher'; - this.type = 'acquirer'; - - this.empty_dirs = []; - this.last_entries = []; - - this.handleRootDrop = this.handleRootDrop.bind(this); - this.createEmptyDirs = this.createEmptyDirs.bind(this); - - this.uppy = uppy; - } - - - - handleRootDrop (e) { - // from https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/getDroppedFiles/index.js - if (e.dataTransfer.items && e.dataTransfer.items[0] && 'webkitGetAsEntry' in e.dataTransfer.items[0]) { - // toArray https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/toArray.js#L4 - let items = Array.prototype.slice.call(e.dataTransfer.items || [], 0); - let entries = items.map(i => i.webkitGetAsEntry()).filter(i => i); - - return Promise.all(entries.map(i => getEmptyDirs(i))).then((dirs) => { - this.empty_dirs = this.empty_dirs.concat(_.flattenDeep(dirs)); - - }); - } - //else we don't have access to directory information - } - - createEmptyDirs (ids) { - if(! this.uppy.getState().error){ // avoid creating empty dirs if error occurred during upload - - //TODO: error checking and reporting - return Promise.all(this.empty_dirs.map((d) => { - // "fullPath" should actually be the path relative to the current directory - let filename = _.trimStart(d.fullPath, '/'); - - return fetch(`${history.state.currentDirectoryUrl}/${encodeURI(filename)}?dir=true`, {method: 'put', headers: { 'X-CSRF-Token': csrfToken() }}) - //TODO: parse json response verify if there was an error creating directory and handle error - - })).then(() => this.empty_dirs = []); - } - } - - install () { - this.uppy.addPostProcessor(this.createEmptyDirs); - } - - uninstall () { - this.uppy.removePostProcessor(this.createEmptyDirs); - } - } - - uppy = new Uppy({ - restrictions: { - maxFileSize: maxFileSize(), - }, - onBeforeUpload: updateEndpoint, - locale: uppyLocale(), - }); - - uppy.use(EmptyDirCreator); - uppy.use(Dashboard, { - trigger: '#upload-btn', - fileManagerSelectionType: 'both', - disableThumbnailGenerator: true, - showLinkToFileUploadResult: false, - closeModalOnClickOutside: true, - closeAfterFinish: true, - allowMultipleUploads: false, - onRequestCloseModal: () => closeAndResetUppyModal(uppy), - note: 'Empty directories will be included in the upload only when a directory upload is initiated via drag and drop. This is because the File and Directory Entries API is available only on a drop event, not during an input change event.' - }); - uppy.use(XHRUpload, { - withCredentials: true, - fieldName: 'file', - limit: 1, - headers: { 'X-CSRF-Token': csrfToken() }, - timeout: 128 * 1000, - }); - - uppy.on('file-added', (file) => { - uppy.setFileMeta(file.id, { parent: history.state.currentDirectory }); - if(file.meta.relativePath == null && file.data.webkitRelativePath){ - uppy.setFileMeta(file.id, { relativePath: file.data.webkitRelativePath }); - } - }); - - uppy.on('complete', (result) => { - if(result.successful.length > 0){ - reloadTable(); - } - }); - - // https://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file - window.addEventListener("dragover",function(e){ - e = e || event; - e.preventDefault(); - },false); - - window.addEventListener("drop",function(e){ - e = e || event; - e.preventDefault(); - },false); - - $('#directory-contents').on('drop', function(e){ - this.classList.remove('dragover'); - // Prevent default behavior (Prevent file from being opened) - - // pass drop event to uppy dashboard - uppy.getPlugin('Dashboard').openModal().then(() => uppy.getPlugin('Dashboard').handleDrop(e.originalEvent)) - }); - - $('#directory-contents').on('dragover', function(e){ - this.classList.add('dragover'); - - // Prevent default behavior (Prevent file from being opened) - e.preventDefault(); - - // specifies what feedback will be shown to the user by setting the dropEffect attribute of the DataTransfer associated with the event - // too bad we can't show an indicator (no dragstart/end when dragging from OS to browser) - e.originalEvent.dataTransfer.dropEffect = 'copy'; - }); - - $('#directory-contents').on('dragleave', function(e){ - this.classList.remove('dragover'); - }); - -}); - -function closeAndResetUppyModal(uppy){ - uppy.getPlugin('Dashboard').closeModal(); - uppy.reset(); -} - -function getEmptyDirs(entry){ - return new Promise((resolve) => { - if(entry.isFile){ - resolve([]); - } - else{ - // getFilesAndDirectoriesFromDirectory has no return value, so turn this into a promise - getFilesAndDirectoriesFromDirectory(entry.createReader(), [], function(error){ console.error(error)}, { - onSuccess: (entries) => { - if(entries.length == 0){ - // this is an empty directory - resolve([entry]); - } - else{ - Promise.all(entries.map(e => getEmptyDirs(e))).then((dirs) => resolve(_.flattenDeep(dirs))); - } - } - }) - } - }); -} - -function updateEndpoint() { - uppy.getPlugin('XHRUpload').setOptions({ - endpoint: history.state.currentFilesUploadPath, - }); -} - -function reloadTable() { - $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable,{}); -} - +import { Uppy, BasePlugin } from '@uppy/core' +import Dashboard from '@uppy/dashboard' +import XHRUpload from '@uppy/xhr-upload' +import _ from 'lodash'; +import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; +import { maxFileSize, csrfToken, uppyLocale } from '../config.js'; + +let uppy = null; + +jQuery(function() { + + class EmptyDirCreator extends BasePlugin { + constructor (uppy, opts){ + super(uppy, opts) + this.id = this.opts.id || 'EmptyDirUploaderCatcher'; + this.type = 'acquirer'; + + this.empty_dirs = []; + this.last_entries = []; + + this.handleRootDrop = this.handleRootDrop.bind(this); + this.createEmptyDirs = this.createEmptyDirs.bind(this); + + this.uppy = uppy; + } + + + + handleRootDrop (e) { + // from https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/getDroppedFiles/index.js + if (e.dataTransfer.items && e.dataTransfer.items[0] && 'webkitGetAsEntry' in e.dataTransfer.items[0]) { + // toArray https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/toArray.js#L4 + let items = Array.prototype.slice.call(e.dataTransfer.items || [], 0); + let entries = items.map(i => i.webkitGetAsEntry()).filter(i => i); + + return Promise.all(entries.map(i => getEmptyDirs(i))).then((dirs) => { + this.empty_dirs = this.empty_dirs.concat(_.flattenDeep(dirs)); + + }); + } + //else we don't have access to directory information + } + + createEmptyDirs (ids) { + if(! this.uppy.getState().error){ // avoid creating empty dirs if error occurred during upload + + //TODO: error checking and reporting + return Promise.all(this.empty_dirs.map((d) => { + // "fullPath" should actually be the path relative to the current directory + let filename = _.trimStart(d.fullPath, '/'); + + return fetch(`${history.state.currentDirectoryUrl}/${encodeURI(filename)}?dir=true`, {method: 'put', headers: { 'X-CSRF-Token': csrfToken() }}) + //TODO: parse json response verify if there was an error creating directory and handle error + + })).then(() => this.empty_dirs = []); + } + } + + install () { + this.uppy.addPostProcessor(this.createEmptyDirs); + } + + uninstall () { + this.uppy.removePostProcessor(this.createEmptyDirs); + } + } + + uppy = new Uppy({ + restrictions: { + maxFileSize: maxFileSize(), + }, + onBeforeUpload: updateEndpoint, + locale: uppyLocale(), + }); + + uppy.use(EmptyDirCreator); + uppy.use(Dashboard, { + trigger: '#upload-btn', + fileManagerSelectionType: 'both', + disableThumbnailGenerator: true, + showLinkToFileUploadResult: false, + closeModalOnClickOutside: true, + closeAfterFinish: true, + allowMultipleUploads: false, + onRequestCloseModal: () => closeAndResetUppyModal(uppy), + note: 'Empty directories will be included in the upload only when a directory upload is initiated via drag and drop. This is because the File and Directory Entries API is available only on a drop event, not during an input change event.' + }); + uppy.use(XHRUpload, { + withCredentials: true, + fieldName: 'file', + limit: 1, + headers: { 'X-CSRF-Token': csrfToken() }, + timeout: 128 * 1000, + }); + + uppy.on('file-added', (file) => { + uppy.setFileMeta(file.id, { parent: history.state.currentDirectory }); + if(file.meta.relativePath == null && file.data.webkitRelativePath){ + uppy.setFileMeta(file.id, { relativePath: file.data.webkitRelativePath }); + } + }); + + uppy.on('complete', (result) => { + if(result.successful.length > 0){ + reloadTable(); + } + }); + + // https://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file + window.addEventListener("dragover",function(e){ + e = e || event; + e.preventDefault(); + },false); + + window.addEventListener("drop",function(e){ + e = e || event; + e.preventDefault(); + },false); + + $('#directory-contents').on('drop', function(e){ + this.classList.remove('dragover'); + // Prevent default behavior (Prevent file from being opened) + + // pass drop event to uppy dashboard + uppy.getPlugin('Dashboard').openModal().then(() => uppy.getPlugin('Dashboard').handleDrop(e.originalEvent)) + }); + + $('#directory-contents').on('dragover', function(e){ + this.classList.add('dragover'); + + // Prevent default behavior (Prevent file from being opened) + e.preventDefault(); + + // specifies what feedback will be shown to the user by setting the dropEffect attribute of the DataTransfer associated with the event + // too bad we can't show an indicator (no dragstart/end when dragging from OS to browser) + e.originalEvent.dataTransfer.dropEffect = 'copy'; + }); + + $('#directory-contents').on('dragleave', function(e){ + this.classList.remove('dragover'); + }); + +}); + +function closeAndResetUppyModal(uppy){ + uppy.getPlugin('Dashboard').closeModal(); + uppy.reset(); +} + +function getEmptyDirs(entry){ + return new Promise((resolve) => { + if(entry.isFile){ + resolve([]); + } + else{ + // getFilesAndDirectoriesFromDirectory has no return value, so turn this into a promise + getFilesAndDirectoriesFromDirectory(entry.createReader(), [], function(error){ console.error(error)}, { + onSuccess: (entries) => { + if(entries.length == 0){ + // this is an empty directory + resolve([entry]); + } + else{ + Promise.all(entries.map(e => getEmptyDirs(e))).then((dirs) => resolve(_.flattenDeep(dirs))); + } + } + }) + } + }); +} + +function updateEndpoint() { + uppy.getPlugin('XHRUpload').setOptions({ + endpoint: history.state.currentFilesUploadPath, + }); +} + +function reloadTable() { + $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable,{}); +} + diff --git a/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb b/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb index 9dfa270975..567eb6a741 100644 --- a/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb +++ b/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb @@ -1,15 +1,15 @@ - -
- -
-
-
- - - <% if !d.nodes.nil? %> - <% d.nodes.each do |node| %> - <%= render partial: 'active_jobs/job_details_node_view.html.erb', :locals => {:data => d, :node => node } %> - <% end %> - <% end %> - + +
+ +
+
+
+ + + <% if !d.nodes.nil? %> + <% d.nodes.each do |node| %> + <%= render partial: 'active_jobs/job_details_node_view.html.erb', :locals => {:data => d, :node => node } %> + <% end %> + <% end %> +
\ No newline at end of file diff --git a/apps/dashboard/app/views/files/_copy_move_popup.html.erb b/apps/dashboard/app/views/files/_copy_move_popup.html.erb index 1c8efb6d9e..e1038a0da2 100755 --- a/apps/dashboard/app/views/files/_copy_move_popup.html.erb +++ b/apps/dashboard/app/views/files/_copy_move_popup.html.erb @@ -1,22 +1,22 @@ - + diff --git a/apps/dashboard/app/views/files/_default_label_error_messages.html.erb b/apps/dashboard/app/views/files/_default_label_error_messages.html.erb index 2dd50bf95b..b7a9a34fb6 100755 --- a/apps/dashboard/app/views/files/_default_label_error_messages.html.erb +++ b/apps/dashboard/app/views/files/_default_label_error_messages.html.erb @@ -1,24 +1,24 @@ - \ No newline at end of file diff --git a/apps/dashboard/app/views/files/_favorites.html.erb b/apps/dashboard/app/views/files/_favorites.html.erb index 4d819fe422..528e74101f 100755 --- a/apps/dashboard/app/views/files/_favorites.html.erb +++ b/apps/dashboard/app/views/files/_favorites.html.erb @@ -1,21 +1,21 @@ -
-
- - +
+
+ + diff --git a/apps/dashboard/app/views/files/_file_action_menu.html.erb b/apps/dashboard/app/views/files/_file_action_menu.html.erb index c8c558061f..f687118377 100755 --- a/apps/dashboard/app/views/files/_file_action_menu.html.erb +++ b/apps/dashboard/app/views/files/_file_action_menu.html.erb @@ -1,21 +1,21 @@ - +