mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-30 00:55:30 +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" />
|
<DocumentDownloadIcon class="size-icon icon-default" />
|
||||||
<span>Download</span>
|
<span>Download</span>
|
||||||
</button>
|
</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>
|
||||||
<div
|
<div
|
||||||
v-else-if="selection[0]?.resolvedType === 'd'"
|
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>
|
<span>Download</span>
|
||||||
<DownloadIcon class="size-icon text-default" />
|
<DownloadIcon class="size-icon text-default" />
|
||||||
</button>
|
</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>
|
</template>
|
||||||
</ModalPopup>
|
</ModalPopup>
|
||||||
<ModalPopup
|
<ModalPopup
|
||||||
@ -256,6 +265,7 @@ import {
|
|||||||
KeyIcon,
|
KeyIcon,
|
||||||
PencilAltIcon,
|
PencilAltIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
FolderDownloadIcon,
|
||||||
} from '@heroicons/vue/solid';
|
} from '@heroicons/vue/solid';
|
||||||
import IconToggle from '../components/IconToggle.vue';
|
import IconToggle from '../components/IconToggle.vue';
|
||||||
import ModalPopup from '../components/ModalPopup.vue';
|
import ModalPopup from '../components/ModalPopup.vue';
|
||||||
@ -264,6 +274,8 @@ import FilePermissions from '../components/FilePermissions.vue';
|
|||||||
import FileNameEditor from '../components/FileNameEditor.vue';
|
import FileNameEditor from '../components/FileNameEditor.vue';
|
||||||
import ContextMenu from '../components/ContextMenu.vue';
|
import ContextMenu from '../components/ContextMenu.vue';
|
||||||
import ModalPrompt from '../components/ModalPrompt.vue';
|
import ModalPrompt from '../components/ModalPrompt.vue';
|
||||||
|
import { commonPath } from '../functions/commonPath';
|
||||||
|
import { streamProcDownload } from '../functions/streamProcDownload';
|
||||||
|
|
||||||
const encodePartial = (string) =>
|
const encodePartial = (string) =>
|
||||||
encodeURIComponent(string)
|
encodeURIComponent(string)
|
||||||
@ -325,8 +337,8 @@ export default {
|
|||||||
openFilePromptModal.show = false;
|
openFilePromptModal.show = false;
|
||||||
openFilePromptModal.resetTimeoutHandle = setTimeout(() => openFilePromptModal.resetTimeoutHandle = openFilePromptModal.entry = null, 500);
|
openFilePromptModal.resetTimeoutHandle = setTimeout(() => openFilePromptModal.resetTimeoutHandle = openFilePromptModal.entry = null, 500);
|
||||||
},
|
},
|
||||||
action: (action) => {
|
action: (action, ...args) => {
|
||||||
handleAction(action, openFilePromptModal.entry);
|
handleAction(action, openFilePromptModal.entry, ...args);
|
||||||
openFilePromptModal.close();
|
openFilePromptModal.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -437,12 +449,34 @@ export default {
|
|||||||
router.push(`/edit/${newHost}${newPath}`);
|
router.push(`/edit/${newHost}${newPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = (selection) => {
|
const download = (selection, zip = false) => {
|
||||||
const items = [].concat(selection); // forces to be array
|
const getZipName = () => {
|
||||||
if (items.length === 1 && items[0].resolvedType === 'f') {
|
const now = new Date();
|
||||||
let { path, name, host } = items[0];
|
return `cockpit-navigator-dowload_${now.getFullYear()}-${now.getMonth()+1}-${now.getDay()}_${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}.zip`;
|
||||||
fileDownload(path, name, host);
|
|
||||||
}
|
}
|
||||||
|
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
|
// TODO: mutlifile & directory downloads
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,6 +684,7 @@ export default {
|
|||||||
KeyIcon,
|
KeyIcon,
|
||||||
PencilAltIcon,
|
PencilAltIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
FolderDownloadIcon,
|
||||||
ModalPrompt,
|
ModalPrompt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user