utp_service/backend/app/models/device.py
2026-04-09 14:09:53 +03:00

114 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin
DEVICE_CATEGORIES = (
"main_server", # caps-cs — основной сервер объекта
"vm", # виртуалки: БД, Asterisk, RTSP, recognition
"router", # MikroTik, IRZ и прочие роутеры
"embedded", # пром-ПК стоек (Raspberry Pi)
"camera", # камеры (role: plate / face / overview)
"io_board", # модули ввода/вывода (Овен МК210-302)
"bank_terminal", # банковские терминалы (SberPilot, PAX IM20)
"cash_register", # ККТ (PayOnline-01-ФА)
"other", # всё остальное: RFID, табло, реле, IP-телефоны
)
# Используется только для camera (остальным выставляется "other" или NULL)
DEVICE_ROLES = ("plate", "face", "overview", "other")
# Физическая локация устройств не привязанных к стойке
DEVICE_LOCATIONS = ("server_room", "street")
DEVICE_STATUSES = ("online", "offline", "unknown")
DEVICE_SOURCES = ("auto_sync", "manual", "csv_import", "sheets_sync", "auto_ssh")
class Device(Base, TimestampMixin):
__tablename__ = "devices"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
object_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("objects.id", ondelete="RESTRICT"),
nullable=False,
index=True,
)
ip: Mapped[str] = mapped_column(String(45), nullable=False)
hostname: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
mac: Mapped[Optional[str]] = mapped_column(String(17), nullable=True)
# Classification
category: Mapped[str] = mapped_column(String(20), nullable=False, default="other")
role: Mapped[str] = mapped_column(String(20), nullable=False, default="other")
vendor: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
model: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
firmware_version: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
# Source tracking
source: Mapped[str] = mapped_column(String(20), nullable=False, default="manual")
external_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
# SSH overrides (NULL = use object defaults)
ssh_user_override: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
ssh_pass_override_enc: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
ssh_port_override: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# Override with a specific SSH key for this device (NULL = follow object/category rules)
ssh_key_id_override: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("ssh_keys.id", ondelete="SET NULL"), nullable=True
)
# Last successful SSH auth method; tried first on next connection
# Format: {"type": "key", "key_id": 2, "username": "root"}
# or {"type": "password", "cred_id": 5, "username": "caps"}
ssh_last_auth: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
# Device-specific metadata (Mikrotik API port, RTSP URL, etc.)
device_meta: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
# Capabilities populated by drivers
capabilities: Mapped[list[str]] = mapped_column(
ARRAY(String), nullable=False, default=list
)
# Monitoring
monitor_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
last_seen: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
last_ping_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="unknown")
rack_id: Mapped[Optional[int]] = mapped_column(
Integer,
ForeignKey("racks.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
# Для устройств не в стойке: "server_room" | "street" | None
# rack_id и location взаимоисключают друг друга
location: Mapped[Optional[str]] = mapped_column(String(20), nullable=True, index=True)
# Параметры подключения для операционной вебки (Вариант В)
# camera: { auth_type, username, password, frame_url, stream_url }
# bank_terminal: { port, timeout_on_sale }
# cash_register: { port, tax, taxation }
# embedded: { config_path }
# io_board: { port, protocol }
connection_params: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
ssh_key_override: Mapped[Optional["SSHKey"]] = relationship(
"SSHKey", foreign_keys=[ssh_key_id_override]
)
object: Mapped["Object"] = relationship("Object", back_populates="devices")
rack: Mapped[Optional["Rack"]] = relationship("Rack", back_populates="devices")
config_snapshots: Mapped[list["ConfigSnapshot"]] = relationship(
"ConfigSnapshot", back_populates="device", cascade="all, delete-orphan"
)