115 lines
5.5 KiB
Python
115 lines
5.5 KiB
Python
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_ping_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||
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"
|
||
)
|