From 7997184442592c3c2ed3c4f07044f2a665281e3a Mon Sep 17 00:00:00 2001 From: MoonTestUse1 Date: Sun, 5 Jan 2025 01:23:12 +0600 Subject: [PATCH] add websockets support --- backend/app/routers/requests.py | 47 +++++- backend/app/websockets/notifications.py | 36 +++++ frontend/src/plugins/websocket.ts | 74 ++++++++++ frontend/src/views/admin/AdminDashboard.vue | 152 ++++++++++++++++++++ 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 backend/app/websockets/notifications.py create mode 100644 frontend/src/plugins/websocket.ts create mode 100644 frontend/src/views/admin/AdminDashboard.vue diff --git a/backend/app/routers/requests.py b/backend/app/routers/requests.py index 3c5e142..6d9e410 100644 --- a/backend/app/routers/requests.py +++ b/backend/app/routers/requests.py @@ -1,5 +1,5 @@ """Requests router""" -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect from sqlalchemy.orm import Session from typing import List, Optional from ..database import get_db @@ -8,9 +8,32 @@ from ..schemas.request import Request, RequestCreate, RequestUpdate from ..models.request import RequestStatus from ..utils.auth import get_current_employee, get_current_admin from ..utils.telegram import notify_new_request +from ..websockets.notifications import notification_manager router = APIRouter() +@router.websocket("/ws/admin") +async def websocket_admin_endpoint(websocket: WebSocket): + """WebSocket endpoint для админов""" + await notification_manager.connect(websocket, "admin") + try: + while True: + data = await websocket.receive_text() + # Здесь можно добавить обработку сообщений от админа + except WebSocketDisconnect: + notification_manager.disconnect(websocket, "admin") + +@router.websocket("/ws/employee/{employee_id}") +async def websocket_employee_endpoint(websocket: WebSocket, employee_id: int): + """WebSocket endpoint для сотрудников""" + await notification_manager.connect(websocket, "employee") + try: + while True: + data = await websocket.receive_text() + # Здесь можно добавить обработку сообщений от сотрудника + except WebSocketDisconnect: + notification_manager.disconnect(websocket, "employee") + @router.post("/", response_model=Request) async def create_request( request: RequestCreate, @@ -21,6 +44,17 @@ async def create_request( db_request = requests.create_request(db, request, current_employee["id"]) # Отправляем уведомление в Telegram await notify_new_request(db_request.id) + # Отправляем уведомление через WebSocket всем админам + await notification_manager.broadcast_to_admins({ + "type": "new_request", + "data": { + "id": db_request.id, + "employee_id": current_employee["id"], + "status": db_request.status, + "priority": db_request.priority, + "created_at": db_request.created_at.isoformat() + } + }) return db_request @router.get("/my", response_model=List[Request]) @@ -43,7 +77,7 @@ def get_all_requests( return requests.get_requests(db, status=status, skip=skip, limit=limit) @router.patch("/{request_id}/status", response_model=Request) -def update_request_status( +async def update_request_status( request_id: int, request_update: RequestUpdate, db: Session = Depends(get_db), @@ -53,6 +87,15 @@ def update_request_status( db_request = requests.update_request_status(db, request_id, request_update.status) if db_request is None: raise HTTPException(status_code=404, detail="Request not found") + + # Отправляем уведомление через WebSocket + await notification_manager.broadcast_to_admins({ + "type": "status_update", + "data": { + "id": request_id, + "status": request_update.status + } + }) return db_request @router.get("/statistics") diff --git a/backend/app/websockets/notifications.py b/backend/app/websockets/notifications.py new file mode 100644 index 0000000..454fe83 --- /dev/null +++ b/backend/app/websockets/notifications.py @@ -0,0 +1,36 @@ +from fastapi import WebSocket +from typing import Dict, List +import json + +class NotificationManager: + def __init__(self): + self.active_connections: Dict[str, List[WebSocket]] = { + "admin": [], # Подключения админов + "employee": [] # Подключения сотрудников + } + + async def connect(self, websocket: WebSocket, client_type: str): + await websocket.accept() + self.active_connections[client_type].append(websocket) + + def disconnect(self, websocket: WebSocket, client_type: str): + self.active_connections[client_type].remove(websocket) + + async def broadcast_to_admins(self, message: dict): + """Отправка сообщения всем подключенным админам""" + for connection in self.active_connections["admin"]: + try: + await connection.send_json(message) + except: + # Если не удалось отправить сообщение, пропускаем + continue + + async def broadcast_to_employees(self, employee_id: int, message: dict): + """Отправка сообщения конкретному сотруднику""" + for connection in self.active_connections["employee"]: + try: + await connection.send_json(message) + except: + continue + +notification_manager = NotificationManager() \ No newline at end of file diff --git a/frontend/src/plugins/websocket.ts b/frontend/src/plugins/websocket.ts new file mode 100644 index 0000000..20fedba --- /dev/null +++ b/frontend/src/plugins/websocket.ts @@ -0,0 +1,74 @@ +import { ref } from 'vue' + +class WebSocketClient { + private socket: WebSocket | null = null + private reconnectAttempts = 0 + private maxReconnectAttempts = 5 + private reconnectTimeout = 3000 + private messageHandlers: ((data: any) => void)[] = [] + + // Состояние подключения + isConnected = ref(false) + + connect(type: 'admin' | 'employee', id?: number) { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + const baseUrl = `${protocol}//${window.location.host}` + const url = type === 'admin' ? + `${baseUrl}/api/requests/ws/admin` : + `${baseUrl}/api/requests/ws/employee/${id}` + + this.socket = new WebSocket(url) + + this.socket.onopen = () => { + console.log('WebSocket connected') + this.isConnected.value = true + this.reconnectAttempts = 0 + } + + this.socket.onmessage = (event) => { + const data = JSON.parse(event.data) + this.messageHandlers.forEach(handler => handler(data)) + } + + this.socket.onclose = () => { + console.log('WebSocket disconnected') + this.isConnected.value = false + this.tryReconnect() + } + + this.socket.onerror = (error) => { + console.error('WebSocket error:', error) + this.isConnected.value = false + } + } + + private tryReconnect() { + if (this.reconnectAttempts < this.maxReconnectAttempts) { + this.reconnectAttempts++ + setTimeout(() => { + console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`) + this.connect('admin') + }, this.reconnectTimeout) + } + } + + addMessageHandler(handler: (data: any) => void) { + this.messageHandlers.push(handler) + } + + removeMessageHandler(handler: (data: any) => void) { + const index = this.messageHandlers.indexOf(handler) + if (index > -1) { + this.messageHandlers.splice(index, 1) + } + } + + disconnect() { + if (this.socket) { + this.socket.close() + this.socket = null + } + } +} + +export const wsClient = new WebSocketClient() \ No newline at end of file diff --git a/frontend/src/views/admin/AdminDashboard.vue b/frontend/src/views/admin/AdminDashboard.vue new file mode 100644 index 0000000..b56fe9e --- /dev/null +++ b/frontend/src/views/admin/AdminDashboard.vue @@ -0,0 +1,152 @@ + + + \ No newline at end of file