more admin stuff
This commit is contained in:
@ -5,6 +5,12 @@
|
||||
* Copy & Paste: xterm.js liefert dies bei SSH bereits nativ ueber die
|
||||
* System-Zwischenablage (Markieren-zum-Kopieren / Strg+Umschalt+V) -- keine
|
||||
* serverseitige Sonderbehandlung noetig, im Gegensatz zu RDP (siehe rdp.js).
|
||||
*
|
||||
* Dateitransfer: eigenes Panel (statt frueherem prompt()-basiertem Upload)
|
||||
* mit Formularen fuer Upload UND Download in beide Richtungen. Der Download
|
||||
* laeuft ueber fetch() + Blob + synthetischen <a download>-Link, damit
|
||||
* Fehler inline im Panel angezeigt werden koennen statt die Seite zu
|
||||
* verlassen (siehe GET /ssh/{host_id}/files/download).
|
||||
*/
|
||||
(() => {
|
||||
"use strict";
|
||||
@ -73,17 +79,100 @@
|
||||
});
|
||||
document.addEventListener("fullscreenchange", sendResize);
|
||||
|
||||
const fileInput = document.getElementById("file-input");
|
||||
document.getElementById("upload-btn").addEventListener("click", () => fileInput.click());
|
||||
fileInput.addEventListener("change", async () => {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
const remotePath = prompt("Zielpfad auf dem Server:", `/tmp/${file.name}`);
|
||||
if (!remotePath) return;
|
||||
// --- Dateitransfer-Panel ------------------------------------------------
|
||||
|
||||
const ftOverlay = document.getElementById("ft-overlay");
|
||||
const ftStatus = document.getElementById("ft-status");
|
||||
const ftList = document.getElementById("ft-list");
|
||||
const transfers = [];
|
||||
|
||||
function openFt() {
|
||||
ftOverlay.classList.remove("hidden");
|
||||
ftStatus.textContent = "";
|
||||
}
|
||||
function closeFt() {
|
||||
ftOverlay.classList.add("hidden");
|
||||
}
|
||||
document.getElementById("filetransfer-btn").addEventListener("click", openFt);
|
||||
document.getElementById("ft-close-btn").addEventListener("click", closeFt);
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function recordTransfer(direction, filename, size, ok, message) {
|
||||
transfers.unshift({ direction, filename, size, ok, message });
|
||||
if (transfers.length > 20) transfers.length = 20;
|
||||
renderTransfers();
|
||||
}
|
||||
|
||||
function renderTransfers() {
|
||||
ftList.innerHTML = "";
|
||||
if (transfers.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "hint";
|
||||
empty.textContent = "Noch keine Transfers in dieser Sitzung.";
|
||||
ftList.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
for (const t of transfers) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "ft-list-row";
|
||||
|
||||
const dir = document.createElement("span");
|
||||
dir.className = "badge " + (t.ok ? "ok" : "danger");
|
||||
dir.textContent = t.direction === "upload" ? "↑ Upload" : "↓ Download";
|
||||
row.appendChild(dir);
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.className = "name";
|
||||
name.textContent = t.ok ? t.filename : `${t.filename} -- ${t.message}`;
|
||||
row.appendChild(name);
|
||||
|
||||
if (t.ok) {
|
||||
const size = document.createElement("span");
|
||||
size.className = "size";
|
||||
size.textContent = formatSize(t.size);
|
||||
row.appendChild(size);
|
||||
}
|
||||
|
||||
ftList.appendChild(row);
|
||||
}
|
||||
}
|
||||
renderTransfers();
|
||||
|
||||
// Upload
|
||||
const uploadRemotePathInput = document.getElementById("ft-upload-remote-path");
|
||||
const uploadFileInput = document.getElementById("ft-upload-file-input");
|
||||
const uploadFilenameLabel = document.getElementById("ft-upload-filename");
|
||||
|
||||
document.getElementById("ft-upload-pick-btn").addEventListener("click", () => uploadFileInput.click());
|
||||
uploadFileInput.addEventListener("change", () => {
|
||||
const file = uploadFileInput.files[0];
|
||||
uploadFilenameLabel.textContent = file ? file.name : "Keine Datei ausgewaehlt";
|
||||
if (file && !uploadRemotePathInput.value) {
|
||||
uploadRemotePathInput.value = `/tmp/${file.name}`;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("ft-upload-submit-btn").addEventListener("click", async () => {
|
||||
const file = uploadFileInput.files[0];
|
||||
const remotePath = uploadRemotePathInput.value.trim();
|
||||
ftStatus.textContent = "";
|
||||
if (!file) {
|
||||
ftStatus.textContent = "Bitte zuerst eine Datei auswaehlen.";
|
||||
return;
|
||||
}
|
||||
if (!remotePath) {
|
||||
ftStatus.textContent = "Bitte einen Zielpfad angeben.";
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
statusEl.textContent = `Lade ${file.name} hoch ...`;
|
||||
ftStatus.textContent = `Lade ${file.name} hoch ...`;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/ssh/${hostId}/files/upload?remote_path=${encodeURIComponent(remotePath)}`,
|
||||
@ -91,10 +180,58 @@
|
||||
);
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || "Upload fehlgeschlagen");
|
||||
statusEl.textContent = `Upload ok (AV: ${data.av_scan_result})`;
|
||||
ftStatus.textContent = `Upload abgeschlossen (AV: ${data.av_scan_result})`;
|
||||
recordTransfer("upload", file.name, data.size, true, "");
|
||||
uploadFileInput.value = "";
|
||||
uploadFilenameLabel.textContent = "Keine Datei ausgewaehlt";
|
||||
uploadRemotePathInput.value = "";
|
||||
} catch (err) {
|
||||
statusEl.textContent = "Upload-Fehler: " + err.message;
|
||||
ftStatus.textContent = "Upload-Fehler: " + err.message;
|
||||
recordTransfer("upload", file.name, 0, false, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Download
|
||||
const downloadRemotePathInput = document.getElementById("ft-download-remote-path");
|
||||
|
||||
document.getElementById("ft-download-submit-btn").addEventListener("click", async () => {
|
||||
const remotePath = downloadRemotePathInput.value.trim();
|
||||
ftStatus.textContent = "";
|
||||
if (!remotePath) {
|
||||
ftStatus.textContent = "Bitte einen Pfad angeben.";
|
||||
return;
|
||||
}
|
||||
const filename = remotePath.split("/").filter(Boolean).pop() || "download";
|
||||
ftStatus.textContent = `Lade ${filename} herunter ...`;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/ssh/${hostId}/files/download?remote_path=${encodeURIComponent(remotePath)}`,
|
||||
{ method: "GET", credentials: "same-origin" }
|
||||
);
|
||||
if (!res.ok) {
|
||||
let message = "Download fehlgeschlagen";
|
||||
try {
|
||||
const data = await res.json();
|
||||
message = data.detail || message;
|
||||
} catch (_) { /* Antwort war kein JSON */ }
|
||||
throw new Error(message);
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
ftStatus.textContent = `Download abgeschlossen: ${filename}`;
|
||||
recordTransfer("download", filename, blob.size, true, "");
|
||||
downloadRemotePathInput.value = "";
|
||||
} catch (err) {
|
||||
ftStatus.textContent = "Download-Fehler: " + err.message;
|
||||
recordTransfer("download", filename, 0, false, err.message);
|
||||
}
|
||||
fileInput.value = "";
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user