You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
576 lines
19 KiB
576 lines
19 KiB
// 全局变量
|
|
let socket = null;
|
|
let currentModalType = '';
|
|
let selectedItem = null;
|
|
let currentPath = '';
|
|
let searchResults = [];
|
|
|
|
// 初始化Socket连接
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
socket = io();
|
|
|
|
// 监听发送输出
|
|
socket.on('send_output', function(data) {
|
|
const output = document.getElementById('send-output');
|
|
output.textContent += data.message;
|
|
output.scrollTop = output.scrollHeight;
|
|
});
|
|
|
|
// 监听发送状态
|
|
socket.on('send_status', function(data) {
|
|
document.getElementById('send-status').textContent = data.status;
|
|
});
|
|
|
|
// 监听接收输出
|
|
socket.on('receive_output', function(data) {
|
|
const output = document.getElementById('receive-output');
|
|
output.textContent += data.message;
|
|
output.scrollTop = output.scrollHeight;
|
|
});
|
|
|
|
// 监听接收状态
|
|
socket.on('receive_status', function(data) {
|
|
document.getElementById('receive-status').textContent = data.status;
|
|
});
|
|
|
|
// 监听文件路径输入变化
|
|
const filePathInput = document.getElementById('send-file-path');
|
|
filePathInput.addEventListener('input', updateFileInfo);
|
|
filePathInput.addEventListener('change', updateFileInfo);
|
|
|
|
// 确保停止所有服务按钮在所有页面都可用
|
|
const stopAllButtons = document.querySelectorAll('[onclick="stopAllServices()"]');
|
|
stopAllButtons.forEach(btn => {
|
|
btn.addEventListener('click', stopAllServices);
|
|
});
|
|
|
|
// 初始更新文件信息
|
|
updateFileInfo();
|
|
});
|
|
|
|
// 更新文件信息
|
|
function updateFileInfo() {
|
|
const filePath = document.getElementById('send-file-path').value.trim();
|
|
const fileSizeElement = document.getElementById('file-size');
|
|
const fileMtimeElement = document.getElementById('file-mtime');
|
|
const fileStatusElement = document.getElementById('file-status');
|
|
|
|
if (!filePath) {
|
|
fileSizeElement.textContent = '-';
|
|
fileMtimeElement.textContent = '-';
|
|
fileStatusElement.textContent = '未选择';
|
|
fileStatusElement.className = 'file-status-unknown';
|
|
return;
|
|
}
|
|
|
|
// 检查文件是否存在
|
|
fetch('/check/file', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({ file_path: filePath })
|
|
})
|
|
.then(response => {
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
return response.text().then(text => {
|
|
throw new Error('服务器返回了非JSON响应');
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
fileSizeElement.textContent = formatFileSize(data.size);
|
|
fileMtimeElement.textContent = new Date(data.mtime * 1000).toLocaleString();
|
|
fileStatusElement.textContent = '文件有效';
|
|
fileStatusElement.className = 'file-status-valid';
|
|
} else {
|
|
fileSizeElement.textContent = '-';
|
|
fileMtimeElement.textContent = '-';
|
|
fileStatusElement.textContent = '文件不存在或无法访问';
|
|
fileStatusElement.className = 'file-status-invalid';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('检查文件错误:', error);
|
|
fileSizeElement.textContent = '-';
|
|
fileMtimeElement.textContent = '-';
|
|
fileStatusElement.textContent = '检查失败';
|
|
fileStatusElement.className = 'file-status-invalid';
|
|
});
|
|
}
|
|
|
|
// 手动浏览文件
|
|
function browseFileManually() {
|
|
currentModalType = 'send';
|
|
currentPath = document.getElementById('send-file-path').value || '/';
|
|
const filePath = document.getElementById('send-file-path').value;
|
|
|
|
if (filePath) {
|
|
try {
|
|
const dirPath = require('path').dirname(filePath);
|
|
if (require('fs').existsSync(dirPath)) {
|
|
currentPath = dirPath;
|
|
}
|
|
} catch (e) {
|
|
// 忽略错误,使用默认路径
|
|
}
|
|
}
|
|
|
|
fetchFileList(currentPath, 'file');
|
|
document.getElementById('file-browser-modal').style.display = 'block';
|
|
}
|
|
|
|
|
|
// 显示指定界面
|
|
function showScreen(screenId) {
|
|
document.querySelectorAll('.screen').forEach(screen => {
|
|
screen.classList.remove('active');
|
|
});
|
|
document.getElementById(screenId).classList.add('active');
|
|
}
|
|
|
|
|
|
// 发送连接
|
|
function sendConnect() {
|
|
const key = document.getElementById('send-key').value;
|
|
|
|
fetch('/send/connect', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ key: key })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
document.getElementById('send-connect-btn').disabled = true;
|
|
document.getElementById('send-stop-btn').disabled = false;
|
|
// 3秒后启用发送文件按钮
|
|
setTimeout(() => {
|
|
document.getElementById('send-file-btn').disabled = false;
|
|
}, 3000);
|
|
} else {
|
|
alert('错误: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('连接失败: ' + error);
|
|
});
|
|
}
|
|
|
|
// 发送文件
|
|
function sendFile() {
|
|
const filePath = document.getElementById('send-file-path').value;
|
|
const key = document.getElementById('send-key').value;
|
|
const parts = key.split('|');
|
|
const psk = parts.length >= 3 ? parts[2].trim() : '';
|
|
|
|
fetch('/send/file', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
file_path: filePath,
|
|
psk: psk
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status !== 'success') {
|
|
alert('错误: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('发送失败: ' + error);
|
|
});
|
|
}
|
|
|
|
// 停止发送
|
|
function sendStop() {
|
|
fetch('/send/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
document.getElementById('send-connect-btn').disabled = false;
|
|
document.getElementById('send-file-btn').disabled = true;
|
|
document.getElementById('send-stop-btn').disabled = true;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
// 接收连接
|
|
function receiveConnect() {
|
|
const key = document.getElementById('receive-key').value;
|
|
const savePath = document.getElementById('receive-save-path').value;
|
|
|
|
fetch('/receive/connect', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
key: key,
|
|
save_path: savePath
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
document.getElementById('receive-connect-btn').disabled = true;
|
|
document.getElementById('receive-stop-btn').disabled = false;
|
|
} else {
|
|
alert('错误: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('连接失败: ' + error);
|
|
});
|
|
}
|
|
|
|
// 停止接收
|
|
function receiveStop() {
|
|
fetch('/receive/stop', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
document.getElementById('receive-connect-btn').disabled = false;
|
|
document.getElementById('receive-stop-btn').disabled = true;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
// 打开搜索模态框
|
|
function openSearchModal() {
|
|
document.getElementById('file-search-modal').style.display = 'block';
|
|
document.getElementById('search-path').value = '/mnt/wwn-0x5000c500a34676a5-part1/docker/kodbox/data/files/';
|
|
document.getElementById('search-keyword').value = '*';
|
|
document.getElementById('search-results-list').innerHTML = '';
|
|
document.getElementById('result-count').textContent = '0';
|
|
document.getElementById('search-select-btn').disabled = true;
|
|
selectedItem = null;
|
|
searchResults = [];
|
|
}
|
|
|
|
// 关闭搜索模态框
|
|
function closeSearchModal() {
|
|
document.getElementById('file-search-modal').style.display = 'none';
|
|
}
|
|
|
|
// 浏览搜索路径
|
|
function browseSearchPath() {
|
|
currentModalType = 'search_path';
|
|
currentPath = document.getElementById('search-path').value || '/';
|
|
|
|
fetchFileList(currentPath, 'folder');
|
|
document.getElementById('file-browser-modal').style.display = 'block';
|
|
}
|
|
|
|
// 开始搜索
|
|
function startSearch() {
|
|
const searchPath = document.getElementById('search-path').value;
|
|
const keyword = document.getElementById('search-keyword').value;
|
|
const maxResults = document.getElementById('max-results').value;
|
|
|
|
if (!keyword) {
|
|
alert('请输入搜索关键词');
|
|
return;
|
|
}
|
|
|
|
// 显示加载状态
|
|
const resultsList = document.getElementById('search-results-list');
|
|
resultsList.innerHTML = '<div class="loading">搜索中...</div>';
|
|
|
|
fetch('/search/files', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
path: searchPath,
|
|
pattern: keyword,
|
|
max_results: parseInt(maxResults)
|
|
})
|
|
})
|
|
.then(response => {
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
return response.text().then(text => {
|
|
throw new Error(`服务器返回了非JSON响应: ${text.substring(0, 100)}...`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
searchResults = data.results;
|
|
displaySearchResults(data.results);
|
|
document.getElementById('result-count').textContent = data.count;
|
|
} else {
|
|
alert('搜索失败: ' + data.message);
|
|
resultsList.innerHTML = '<div class="no-results">搜索失败</div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('搜索错误:', error);
|
|
alert('搜索失败: ' + error.message);
|
|
resultsList.innerHTML = '<div class="no-results">搜索错误</div>';
|
|
});
|
|
}
|
|
|
|
// 显示搜索结果
|
|
function displaySearchResults(results) {
|
|
const resultsList = document.getElementById('search-results-list');
|
|
resultsList.innerHTML = '';
|
|
|
|
if (results.length === 0) {
|
|
resultsList.innerHTML = '<div class="no-results">未找到匹配的文件</div>';
|
|
return;
|
|
}
|
|
|
|
results.forEach(result => {
|
|
const div = document.createElement('div');
|
|
div.className = 'file-item';
|
|
|
|
const fileSize = formatFileSize(result.size || 0);
|
|
|
|
div.innerHTML = `
|
|
<div class="file-name">📄 ${result.name}</div>
|
|
<div class="file-details">
|
|
<span class="file-path">${result.relative_path}</span>
|
|
<span class="file-size">${fileSize}</span>
|
|
</div>
|
|
`;
|
|
div.dataset.path = result.path;
|
|
|
|
div.addEventListener('click', function() {
|
|
// 取消之前的选择
|
|
document.querySelectorAll('.file-item.selected').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
|
|
// 选择当前项
|
|
this.classList.add('selected');
|
|
selectedItem = this;
|
|
document.getElementById('search-select-btn').disabled = false;
|
|
});
|
|
|
|
resultsList.appendChild(div);
|
|
});
|
|
}
|
|
|
|
|
|
// 格式化文件大小
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// 选择搜索结果
|
|
function selectSearchResult() {
|
|
if (!selectedItem) {
|
|
alert('请先选择一个文件');
|
|
return;
|
|
}
|
|
|
|
const filePath = selectedItem.dataset.path;
|
|
document.getElementById('send-file-path').value = filePath;
|
|
closeSearchModal();
|
|
updateFileInfo(); // 更新文件信息显示
|
|
}
|
|
|
|
// 获取文件列表
|
|
function fetchFileList(path, type) {
|
|
fetch('/browse', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
path: path,
|
|
type: type
|
|
})
|
|
})
|
|
.then(response => {
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
return response.text().then(text => {
|
|
throw new Error(`服务器返回了非JSON响应: ${text.substring(0, 100)}...`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
currentPath = data.current_path;
|
|
document.getElementById('current-path').textContent = currentPath;
|
|
|
|
const fileList = document.getElementById('file-list');
|
|
fileList.innerHTML = '';
|
|
|
|
data.items.forEach(item => {
|
|
const div = document.createElement('div');
|
|
div.className = 'file-item';
|
|
div.innerHTML = item.type === 'folder' ?
|
|
`📁 ${item.name}` : `📄 ${item.name}`;
|
|
div.dataset.path = item.path;
|
|
div.dataset.type = item.type;
|
|
|
|
div.addEventListener('click', function() {
|
|
// 取消之前的选择
|
|
document.querySelectorAll('.file-item.selected').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
|
|
// 选择当前项
|
|
this.classList.add('selected');
|
|
selectedItem = this;
|
|
|
|
// 如果是文件夹,双击进入
|
|
this.addEventListener('dblclick', function() {
|
|
if (item.type === 'folder') {
|
|
if (currentModalType === 'search_path') {
|
|
document.getElementById('search-path').value = item.path;
|
|
closeModal();
|
|
} else {
|
|
fetchFileList(item.path, type);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
fileList.appendChild(div);
|
|
});
|
|
} else {
|
|
alert('错误: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('获取文件列表失败: ' + error.message);
|
|
});
|
|
}
|
|
|
|
// 导航到上级目录
|
|
function navigateUp() {
|
|
const parentPath = currentPath.split('/').slice(0, -1).join('/');
|
|
if (parentPath === '') {
|
|
currentPath = '/';
|
|
} else {
|
|
currentPath = parentPath;
|
|
}
|
|
|
|
const type = currentModalType === 'send' ? 'file' : 'folder';
|
|
fetchFileList(currentPath, type);
|
|
}
|
|
|
|
// 关闭模态框
|
|
function closeModal() {
|
|
document.getElementById('file-browser-modal').style.display = 'none';
|
|
selectedItem = null;
|
|
}
|
|
|
|
// 选择文件或文件夹
|
|
function selectItem() {
|
|
if (!selectedItem) {
|
|
alert('请先选择一个项目');
|
|
return;
|
|
}
|
|
|
|
const path = selectedItem.dataset.path;
|
|
const type = selectedItem.dataset.type;
|
|
|
|
if (currentModalType === 'send') {
|
|
if (type === 'file') {
|
|
document.getElementById('send-file-path').value = path;
|
|
closeModal();
|
|
updateFileInfo(); // 更新文件信息显示
|
|
} else {
|
|
alert('请选择一个文件');
|
|
}
|
|
} else if (currentModalType === 'folder') {
|
|
document.getElementById('receive-save-path').value = path;
|
|
closeModal();
|
|
} else if (currentModalType === 'search_path') {
|
|
document.getElementById('search-path').value = path;
|
|
closeModal();
|
|
}
|
|
}
|
|
|
|
|
|
// 停止所有服务
|
|
function stopAllServices() {
|
|
if (confirm('确定要停止所有服务吗?这将终止所有文件传输和连接进程。')) {
|
|
fetch('/stop/all', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
alert('所有服务已停止');
|
|
// 更新按钮状态
|
|
document.getElementById('send-connect-btn').disabled = false;
|
|
document.getElementById('send-file-btn').disabled = true;
|
|
document.getElementById('send-stop-btn').disabled = true;
|
|
document.getElementById('receive-connect-btn').disabled = false;
|
|
document.getElementById('receive-stop-btn').disabled = true;
|
|
|
|
// 更新状态显示
|
|
document.getElementById('send-status').textContent = '已停止';
|
|
document.getElementById('receive-status').textContent = '已停止';
|
|
} else {
|
|
alert('停止服务失败: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('停止服务失败: ' + error);
|
|
});
|
|
}
|
|
}
|
|
|
|
// 监听服务停止事件
|
|
socket.on('service_stopped', function(data) {
|
|
// 更新按钮状态
|
|
document.getElementById('send-connect-btn').disabled = false;
|
|
document.getElementById('send-file-btn').disabled = true;
|
|
document.getElementById('send-stop-btn').disabled = true;
|
|
document.getElementById('receive-connect-btn').disabled = false;
|
|
document.getElementById('receive-stop-btn').disabled = true;
|
|
|
|
// 更新状态显示
|
|
document.getElementById('send-status').textContent = '已停止';
|
|
document.getElementById('receive-status').textContent = '已停止';
|
|
});
|
|
|