Загрузка данных


message_generator_queue_d_11 (Устройство) в статусе "Нестабильно": частое изменение статуса actionNames.28. 20.05.2026 15:30:00


import { Notification } from '@/shared/api/notifications/client';
import { t } from 'i18next';
import {
  CRUD_ACTIONS,
  ExtendedActionName,
  SERVICE_STATUS_ACTIONS,
  STATUS_CHANGE_ACTIONS,
} from './model';
import {
  buildAggregatedMessage,
  extractObjectName,
  extractRepeatedStatus,
  formatTimestamp,
} from './utils';

export const buildMessage = (notification: Notification): string => {
  const actionName = (notification.action?.name?.trim() ??
    '') as ExtendedActionName;
  const entityType = notification.entity?.type;
  const objectName = extractObjectName(notification);

  const unknownUser = t('notifications:messageBuilder.unknownUser');
  const system = t('notifications:messageBuilder.system');
  let authorName = notification.actor?.fullName || unknownUser;

  const timestamp = notification.createdAt
    ? formatTimestamp(notification.createdAt)
    : '';

  // Агрегированное уведомление
  const attrs = notification.attrs as Record<string, unknown> | undefined;
  if (attrs?.isAggregated === true) {
    const eventCount =
      typeof attrs.eventCount === 'number' ? attrs.eventCount : 0;
    if (eventCount > 1) {
      return buildAggregatedMessage(
        actionName,
        entityType,
        objectName,
        eventCount,
        timestamp,
      );
    }
  }

  const objectType = t(
    `notifications:messageBuilder.objectTypes.${entityType}`,
  );

  if (STATUS_CHANGE_ACTIONS.includes(actionName)) {
    if (authorName === unknownUser) authorName = system;
    return t('notifications:messageBuilder.statusChange', {
      objectName,
      objectType,
      status: t(`notifications:actionNames.${actionName}`),
      author: authorName,
      timestamp,
    });
  }

  if (SERVICE_STATUS_ACTIONS.includes(actionName)) {
    return t('notifications:messageBuilder.serviceStatusChange', {
      objectName,
      status: t(`notifications:actionNames.${actionName}`),
      author: authorName,
      timestamp,
    });
  }

  if (CRUD_ACTIONS.includes(actionName)) {
    return t('notifications:messageBuilder.crudAction', {
      objectName,
      objectType,
      action: t(`notifications:messageBuilder.actions.${actionName}`),
      author: authorName,
      timestamp,
    });
  }

  switch (actionName) {
    case 'due_today':
      return t('notifications:messageBuilder.dueToday', {
        objectName,
        timestamp,
      });

    case 'due_tomorrow':
      return t('notifications:messageBuilder.dueTomorrow', {
        objectName,
        timestamp,
      });

    case 'flapping':
      return t('notifications:messageBuilder.flapping', {
        objectName,
        objectType,
        repeatedStatus: extractRepeatedStatus(notification),
        timestamp,
      });

    case 'flapping_resolved':
      return t('notifications:messageBuilder.flappingResolved', {
        objectName,
        objectType,
        author: authorName,
        timestamp,
      });

    case 'service_status_warranty_expired':
      return t('notifications:messageBuilder.warrantyExpired', {
        objectName,
        objectType,
        system,
        timestamp,
      });

    case 'service_status_warranty_will_expire_30day':
      return t('notifications:messageBuilder.warrantyExpire30Day', {
        objectName,
        objectType,
        system,
        timestamp,
      });

    case 'service_status_warranty_will_expire_5day':
      return t('notifications:messageBuilder.warrantyExpire5Day', {
        objectName,
        objectType,
        system,
        timestamp,
      });

    case 'service_status_warranty_will_expire_today':
      return t('notifications:messageBuilder.warrantyExpireToday', {
        objectName,
        objectType,
        system,
        timestamp,
      });

    default:
      return '';
  }
};


import { ActionNames } from '@/shared/api/notifications/client';

export type ExtendedActionName =
  | ActionNames
  | 'status_warning'
  | 'status_disabled'
  | 'gve_failed'
  | 'gve_success'
  | 'gve_unknown'
  | 'gve_missing_packets'
  | 'flapping'
  | 'flapping_resolved'
  | 'service_status_warranty_expired'
  | 'service_status_warranty_will_expire_30day'
  | 'service_status_warranty_will_expire_5day'
  | 'service_status_warranty_will_expire_today'
  | 'archive';

export const STATUS_CHANGE_ACTIONS: ExtendedActionName[] = [
  'status_disconnected',
  'status_unknown',
  'status_connected',
  'status_warning',
  'icmp_failed',
  'tcp_failed',
  'icmp_success',
  'tcp_success',
  'network_access_false',
  'status_disabled',
  'gve_failed',
  'gve_missing_packets',
  'gve_success',
  'gve_unknown',
];

export const SERVICE_STATUS_ACTIONS: ExtendedActionName[] = [
  'service_status_psi',
  'service_status_in_service',
  'service_status_exploitation',
];

export const CRUD_ACTIONS: ExtendedActionName[] = [
  'create',
  'update',
  'delete',
  'archive',
];


import { EntityType, Notification } from '@/shared/api/notifications/client';
import { t } from 'i18next';
import { ExtendedActionName, STATUS_CHANGE_ACTIONS } from './model';

export const formatTimestamp = (dateStr: string): string => {
  const date = new Date(dateStr);
  return date
    .toLocaleString('ru-RU', {
      day: '2-digit',
      month: '2-digit',
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      timeZone: 'Europe/Moscow',
    })
    .replace(',', '');
};

export const extractObjectName = (notification: Notification): string => {
  if (notification?.action?.name !== 'update') {
    return notification?.entity?.name ?? '';
  }
  const attrs = notification.attrs as Record<string, unknown> | undefined;
  if (attrs?.oldEntityName && typeof attrs.oldEntityName === 'string') {
    return attrs.oldEntityName;
  }
  // старое условие тоже оставляем, т.к. в бд есть старые записи
  if (attrs?.objectName && typeof attrs.objectName === 'string') {
    return attrs.objectName;
  }
  return notification.entity?.id ?? '';
};

export const extractRepeatedStatus = (notification: Notification): string => {
  const attrs = notification.attrs as Record<string, unknown> | undefined;
  if (attrs?.status != null) {
    const statusName = String(attrs.status);
    return t(`notifications:actionNames.${statusName}`);
  }
  return '-';
};

export const buildAggregatedMessage = (
  actionName: ExtendedActionName,
  entityType: EntityType | undefined,
  objectName: string,
  eventCount: number,
  timestamp: string,
): string => {
  if (!STATUS_CHANGE_ACTIONS.includes(actionName)) return '';

  return t('notifications:messageBuilder.statusChangeAggregated', {
    objectName,
    objectType: t(`notifications:messageBuilder.objectTypes.${entityType}`),
    status: t(`notifications:actionNames.${actionName}`),
    eventCount,
    system: t('notifications:messageBuilder.system'),
    timestamp,
  });
};


{
  "notifications": "Уведомления",
  "criticalNotifications": "Критичные уведомления",
  "notificationDetails": "Детали уведомления",

  "detailsFields": {
    "message": "Сообщение",
    "createdAt": "Дата создания события",
    "archivedAt": "Дата архивации события",
    "actionPriority": "Тип события",
    "actionName": "Событие",
    "entity": "Сущность",
    "actor": "Создатель события",
    "reader": "Уведомление прочитано пользователем",
    "readAt": "Дата прочтения",
    "attrs": "Атрибуты события",
    "locationPath": "Локация"
  },

  "actual": "Актуальные",
  "archived": "Архив",

  "buttons": {
    "readAll": "Прочитать все",
    "archiveAll": "В архив",
    "read": "Прочитать",
    "archive": "В архив",
    "enableNotifications": "Включить уведомления",
    "disableNotifications": "Выключить уведомления"
  },

  "filters": {
    "byPriority": "По приоритету",
    "byEntityType": "По объекту",
    "byAction": "По событию",
    "byDate": "По дате",
    "searchPlaceholder": "Поиск по названию"
  },

  "tooltips": {
    "archiveAll": "Перенести все уведомления в архив",
    "details": "Детали",
    "read": "Прочитать",
    "archive": "В архив"
  },

  "priority": {
    "critical": "Критический",
    "important": "Требует внимания",
    "normal": "Важный",
    "info": "Информационный"
  },

  "actionNames": {
    "create": "Создание",
    "update": "Редактирование",
    "delete": "Удаление",
    "status_disconnected": "Отключено",
    "status_unknown": "Неизвестно",
    "status_connected": "Подключено",
    "status_warning": "Внимание",
    "status_missing_packets": "Потеря пакетов",
    "service_status_psi": "Сервисный статус ПСИ",
    "service_status_in_service": "Сервисный статус В сервисе",
    "service_status_exploitation": "Сервисный статус Эксплуатация",
    "icmp_failed": "ICMP не удался",
    "tcp_failed": "TCP не удался",
    "icmp_success": "ICMP успешен",
    "tcp_success": "TCP успешен",
    "icmp_unknown": "ICMP неизвестно",
    "tcp_unknown": "TCP неизвестно",
    "due_today": "Заявка истекает сегодня",
    "due_tomorrow": "Заявка истекает завтра",
    "network_access_false": "Отсутствие сетевого доступа",
    "is_monitoring_true": "Только учёт включен",
    "status_disabled": "Выключено",
    "flapping": "Нестабильно",
    "flapping_resolved": "Решено",
    "archive": "В архиве",
    "service_status_warranty_expired": "Гарантийный срок истёк",
    "service_status_warranty_will_expire_today": "Гарантийный срок истекает сегодня",
    "service_status_warranty_will_expire_5day": "Гарантийный срок истекает через 5 дней",
    "service_status_warranty_will_expire_30day": "Гарантийный срок истекает через 30 дней",
    "gve_failed": "UDP недоступен",
    "gve_success": "UDP доступен",
    "gve_unknown": "UDP неизвестно",
    "gve_missing_packets": "UDP потеря пакетов"
  },

  "messageBuilder": {
    "unknownUser": "Неизвестный пользователь",
    "system": "Система",

    "objectTypes": {
      "controller": "Контроллер",
      "padlet": "Панель SmartRoom",
      "device": "Устройство",
      "network-device": "ССУ",
      "offline-device": "СНУ",
      "location": "Локация",
      "ticket": "Заявка",
      "service-status": "Комната"
    },

    "actions": {
      "create": "создание",
      "update": "редактирование",
      "delete": "удаление",
      "archive": "архивация"
    },

    "statusChange": "{{objectName}} ({{objectType}}) - изменение статуса на {{status}}, {{author}} {{timestamp}}",
    "statusChangeAggregated": "{{objectName}} ({{objectType}}) - изменение статуса на {{status}} произошло {{eventCount}} раз(а), {{system}} {{timestamp}}",
    "serviceStatusChange": "Локация {{objectName}} : изменение сервисного статуса на {{status}}, пользователем {{author}}, {{timestamp}}",
    "crudAction": "{{objectName}} ({{objectType}}) - {{action}} пользователем {{author}} {{timestamp}}",
    "dueToday": "Срок выполнения заявки {{objectName}} истекает сегодня. {{timestamp}}",
    "dueTomorrow": "Срок выполнения заявки {{objectName}} истекает завтра. {{timestamp}}",
    "flapping": "{{objectName}} ({{objectType}}) в статусе \"Нестабильно\": частое изменение статуса {{repeatedStatus}}. {{timestamp}}",
    "flappingResolved": "{{objectName}} ({{objectType}}): проблема с частым изменением статуса решена пользователем {{author}}, {{timestamp}}",
    "warrantyExpired": "{{objectName}} ({{objectType}}) - Гарантийный срок истек, {{system}} {{timestamp}}",
    "warrantyExpire30Day": "{{objectName}} ({{objectType}}) - Гарантийный срок истекает через 30 дней, {{system}} {{timestamp}}",
    "warrantyExpire5Day": "{{objectName}} ({{objectType}}) - Гарантийный срок истекает через 5 дней, {{system}} {{timestamp}}",
    "warrantyExpireToday": "{{objectName}} ({{objectType}}) - Гарантийный срок истекает сегодня, {{system}} {{timestamp}}"
  },

  "locationHasBeenDeleted": "Локация была удалена"
}


{
    "notifications": [
        {
            "id": "06516318-897e-4951-8c7e-f2a3606c6ce1",
            "entity": {
                "id": "0e366339-d1a4-4c2c-95a8-9203d24f3d03",
                "name": "message_generator_queue_d_11",
                "type": "device"
            },
            "actor": {
                "id": "00000000-0000-0000-0000-000000000000",
                "fullName": ""
            },
            "reader": {
                "id": "00000000-0000-0000-0000-000000000000",
                "fullName": ""
            },
            "action": {
                "id": 19,
                "name": "flapping",
                "priority": "important"
            },
            "isVip": null,
            "attrs": {
                "eventCount": 1,
                "firstEventTime": "2026-05-20T12:30:00Z",
                "isAggregated": true,
                "lastEventTime": "2026-05-20T12:30:00Z",
                "status": 28
            },
            "createdAt": "2026-05-20T15:30:00+03:00",
            "readAt": null,
            "archivedAt": null
        },