addressbook_web/templates/index.html

508 lines
21 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.

<script>
const API_URL = "http://<server-ip>:8002/api"; // Замени <server-ip> на IP или домен сервера
let selectedFolderId = null;
let allInstalls = [];
let allFolders = []; // Добавляем для отслеживания папок
let selectedInstallId = null;
let sortField = null;
let sortDirection = 'asc';
const protocolIcons = {
'rustdesk': '<img src="/icons/rustdesk.png" class="protocol-icon">',
'anydesk': '<img src="/icons/anydesk.png" class="protocol-icon">',
'ammyy': '<img src="/icons/ammyy.png" class="protocol-icon">',
'teamviewer': '<img src="/icons/teamviewer.png" class="protocol-icon">',
'vnc': '<img src="/icons/vnc.png" class="protocol-icon">',
'rdp': '<img src="/icons/rdp.png" class="protocol-icon">'
};
const protocolLinks = {
'rustdesk': 'rustdesk://',
'anydesk': 'anydesk://',
'ammyy': 'ammyy://',
'teamviewer': 'teamviewer://remote-control/',
'vnc': 'vnc://',
'rdp': 'rdp://'
};
$(document).ready(function () {
// Загружаем все папки и записи при инициализации
loadFolders();
loadInstalls(null);
// Инициализация дерева папок
$('#folder-tree').jstree({
'core': {
'data': function (node, cb) {
$.getJSON(`${API_URL}/folders`, function (data) {
allFolders = 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);
updateFolderActions();
});
// Обработчики для поиска и выбора папки
$('#search-input').on('input', function () {
performSearch();
});
$('#folder-select').on('change', function () {
performSearch();
});
// Drag-and-drop для записей
$('#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);
}
});
});
function loadFolders() {
$.getJSON(`${API_URL}/folders`, function (data) {
allFolders = data; // Обновляем список папок
updateFolderSelect(); // Обновляем выпадающий список
});
}
function loadInstalls(folderId) {
$.getJSON(`${API_URL}/installs`, function (data) {
console.log("Loaded installs:", data); // Логируем данные для отладки
allInstalls = data;
let filtered = folderId ? data.filter(i => i.folder_id == folderId && i.folder_id !== null) : data;
displayInstalls(filtered);
updateNotesPanel();
}).fail(function(jqxhr, textStatus, error) {
console.error("Error loading installs:", textStatus, error);
});
selectedFolderId = folderId;
}
function performSearch() {
const query = $('#search-input').val().toLowerCase().trim();
const folderId = $('#folder-select').val() || '';
console.log("Search query:", query, "Folder ID:", folderId); // Логируем для отладки
let filteredInstalls = [...allInstalls]; // Копируем массив, чтобы не изменять оригинал
// Фильтрация по папке, исключая удаленные или несуществующие папки
if (folderId) {
filteredInstalls = filteredInstalls.filter(i =>
i.folder_id == folderId &&
allFolders.some(f => f.id === i.folder_id) // Проверяем, существует ли папка
);
}
// Фильтрация по запросу (по rust_id и computer_name)
if (query) {
filteredInstalls = filteredInstalls.filter(i =>
i.rust_id.toLowerCase().trim().includes(query) ||
i.computer_name.toLowerCase().trim().includes(query)
);
}
sortInstalls(null, filteredInstalls); // Сортируем и отображаем результаты
}
function displayInstalls(installs) {
console.log("Displaying installs:", installs); // Логируем для отладки
if (installs.length === 0) {
$('#installs-list').html('<p>Нет результатов</p>');
} else {
$('#installs-list').html(installs.map(item => `
<div class="install-item" data-id="${item.id}" draggable="true" style="display: flex; justify-content: space-between; position: relative;">
<div style="flex: 1; text-align: center; position: relative;">
${item.computer_name}
<button class="edit-button" onclick="editField(${item.id}, 'computer_name', '${item.computer_name}')">✏️</button>
</div>
<div style="flex: 1; text-align: center; position: relative;">
<span class="protocol-icon">${protocolIcons[item.protocol]}</span>
<a href="${protocolLinks[item.protocol]}${item.rust_id}" class="connection-link" onclick="selectInstall(${item.id}, '${item.rust_id}', '${item.protocol}'); return false;">${item.rust_id}</a>
<button class="edit-button" onclick="editField(${item.id}, 'rust_id', '${item.rust_id}')">✏️</button>
</div>
<div style="flex: 1; text-align: center; position: relative;">
${item.install_time}
<button class="edit-button" onclick="editField(${item.id}, 'install_time', '${item.install_time}')">✏️</button>
</div>
<div style="flex: 1; text-align: center; position: relative;">
<span class="action-buttons">
<button onclick="deleteInstall(${item.id})">Удалить</button>
<button onclick="openNoteModal(${item.id}, '${item.note}')">Заметка</button>
</span>
</div>
</div>
`).join(''));
}
updateSortArrows();
}
// Остальные функции (selectInstall, sortInstalls, updateSortArrows, addFolder, editFolder, deleteFolder, addInstall, editField, deleteInstall, moveInstallToFolder, openRemote, exportCSV, importCSV, openNoteModal, saveNote, closeNoteModal, updateNotesPanel, updateFolderActions, updateFolderSelect, formatBold, formatItalic, formatLink) остаются без изменений, но я их включу для полноты кода ниже.
function selectInstall(installId, rustId, protocol) {
selectedInstallId = installId;
$('.install-item').removeClass('selected');
$(`#installs-list .install-item[data-id="${installId}"]`).addClass('selected');
updateNotesPanel();
if (confirm(`Подключиться к ${rustId} через ${protocol}?`)) {
const link = protocolLinks[protocol] + rustId;
window.location.href = link;
}
}
function sortInstalls(field, filteredInstalls = null) {
let installs = filteredInstalls || (selectedFolderId
? allInstalls.filter(i => i.folder_id == selectedFolderId && i.folder_id !== null)
: allInstalls);
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 === 'computer_name' ? 'Имя компьютера' : sortField === 'rust_id' ? 'ID подключения' : 'Время установки'}") .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 () {
loadFolders(); // Обновляем список папок
$('#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 () {
loadFolders(); // Обновляем список папок
$('#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);
});
});
loadFolders(); // Обновляем список папок
$('#folder-tree').jstree(true).refresh();
loadInstalls(selectedFolderId);
},
error: function (xhr, status, error) {
if (xhr.status === 403) {
alert("Нельзя удалить папку 'Несортированные'.");
} else {
console.error("Ошибка удаления:", status, error);
}
}
});
}
}
function addInstall() {
const rustId = $('#rustId').val();
const computerName = $('#computerName').val();
const installTime = $('#installTime').val() || '';
const protocol = $('#protocol').val();
const note = $('#note').val() || '';
if (installTime && !/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(installTime)) {
alert("Неверный формат времени. Используйте ГГГГ-ММ-ДД ЧЧ:ММ:СС (например, 2025-03-05 14:30:00)");
return;
}
$.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, note: note }),
success: function () {
loadInstalls(selectedFolderId);
},
error: function (xhr, status, error) {
if (xhr.status === 400) {
alert(xhr.responseJSON.detail);
} else {
console.error("Ошибка добавления:", status, error);
}
}
});
}
function editField(installId, field, currentValue) {
let newValue;
if (field === 'install_time') {
newValue = prompt(`Новое ${field === 'computer_name' ? 'Имя компьютера' : field === 'rust_id' ? 'ID подключения' : 'Время установки'} (опционально, формат для времени: ГГГГ-ММ-ДД ЧЧ:ММ:СС):`, currentValue || '');
if (newValue && field === 'install_time' && !/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(newValue)) {
alert("Неверный формат времени. Используйте ГГГГ-ММ-ДД ЧЧ:ММ:СС (например, 2025-03-05 14:30:00)");
return;
}
} else {
newValue = prompt(`Новое ${field === 'computer_name' ? 'Имя компьютера' : field === 'rust_id' ? 'ID подключения' : 'Время установки'}:`, currentValue || '');
}
if (newValue !== null && newValue !== currentValue) {
let data = {};
data[field] = newValue;
if (field === 'install_time') {
data['folder_id'] = selectedFolderId;
}
$.ajax({
url: `${API_URL}/install/${installId}`,
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function () {
loadInstalls(selectedFolderId);
},
error: function (xhr, status, error) {
if (xhr.status === 400) {
alert(xhr.responseJSON.detail);
} else {
console.error(`Ошибка редактирования ${field}:`, status, error);
}
}
});
}
}
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}?`)) {
const link = protocolLinks[protocol] + id;
window.location.href = link;
}
}
function exportCSV() {
const folderId = $('#folder-select').val() || '';
window.location.href = `${API_URL}/export/csv?folder_id=${folderId}`;
}
function importCSV(file) {
if (!file) return;
const folderId = $('#folder-select').val() || '';
const formData = new FormData();
formData.append('file', file);
if (folderId) {
formData.append('folder_id', folderId);
}
$.ajax({
url: `${API_URL}/import/csv`,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function (response) {
alert(response.message);
loadInstalls(selectedFolderId);
},
error: function (xhr, status, error) {
alert(`Ошибка импорта: ${xhr.responseJSON.detail || error}`);
}
});
}
function openNoteModal(installId, currentNote) {
selectedInstallId = installId;
$('#note-textarea').val(currentNote);
$('#note-modal').show();
$('#modal-overlay').show();
}
function saveNote() {
const note = $('#note-textarea').val() || '';
$.ajax({
url: `${API_URL}/install/${selectedInstallId}`,
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify({ note: note }),
success: function () {
loadInstalls(selectedFolderId);
closeNoteModal();
},
error: function (xhr, status, error) {
console.error("Ошибка сохранения заметки:", status, error);
alert("Не удалось сохранить заметку");
}
});
}
function closeNoteModal() {
$('#note-modal').hide();
$('#modal-overlay').hide();
$('#note-textarea').val('');
}
function updateNotesPanel() {
if (selectedInstallId) {
const install = allInstalls.find(i => i.id === selectedInstallId);
if (install && install.note) {
$('#notes-content').html(marked.parse(install.note));
} else {
$('#notes-content').html('<p>Нет заметок</p>');
}
} else {
$('#notes-content').html('<p>Выберите подключение для просмотра заметки</p>');
}
}
function updateFolderActions() {
$('.folder-actions').hide();
if (selectedFolderId) {
$(`#${selectedFolderId} .folder-actions`).show();
} else {
$('#root .folder-actions').show();
}
}
function updateFolderSelect() {
const $folderSelect = $('#folder-select');
$folderSelect.empty().append('<option value="">Все папки</option>');
allFolders.forEach(folder => {
$folderSelect.append(`<option value="${folder.id}">${folder.name}</option>`);
});
}
$('#installs-list').on('click', '.install-item', function () {
selectedInstallId = $(this).data('id');
$('.install-item').removeClass('selected');
$(this).addClass('selected');
updateNotesPanel();
});
function formatBold() {
const textarea = $('#note-textarea');
const start = textarea[0].selectionStart;
const end = textarea[0].selectionEnd;
const text = textarea.val();
const selected = text.substring(start, end);
const newText = `**${selected}**`;
textarea.val(text.substring(0, start) + newText + text.substring(end));
textarea[0].selectionStart = start;
textarea[0].selectionEnd = start + newText.length - 4;
}
function formatItalic() {
const textarea = $('#note-textarea');
const start = textarea[0].selectionStart;
const end = textarea[0].selectionEnd;
const text = textarea.val();
const selected = text.substring(start, end);
const newText = `*${selected}*`;
textarea.val(text.substring(0, start) + newText + text.substring(end));
textarea[0].selectionStart = start;
textarea[0].selectionEnd = start + newText.length - 2;
}
function formatLink() {
const textarea = $('#note-textarea');
const start = textarea[0].selectionStart;
const end = textarea[0].selectionEnd;
const text = textarea.val();
const selected = text.substring(start, end) || 'текст';
const url = prompt('Введите URL ссылки:', 'https://example.com');
if (url) {
const newText = `[${selected}](${url})`;
textarea.val(text.substring(0, start) + newText + text.substring(end));
textarea[0].selectionStart = start;
textarea[0].selectionEnd = start + newText.length - (selected ? 0 : '[текст]'.length);
}
}
</script>