Satur@it-depot.ru 2025-03-04 17:43:38 +03:00
parent 607af48ad2
commit 01b1515172
10 changed files with 368 additions and 334 deletions

1
123 Normal file
View File

@ -0,0 +1 @@
323

8
Dockerfile Normal file
View File

@ -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"]

View File

@ -1,3 +1,2 @@
# addressbook_web
test

137
app.py Normal file
View File

@ -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=["*"],
)

View File

@ -1,23 +1,9 @@
version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
rustdesk-organizer:
build: .
ports:
- "8001:8001" # Порт для FastAPI
- "8001:8001"
volumes:
- ./db:/db # Монтирование базы данных
frontend:
image: nginx:latest
ports:
- "8081:80" # Порт для фронтенда
volumes:
- ./frontend:/usr/share/nginx/html # Монтирование фронтенда
db:
image: sqlite3:latest # Или используй существующий volume с rustdesk.db
volumes:
- ./db:/db
- ./db:/db
restart: unless-stopped

View File

@ -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>

View File

@ -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}`;
}
}
};
});

View File

@ -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;
}

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
fastapi
uvicorn

215
templates/index.html Normal file
View File

@ -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>