mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-29 16:45:13 +02:00
implement streaming zip for downloading dirs and multiple files
This commit is contained in:
parent
fdf30b1fb6
commit
c42cff88eb
@ -121,6 +121,13 @@ If not, see <https://www.gnu.org/licenses/>.
|
||||
<DocumentDownloadIcon class="size-icon icon-default" />
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button
|
||||
class="context-menu-button"
|
||||
@click="$emit('browserAction', 'download', selection[0], true)"
|
||||
>
|
||||
<FolderDownloadIcon class="size-icon icon-default" />
|
||||
<span>Zip and download</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="selection[0]?.resolvedType === 'd'"
|
||||
|
25
navigator/src/functions/streamProcDownload.js
Normal file
25
navigator/src/functions/streamProcDownload.js
Normal file
@ -0,0 +1,25 @@
|
||||
export function streamProcDownload(argv, filename, opts = {}) {
|
||||
const query = window.btoa(JSON.stringify({
|
||||
payload: 'stream',
|
||||
binary: 'raw',
|
||||
spawn: [...argv],
|
||||
external: {
|
||||
'content-disposition': 'attachment; filename="' + encodeURIComponent(filename) + '"',
|
||||
'content-type': 'application/x-xz, application/octet-stream'
|
||||
},
|
||||
...opts,
|
||||
}));
|
||||
const prefix = (new URL(cockpit.transport.uri('channel/' + cockpit.transport.csrf_token))).pathname;
|
||||
const a = document.createElement("a");
|
||||
a.href = prefix + "?" + query;
|
||||
a.style.display = "none";
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
const event = new MouseEvent('click', {
|
||||
'view': window,
|
||||
'bubbles': false,
|
||||
'cancelable': true
|
||||
});
|
||||
a.dispatchEvent(event);
|
||||
document.body.removeChild(a);
|
||||
}
|
@ -157,6 +157,15 @@
|
||||
<span>Download</span>
|
||||
<DownloadIcon class="size-icon text-default" />
|
||||
</button>
|
||||
<button
|
||||
v-if="openFilePromptModal.entry?.resolvedType === 'f'"
|
||||
type="button"
|
||||
class="btn btn-primary flex items-center gap-1"
|
||||
@click="() => openFilePromptModal.action('download', true)"
|
||||
>
|
||||
<span>Zip and download</span>
|
||||
<FolderDownloadIcon class="size-icon text-default" />
|
||||
</button>
|
||||
</template>
|
||||
</ModalPopup>
|
||||
<ModalPopup
|
||||
@ -256,6 +265,7 @@ import {
|
||||
KeyIcon,
|
||||
PencilAltIcon,
|
||||
DownloadIcon,
|
||||
FolderDownloadIcon,
|
||||
} from '@heroicons/vue/solid';
|
||||
import IconToggle from '../components/IconToggle.vue';
|
||||
import ModalPopup from '../components/ModalPopup.vue';
|
||||
@ -264,6 +274,8 @@ import FilePermissions from '../components/FilePermissions.vue';
|
||||
import FileNameEditor from '../components/FileNameEditor.vue';
|
||||
import ContextMenu from '../components/ContextMenu.vue';
|
||||
import ModalPrompt from '../components/ModalPrompt.vue';
|
||||
import { commonPath } from '../functions/commonPath';
|
||||
import { streamProcDownload } from '../functions/streamProcDownload';
|
||||
|
||||
const encodePartial = (string) =>
|
||||
encodeURIComponent(string)
|
||||
@ -325,8 +337,8 @@ export default {
|
||||
openFilePromptModal.show = false;
|
||||
openFilePromptModal.resetTimeoutHandle = setTimeout(() => openFilePromptModal.resetTimeoutHandle = openFilePromptModal.entry = null, 500);
|
||||
},
|
||||
action: (action) => {
|
||||
handleAction(action, openFilePromptModal.entry);
|
||||
action: (action, ...args) => {
|
||||
handleAction(action, openFilePromptModal.entry, ...args);
|
||||
openFilePromptModal.close();
|
||||
}
|
||||
});
|
||||
@ -437,12 +449,34 @@ export default {
|
||||
router.push(`/edit/${newHost}${newPath}`);
|
||||
}
|
||||
|
||||
const download = (selection) => {
|
||||
const items = [].concat(selection); // forces to be array
|
||||
if (items.length === 1 && items[0].resolvedType === 'f') {
|
||||
let { path, name, host } = items[0];
|
||||
fileDownload(path, name, host);
|
||||
const download = (selection, zip = false) => {
|
||||
const getZipName = () => {
|
||||
const now = new Date();
|
||||
return `cockpit-navigator-dowload_${now.getFullYear()}-${now.getMonth()+1}-${now.getDay()}_${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}.zip`;
|
||||
}
|
||||
let items = [].concat(selection); // forces to be array
|
||||
console.log(items);
|
||||
if (items.length > 1) {
|
||||
const dirs = items.filter(item => item.type === 'd').map(item => item.path);
|
||||
if (dirs.length) {
|
||||
// remove items beyond any given dirs because we will tar dirs recursively
|
||||
const containedRegex = new RegExp(`^(${dirs.join('|')}).+`);
|
||||
items = items.filter(item => !containedRegex.test(item.path));
|
||||
}
|
||||
const { common, relativePaths } = commonPath(items.map(item => item.path));
|
||||
console.log(common, relativePaths);
|
||||
streamProcDownload(['zip', '-rq', '-', ...relativePaths], getZipName(), { superuser: 'try', directory: common });
|
||||
} else {
|
||||
let { path, name, host, resolvedType } = items[0];
|
||||
if (resolvedType === 'd') {
|
||||
streamProcDownload(['zip', '-rq', '-', '.'], `${name}.zip`, { superuser: 'try', directory: path });
|
||||
} else if (zip) {
|
||||
streamProcDownload(['zip', '-q', '-', name], `${name}.zip`, { superuser: 'try', directory: path.split('/').slice(0, -1).join('/') || '/' });
|
||||
} else {
|
||||
fileDownload(path, name, host);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: mutlifile & directory downloads
|
||||
}
|
||||
|
||||
@ -650,6 +684,7 @@ export default {
|
||||
KeyIcon,
|
||||
PencilAltIcon,
|
||||
DownloadIcon,
|
||||
FolderDownloadIcon,
|
||||
ModalPrompt,
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user