New
parent
607af48ad2
commit
01b1515172
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
COPY templates/ ./templates/
|
||||||
|
EXPOSE 8001
|
||||||
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import sqlite3
|
||||||
|
import datetime
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Соединение с базой данных
|
||||||
|
conn = sqlite3.connect("/db/rustdesk.db", check_same_thread=False)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Создаем таблицы
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS folders (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
parent_id INTEGER,
|
||||||
|
FOREIGN KEY (parent_id) REFERENCES folders(id)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS installs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
rust_id TEXT,
|
||||||
|
computer_name TEXT,
|
||||||
|
install_time TEXT,
|
||||||
|
folder_id INTEGER,
|
||||||
|
FOREIGN KEY (folder_id) REFERENCES folders(id)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# Модели данных
|
||||||
|
class Folder(BaseModel):
|
||||||
|
name: str
|
||||||
|
parent_id: int | None = None
|
||||||
|
|
||||||
|
class FolderUpdate(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class InstallData(BaseModel):
|
||||||
|
rust_id: str
|
||||||
|
computer_name: str
|
||||||
|
install_time: str | None = None
|
||||||
|
folder_id: int | None = None
|
||||||
|
|
||||||
|
# Монтируем папку templates как статические файлы
|
||||||
|
app.mount("/templates", StaticFiles(directory="templates"), name="templates")
|
||||||
|
|
||||||
|
# Главная страница
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return FileResponse("templates/index.html")
|
||||||
|
|
||||||
|
# --- Folders API ---
|
||||||
|
@app.get("/api/folders")
|
||||||
|
def get_folders():
|
||||||
|
cursor.execute("SELECT * FROM folders")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
return [{"id": row[0], "name": row[1], "parent_id": row[2]} for row in rows]
|
||||||
|
|
||||||
|
@app.post("/api/folders")
|
||||||
|
def add_folder(folder: Folder):
|
||||||
|
cursor.execute("INSERT INTO folders (name, parent_id) VALUES (?, ?)",
|
||||||
|
(folder.name, folder.parent_id))
|
||||||
|
conn.commit()
|
||||||
|
return {"status": "success", "id": cursor.lastrowid}
|
||||||
|
|
||||||
|
@app.put("/api/folders/{folder_id}")
|
||||||
|
def update_folder(folder_id: int, folder: FolderUpdate):
|
||||||
|
cursor.execute("UPDATE folders SET name = ? WHERE id = ?", (folder.name, folder_id))
|
||||||
|
conn.commit()
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
raise HTTPException(status_code=404, detail="Папка не найдена")
|
||||||
|
return {"status": "success"}
|
||||||
|
|
||||||
|
@app.delete("/api/folders/{folder_id}")
|
||||||
|
def delete_folder(folder_id: int):
|
||||||
|
cursor.execute("DELETE FROM folders WHERE id = ?", (folder_id,))
|
||||||
|
conn.commit()
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
raise HTTPException(status_code=404, detail="Папка не найдена")
|
||||||
|
return {"status": "success"}
|
||||||
|
|
||||||
|
# --- Installs API ---
|
||||||
|
@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
|
||||||
|
FROM installs i
|
||||||
|
LEFT JOIN folders f ON i.folder_id = f.id
|
||||||
|
""")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
return [{"id": row[0], "rust_id": row[1], "computer_name": row[2],
|
||||||
|
"install_time": row[3], "folder_id": row[4], "folder_name": row[5]}
|
||||||
|
for row in rows]
|
||||||
|
|
||||||
|
@app.post("/api/install")
|
||||||
|
def add_install(data: InstallData):
|
||||||
|
install_time = data.install_time or datetime.datetime.now().isoformat()
|
||||||
|
cursor.execute("INSERT INTO installs (rust_id, computer_name, install_time, folder_id) VALUES (?, ?, ?, ?)",
|
||||||
|
(data.rust_id, data.computer_name, install_time, data.folder_id))
|
||||||
|
conn.commit()
|
||||||
|
return {"status": "success"}
|
||||||
|
|
||||||
|
@app.put("/api/install/{install_id}")
|
||||||
|
def update_install(install_id: int, data: InstallData):
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE installs
|
||||||
|
SET rust_id = ?, computer_name = ?, install_time = ?, folder_id = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""", (data.rust_id, data.computer_name, data.install_time or datetime.datetime.now().isoformat(),
|
||||||
|
data.folder_id, install_id))
|
||||||
|
conn.commit()
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
raise HTTPException(status_code=404, detail="Запись не найдена")
|
||||||
|
return {"status": "success"}
|
||||||
|
|
||||||
|
@app.delete("/api/install/{install_id}")
|
||||||
|
def delete_install(install_id: int):
|
||||||
|
cursor.execute("DELETE FROM installs WHERE id = ?", (install_id,))
|
||||||
|
conn.commit()
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
raise HTTPException(status_code=404, detail="Запись не найдена")
|
||||||
|
return {"status": "success"}
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["http://10.0.0.10:8001", "http://localhost:8001"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
@ -1,23 +1,9 @@
|
||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
rustdesk-organizer:
|
||||||
build:
|
build: .
|
||||||
context: ./backend
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ports:
|
ports:
|
||||||
- "8001:8001" # Порт для FastAPI
|
- "8001:8001"
|
||||||
volumes:
|
volumes:
|
||||||
- ./db:/db # Монтирование базы данных
|
- ./db:/db
|
||||||
|
restart: unless-stopped
|
||||||
frontend:
|
|
||||||
image: nginx:latest
|
|
||||||
ports:
|
|
||||||
- "8081:80" # Порт для фронтенда
|
|
||||||
volumes:
|
|
||||||
- ./frontend:/usr/share/nginx/html # Монтирование фронтенда
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: sqlite3:latest # Или используй существующий volume с rustdesk.db
|
|
||||||
volumes:
|
|
||||||
- ./db:/db
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Адресная книга подключений</title>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
||||||
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.min.css">
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
<script src="script.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Адресная книга подключений</h1>
|
|
||||||
|
|
||||||
<!-- Панель управления папками -->
|
|
||||||
<div class="folder-controls">
|
|
||||||
<h2>Управление папками</h2>
|
|
||||||
<input type="text" id="folderName" placeholder="Имя папки">
|
|
||||||
<select id="parentFolder">
|
|
||||||
<option value="">Корневая папка</option>
|
|
||||||
</select>
|
|
||||||
<button onclick="createFolder()">Создать папку</button>
|
|
||||||
<button onclick="editFolder()">Редактировать папку</button>
|
|
||||||
<button onclick="deleteFolder()">Удалить папку</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма для добавления подключения -->
|
|
||||||
<div class="connection-controls">
|
|
||||||
<h2>Добавить подключение</h2>
|
|
||||||
<form id="addConnectionForm">
|
|
||||||
<input type="text" id="connectionId" placeholder="ID подключения" required>
|
|
||||||
<input type="text" id="connectionName" placeholder="Имя подключения" required>
|
|
||||||
<select id="connectionType">
|
|
||||||
<option value="RustDesk">RustDesk</option>
|
|
||||||
<option value="SSH">SSH</option>
|
|
||||||
<option value="RDP">RDP</option>
|
|
||||||
</select>
|
|
||||||
<select id="connectionFolder">
|
|
||||||
<option value="">Без папки</option>
|
|
||||||
</select>
|
|
||||||
<button type="submit">Добавить</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Таблица подключений -->
|
|
||||||
<table id="connectionsTable" class="display">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Connection ID</th>
|
|
||||||
<th>Имя</th>
|
|
||||||
<th>Тип</th>
|
|
||||||
<th>Папка</th>
|
|
||||||
<th>Действие</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,239 +0,0 @@
|
||||||
const API_URL = "http://localhost:8001/api";
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
loadFolders();
|
|
||||||
loadConnections();
|
|
||||||
|
|
||||||
// Загрузка папок
|
|
||||||
function loadFolders() {
|
|
||||||
$.getJSON(`${API_URL}/folders`, function (folders) {
|
|
||||||
$('#parentFolder, #connectionFolder').empty();
|
|
||||||
$('#parentFolder, #connectionFolder').append('<option value="">Корневая папка</option>');
|
|
||||||
folders.forEach(folder => {
|
|
||||||
$('#parentFolder, #connectionFolder').append(
|
|
||||||
`<option value="${folder.id}">${folder.name} (ID: ${folder.id})</option>`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузка подключений в таблицу
|
|
||||||
function loadConnections() {
|
|
||||||
$.getJSON(`${API_URL}/connections`, function (data) {
|
|
||||||
let table = $('#connectionsTable').DataTable({
|
|
||||||
"destroy": true,
|
|
||||||
"data": data,
|
|
||||||
"columns": [
|
|
||||||
{ "data": "id" },
|
|
||||||
{
|
|
||||||
"data": "connection_id",
|
|
||||||
"render": function (data, type, row) {
|
|
||||||
if (row.type === "RustDesk") {
|
|
||||||
return `<a href="rustdesk://${data}" onclick="openRustDesk('${data}'); return false;">${data}</a>`;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "data": "name" },
|
|
||||||
{ "data": "type" },
|
|
||||||
{
|
|
||||||
"data": "folder_id",
|
|
||||||
"render": function (data, type, row) {
|
|
||||||
return data ? getFolderName(data) : "Без папки";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"data": "id",
|
|
||||||
"render": function (data) {
|
|
||||||
return `
|
|
||||||
<button onclick="editConnection(${data})">Редактировать</button>
|
|
||||||
<button onclick="deleteConnection(${data})">Удалить</button>
|
|
||||||
<button onclick="moveConnection(${data})">Переместить</button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получение имени папки по ID
|
|
||||||
function getFolderName(folderId) {
|
|
||||||
let folderName = "Без папки";
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/folders/${folderId}`,
|
|
||||||
async: false,
|
|
||||||
success: function (folder) {
|
|
||||||
folderName = folder.name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return folderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создание папки
|
|
||||||
window.createFolder = function () {
|
|
||||||
let name = $('#folderName').val();
|
|
||||||
let parentId = $('#parentFolder').val() || null;
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/folders`,
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({ name, parent_id: parentId }),
|
|
||||||
success: function () {
|
|
||||||
alert("Папка создана!");
|
|
||||||
loadFolders();
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Редактирование папки (пример, нужно уточнить UI)
|
|
||||||
window.editFolder = function () {
|
|
||||||
let folderId = prompt("Введите ID папки для редактирования:");
|
|
||||||
if (folderId) {
|
|
||||||
let name = prompt("Новое имя папки:");
|
|
||||||
let parentId = prompt("ID родительской папки (оставьте пустым для корневой):") || null;
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/folders/${folderId}`,
|
|
||||||
type: 'PUT',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({ name, parent_id: parentId }),
|
|
||||||
success: function () {
|
|
||||||
alert("Папка обновлена!");
|
|
||||||
loadFolders();
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Удаление папки
|
|
||||||
window.deleteFolder = function () {
|
|
||||||
let folderId = prompt("Введите ID папки для удаления:");
|
|
||||||
if (folderId && confirm("Вы уверены?")) {
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/folders/${folderId}`,
|
|
||||||
type: 'DELETE',
|
|
||||||
success: function () {
|
|
||||||
alert("Папка удалена!");
|
|
||||||
loadFolders();
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Добавление подключения
|
|
||||||
$('#addConnectionForm').submit(function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
let connectionId = $('#connectionId').val();
|
|
||||||
let name = $('#connectionName').val();
|
|
||||||
let type = $('#connectionType').val();
|
|
||||||
let folderId = $('#connectionFolder').val() || null;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/connections`,
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({ connection_id: connectionId, name, type, folder_id: folderId }),
|
|
||||||
success: function () {
|
|
||||||
alert("Подключение добавлено!");
|
|
||||||
loadConnections();
|
|
||||||
$('#addConnectionForm')[0].reset();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Редактирование подключения
|
|
||||||
window.editConnection = function (id) {
|
|
||||||
let connection = promptConnectionDetails(id);
|
|
||||||
if (connection) {
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/connections/${id}`,
|
|
||||||
type: 'PUT',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(connection),
|
|
||||||
success: function () {
|
|
||||||
alert("Подключение обновлено!");
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Удаление подключения
|
|
||||||
window.deleteConnection = function (id) {
|
|
||||||
if (confirm("Вы уверены, что хотите удалить подключение?")) {
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/connections/${id}`,
|
|
||||||
type: 'DELETE',
|
|
||||||
success: function () {
|
|
||||||
alert("Подключение удалено!");
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Перемещение подключения
|
|
||||||
window.moveConnection = function (id) {
|
|
||||||
let folderId = prompt("Введите ID папки для перемещения (оставьте пустым для корневой):") || null;
|
|
||||||
$.ajax({
|
|
||||||
url: `${API_URL}/connections/${id}`,
|
|
||||||
type: 'PUT',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({ folder_id: folderId }),
|
|
||||||
success: function () {
|
|
||||||
alert("Подключение перемещено!");
|
|
||||||
loadConnections();
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert("Ошибка: " + xhr.responseJSON.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция для запроса деталей подключения через prompt (можно улучшить с модальным окном)
|
|
||||||
function promptConnectionDetails(id) {
|
|
||||||
let connectionId = prompt("Новый ID подключения:");
|
|
||||||
let name = prompt("Новое имя подключения:");
|
|
||||||
let type = prompt("Новый тип подключения (RustDesk, SSH, RDP):");
|
|
||||||
let folderId = prompt("ID папки (оставьте пустым для корневой):") || null;
|
|
||||||
if (connectionId && name && type) {
|
|
||||||
return { connection_id: connectionId, name, type, folder_id: folderId };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка клика по RustDesk ссылке
|
|
||||||
window.openRustDesk = function(rustId) {
|
|
||||||
if (confirm("Подключиться к устройству с ID " + rustId + "?")) {
|
|
||||||
if (typeof navigator.msLaunchUri !== 'undefined') {
|
|
||||||
navigator.msLaunchUri(`rustdesk://${rustId}`,
|
|
||||||
function() { console.log("Успешно запущен RustDesk"); },
|
|
||||||
function() { alert("Не удалось запустить RustDesk. Убедитесь, что RustDesk установлен и зарегистрирован для обработки rustdesk://."); }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.location.href = `rustdesk://${rustId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-controls, .connection-controls {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, select, button {
|
|
||||||
margin: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
<!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; }
|
||||||
|
.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; }
|
||||||
|
</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 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 id="installs-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_URL = "/api";
|
||||||
|
let selectedFolderId = null;
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Инициализация дерева папок
|
||||||
|
$('#folder-tree').jstree({
|
||||||
|
'core': {
|
||||||
|
'data': function (node, cb) {
|
||||||
|
$.getJSON(`${API_URL}/folders`, function (data) {
|
||||||
|
const treeData = 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 : '#'
|
||||||
|
}));
|
||||||
|
cb(treeData);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'check_callback': true
|
||||||
|
},
|
||||||
|
'plugins': ['dnd', 'html_data']
|
||||||
|
}).on('select_node.jstree', function (e, data) {
|
||||||
|
selectedFolderId = data.node.id;
|
||||||
|
loadInstalls(selectedFolderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(); // Разрешаем drop
|
||||||
|
}).on('drop', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const installId = e.originalEvent.dataTransfer.getData('text');
|
||||||
|
const folderId = $(e.target).closest('.jstree-node').attr('id');
|
||||||
|
if (installId && folderId) {
|
||||||
|
updateInstallFolder(installId, folderId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загрузка записей без выбранной папки
|
||||||
|
loadInstalls(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadInstalls(folderId) {
|
||||||
|
$.getJSON(`${API_URL}/installs`, function (data) {
|
||||||
|
const filtered = folderId ? data.filter(i => i.folder_id == folderId) : data;
|
||||||
|
$('#installs-list').html(filtered.map(item => `
|
||||||
|
<div class="install-item" data-id="${item.id}" draggable="true">
|
||||||
|
<a href="rustdesk://${item.rust_id}" onclick="openRustDesk('${item.rust_id}'); return false;">${item.rust_id}</a>
|
||||||
|
- ${item.computer_name} (${item.install_time})
|
||||||
|
<button onclick="editInstall(${item.id}, '${item.rust_id}', '${item.computer_name}', '${item.install_time}')">Редактировать</button>
|
||||||
|
<button onclick="deleteInstall(${item.id})">Удалить</button>
|
||||||
|
</div>
|
||||||
|
`).join(''));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
// Перемещаем записи в корень (folder_id = null)
|
||||||
|
$.getJSON(`${API_URL}/installs`, function (data) {
|
||||||
|
const folderInstalls = data.filter(i => i.folder_id == folderId);
|
||||||
|
folderInstalls.forEach(item => {
|
||||||
|
updateInstallFolder(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 updateInstallFolder(installId, folderId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_URL}/install/${installId}`,
|
||||||
|
type: 'PUT',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({ folder_id: folderId === 'undefined' ? null : 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>
|
||||||
Loading…
Reference in New Issue