addressbook_web/templates/index.html

296 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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; }
</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="Rust ID" required>
<input type="text" id="computerName" placeholder="Имя компьютера" required>
<input type="text" id="installTime" placeholder="Время (опционально)">
<button onclick="addInstall()">Добавить</button>
</div>
<div class="header">
<div onclick="sortInstalls('rust_id')">Rust 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';
$(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;">${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="rustdesk://${item.rust_id}" onclick="openRustDesk('${item.rust_id}'); return false;">Подключиться</a>
<button onclick="editInstall(${item.id}, '${item.rust_id}', '${item.computer_name}', '${item.install_time}')">Редактировать</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' ? 'Rust 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();
$.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 }),
success: function () {
loadInstalls(selectedFolderId);
}
});
}
function editInstall(id, rustId, computerName, installTime) {
const newRustId = prompt("Rust ID:", rustId);
const newComputerName = prompt("Имя компьютера:", computerName);
const newInstallTime = prompt("Время установки:", installTime);
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 }),
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 openRustDesk(rustId) {
if (confirm("Подключиться к " + rustId + "?")) {
window.location.href = `rustdesk://${rustId}`;
}
}
</script>
</body>
</html>