New api and zone management; highligh occlusion and highlighAll functionality; improved search in reports and alerts history + autofill; refactored alert panel
This commit is contained in:
@@ -9,10 +9,11 @@ class AlertSerializer(serializers.ModelSerializer):
|
||||
object = serializers.SerializerMethodField()
|
||||
metric_value = serializers.SerializerMethodField()
|
||||
detector_type = serializers.SerializerMethodField()
|
||||
detector_id = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Alert
|
||||
fields = ('id', 'name', 'object', 'metric_value', 'detector_type', 'message', 'severity', 'created_at', 'resolved')
|
||||
fields = ('id', 'name', 'object', 'metric_value', 'detector_type', 'detector_id', 'message', 'severity', 'created_at', 'resolved')
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_name(self, obj) -> str:
|
||||
@@ -38,3 +39,9 @@ class AlertSerializer(serializers.ModelSerializer):
|
||||
if sensor_type is None:
|
||||
return ''
|
||||
return (getattr(sensor_type, 'code', '') or '').upper()
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_detector_id(self, obj) -> str:
|
||||
if hasattr(obj, 'sensor') and obj.sensor:
|
||||
return obj.sensor.name or f"{obj.sensor.sensor_type.code}-{obj.sensor.id}"
|
||||
return ""
|
||||
|
||||
@@ -15,7 +15,7 @@ class ZoneSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Zone
|
||||
fields = ('id', 'name', 'sensors')
|
||||
fields = ('id', 'name', 'floor', 'image_path', 'model_path', 'order', 'sensors')
|
||||
|
||||
class ObjectSerializer(serializers.ModelSerializer):
|
||||
zones = ZoneSerializer(many=True, read_only=True)
|
||||
|
||||
@@ -4,6 +4,7 @@ from .views.objects_views import ObjectView
|
||||
from .views.sensors_views import SensorView
|
||||
from .views.alert_views import AlertView, ReportView
|
||||
from .views.dashboard_views import DashboardView
|
||||
from .views.zones_views import ZoneView
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
@@ -36,4 +37,6 @@ urlpatterns = [
|
||||
path("get-reports/", ReportView.as_view({'post': 'get_reports'}), name="reports"),
|
||||
|
||||
path("get-dashboard/", DashboardView.as_view(), name="dashboard"),
|
||||
|
||||
path("get-zones/", ZoneView.as_view(), name="zones"),
|
||||
]
|
||||
|
||||
@@ -116,7 +116,11 @@ class ReportView(ViewSet):
|
||||
@extend_schema(
|
||||
summary="Генерация отчета",
|
||||
description="Генерирует отчет в выбранном формате (PDF или CSV)",
|
||||
request={'application/json': {'type': 'object', 'properties': {'report_format': {'type': 'string', 'enum': ['pdf', 'csv']}}}},
|
||||
request={'application/json': {'type': 'object', 'properties': {
|
||||
'report_format': {'type': 'string', 'enum': ['pdf', 'csv']},
|
||||
'hours': {'type': 'integer', 'description': 'Количество часов для фильтрации по времени'},
|
||||
'detector_ids': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Список ID датчиков для фильтрации (может быть числом или строкой вида "GLE-79")'}
|
||||
}}},
|
||||
methods=['POST'],
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
@@ -153,6 +157,10 @@ class ReportView(ViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Получаем параметры фильтрации
|
||||
hours = request.data.get('hours')
|
||||
detector_ids = request.data.get('detector_ids', [])
|
||||
|
||||
alerts = Alert.objects.select_related(
|
||||
'sensor',
|
||||
'sensor__signal_format',
|
||||
@@ -163,6 +171,35 @@ class ReportView(ViewSet):
|
||||
'sensor__zones__object'
|
||||
).all()
|
||||
|
||||
# Фильтрация по времени (если указано количество часов)
|
||||
if hours and isinstance(hours, (int, float)) and hours > 0:
|
||||
from datetime import timedelta
|
||||
cutoff_time = timezone.now() - timedelta(hours=hours)
|
||||
alerts = alerts.filter(created_at__gte=cutoff_time)
|
||||
|
||||
# Фильтрация по датчикам (если указаны ID)
|
||||
if detector_ids and isinstance(detector_ids, list) and len(detector_ids) > 0:
|
||||
# Конвертируем строковые ID в числовые ID базы данных
|
||||
numeric_ids = []
|
||||
for detector_id in detector_ids:
|
||||
if isinstance(detector_id, (int, float)):
|
||||
numeric_ids.append(int(detector_id))
|
||||
elif isinstance(detector_id, str):
|
||||
# Парсим строковые ID вида "GLE-79" или "GA-123"
|
||||
try:
|
||||
if '-' in detector_id:
|
||||
# Формат "TYPE-ID" - извлекаем числовую часть
|
||||
numeric_ids.append(int(detector_id.split('-')[-1]))
|
||||
else:
|
||||
# Предполагаем, что это просто число в строковом формате
|
||||
numeric_ids.append(int(detector_id))
|
||||
except (ValueError, IndexError):
|
||||
# Пропускаем некорректные ID
|
||||
continue
|
||||
|
||||
if numeric_ids:
|
||||
alerts = alerts.filter(sensor_id__in=numeric_ids)
|
||||
|
||||
# текущая дата для имени файла
|
||||
timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
|
||||
@@ -60,8 +60,9 @@ class DashboardView(APIView):
|
||||
|
||||
if time_period not in ['720', '168', '72', '24']:
|
||||
return Response(
|
||||
{"error": "Неверный период. Допустимые значения: 720, 168, 72, 24"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
- {"error": "Неверный период. Допустимые значения: 720, 168, 72, 24"},
|
||||
+ {"error": "Неверный период. Допустимые значения: 24, 72, 168, 720"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# определяем начальную дату
|
||||
|
||||
66
backend/api/account/views/zones_views.py
Normal file
66
backend/api/account/views/zones_views.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample
|
||||
|
||||
from api.account.serializers.objects_serializers import ZoneSerializer
|
||||
from sitemanagement.models import Zone
|
||||
|
||||
from api.utils.decorators import handle_exceptions
|
||||
|
||||
@extend_schema(tags=['Зоны'])
|
||||
class ZoneView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ZoneSerializer
|
||||
|
||||
@extend_schema(
|
||||
summary="Получение зон для указанного объекта",
|
||||
description="Возвращает список зон для объекта по query-параметру objectId",
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
response=ZoneSerializer,
|
||||
description="Зоны успешно получены",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Успешный ответ',
|
||||
value=[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Зона 1",
|
||||
"floor": 1,
|
||||
"image_path": "test_image_2.png",
|
||||
"model_path": "/static-models/AerBIM-Monitor_ASM-HTViewer_Expo2017Astana_20250908_L_+76190.glb",
|
||||
"order": 0,
|
||||
"sensors": [
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Датчик 1",
|
||||
"serial_number": "GA-123",
|
||||
"sensor_type": "Тип датчика 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
)
|
||||
@handle_exceptions
|
||||
def get(self, request):
|
||||
"""Получение всех зон для указанного объекта"""
|
||||
object_id = request.query_params.get('objectId') or request.query_params.get('object_id') or request.query_params.get('object')
|
||||
if not object_id:
|
||||
return Response({"error": "objectId query parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
zones = Zone.objects.filter(object_id=object_id).prefetch_related(
|
||||
'sensors',
|
||||
'sensors__sensor_type'
|
||||
).order_by('order', 'name')
|
||||
|
||||
serializer = ZoneSerializer(zones, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except Zone.DoesNotExist:
|
||||
return Response({"error": "Зоны не найдены"}, status=status.HTTP_404_NOT_FOUND)
|
||||
Reference in New Issue
Block a user