From 0c6b9494242b279b5ad1cd3a9c3d77309da37e1d Mon Sep 17 00:00:00 2001 From: dv Date: Wed, 8 Apr 2026 23:31:07 +0300 Subject: [PATCH] =?UTF-8?q?=D1=8B=D0=B2=D1=8B=D1=84=D0=B2=D1=84=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/global_devices.py | 5 ---- backend/app/services/task.py | 26 ++++++++++++++++++-- frontend/src/pages/WorkDevices.tsx | 35 +++++++++++++++++++++------ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/backend/app/routers/global_devices.py b/backend/app/routers/global_devices.py index 183a7cb..0b40607 100644 --- a/backend/app/routers/global_devices.py +++ b/backend/app/routers/global_devices.py @@ -1,12 +1,9 @@ """Global devices endpoint — cross-object device listing with filters.""" -import logging from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy import select - -logger = logging.getLogger(__name__) from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -37,8 +34,6 @@ async def list_all_devices( - All bank terminals for a client: ?category=bank_terminal&client=Авангард - All devices in a city: ?city=Санкт-Петербург """ - logger.info("list_all_devices called: category=%r city=%r client=%r object_id=%r status=%r", category, city, client, object_id, status) - query = ( select(Device) .join(Object, Device.object_id == Object.id) diff --git a/backend/app/services/task.py b/backend/app/services/task.py index 92c99a8..6a5884a 100644 --- a/backend/app/services/task.py +++ b/backend/app/services/task.py @@ -49,6 +49,8 @@ async def _resolve_scope(scope: dict) -> list[tuple[Device, Object]]: scope_type = scope.get("type") if scope_type == "device": query = query.where(Device.id == scope["id"]) + elif scope_type == "device_list": + query = query.where(Device.id.in_(scope["ids"])) elif scope_type == "object": query = query.where(Device.object_id == scope["id"]) elif scope_type == "rack": @@ -63,17 +65,37 @@ async def _resolve_scope(scope: dict) -> list[tuple[Device, Object]]: return [] query = query.where(Device.rack_id.in_(rack_ids)) elif scope_type == "category": - query = query.where(Device.category == scope["value"]) + # support both "value" (legacy) and "category" key + cat = scope.get("category") or scope.get("value") + if cat: + query = query.where(Device.category == cat) if "object_ids" in scope: query = query.where(Device.object_id.in_(scope["object_ids"])) elif "object_id" in scope: query = query.where(Device.object_id == scope["object_id"]) elif scope_type == "role": - query = query.where(Device.role == scope["value"]) + role = scope.get("role") or scope.get("value") + if role: + query = query.where(Device.role == role) if "object_ids" in scope: query = query.where(Device.object_id.in_(scope["object_ids"])) elif "object_id" in scope: query = query.where(Device.object_id == scope["object_id"]) + elif scope_type == "filter": + # Cross-object filter scope (from WorkDevices global page) + from app.models.object import Object as Obj + if scope.get("category"): + cats = scope["category"] + if isinstance(cats, list): + query = query.where(Device.category.in_(cats)) + else: + query = query.where(Device.category == cats) + if scope.get("object_id"): + query = query.where(Device.object_id == scope["object_id"]) + if scope.get("city"): + query = query.where(Obj.city == scope["city"]) + if scope.get("client"): + query = query.where(Obj.client == scope["client"]) else: return [] diff --git a/frontend/src/pages/WorkDevices.tsx b/frontend/src/pages/WorkDevices.tsx index 5468482..0fa14c0 100644 --- a/frontend/src/pages/WorkDevices.tsx +++ b/frontend/src/pages/WorkDevices.tsx @@ -52,6 +52,7 @@ export function WorkDevicesPage() { const [filterClient, setFilterClient] = useState() const [filterObjectId, setFilterObjectId] = useState() const [filterStatus, setFilterStatus] = useState() + const [searchText, setSearchText] = useState('') const [selectedRowKeys, setSelectedRowKeys] = useState([]) const [taskModal, setTaskModal] = useState(false) @@ -68,6 +69,14 @@ export function WorkDevicesPage() { const currentAction = actions?.find((a) => a.name === selectedAction) + const displayedDevices = useMemo(() => { + if (!searchText) return devices ?? [] + const q = searchText.toLowerCase() + return (devices ?? []).filter( + (d) => d.ip.toLowerCase().includes(q) || (d.hostname ?? '').toLowerCase().includes(q) + ) + }, [devices, searchText]) + // Object dropdown filtered by active city/client selectors const objectOptions = useMemo(() => { return (allObjects ?? []) @@ -275,15 +284,27 @@ export function WorkDevicesPage() { - {devices && ( - - Найдено: {devices.length} устройств - {selectedRowKeys.length > 0 && ` · Выбрано: ${selectedRowKeys.length}`} - - )} + + + setSearchText(e.target.value)} + /> + + + {devices && ( + + {searchText ? `${displayedDevices.length} из ${devices.length}` : `${devices.length} устройств`} + {selectedRowKeys.length > 0 && ` · Выбрано: ${selectedRowKeys.length}`} + + )} + +