diff --git a/backend/alembic/versions/0013_device_last_ping_at.py b/backend/alembic/versions/0013_device_last_ping_at.py new file mode 100644 index 0000000..feae731 --- /dev/null +++ b/backend/alembic/versions/0013_device_last_ping_at.py @@ -0,0 +1,25 @@ +"""device last_ping_at + +Revision ID: 0013 +Revises: 0012 +Create Date: 2026-04-10 +""" + +from alembic import op +import sqlalchemy as sa + +revision = "0013" +down_revision = "0012" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "devices", + sa.Column("last_ping_at", sa.DateTime(timezone=True), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("devices", "last_ping_at") diff --git a/backend/app/models/device.py b/backend/app/models/device.py index 54a2da6..3aa22c5 100644 --- a/backend/app/models/device.py +++ b/backend/app/models/device.py @@ -78,6 +78,7 @@ class Device(Base, TimestampMixin): # 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") diff --git a/backend/app/schemas/device.py b/backend/app/schemas/device.py index 46da3a1..05521a6 100644 --- a/backend/app/schemas/device.py +++ b/backend/app/schemas/device.py @@ -81,6 +81,7 @@ class DeviceRead(BaseModel): device_meta: dict[str, Any] = {} capabilities: list[str] = [] monitor_enabled: bool + last_ping_at: Optional[datetime] = None last_seen: Optional[datetime] = None last_ping_ms: Optional[int] = None status: str diff --git a/backend/app/workers/monitor.py b/backend/app/workers/monitor.py index f84a61e..9501448 100644 --- a/backend/app/workers/monitor.py +++ b/backend/app/workers/monitor.py @@ -112,6 +112,7 @@ async def _check_device( return device.status = new_status + device.last_ping_at = now if success: device.last_seen = now device.last_ping_ms = ping_ms diff --git a/frontend/src/api/objects.ts b/frontend/src/api/objects.ts index 8931d78..0d43d56 100644 --- a/frontend/src/api/objects.ts +++ b/frontend/src/api/objects.ts @@ -49,9 +49,10 @@ export interface DeviceItem { connection_params: Record capabilities: string[] status: string + monitor_enabled: boolean + last_ping_at: string | null last_seen: string | null last_ping_ms: number | null - monitor_enabled: boolean is_active: boolean notes: string | null created_at: string diff --git a/frontend/src/pages/ObjectDetail.tsx b/frontend/src/pages/ObjectDetail.tsx index ec282a1..94c580c 100644 --- a/frontend/src/pages/ObjectDetail.tsx +++ b/frontend/src/pages/ObjectDetail.tsx @@ -427,19 +427,50 @@ export function ObjectDetailPage({ defaultMode = 'inventory' }: { defaultMode?: row._type === 'group' ? null : , }, { - title: 'Последний ping', - dataIndex: 'last_seen', - key: 'last_seen', + title: 'Мониторинг', + key: 'monitoring', onCell: (row: TableRow) => row._type === 'group' ? { colSpan: 0 } : {}, render: (_: unknown, row: TableRow) => { if (row._type === 'group') return null const d = row as DeviceRow - if (!d.last_seen) return '—' + + // Мониторинг выключен и история отсутствует + if (!d.monitor_enabled && !d.last_ping_at) { + return выключен + } + + // Мониторинг включён, но ни разу не пинговалось + if (d.monitor_enabled && !d.last_ping_at) { + return ожидание... + } + + const pauseIcon = !d.monitor_enabled + ? + : null + + // Устройство online — last_ping_at ≈ last_seen + if (d.status === 'online') { + return ( + + + {dayjs(d.last_ping_at).format('DD.MM.YYYY HH:mm')} + + {pauseIcon} + + ) + } + + // Устройство offline или unknown — показываем время попытки, в tooltip — когда был онлайн + const lastSeenHint = d.last_seen + ? `онлайн был: ${dayjs(d.last_seen).format('DD.MM.YYYY HH:mm')}` + : 'онлайн не был' + return ( - - - {dayjs(d.last_seen).format('DD.MM.YYYY HH:mm')} + + + {dayjs(d.last_ping_at).format('DD.MM.YYYY HH:mm')} + {pauseIcon} ) },