From a1325017b0b2b55a3c85523d33947af1aeeb7cc7 Mon Sep 17 00:00:00 2001 From: "Satur@it-depot.ru" Date: Wed, 5 Mar 2025 17:45:16 +0300 Subject: [PATCH] POST2 --- 123 | 1 - Dockerfile | 8 ++- app.py | 129 ++++++++++++++++++++------------------------- docker-compose.yml | 3 +- 4 files changed, 64 insertions(+), 77 deletions(-) delete mode 100644 123 diff --git a/123 b/123 deleted file mode 100644 index 871cae9..0000000 --- a/123 +++ /dev/null @@ -1 +0,0 @@ -323 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a0cc6eb..c804c64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file + +# Экспонируем оба порта +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"] \ No newline at end of file diff --git a/app.py b/app.py index 95fd0f1..76ceff0 100644 --- a/app.py +++ b/app.py @@ -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, diff --git a/docker-compose.yml b/docker-compose.yml index 6314e60..77426fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,8 @@ services: rustdesk-organizer: build: . ports: - - "8001:8001" + - "8001:8001" # Веб-интерфейс (локальный доступ) + - "8002:8002" # API (доступ для POST-запросов) volumes: - ./db:/db restart: unless-stopped \ No newline at end of file