Online check_0.1

This commit is contained in:
2025-03-07 10:44:01 +03:00
parent dc18fea2a8
commit 0acdb5f1b8
3 changed files with 111 additions and 363 deletions
+47 -346
View File
@@ -8,320 +8,10 @@
<script src="https://cdn.jsdelivr.net/npm/marked/marked.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;
margin: 0;
transition: background-color 0.3s, color 0.3s;
}
body.dark-theme {
background-color: #1a1a1a;
color: #e0e0e0;
}
#tree-container {
width: 15%; /* Начальная ширина */
padding: 10px;
border-right: none; /* Убираем стандартную границу */
min-width: 150px; /* Минимальная ширина */
max-width: 50%; /* Максимальная ширина */
overflow: auto; /* Для длинных списков папок */
}
#tree-container.dark-theme { border-right-color: #444; }
#splitter {
width: 5px;
background: #ccc;
cursor: col-resize;
user-select: none;
}
#splitter.dark-theme {
background: #444;
}
#splitter:hover {
background: #aaa;
}
#splitter.dark-theme:hover {
background: #666;
}
#installs-container {
flex: 1; /* Занимает оставшееся пространство */
padding: 10px;
position: relative;
min-width: 300px; /* Минимальная ширина для центральной части */
}
.install-item {
display: table;
width: 100%;
border-collapse: collapse;
margin: 2px 0;
background: #fff;
cursor: move;
color: #000;
}
.install-item.dark-theme {
background: #2a2a2a;
color: #e0e0e0;
}
.install-item.selected { background-color: #e6f7ff; }
.install-item.selected.dark-theme { background-color: #003a6d; }
.install-item > div {
display: table-cell;
vertical-align: middle;
padding: 5px;
text-align: center;
position: relative;
}
.install-item .computer-name {
width: 20%;
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.install-item .connection-id {
width: 30%;
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.install-item .install-time {
width: 25%;
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.install-item .actions { width: 25%; }
.form-container { margin-bottom: 20px; }
.jstree-node { position: relative; }
.folder-actions {
display: none;
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
margin-left: 0;
}
.jstree-node.highlight {
background-color: #d4edda !important;
border: 2px dashed #28a745;
}
.jstree-node.highlight.dark-theme {
background-color: #2f4f4f !important;
border: 2px dashed #5cb85c;
}
.jstree-default-dark .jstree-clicked {
background: #005580 !important;
color: #e0e0e0 !important;
}
.jstree-default-dark .jstree-hovered {
background: #003d66 !important;
color: #e0e0e0 !important;
}
#search-container {
margin-top: 20px;
display: flex;
align-items: center;
gap: 10px; /* Расстояние между элементами */
}
#search-container.dark-theme input, #search-container.dark-theme select {
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
}
#search-input {
width: 300px;
height: 60px;
font-size: 16px;
padding: 5px 10px;
border: 2px solid #007bff;
border-radius: 5px;
transition: border-color 0.3s ease;
}
#search-input:focus {
border-color: #0056b3;
outline: none;
}
#search-input.dark-theme {
border-color: #66b3ff;
}
#search-input.dark-theme:focus {
border-color: #99ccff;
}
#folder-select {
width: 300px;
height: 60px;
font-size: 16px;
padding: 5px 10px;
border: 2px solid #007bff;
border-radius: 5px;
transition: border-color 0.3s ease;
}
#folder-select:focus {
border-color: #0056b3;
outline: none;
}
#folder-select.dark-theme {
border-color: #66b3ff;
}
#folder-select.dark-theme:focus {
border-color: #99ccff;
}
#installs-list { margin-top: 0; }
.header {
display: table;
width: 100%;
border-collapse: collapse;
background: #f0f0f0;
cursor: pointer;
color: #000;
}
.header.dark-theme {
background: #333;
color: #e0e0e0;
}
.header > div {
display: table-cell;
padding: 5px;
text-align: center;
font-weight: bold;
}
.header > div:hover {
background: #e0e0e0;
color: #000;
}
.header.dark-theme > div:hover {
background: #444;
color: #e0e0e0;
}
.sort-arrow { font-size: 12px; margin-left: 5px; }
.protocol-icon {
margin-right: 5px;
height: 16px;
width: 16px;
vertical-align: middle;
}
.connection-link {
color: blue;
text-decoration: underline;
cursor: pointer;
margin-right: 5px;
}
.connection-link.dark-theme { color: #66b3ff; }
.edit-button, .copy-button, .action-buttons button, #add-modal button, #note-modal button, .export-import-container button, #import-label, .theme-toggle {
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
padding: 5px 10px;
font-size: 14px;
}
.edit-button:hover, .copy-button:hover, .action-buttons button:hover, #add-modal button:hover, #note-modal button:hover, .export-import-container button:hover, #import-label:hover, .theme-toggle:hover {
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
background: linear-gradient(90deg, #3d9afe 0%, #00d4fe 100%);
}
.edit-button.dark-theme, .copy-button.dark-theme, .action-buttons button.dark-theme, #add-modal button.dark-theme, #note-modal button.dark-theme, .export-import-container button.dark-theme, #import-label.dark-theme, .theme-toggle.dark-theme {
background: linear-gradient(90deg, #2a6eb4 0%, #1e90ff 100%);
}
.edit-button.dark-theme:hover, .copy-button.dark-theme:hover, .action-buttons button.dark-theme:hover, #add-modal button.dark-theme:hover, #note-modal button.dark-theme:hover, .export-import-container button.dark-theme:hover, #import-label.dark-theme:hover, .theme-toggle.dark-theme:hover {
background: linear-gradient(90deg, #1e5ea4 0%, #1c7ed6 100%);
}
#add-record-button {
display: block;
margin-top: 30px; /* Увеличенный отступ сверху */
margin-left: auto; /* Сдвиг вправо */
width: 250px;
height: 60px;
font-size: 18px;
font-weight: bold;
color: #fff;
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
border: none;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
#add-record-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
background: linear-gradient(90deg, #3d9afe 0%, #00d4fe 100%);
}
#add-record-button.dark-theme {
background: linear-gradient(90deg, #2a6eb4 0%, #1e90ff 100%);
}
#add-record-button.dark-theme:hover {
background: linear-gradient(90deg, #1e5ea4 0%, #1c7ed6 100%);
}
#import-form { margin-top: 5px; }
#import-file { display: none; }
.time-hint { font-size: 12px; color: #666; margin-top: 5px; }
.time-hint.dark-theme { color: #bbb; }
#notes-panel {
width: 25%;
padding: 10px;
border-left: 1px solid #ccc;
background: #f9f9f9;
min-height: 200px;
margin-top: 240px;
}
#notes-panel.dark-theme {
border-left-color: #444;
background: #222;
}
#notes-content { margin-top: 10px; }
#notes-content img {
max-width: 100%; /* Ограничение ширины изображений */
height: auto;
}
#add-modal, #note-modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
z-index: 1000;
width: 500px;
}
#add-modal.dark-theme, #note-modal.dark-theme {
background: #2a2a2a;
border-color: #444;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
#add-modal .markdown-buttons, #note-modal .markdown-buttons { margin-bottom: 10px; }
#add-modal .markdown-buttons button, #note-modal .markdown-buttons button { margin-right: 5px; }
#add-modal textarea, #note-modal textarea { width: 100%; height: 200px; margin-bottom: 10px; }
#add-modal.dark-theme textarea, #note-modal.dark-theme textarea {
background: #333;
color: #e0e0e0;
border: 1px solid #555;
}
#add-modal .image-upload, #note-modal .image-upload {
margin-bottom: 10px;
}
#add-modal .image-upload input, #note-modal .image-upload input {
margin-right: 10px;
}
#modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 999;
}
.header-logo { display: flex; align-items: center; }
.header-logo img { height: 50px; margin-right: 10px; }
/* Существующие стили остаются без изменений */
.status-online { background-color: green; width: 10px; height: 10px; display: inline-block; border-radius: 50%; margin-left: 5px; }
.status-offline { background-color: red; width: 10px; height: 10px; display: inline-block; border-radius: 50%; margin-left: 5px; }
/* Остальные стили остаются без изменений */
</style>
</head>
<body>
@@ -407,6 +97,7 @@
let selectedInstallId = null;
let sortField = null;
let sortDirection = 'asc';
let ws = null;
const protocolIcons = {
'rustdesk': '<img src="/icons/rustdesk.png" class="protocol-icon">',
@@ -445,14 +136,12 @@
$('#folder-tree').addClass('jstree-default-dark');
}
// Делегирование событий для кнопок "Заметка"
$('#installs-list').on('click', '.action-buttons button:nth-child(2)', function () {
const installId = $(this).closest('.install-item').data('id');
const install = allInstalls.find(i => i.id === installId);
openNoteModal(installId, install?.note || '');
});
// Логика изменения размера панели
const splitter = document.getElementById('splitter');
const treeContainer = document.getElementById('tree-container');
let isResizing = false;
@@ -469,7 +158,7 @@
const containerRect = document.body.getBoundingClientRect();
let newWidth = e.clientX - containerRect.left;
const minWidth = parseInt(getComputedStyle(treeContainer).minWidth, 10);
const maxWidth = containerRect.width * 0.5; // Максимум 50% ширины окна
const maxWidth = containerRect.width * 0.5;
if (newWidth < minWidth) newWidth = minWidth;
if (newWidth > maxWidth) newWidth = maxWidth;
@@ -482,7 +171,6 @@
document.body.style.cursor = 'default';
});
// Обработка клавиш для навигации
document.addEventListener('keydown', (e) => {
if (!selectedFolderId || !allInstalls.length) return;
@@ -505,6 +193,9 @@
updateNotesPanel();
$(items[currentIndex])[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
// Подключение к WebSocket
connectWebSocket();
});
function toggleTheme() {
@@ -665,6 +356,24 @@
});
});
function connectWebSocket() {
if (ws) {
ws.close();
}
ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = function(event) {
allInstalls = JSON.parse(event.data);
displayInstalls(allInstalls.filter(i => !selectedFolderId || i.folder_id == selectedFolderId));
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
ws.onclose = function() {
console.log('WebSocket disconnected, retrying in 5 seconds...');
setTimeout(connectWebSocket, 5000);
};
}
function loadFolders() {
$.getJSON(`${API_URL}/folders`, function (data) {
allFolders = [...data].sort((a, b) => a.name.localeCompare(b.name));
@@ -681,9 +390,7 @@
let filtered = folderId ? data.filter(i => i.folder_id == folderId && i.folder_id !== null) : data;
displayInstalls(filtered);
updateNotesPanel();
// Восстанавливаем выделение после перерендера
if (selectedInstallId) {
const items = $('#installs-list .install-item');
$(`#installs-list .install-item[data-id="${selectedInstallId}"]`).addClass('selected');
}
}).fail(function(jqxhr, textStatus, error) {
@@ -724,17 +431,21 @@
if (installs.length === 0) {
$('#installs-list').html('<p>Нет результатов</p>');
} else {
$('#installs-list').html(installs.map(item => `
<div class="install-item ${isDarkTheme ? 'dark-theme' : ''}" data-id="${item.id}" draggable="true">
<div class="computer-name">${item.computer_name} <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'computer_name', '${item.computer_name}')">✏️</button></div>
<div class="connection-id"><span class="protocol-icon">${protocolIcons[item.protocol]}</span><a href="${protocolLinks[item.protocol]}${item.rust_id}" class="connection-link ${isDarkTheme ? 'dark-theme' : ''}" target="_blank">${item.rust_id}</a> <button class="copy-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="copyToClipboard('${item.rust_id}')">📋</button> <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'rust_id', '${item.rust_id}')">✏️</button></div>
<div class="install-time">${item.install_time} <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'install_time', '${item.install_time}')">✏️</button></div>
<div class="actions"><span class="action-buttons"><button onclick="deleteInstall(${item.id})">Удалить</button> <button class="note-button">Заметка</button></span></div>
</div>
`).join(''));
$('#installs-list').html(installs.map(item => {
const lastSeen = new Date(item.last_seen || '1970-01-01');
const isOnline = (new Date() - lastSeen) < 60000; // Онлайн, если менее 1 минуты
const statusClass = isOnline ? 'status-online' : 'status-offline';
return `
<div class="install-item ${isDarkTheme ? 'dark-theme' : ''}" data-id="${item.id}" draggable="true">
<div class="computer-name">${item.computer_name} <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'computer_name', '${item.computer_name}')">✏️</button></div>
<div class="connection-id"><span class="protocol-icon">${protocolIcons[item.protocol]}</span><a href="${protocolLinks[item.protocol]}${item.rust_id}" class="connection-link ${isDarkTheme ? 'dark-theme' : ''}" target="_blank">${item.rust_id}</a> <span class="${statusClass}"></span> <button class="copy-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="copyToClipboard('${item.rust_id}')">📋</button> <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'rust_id', '${item.rust_id}')">✏️</button></div>
<div class="install-time">${item.install_time} <button class="edit-button ${isDarkTheme ? 'dark-theme' : ''}" onclick="editField(${item.id}, 'install_time', '${item.install_time}')">✏️</button></div>
<div class="actions"><span class="action-buttons"><button onclick="deleteInstall(${item.id})">Удалить</button> <button class="note-button">Заметка</button></span></div>
</div>
`;
}).join(''));
}
updateSortArrows();
// Восстанавливаем выделение после перерендера
if (selectedInstallId) {
$(`#installs-list .install-item[data-id="${selectedInstallId}"]`).addClass('selected');
}
@@ -907,7 +618,7 @@
const install = allInstalls.find(i => i.id === installId);
if (install) {
data['protocol'] = install.protocol;
data['note'] = install.note; // Сохраняем текущую заметку
data['note'] = install.note;
}
$.ajax({
url: `${API_URL}/install/${installId}`,
@@ -947,7 +658,6 @@
console.error('Install not found:', installId);
return;
}
// Передаем все текущие данные записи, чтобы ничего не очистилось
$.ajax({
url: `${API_URL}/install/${installId}`,
type: 'PUT',
@@ -958,7 +668,7 @@
install_time: install.install_time,
folder_id: folderId,
protocol: install.protocol,
note: install.note // Явно передаем заметку
note: install.note
}),
success: function () {
console.log('Move successful');
@@ -1008,7 +718,7 @@
function openNoteModal(installId, currentNote) {
console.log('Opening note modal for installId:', installId, 'with note:', currentNote);
selectedInstallId = installId; // Сохраняем выделение
selectedInstallId = installId;
$('#note-textarea').val(currentNote || '');
$('#note-modal').show();
$('#modal-overlay').show();
@@ -1063,7 +773,7 @@
const note = $('#note-textarea').val() || '';
const install = allInstalls.find(i => i.id === selectedInstallId);
if (install) {
const currentSelectedId = selectedInstallId; // Сохраняем текущий selectedInstallId
const currentSelectedId = selectedInstallId;
$.ajax({
url: `${API_URL}/install/${selectedInstallId}`,
type: 'PUT',
@@ -1078,11 +788,9 @@
}),
success: function () {
console.log('Note saved successfully');
loadInstalls(selectedFolderId); // Перезагружаем список
selectedInstallId = currentSelectedId; // Восстанавливаем selectedInstallId
closeNoteModal(); // Закрываем модальное окно
// Восстанавливаем выделение
const items = $('#installs-list .install-item');
loadInstalls(selectedFolderId);
selectedInstallId = currentSelectedId;
closeNoteModal();
$(`#installs-list .install-item[data-id="${selectedInstallId}"]`).addClass('selected');
updateNotesPanel();
},
@@ -1099,19 +807,12 @@
$('#modal-overlay').hide();
$('#note-textarea').val('');
document.getElementById('image-upload-input').value = '';
// Фокус остаётся на текущей записи
if (selectedInstallId) {
const items = $('#installs-list .install-item');
$(`#installs-list .install-item[data-id="${selectedInstallId}"]`).addClass('selected');
updateNotesPanel();
}
}
function closeAddModal() {
$('#add-modal').hide();
$('#modal-overlay').hide();
}
function updateNotesPanel() {
if (selectedInstallId) {
const install = allInstalls.find(i => i.id === selectedInstallId);