main
Satur@it-depot.ru 2025-03-05 17:45:16 +03:00
parent 6851b39c28
commit a1325017b0
4 changed files with 64 additions and 77 deletions

1
123
View File

@ -1 +0,0 @@
323

View File

@ -5,5 +5,9 @@ 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"]
# Экспонируем оба порта
EXPOSE 8001 8002
# Запускаем два процесса uvicorn
CMD ["sh", "-c", "uvicorn app:web_app --host 0.0.0.0 --port 8001 & uvicorn app:api_app --host 0.0.0.0 --port 8002"]

129
app.py
View File

@ -8,13 +8,15 @@ from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, StreamingResponse
import io
app = FastAPI()
# Создаем два экземпляра FastAPI
web_app = FastAPI(title="Web Interface") # Для веб-интерфейса (порт 8001)
api_app = FastAPI(title="API Endpoints") # Для API (порт 8002)
# Соединение с базой данных
# Общий код для подключения к базе данных
conn = sqlite3.connect("/db/rustdesk.db", check_same_thread=False)
cursor = conn.cursor()
# Проверяем и обновляем структуру таблицы installs
# Проверяем и обновляем структуру таблицы installs (как в исходном коде)
cursor.execute("PRAGMA table_info(installs)")
columns = [row[1] for row in cursor.fetchall()]
if 'protocol' not in columns:
@ -24,7 +26,7 @@ if 'note' not in columns:
cursor.execute("ALTER TABLE installs ADD COLUMN note TEXT DEFAULT ''")
conn.commit()
# Создаем таблицы (добавляем поле protocol и note, если таблицы нет)
# Создаем таблицы (как в исходном коде)
cursor.execute("""
CREATE TABLE IF NOT EXISTS folders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -47,7 +49,7 @@ CREATE TABLE IF NOT EXISTS installs (
""")
conn.commit()
# Проверяем/создаем папку "Несортированные" и получаем её ID
# Проверяем/создаем папку "Несортированные" и получаем её ID (как в исходном коде)
cursor.execute("SELECT id FROM folders WHERE name = 'Несортированные'")
unsorted_folder = cursor.fetchone()
if not unsorted_folder:
@ -57,7 +59,7 @@ if not unsorted_folder:
else:
unsorted_folder_id = unsorted_folder[0]
# Модели данных
# Модели данных (как в исходном коде)
class Folder(BaseModel):
name: str
parent_id: int | None = None
@ -68,35 +70,49 @@ class FolderUpdate(BaseModel):
class InstallData(BaseModel):
rust_id: str | None = None
computer_name: str | None = None
install_time: str | None = None # Принимаем строку в формате YYYY-MM-DD HH:MM:SS
install_time: str | None = None
folder_id: int | None = None
protocol: str | None = 'rustdesk' # По умолчанию RustDesk
note: str | None = '' # Новая заметка, по умолчанию пустая
protocol: str | None = 'rustdesk'
note: str | None = ''
# Монтируем папки templates и icons как статические файлы
app.mount("/templates", StaticFiles(directory="templates"), name="templates")
app.mount("/icons", StaticFiles(directory="templates/icons"), name="icons")
# Функция форматирования времени (как в исходном коде)
def format_time(time_str):
if not time_str:
return None
try:
dt = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%fZ")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
try:
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
return time_str
# Главная страница
@app.get("/")
# Монтируем папки templates и icons как статические файлы для веб-интерфейса
web_app.mount("/templates", StaticFiles(directory="templates"), name="templates")
web_app.mount("/icons", StaticFiles(directory="templates/icons"), name="icons")
# Веб-интерфейс (только GET-запросы и статические файлы)
@web_app.get("/")
async def root():
return FileResponse("templates/index.html")
# --- Folders API ---
@app.get("/api/folders")
# API-эндпоинты (POST, PUT, DELETE и т.д.) — только для порта 8002
@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")
@api_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}")
@api_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()
@ -104,9 +120,8 @@ def update_folder(folder_id: int, folder: FolderUpdate):
raise HTTPException(status_code=404, detail="Папка не найдена")
return {"status": "success"}
@app.delete("/api/folders/{folder_id}")
@api_app.delete("/api/folders/{folder_id}")
def delete_folder(folder_id: int):
# Проверяем, не является ли папка "Несортированные"
cursor.execute("SELECT name FROM folders WHERE id = ?", (folder_id,))
folder_name = cursor.fetchone()
if folder_name and folder_name[0] == 'Несортированные':
@ -118,8 +133,7 @@ def delete_folder(folder_id: int):
raise HTTPException(status_code=404, detail="Папка не найдена")
return {"status": "success"}
# --- Installs API ---
@app.get("/api/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, i.protocol, i.note
@ -127,39 +141,23 @@ def get_installs():
LEFT JOIN folders f ON i.folder_id = f.id
""")
rows = cursor.fetchall()
# Обработка времени в разных форматах
def format_time(time_str):
if not time_str:
return None
try:
# Пробуем разобрать ISO 8601 (с T и Z)
dt = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%fZ")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
try:
# Пробуем разобрать наш читаемый формат
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
return time_str # Возвращаем как есть, если формат не распознан
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], "note": row[7]}
for row in rows]
@app.post("/api/install")
@api_app.post("/api/install")
def add_install(data: InstallData):
install_time = data.install_time or datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Форматируем в читаемый формат
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-запросов
note = data.note or '' # Новая заметка, по умолчанию пустая
protocol = data.protocol or 'rustdesk'
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}")
@api_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, note FROM installs WHERE id = ?", (install_id,))
current = cursor.fetchone()
@ -173,10 +171,8 @@ def update_install(install_id: int, data: InstallData):
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:
try:
# Проверяем, что время в формате YYYY-MM-DD HH:MM:SS
datetime.datetime.strptime(new_install_time, "%Y-%m-%d %H:%M:%S")
except ValueError:
raise HTTPException(status_code=400, detail="Неверный формат времени. Используйте YYYY-MM-DD HH:MM:SS")
@ -191,7 +187,7 @@ def update_install(install_id: int, data: InstallData):
conn.commit()
return {"status": "success"}
@app.delete("/api/install/{install_id}")
@api_app.delete("/api/install/{install_id}")
def delete_install(install_id: int):
cursor.execute("DELETE FROM installs WHERE id = ?", (install_id,))
conn.commit()
@ -199,8 +195,7 @@ def delete_install(install_id: int):
raise HTTPException(status_code=404, detail="Запись не найдена")
return {"status": "success"}
# --- CSV Export/Import API ---
@app.get("/api/export/csv")
@api_app.get("/api/export/csv")
async def export_csv(folder_id: int | None = Query(None, description="ID папки для экспорта, если None - экспортировать все папки")):
if folder_id:
cursor.execute("""
@ -221,7 +216,6 @@ async def export_csv(folder_id: int | None = Query(None, description="ID пап
writer = csv.writer(output, lineterminator='\n')
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], row[5]])
@ -231,22 +225,7 @@ async def export_csv(folder_id: int | None = Query(None, description="ID пап
}
return StreamingResponse(iter([output.getvalue()]), headers=headers)
def format_time(time_str):
if not time_str:
return None
try:
# Пробуем разобрать ISO 8601 (с T и Z)
dt = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%fZ")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
try:
# Пробуем разобрать наш читаемый формат
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
return time_str # Возвращаем как есть, если формат не распознан
@app.post("/api/import/csv")
@api_app.post("/api/import/csv")
async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query(None, description="ID папки для импорта, если None - использовать 'Несортированные'")):
try:
contents = await file.read()
@ -263,26 +242,21 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query
protocol = row.get('Протокол', 'rustdesk')
note = row.get('Заметка', '')
# Проверяем, существует ли запись с таким rust_id
cursor.execute("SELECT id FROM installs WHERE rust_id = ?", (rust_id,))
if cursor.fetchone():
continue # Пропускаем дублирующуюся запись
continue
# Проверяем и форматируем время
if install_time:
try:
# Пробуем разобрать время в формате YYYY-MM-DD HH:MM:SS
dt = datetime.datetime.strptime(install_time, "%Y-%m-%d %H:%M:%S")
install_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
try:
# Пробуем разобрать ISO 8601 (если в CSV другой формат)
dt = datetime.datetime.strptime(install_time, "%Y-%m-%dT%H:%M:%S.%fZ")
install_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
raise HTTPException(status_code=400, detail=f"Неверный формат времени для записи с ID {rust_id}. Используйте YYYY-MM-DD HH:MM:SS или ISO 8601")
# Получаем или создаем ID папки
if folder_name:
cursor.execute("SELECT id FROM folders WHERE name = ?", (folder_name,))
folder = cursor.fetchone()
@ -300,9 +274,18 @@ async def import_csv(file: UploadFile = File(...), folder_id: int | None = Query
except Exception as e:
raise HTTPException(status_code=400, detail=f"Ошибка импорта: {str(e)}")
# CORS
# CORS для API (можно настроить для конкретных источников)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
api_app.add_middleware(
CORSMiddleware,
allow_origins=["http://10.0.0.10:8001", "http://localhost:8001"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# CORS для веб-интерфейса (если нужно)
web_app.add_middleware(
CORSMiddleware,
allow_origins=["http://10.0.0.10:8001", "http://localhost:8001"],
allow_credentials=True,

View File

@ -3,7 +3,8 @@ services:
rustdesk-organizer:
build: .
ports:
- "8001:8001"
- "8001:8001" # Веб-интерфейс (локальный доступ)
- "8002:8002" # API (доступ для POST-запросов)
volumes:
- ./db:/db
restart: unless-stopped