VNC-8 Adding upload and download buttons
This commit is contained in:
parent
887662b0ad
commit
cba6e69af6
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="25"
|
||||||
|
height="25"
|
||||||
|
viewBox="0 0 25 25"
|
||||||
|
version="1.1">
|
||||||
|
<g transform="translate(0.5,0.5)">
|
||||||
|
<!-- Cloud/folder base -->
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 21,15 21,19 C 21,20.104569 20.104569,21 19,21 L 5,21 C 3.8954305,21 3,20.104569 3,19 L 3,15" />
|
||||||
|
<!-- Arrow shaft -->
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 12,3 L 12,15" />
|
||||||
|
<!-- Arrow head (pointing down) -->
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 7,10 L 12,15 L 17,10" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
313
app/ui.js
313
app/ui.js
|
|
@ -147,6 +147,8 @@ const UI = {
|
||||||
UI.addMachineHandlers();
|
UI.addMachineHandlers();
|
||||||
UI.addConnectionControlHandlers();
|
UI.addConnectionControlHandlers();
|
||||||
UI.addClipboardHandlers();
|
UI.addClipboardHandlers();
|
||||||
|
UI.addUploadHandlers();
|
||||||
|
UI.addDownloadHandlers();
|
||||||
UI.addSettingsHandlers();
|
UI.addSettingsHandlers();
|
||||||
UI.addDisplaysHandler();
|
UI.addDisplaysHandler();
|
||||||
// UI.addMultiMonitorAddHandler();
|
// UI.addMultiMonitorAddHandler();
|
||||||
|
|
@ -532,6 +534,20 @@ const UI = {
|
||||||
.addEventListener('click', UI.clipboardClear);
|
.addEventListener('click', UI.clipboardClear);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addUploadHandlers() {
|
||||||
|
UI.addClickHandle('noVNC_upload_button', UI.toggleUploadPanel);
|
||||||
|
|
||||||
|
document.getElementById("noVNC_file_input")
|
||||||
|
.addEventListener('change', UI.handleFileSelect);
|
||||||
|
},
|
||||||
|
|
||||||
|
addDownloadHandlers() {
|
||||||
|
UI.addClickHandle('noVNC_download_button', UI.toggleDownloadPanel);
|
||||||
|
|
||||||
|
document.getElementById("noVNC_refresh_downloads_button")
|
||||||
|
.addEventListener('click', UI.refreshDownloadsList);
|
||||||
|
},
|
||||||
|
|
||||||
// Add a call to save settings when the element changes,
|
// Add a call to save settings when the element changes,
|
||||||
// unless the optional parameter changeFunc is used instead.
|
// unless the optional parameter changeFunc is used instead.
|
||||||
addSettingChangeHandler(name, changeFunc) {
|
addSettingChangeHandler(name, changeFunc) {
|
||||||
|
|
@ -1216,6 +1232,8 @@ const UI = {
|
||||||
UI.closeSettingsPanel();
|
UI.closeSettingsPanel();
|
||||||
UI.closePowerPanel();
|
UI.closePowerPanel();
|
||||||
UI.closeClipboardPanel();
|
UI.closeClipboardPanel();
|
||||||
|
UI.closeUploadPanel();
|
||||||
|
UI.closeDownloadPanel();
|
||||||
UI.closeExtraKeys();
|
UI.closeExtraKeys();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1368,6 +1386,301 @@ const UI = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openUploadPanel() {
|
||||||
|
UI.closeAllPanels();
|
||||||
|
UI.openControlbar();
|
||||||
|
|
||||||
|
document.getElementById('noVNC_upload_panel')
|
||||||
|
.classList.add("noVNC_open");
|
||||||
|
document.getElementById('noVNC_upload_button')
|
||||||
|
.classList.add("noVNC_selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
closeUploadPanel() {
|
||||||
|
document.getElementById('noVNC_upload_panel')
|
||||||
|
.classList.remove("noVNC_open");
|
||||||
|
document.getElementById('noVNC_upload_button')
|
||||||
|
.classList.remove("noVNC_selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleUploadPanel(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('noVNC_upload_panel')
|
||||||
|
.classList.contains("noVNC_open")) {
|
||||||
|
UI.closeUploadPanel();
|
||||||
|
} else {
|
||||||
|
UI.openUploadPanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFileSelect(e) {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
UI.uploadFile(files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the input so the same file can be selected again
|
||||||
|
e.target.value = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadFile(file) {
|
||||||
|
const uploadsList = document.getElementById('noVNC_upload_files_list');
|
||||||
|
|
||||||
|
// Create progress item
|
||||||
|
const progressItem = document.createElement('div');
|
||||||
|
progressItem.className = 'noVNC_upload_item';
|
||||||
|
progressItem.style.marginBottom = '10px';
|
||||||
|
progressItem.style.padding = '8px';
|
||||||
|
progressItem.style.border = '1px solid #ccc';
|
||||||
|
progressItem.style.borderRadius = '4px';
|
||||||
|
|
||||||
|
const fileName = document.createElement('div');
|
||||||
|
fileName.textContent = file.name + ' (' + UI.formatFileSize(file.size) + ')';
|
||||||
|
fileName.style.fontSize = '13px';
|
||||||
|
fileName.style.fontWeight = 'bold';
|
||||||
|
fileName.style.marginBottom = '8px';
|
||||||
|
fileName.style.wordBreak = 'break-all';
|
||||||
|
fileName.style.color = '#ffffff';
|
||||||
|
|
||||||
|
const progressBarContainer = document.createElement('div');
|
||||||
|
progressBarContainer.style.width = '100%';
|
||||||
|
progressBarContainer.style.height = '20px';
|
||||||
|
progressBarContainer.style.backgroundColor = '#f0f0f0';
|
||||||
|
progressBarContainer.style.borderRadius = '10px';
|
||||||
|
progressBarContainer.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
const progressBar = document.createElement('div');
|
||||||
|
progressBar.style.height = '100%';
|
||||||
|
progressBar.style.width = '0%';
|
||||||
|
progressBar.style.backgroundColor = '#4CAF50';
|
||||||
|
progressBar.style.transition = 'width 0.3s';
|
||||||
|
|
||||||
|
const progressText = document.createElement('div');
|
||||||
|
progressText.textContent = '0%';
|
||||||
|
progressText.style.fontSize = '11px';
|
||||||
|
progressText.style.marginTop = '3px';
|
||||||
|
progressText.style.textAlign = 'center';
|
||||||
|
|
||||||
|
progressBarContainer.appendChild(progressBar);
|
||||||
|
progressItem.appendChild(fileName);
|
||||||
|
progressItem.appendChild(progressBarContainer);
|
||||||
|
progressItem.appendChild(progressText);
|
||||||
|
uploadsList.appendChild(progressItem);
|
||||||
|
|
||||||
|
// Prepare FormData
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
// Create XMLHttpRequest
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
// Progress handler
|
||||||
|
xhr.upload.addEventListener('progress', (e) => {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
const percentComplete = (e.loaded / e.total) * 100;
|
||||||
|
progressBar.style.width = percentComplete + '%';
|
||||||
|
progressText.textContent = Math.round(percentComplete) + '%';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Completion handler
|
||||||
|
xhr.addEventListener('load', () => {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
progressBar.style.backgroundColor = '#4CAF50';
|
||||||
|
progressText.textContent = 'Complete!';
|
||||||
|
|
||||||
|
// Remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.style.transition = 'opacity 0.5s';
|
||||||
|
progressItem.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.remove();
|
||||||
|
}, 500);
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
progressBar.style.backgroundColor = '#f44336';
|
||||||
|
progressText.textContent = 'Failed!';
|
||||||
|
|
||||||
|
// Remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.style.transition = 'opacity 0.5s';
|
||||||
|
progressItem.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.remove();
|
||||||
|
}, 500);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error handler
|
||||||
|
xhr.addEventListener('error', () => {
|
||||||
|
progressBar.style.backgroundColor = '#f44336';
|
||||||
|
progressText.textContent = 'Error!';
|
||||||
|
|
||||||
|
// Remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.style.transition = 'opacity 0.5s';
|
||||||
|
progressItem.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
progressItem.remove();
|
||||||
|
}, 500);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
xhr.open('POST', '/upload', true);
|
||||||
|
xhr.send(formData);
|
||||||
|
},
|
||||||
|
|
||||||
|
openDownloadPanel() {
|
||||||
|
UI.closeAllPanels();
|
||||||
|
UI.openControlbar();
|
||||||
|
|
||||||
|
document.getElementById('noVNC_download_panel')
|
||||||
|
.classList.add("noVNC_open");
|
||||||
|
document.getElementById('noVNC_download_button')
|
||||||
|
.classList.add("noVNC_selected");
|
||||||
|
|
||||||
|
// Refresh file list when opening
|
||||||
|
UI.refreshDownloadsList();
|
||||||
|
},
|
||||||
|
|
||||||
|
closeDownloadPanel() {
|
||||||
|
document.getElementById('noVNC_download_panel')
|
||||||
|
.classList.remove("noVNC_open");
|
||||||
|
document.getElementById('noVNC_download_button')
|
||||||
|
.classList.remove("noVNC_selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleDownloadPanel(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('noVNC_download_panel')
|
||||||
|
.classList.contains("noVNC_open")) {
|
||||||
|
UI.closeDownloadPanel();
|
||||||
|
} else {
|
||||||
|
UI.openDownloadPanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshDownloadsList() {
|
||||||
|
const downloadsList = document.getElementById('noVNC_download_files_list');
|
||||||
|
|
||||||
|
// Show loading message
|
||||||
|
downloadsList.innerHTML = '<div style="padding: 10px; text-align: center;">Loading files...</div>';
|
||||||
|
|
||||||
|
// Fetch file list from API
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', '/api/downloads', true);
|
||||||
|
|
||||||
|
xhr.addEventListener('load', () => {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
|
if (response.success && response.downloads && response.downloads.length > 0) {
|
||||||
|
// Clear loading message
|
||||||
|
downloadsList.innerHTML = '';
|
||||||
|
|
||||||
|
// Display each file
|
||||||
|
response.downloads.forEach(file => {
|
||||||
|
const fileItem = document.createElement('div');
|
||||||
|
fileItem.className = 'noVNC_download_item';
|
||||||
|
fileItem.style.marginBottom = '8px';
|
||||||
|
fileItem.style.padding = '8px';
|
||||||
|
fileItem.style.border = '1px solid #ccc';
|
||||||
|
fileItem.style.borderRadius = '4px';
|
||||||
|
fileItem.style.display = 'flex';
|
||||||
|
fileItem.style.justifyContent = 'space-between';
|
||||||
|
fileItem.style.alignItems = 'center';
|
||||||
|
|
||||||
|
const fileInfo = document.createElement('div');
|
||||||
|
fileInfo.style.flex = '1';
|
||||||
|
fileInfo.style.minWidth = '0';
|
||||||
|
|
||||||
|
const fileName = document.createElement('div');
|
||||||
|
fileName.textContent = file.filename;
|
||||||
|
fileName.style.fontSize = '13px';
|
||||||
|
fileName.style.fontWeight = 'bold';
|
||||||
|
fileName.style.color = '#ffffff';
|
||||||
|
fileName.style.wordBreak = 'break-all';
|
||||||
|
|
||||||
|
const fileDetails = document.createElement('div');
|
||||||
|
fileDetails.style.fontSize = '11px';
|
||||||
|
fileDetails.style.color = '#cccccc';
|
||||||
|
fileDetails.style.marginTop = '3px';
|
||||||
|
|
||||||
|
if (file.is_dir) {
|
||||||
|
fileDetails.textContent = 'Directory';
|
||||||
|
} else {
|
||||||
|
fileDetails.textContent = UI.formatFileSize(file.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo.appendChild(fileName);
|
||||||
|
fileInfo.appendChild(fileDetails);
|
||||||
|
|
||||||
|
// Only add download button for files, not directories
|
||||||
|
if (!file.is_dir) {
|
||||||
|
const downloadBtn = document.createElement('button');
|
||||||
|
downloadBtn.textContent = 'Download';
|
||||||
|
downloadBtn.style.marginLeft = '10px';
|
||||||
|
downloadBtn.style.padding = '5px 10px';
|
||||||
|
downloadBtn.style.fontSize = '12px';
|
||||||
|
downloadBtn.style.cursor = 'pointer';
|
||||||
|
downloadBtn.addEventListener('click', () => {
|
||||||
|
UI.downloadFile(file.filename);
|
||||||
|
});
|
||||||
|
fileItem.appendChild(fileInfo);
|
||||||
|
fileItem.appendChild(downloadBtn);
|
||||||
|
} else {
|
||||||
|
fileItem.appendChild(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadsList.appendChild(fileItem);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
downloadsList.innerHTML = '<div style="padding: 10px; text-align: center; color: #999;">No files available</div>';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
downloadsList.innerHTML = '<div style="padding: 10px; text-align: center; color: #f44336;">Error parsing response</div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadsList.innerHTML = '<div style="padding: 10px; text-align: center; color: #f44336;">Failed to load files</div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('error', () => {
|
||||||
|
downloadsList.innerHTML = '<div style="padding: 10px; text-align: center; color: #f44336;">Network error</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadFile(filename) {
|
||||||
|
// Create a temporary anchor element and trigger download
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = '/downloads/' + encodeURIComponent(filename);
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
},
|
||||||
|
|
||||||
clipboardReceive(e) {
|
clipboardReceive(e) {
|
||||||
if (UI.rfb.clipboardDown) {
|
if (UI.rfb.clipboardDown) {
|
||||||
var curvalue = document.getElementById('noVNC_clipboard_text').value;
|
var curvalue = document.getElementById('noVNC_clipboard_text').value;
|
||||||
|
|
|
||||||
38
index.html
38
index.html
|
|
@ -174,6 +174,44 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- File Upload -->
|
||||||
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
|
<input type="image" alt="Upload Files" src="app/images/upload.svg"
|
||||||
|
id="noVNC_upload_button" class="noVNC_button"
|
||||||
|
title="Upload Files">
|
||||||
|
Upload Files
|
||||||
|
<div class="noVNC_vcenter">
|
||||||
|
<div id="noVNC_upload_panel" class="noVNC_panel">
|
||||||
|
<div class="noVNC_heading">
|
||||||
|
<img alt="" src="app/images/upload.svg"> Upload Files
|
||||||
|
</div>
|
||||||
|
<input type="file" id="noVNC_file_input" multiple style="margin: 10px 0;">
|
||||||
|
<div id="noVNC_upload_files_list" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
<!-- Upload progress items will be added here dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File Download -->
|
||||||
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
|
<input type="image" alt="Download Files" src="app/images/download.svg"
|
||||||
|
id="noVNC_download_button" class="noVNC_button"
|
||||||
|
title="Download Files">
|
||||||
|
Download Files
|
||||||
|
<div class="noVNC_vcenter">
|
||||||
|
<div id="noVNC_download_panel" class="noVNC_panel">
|
||||||
|
<div class="noVNC_heading">
|
||||||
|
<img alt="" src="app/images/download.svg"> Download Files
|
||||||
|
</div>
|
||||||
|
<button id="noVNC_refresh_downloads_button" style="margin: 10px 0; padding: 5px 10px;">Refresh File List</button>
|
||||||
|
<div id="noVNC_download_files_list" style="max-height: 400px; overflow-y: auto; margin-top: 10px;">
|
||||||
|
<!-- Download file list will be added here dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Toggle fullscreen -->
|
<!-- Toggle fullscreen -->
|
||||||
<div class="noVNC_button_div noVNC_hidden" >
|
<div class="noVNC_button_div noVNC_hidden" >
|
||||||
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue