316 lines
15 KiB
HTML
316 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Органайзер RustDesk</title>
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js"></script>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/themes/default/style.min.css">
|
||
<style>
|
||
body { display: flex; font-family: Arial, sans-serif; }
|
||
#tree-container { width: 30%; padding: 10px; border-right: 1px solid #ccc; }
|
||
#installs-container { width: 70%; padding: 10px; position: relative; }
|
||
.install-item { padding: 5px; margin: 2px; border: 1px solid #ddd; cursor: move; }
|
||
.form-container { margin-bottom: 20px; }
|
||
.jstree-node { position: relative; }
|
||
.folder-actions { display: inline; margin-left: 10px; }
|
||
#search-container { position: absolute; top: 10px; right: 10px; text-align: right; }
|
||
#search-input { width: 200px; }
|
||
#installs-list { margin-top: 20px; }
|
||
.header { display: flex; justify-content: space-between; font-weight: bold; padding: 5px; background: #f0f0f0; cursor: pointer; }
|
||
.header div { flex: 1; text-align: center; }
|
||
.header div:hover { background: #e0e0e0; }
|
||
.sort-arrow { font-size: 12px; margin-left: 5px; }
|
||
.protocol-icon { margin-right: 5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="tree-container">
|
||
<h2>Папки</h2>
|
||
<div id="folder-tree"></div>
|
||
<button onclick="addFolder()">Добавить папку</button>
|
||
</div>
|
||
<div id="installs-container">
|
||
<h1>Органайзер RustDesk</h1>
|
||
<div id="search-container">
|
||
<input type="text" id="search-input" placeholder="Поиск по ID или имени" onkeyup="filterInstalls()">
|
||
<div>
|
||
<input type="radio" id="search-current" name="search-scope" value="current" checked>
|
||
<label for="search-current">Текущая папка</label>
|
||
<input type="radio" id="search-all" name="search-scope" value="all">
|
||
<label for="search-all">Все папки</label>
|
||
</div>
|
||
</div>
|
||
<div class="form-container">
|
||
<h2>Добавить запись</h2>
|
||
<input type="text" id="rustId" placeholder="ID подключения" required>
|
||
<input type="text" id="computerName" placeholder="Имя компьютера" required>
|
||
<input type="text" id="installTime" placeholder="Время (опционально)">
|
||
<select id="protocol">
|
||
<option value="rustdesk" selected>RustDesk</option>
|
||
<option value="anydesk">AnyDesk</option>
|
||
<option value="ammyy">Ammyy Admin</option>
|
||
<option value="teamviewer">TeamViewer</option>
|
||
<option value="vnc">VNC</option>
|
||
<option value="rdp">RDP</option>
|
||
</select>
|
||
<button onclick="addInstall()">Добавить</button>
|
||
</div>
|
||
<div class="header">
|
||
<div onclick="sortInstalls('rust_id')">ID подключения<span class="sort-arrow"></span></div>
|
||
<div onclick="sortInstalls('computer_name')">Имя компьютера<span class="sort-arrow"></span></div>
|
||
<div onclick="sortInstalls('install_time')">Время установки<span class="sort-arrow"></span></div>
|
||
<div>Действия</div>
|
||
</div>
|
||
<div id="installs-list"></div>
|
||
</div>
|
||
|
||
<script>
|
||
const API_URL = "/api";
|
||
let selectedFolderId = null;
|
||
let allInstalls = [];
|
||
let sortField = null;
|
||
let sortDirection = 'asc';
|
||
|
||
const protocolIcons = {
|
||
'rustdesk': '🖥️', // Эмодзи для RustDesk
|
||
'anydesk': '🔴', // Эмодзи для AnyDesk
|
||
'ammyy': '🟢', // Эмодзи для Ammyy Admin
|
||
'teamviewer': '🔵', // Эмодзи для TeamViewer
|
||
'vnc': '🟡', // Эмодзи для VNC
|
||
'rdp': '🟣' // Эмодзи для RDP
|
||
};
|
||
|
||
$(document).ready(function () {
|
||
$('#folder-tree').jstree({
|
||
'core': {
|
||
'data': function (node, cb) {
|
||
$.getJSON(`${API_URL}/folders`, function (data) {
|
||
const treeData = [
|
||
{ id: "root", text: "Корень", parent: "#" }
|
||
].concat(data.map(folder => ({
|
||
id: folder.id,
|
||
text: `${folder.name} <span class="folder-actions">` +
|
||
`<button onclick="editFolder(${folder.id}, '${folder.name}')">✏️</button>` +
|
||
`<button onclick="deleteFolder(${folder.id})">🗑️</button></span>`,
|
||
parent: folder.parent_id ? folder.parent_id : "root"
|
||
})));
|
||
cb(treeData);
|
||
});
|
||
},
|
||
'check_callback': true
|
||
},
|
||
'plugins': ['dnd', 'html_data']
|
||
}).on('select_node.jstree', function (e, data) {
|
||
selectedFolderId = data.node.id === "root" ? null : data.node.id;
|
||
loadInstalls(selectedFolderId);
|
||
});
|
||
|
||
$('#installs-list').on('dragstart', '.install-item', function (e) {
|
||
e.originalEvent.dataTransfer.setData('text/plain', $(this).data('id'));
|
||
});
|
||
|
||
$('#folder-tree').on('dragover', function (e) {
|
||
e.preventDefault();
|
||
}).on('drop', function (e) {
|
||
e.preventDefault();
|
||
const installId = e.originalEvent.dataTransfer.getData('text');
|
||
let targetFolderId = $(e.target).closest('.jstree-node').attr('id');
|
||
if (installId && targetFolderId) {
|
||
targetFolderId = targetFolderId === "root" ? null : targetFolderId;
|
||
moveInstallToFolder(installId, targetFolderId);
|
||
}
|
||
});
|
||
|
||
loadInstalls(null);
|
||
$('#folder-tree').jstree('select_node', 'root');
|
||
});
|
||
|
||
function loadInstalls(folderId) {
|
||
$.getJSON(`${API_URL}/installs`, function (data) {
|
||
allInstalls = data;
|
||
let filtered = folderId ? data.filter(i => i.folder_id == folderId) : data.filter(i => i.folder_id === null);
|
||
displayInstalls(filtered);
|
||
});
|
||
selectedFolderId = folderId;
|
||
}
|
||
|
||
function displayInstalls(installs) {
|
||
$('#installs-list').html(installs.map(item => `
|
||
<div class="install-item" data-id="${item.id}" draggable="true" style="display: flex; justify-content: space-between;">
|
||
<div style="flex: 1; text-align: center;"><span class="protocol-icon">${protocolIcons[item.protocol]}</span>${item.rust_id}</div>
|
||
<div style="flex: 1; text-align: center;">${item.computer_name}</div>
|
||
<div style="flex: 1; text-align: center;">${item.install_time}</div>
|
||
<div style="flex: 1; text-align: center;">
|
||
<a href="${item.protocol}://${item.rust_id}" onclick="openRemote('${item.rust_id}', '${item.protocol}'); return false;">Подключиться</a>
|
||
<button onclick="editInstall(${item.id}, '${item.rust_id}', '${item.computer_name}', '${item.install_time}', '${item.protocol}')">Редактировать</button>
|
||
<button onclick="deleteInstall(${item.id})">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join(''));
|
||
updateSortArrows();
|
||
}
|
||
|
||
function filterInstalls() {
|
||
const query = $('#search-input').val().toLowerCase();
|
||
const scope = $('input[name="search-scope"]:checked').val();
|
||
let filtered = scope === 'current'
|
||
? (selectedFolderId ? allInstalls.filter(i => i.folder_id == selectedFolderId) : allInstalls.filter(i => i.folder_id === null))
|
||
: allInstalls;
|
||
filtered = filtered.filter(i =>
|
||
i.rust_id.toLowerCase().includes(query) ||
|
||
i.computer_name.toLowerCase().includes(query)
|
||
);
|
||
sortInstalls(null, filtered);
|
||
}
|
||
|
||
function sortInstalls(field, filteredInstalls = null) {
|
||
let installs = filteredInstalls || (selectedFolderId
|
||
? allInstalls.filter(i => i.folder_id == selectedFolderId)
|
||
: allInstalls.filter(i => i.folder_id === null));
|
||
|
||
if (field) {
|
||
if (sortField === field) {
|
||
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||
} else {
|
||
sortField = field;
|
||
sortDirection = 'asc';
|
||
}
|
||
installs.sort((a, b) => {
|
||
let valA = a[field], valB = b[field];
|
||
if (field === 'install_time') {
|
||
valA = new Date(valA);
|
||
valB = new Date(valB);
|
||
}
|
||
return sortDirection === 'asc' ? (valA > valB ? 1 : -1) : (valA < valB ? 1 : -1);
|
||
});
|
||
}
|
||
displayInstalls(installs);
|
||
}
|
||
|
||
function updateSortArrows() {
|
||
$('.sort-arrow').text('');
|
||
if (sortField) {
|
||
$(`.header div:contains("${sortField === 'rust_id' ? 'ID подключения' : sortField === 'computer_name' ? 'Имя компьютера' : 'Время установки'}") .sort-arrow`)
|
||
.text(sortDirection === 'asc' ? '↑' : '↓');
|
||
}
|
||
}
|
||
|
||
function addFolder() {
|
||
const name = prompt("Введите имя папки:");
|
||
if (name) {
|
||
$.ajax({
|
||
url: `${API_URL}/folders`,
|
||
type: 'POST',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ name, parent_id: selectedFolderId }),
|
||
success: function () {
|
||
$('#folder-tree').jstree(true).refresh();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function editFolder(folderId, currentName) {
|
||
const newName = prompt("Введите новое имя папки:", currentName);
|
||
if (newName) {
|
||
$.ajax({
|
||
url: `${API_URL}/folders/${folderId}`,
|
||
type: 'PUT',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ name: newName }),
|
||
success: function () {
|
||
$('#folder-tree').jstree(true).refresh();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function deleteFolder(folderId) {
|
||
if (confirm("Удалить папку? Все записи будут перемещены в корень.")) {
|
||
$.ajax({
|
||
url: `${API_URL}/folders/${folderId}`,
|
||
type: 'DELETE',
|
||
success: function () {
|
||
$.getJSON(`${API_URL}/installs`, function (data) {
|
||
const folderInstalls = data.filter(i => i.folder_id == folderId);
|
||
folderInstalls.forEach(item => {
|
||
moveInstallToFolder(item.id, null);
|
||
});
|
||
});
|
||
$('#folder-tree').jstree(true).refresh();
|
||
loadInstalls(selectedFolderId);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function addInstall() {
|
||
const rustId = $('#rustId').val();
|
||
const computerName = $('#computerName').val();
|
||
const installTime = $('#installTime').val() || new Date().toISOString();
|
||
const protocol = $('#protocol').val();
|
||
$.ajax({
|
||
url: `${API_URL}/install`,
|
||
type: 'POST',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ rust_id: rustId, computer_name: computerName, install_time: installTime, folder_id: selectedFolderId, protocol: protocol }),
|
||
success: function () {
|
||
loadInstalls(selectedFolderId);
|
||
}
|
||
});
|
||
}
|
||
|
||
function editInstall(id, rustId, computerName, installTime, protocol) {
|
||
const newRustId = prompt("ID подключения:", rustId);
|
||
const newComputerName = prompt("Имя компьютера:", computerName);
|
||
const newInstallTime = prompt("Время установки:", installTime);
|
||
const newProtocol = prompt("Протокол (rustdesk, anydesk, ammyy, teamviewer, vnc, rdp):", protocol);
|
||
if (newRustId && newComputerName) {
|
||
$.ajax({
|
||
url: `${API_URL}/install/${id}`,
|
||
type: 'PUT',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ rust_id: newRustId, computer_name: newComputerName, install_time: newInstallTime, folder_id: selectedFolderId, protocol: newProtocol }),
|
||
success: function () {
|
||
loadInstalls(selectedFolderId);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function deleteInstall(id) {
|
||
if (confirm("Удалить запись?")) {
|
||
$.ajax({
|
||
url: `${API_URL}/install/${id}`,
|
||
type: 'DELETE',
|
||
success: function () {
|
||
loadInstalls(selectedFolderId);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function moveInstallToFolder(installId, folderId) {
|
||
$.ajax({
|
||
url: `${API_URL}/install/${installId}`,
|
||
type: 'PUT',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ folder_id: folderId }),
|
||
success: function () {
|
||
loadInstalls(selectedFolderId);
|
||
},
|
||
error: function (xhr, status, error) {
|
||
console.error("Ошибка переноса:", status, error);
|
||
}
|
||
});
|
||
}
|
||
|
||
function openRemote(id, protocol) {
|
||
if (confirm(`Подключиться к ${id} через ${protocol}?`)) {
|
||
window.location.href = `${protocol}://${id}`;
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |