notes
parent
881210cd04
commit
22fa5e55af
39
app.py
39
app.py
|
|
@ -3,6 +3,7 @@ from pydantic import BaseModel
|
|||
import sqlite3
|
||||
import datetime
|
||||
import csv
|
||||
import markdown
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
import io
|
||||
|
|
@ -19,8 +20,11 @@ columns = [row[1] for row in cursor.fetchall()]
|
|||
if 'protocol' not in columns:
|
||||
cursor.execute("ALTER TABLE installs ADD COLUMN protocol TEXT DEFAULT 'rustdesk'")
|
||||
conn.commit()
|
||||
if 'note' not in columns:
|
||||
cursor.execute("ALTER TABLE installs ADD COLUMN note TEXT DEFAULT ''")
|
||||
conn.commit()
|
||||
|
||||
# Создаем таблицы (добавляем поле protocol, если таблицы нет)
|
||||
# Создаем таблицы (добавляем поле protocol и note, если таблицы нет)
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -37,6 +41,7 @@ CREATE TABLE IF NOT EXISTS installs (
|
|||
install_time TEXT,
|
||||
folder_id INTEGER,
|
||||
protocol TEXT DEFAULT 'rustdesk',
|
||||
note TEXT DEFAULT '',
|
||||
FOREIGN KEY (folder_id) REFERENCES folders(id)
|
||||
)
|
||||
""")
|
||||
|
|
@ -66,6 +71,7 @@ class InstallData(BaseModel):
|
|||
install_time: str | None = None # Принимаем строку в формате YYYY-MM-DD HH:MM:SS
|
||||
folder_id: int | None = None
|
||||
protocol: str | None = 'rustdesk' # По умолчанию RustDesk
|
||||
note: str | None = '' # Новая заметка, по умолчанию пустая
|
||||
|
||||
# Монтируем папки templates и icons как статические файлы
|
||||
app.mount("/templates", StaticFiles(directory="templates"), name="templates")
|
||||
|
|
@ -116,7 +122,7 @@ def delete_folder(folder_id: int):
|
|||
@app.get("/api/installs")
|
||||
def get_installs():
|
||||
cursor.execute("""
|
||||
SELECT i.id, i.rust_id, i.computer_name, i.install_time, i.folder_id, f.name as folder_name, i.protocol
|
||||
SELECT i.id, i.rust_id, i.computer_name, i.install_time, i.folder_id, f.name as folder_name, i.protocol, i.note
|
||||
FROM installs i
|
||||
LEFT JOIN folders f ON i.folder_id = f.id
|
||||
""")
|
||||
|
|
@ -139,7 +145,7 @@ def get_installs():
|
|||
|
||||
return [{"id": row[0], "rust_id": row[1], "computer_name": row[2],
|
||||
"install_time": format_time(row[3]),
|
||||
"folder_id": row[4], "folder_name": row[5], "protocol": row[6]}
|
||||
"folder_id": row[4], "folder_name": row[5], "protocol": row[6], "note": row[7]}
|
||||
for row in rows]
|
||||
|
||||
@app.post("/api/install")
|
||||
|
|
@ -147,14 +153,15 @@ def add_install(data: InstallData):
|
|||
install_time = data.install_time or datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Форматируем в читаемый формат
|
||||
folder_id = data.folder_id if data.folder_id is not None else unsorted_folder_id
|
||||
protocol = data.protocol or 'rustdesk' # По умолчанию RustDesk для POST-запросов
|
||||
cursor.execute("INSERT INTO installs (rust_id, computer_name, install_time, folder_id, protocol) VALUES (?, ?, ?, ?, ?)",
|
||||
(data.rust_id, data.computer_name, install_time, folder_id, protocol))
|
||||
note = data.note or '' # Новая заметка, по умолчанию пустая
|
||||
cursor.execute("INSERT INTO installs (rust_id, computer_name, install_time, folder_id, protocol, note) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(data.rust_id, data.computer_name, install_time, folder_id, protocol, note))
|
||||
conn.commit()
|
||||
return {"status": "success"}
|
||||
|
||||
@app.put("/api/install/{install_id}")
|
||||
def update_install(install_id: int, data: InstallData):
|
||||
cursor.execute("SELECT rust_id, computer_name, install_time, folder_id, protocol FROM installs WHERE id = ?", (install_id,))
|
||||
cursor.execute("SELECT rust_id, computer_name, install_time, folder_id, protocol, note FROM installs WHERE id = ?", (install_id,))
|
||||
current = cursor.fetchone()
|
||||
if not current:
|
||||
raise HTTPException(status_code=404, detail="Запись не найдена")
|
||||
|
|
@ -164,6 +171,7 @@ def update_install(install_id: int, data: InstallData):
|
|||
new_install_time = data.install_time if data.install_time is not None else current[2]
|
||||
new_folder_id = data.folder_id if data.folder_id is not None else current[3]
|
||||
new_protocol = data.protocol if data.protocol is not None else current[4]
|
||||
new_note = data.note if data.note is not None else current[5]
|
||||
|
||||
# Форматируем время, если оно предоставлено в запросе
|
||||
if new_install_time:
|
||||
|
|
@ -177,9 +185,9 @@ def update_install(install_id: int, data: InstallData):
|
|||
|
||||
cursor.execute("""
|
||||
UPDATE installs
|
||||
SET rust_id = ?, computer_name = ?, install_time = ?, folder_id = ?, protocol = ?
|
||||
SET rust_id = ?, computer_name = ?, install_time = ?, folder_id = ?, protocol = ?, note = ?
|
||||
WHERE id = ?
|
||||
""", (new_rust_id, new_computer_name, new_install_time, new_folder_id, new_protocol, install_id))
|
||||
""", (new_rust_id, new_computer_name, new_install_time, new_folder_id, new_protocol, new_note, install_id))
|
||||
conn.commit()
|
||||
return {"status": "success"}
|
||||
|
||||
|
|
@ -196,14 +204,14 @@ def delete_install(install_id: int):
|
|||
async def export_csv(folder_id: int | None = Query(None, description="ID папки для экспорта, если None - экспортировать все папки")):
|
||||
if folder_id:
|
||||
cursor.execute("""
|
||||
SELECT i.rust_id, i.computer_name, i.install_time, f.name as folder_name, i.protocol
|
||||
SELECT i.rust_id, i.computer_name, i.install_time, f.name as folder_name, i.protocol, i.note
|
||||
FROM installs i
|
||||
LEFT JOIN folders f ON i.folder_id = f.id
|
||||
WHERE i.folder_id = ?
|
||||
""", (folder_id,))
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT i.rust_id, i.computer_name, i.install_time, f.name as folder_name, i.protocol
|
||||
SELECT i.rust_id, i.computer_name, i.install_time, f.name as folder_name, i.protocol, i.note
|
||||
FROM installs i
|
||||
LEFT JOIN folders f ON i.folder_id = f.id
|
||||
""")
|
||||
|
|
@ -211,11 +219,11 @@ async def export_csv(folder_id: int | None = Query(None, description="ID пап
|
|||
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output, lineterminator='\n')
|
||||
writer.writerow(['ID подключения', 'Имя компьютера', 'Время установки', 'Папка', 'Протокол'])
|
||||
writer.writerow(['ID подключения', 'Имя компьютера', 'Время установки', 'Папка', 'Протокол', 'Заметка'])
|
||||
for row in rows:
|
||||
# Обработка времени в разных форматах
|
||||
install_time = format_time(row[2]) if row[2] else ""
|
||||
writer.writerow([row[0], row[1], install_time, row[3], row[4]])
|
||||
writer.writerow([row[0], row[1], install_time, row[3], row[4], row[5]])
|
||||
|
||||
headers = {
|
||||
'Content-Disposition': 'attachment; filename="rustdesk_data.csv"',
|
||||
|
|
@ -253,6 +261,7 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query
|
|||
install_time = row['Время установки']
|
||||
folder_name = row.get('Папка', None)
|
||||
protocol = row.get('Протокол', 'rustdesk')
|
||||
note = row.get('Заметка', '')
|
||||
|
||||
# Проверяем, существует ли запись с таким rust_id
|
||||
cursor.execute("SELECT id FROM installs WHERE rust_id = ?", (rust_id,))
|
||||
|
|
@ -282,9 +291,9 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query
|
|||
folder_id = target_folder_id
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO installs (rust_id, computer_name, install_time, folder_id, protocol)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (rust_id, computer_name, install_time, folder_id, protocol))
|
||||
INSERT INTO installs (rust_id, computer_name, install_time, folder_id, protocol, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (rust_id, computer_name, install_time, folder_id, protocol, note))
|
||||
|
||||
conn.commit()
|
||||
return {"status": "success", "message": "Данные успешно импортированы"}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <!-- Библиотека для Markdown -->
|
||||
<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; }
|
||||
|
|
@ -29,6 +30,12 @@
|
|||
#import-label:hover { background: #0056b3; }
|
||||
.folder-select { margin: 5px 0; }
|
||||
.time-hint { font-size: 12px; color: #666; margin-top: 5px; }
|
||||
#notes-panel { width: 100%; padding: 10px; border-left: 1px solid #ccc; background: #f9f9f9; min-height: 200px; }
|
||||
#notes-content { margin-top: 10px; }
|
||||
#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; }
|
||||
#note-modal textarea { width: 100%; height: 200px; margin-bottom: 10px; }
|
||||
#note-modal button { 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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -71,6 +78,7 @@
|
|||
<option value="vnc">VNC</option>
|
||||
<option value="rdp">RDP</option>
|
||||
</select>
|
||||
<textarea id="note" placeholder="Заметка (Markdown поддерживается)"></textarea>
|
||||
<button onclick="addInstall()">Добавить</button>
|
||||
<div class="time-hint">Пример: 2025-03-05 14:30:00</div>
|
||||
</div>
|
||||
|
|
@ -82,11 +90,25 @@
|
|||
</div>
|
||||
<div id="installs-list"></div>
|
||||
</div>
|
||||
<div id="notes-panel">
|
||||
<h2>Заметка</h2>
|
||||
<div id="notes-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно для редактирования заметок -->
|
||||
<div id="modal-overlay"></div>
|
||||
<div id="note-modal">
|
||||
<h3>Редактировать заметку</h3>
|
||||
<textarea id="note-textarea" placeholder="Введите заметку (Markdown поддерживается)"></textarea>
|
||||
<button onclick="saveNote()">Сохранить</button>
|
||||
<button onclick="closeNoteModal()">Закрыть</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_URL = "/api";
|
||||
let selectedFolderId = null;
|
||||
let allInstalls = [];
|
||||
let selectedInstallId = null;
|
||||
let sortField = null;
|
||||
let sortDirection = 'asc';
|
||||
|
||||
|
|
@ -181,6 +203,7 @@
|
|||
allInstalls = data;
|
||||
let filtered = folderId ? data.filter(i => i.folder_id == folderId) : data.filter(i => i.folder_id === null);
|
||||
displayInstalls(filtered);
|
||||
updateNotesPanel(); // Обновляем заметки после загрузки
|
||||
});
|
||||
selectedFolderId = folderId;
|
||||
}
|
||||
|
|
@ -192,8 +215,9 @@
|
|||
<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;">
|
||||
<button onclick="editInstall(${item.id}, '${item.rust_id}', '${item.computer_name}', '${item.install_time}', '${item.protocol}')">Редактировать</button>
|
||||
<button onclick="editInstall(${item.id}, '${item.rust_id}', '${item.computer_name}', '${item.install_time}', '${item.protocol}', '${item.note}')">Редактировать</button>
|
||||
<button onclick="deleteInstall(${item.id})">Удалить</button>
|
||||
<button onclick="openNoteModal(${item.id}, '${item.note}')">Заметка</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join(''));
|
||||
|
|
@ -306,6 +330,7 @@
|
|||
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)");
|
||||
|
|
@ -316,7 +341,7 @@
|
|||
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 }),
|
||||
data: JSON.stringify({ rust_id: rustId, computer_name: computerName, install_time: installTime, folder_id: selectedFolderId, protocol: protocol, note: note }),
|
||||
success: function () {
|
||||
loadInstalls(selectedFolderId);
|
||||
},
|
||||
|
|
@ -330,11 +355,12 @@
|
|||
});
|
||||
}
|
||||
|
||||
function editInstall(id, rustId, computerName, installTime, protocol) {
|
||||
function editInstall(id, rustId, computerName, installTime, protocol, note) {
|
||||
const newRustId = prompt("ID подключения:", rustId);
|
||||
const newComputerName = prompt("Имя компьютера:", computerName);
|
||||
let newInstallTime = prompt("Время установки (опционально, формат: ГГГГ-ММ-ДД ЧЧ:ММ:СС):", installTime || '');
|
||||
const newProtocol = prompt("Протокол (rustdesk, anydesk, ammyy, teamviewer, vnc, rdp):", protocol);
|
||||
const newNote = prompt("Заметка (Markdown поддерживается):", note || '');
|
||||
|
||||
if (newInstallTime && !/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$/.test(newInstallTime)) {
|
||||
alert("Неверный формат времени. Используйте ГГГГ-ММ-ДД ЧЧ:ММ:СС (например, 2025-03-05 14:30:00)");
|
||||
|
|
@ -346,7 +372,7 @@
|
|||
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 }),
|
||||
data: JSON.stringify({ rust_id: newRustId, computer_name: newComputerName, install_time: newInstallTime, folder_id: selectedFolderId, protocol: newProtocol, note: newNote }),
|
||||
success: function () {
|
||||
loadInstalls(selectedFolderId);
|
||||
},
|
||||
|
|
@ -425,6 +451,56 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)); // Рендеринг Markdown
|
||||
} else {
|
||||
$('#notes-content').html('<p>Нет заметок</p>');
|
||||
}
|
||||
} else {
|
||||
$('#notes-content').html('<p>Выберите подключение для просмотра заметки</p>');
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем заметки при клике на подключение
|
||||
$('#installs-list').on('click', '.install-item', function () {
|
||||
selectedInstallId = $(this).data('id');
|
||||
updateNotesPanel();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue