diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index 8474613..8b3bec5 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -12,7 +12,6 @@ #include "dao/MaterialDAO.h" #include "dao/BankProfileDAO.h" -#include "dao/BankDAO.h" #include "dao/DeviceDAO.h" #include "dao/EventDAO.h" #include "dao/TransactionDAO.h" @@ -134,11 +133,10 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) { event.amount = body["amount"].toDouble() / 100.0; event.phone = body["bank_participator_id"].toString(); - // Резолвим название банка-получателя по алиасу - const QString bankAlias = body["extra"].toObject()["bank_participator_name"].toString(); - if (!bankAlias.isEmpty()) { - const BankInfo bank = BankDAO::findByAlias(bankAlias); - event.comment = !bank.nspkName.isEmpty() ? bank.nspkName : bank.name; + // Название банка-получателя из extra + const QString bankNameRu = body["extra"].toObject()["bank_participator_name_ru"].toString(); + if (!bankNameRu.isEmpty()) { + event.comment = bankNameRu; } } diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp index d66319c..d6cdff0 100644 --- a/services/net/NetworkService.cpp +++ b/services/net/NetworkService.cpp @@ -239,7 +239,8 @@ QJsonValue NetworkService::postJson(const QString &path, const QJsonObject &body result = QJsonValue::Undefined; } else if (reply->error() != QNetworkReply::NoError) { const QByteArray responseBody = reply->readAll(); - qCritical() << "Network error (POST):" << reply->errorString() << path + qCritical().noquote() << "Network error (POST):" << reply->errorString() << path + << (m_deviceId.isEmpty() ? "" : " device=" + m_deviceId) << "\n response body:" << responseBody; result = QJsonValue::Undefined; } else { @@ -349,8 +350,7 @@ void NetworkService::loginAndSetup() { // Шаг 4: синхронизация банковских профилей и материалов с сервера syncBankProfilesFromServer(); - // Шаг 5: загрузка справочника банков если пустой - syncBanksIfEmpty(); + // Шаг 5: убран (bank_participator_name_ru приходит в задаче) // Шаг 6: синхронизация статусов профилей/материалов с учётом онлайн-устройств syncCurrentProfile(); @@ -446,7 +446,8 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod reply->abort(); } else if (reply->error() != QNetworkReply::NoError) { const QByteArray responseBody = reply->readAll(); - qCritical() << "Network error (PATCH):" << reply->errorString() << path + qCritical().noquote() << "Network error (PATCH):" << reply->errorString() << path + << (m_deviceId.isEmpty() ? "" : " device=" + m_deviceId) << "\n response body:" << responseBody; } else { const QByteArray raw = reply->readAll(); @@ -488,7 +489,8 @@ QJsonValue NetworkService::deleteJson(const QString &path, bool withAuth) { reply->abort(); result = QJsonValue::Undefined; } else if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Network error (DELETE):" << reply->errorString() << path; + qCritical().noquote() << "Network error (DELETE):" << reply->errorString() << path + << (m_deviceId.isEmpty() ? "" : " device=" + m_deviceId); result = QJsonValue::Undefined; } else { const QByteArray raw = reply->readAll(); @@ -533,7 +535,8 @@ QJsonValue NetworkService::getJson(const QString &path, bool withAuth) { reply->abort(); result = QJsonValue::Undefined; } else if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Network error (GET):" << reply->errorString() << path; + qCritical().noquote() << "Network error (GET):" << reply->errorString() << path + << (m_deviceId.isEmpty() ? "" : " device=" + m_deviceId); result = QJsonValue::Undefined; } else { const QByteArray raw = reply->readAll(); diff --git a/views/MainWindow.cpp b/views/MainWindow.cpp index a273f25..439e9fd 100644 --- a/views/MainWindow.cpp +++ b/views/MainWindow.cpp @@ -20,7 +20,9 @@ #include "net/NetworkService.h" #include "db/DeviceInfo.h" #include "device/DeviceWidget.h" +#include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" +#include "dao/MaterialDAO.h" #include "dao/SettingsDAO.h" #include #include @@ -51,13 +53,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenu *menu = menuBar()->addMenu("Меню"); auto *devicesPageAction = new QAction("Активные устройства", this); // auto *accountPageAction = new QAction("Аккаунты", this); - auto *bankListAction = new QAction("Банки", this); auto *tutorialPageAction = new QAction("Инструкция", this); auto *eventAction = new QAction("События", this); auto *fileLogAction = new QAction("Логи", this); menu->addAction(devicesPageAction); // menu->addAction(accountPageAction); - menu->addAction(bankListAction); menu->addAction(eventAction); menu->addAction(tutorialPageAction); menu->addAction(fileLogAction); @@ -74,15 +74,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // deleteDevicePage(); // освобождаем ресурсы // }); - connect(bankListAction, &QAction::triggered, this, [=]() { - if (!m_bankListWindow) { - m_bankListWindow = new BankListWindow(this); - } - m_bankListWindow->show(); - m_bankListWindow->raise(); - m_bankListWindow->activateWindow(); - }); - connect(tutorialPageAction, &QAction::triggered, this, [=]() { if (!m_tutorialWindow) { m_tutorialWindow = new TutorialWindow(this); @@ -160,6 +151,18 @@ void MainWindow::createDevicePage() { m_noDevicesLabel->setVisible(devices.isEmpty()); m_scrollArea->setVisible(!devices.isEmpty()); + // Панель логов внизу + m_logList = new QListWidget(container); + m_logList->setMaximumHeight(120); + m_logList->setStyleSheet( + "QListWidget { background-color: #1e1e1e; color: #cccccc; font-family: monospace; font-size: 11px; border-top: 1px solid #444; }" + "QListWidget::item { padding: 1px 4px; }" + ); + m_logList->setSelectionMode(QAbstractItemView::NoSelection); + m_logList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + containerLayout->addWidget(m_logList); + updateLogPanel(); + m_devicesWindow = container; stackedWidget->addWidget(m_devicesWindow); @@ -209,6 +212,166 @@ void MainWindow::loadDevices() { m_noDevicesLabel->setVisible(devices.isEmpty()); m_scrollArea->setVisible(!devices.isEmpty()); + + updateLogPanel(); +} + +void MainWindow::updateLogPanel() { + if (!m_logList) return; + + const QList logs = GeneralLogDAO::getLogs(0, 5, {"WARNING", "CRITICAL", "FATAL"}); + m_logList->clear(); + for (const auto &entry : logs) { + // Преобразуем в человекопонятные сообщения + QString msg = entry.message; + + // Извлекаем контекст: банк, устройство + QString bank; + QString device; + { + // bank=ozon + QRegularExpression bankRe(R"(bank=(\w+))"); + if (auto m = bankRe.match(msg); m.hasMatch()) { + const QString b = m.captured(1); + if (b == "ozon") bank = "Ozon"; + else if (b == "black") bank = "Black"; + else if (b == "dushanbe_city_bank") bank = "Dushanbe"; + else bank = b; + } + // Устройство: device=ID или "SERIAL" + QRegularExpression devEqRe(R"(device=(\S+))"); + if (auto m = devEqRe.match(msg); m.hasMatch()) { + const QString devId = m.captured(1); + const DeviceInfo dev = DeviceDAO::getDeviceById(devId); + if (!dev.name.isEmpty()) device = dev.name; + else { + const DeviceInfo dev2 = DeviceDAO::findByAdbSerial(devId); + device = dev2.name.isEmpty() ? devId.left(10) + "..." : dev2.name; + } + } + if (device.isEmpty()) { + QRegularExpression devRe(R"x("?([A-Z0-9]{12,})"?)x"); + if (auto m = devRe.match(msg); m.hasMatch()) { + const DeviceInfo dev = DeviceDAO::findByAdbSerial(m.captured(1)); + device = dev.name.isEmpty() ? m.captured(1).left(8) + "..." : dev.name; + } + } + // UUID в URL — ищем bank_profile или material по ID + if (bank.isEmpty() || device.isEmpty()) { + QRegularExpression uuidRe(R"(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))"); + auto it = uuidRe.globalMatch(msg); + while (it.hasNext() && (bank.isEmpty() || device.isEmpty())) { + const QString uuid = it.next().captured(1); + if (bank.isEmpty()) { + const QList bpList = BankProfileDAO::findAllByBankProfileId(uuid); + const BankProfileInfo bp = bpList.isEmpty() ? BankProfileInfo() : bpList.first(); + if (bp.id >= 0) { + if (bank.isEmpty()) { + const QString b = bp.code; + if (b == "ozon") bank = "Ozon"; + else if (b == "black") bank = "Black"; + else if (b == "dushanbe_city_bank") bank = "Dushanbe"; + else bank = b; + } + if (device.isEmpty() && !bp.deviceId.isEmpty()) { + const DeviceInfo dev = DeviceDAO::getDeviceById(bp.deviceId); + device = dev.name.isEmpty() ? bp.deviceId.left(8) + "..." : dev.name; + } + } + } + if (bank.isEmpty()) { + const MaterialInfo mat = MaterialDAO::findByMaterialId(uuid); + if (mat.id >= 0) { + const QString b = mat.appCode; + if (b == "ozon") bank = "Ozon"; + else if (b == "black") bank = "Black"; + else if (b == "dushanbe_city_bank") bank = "Dushanbe"; + else bank = b; + if (device.isEmpty() && !mat.deviceId.isEmpty()) { + const DeviceInfo dev = DeviceDAO::getDeviceById(mat.deviceId); + device = dev.name.isEmpty() ? mat.deviceId.left(8) + "..." : dev.name; + } + } + } + } + } + } + // Формируем префикс [девайс | банк] + QString ctxPrefix; + if (!device.isEmpty() || !bank.isEmpty()) { + QStringList parts; + if (!device.isEmpty()) parts << device; + if (!bank.isEmpty()) parts << bank; + ctxPrefix = "[" + parts.join(" | ") + "] "; + } + + if (msg.contains("Failed to send result to server")) + msg = ctxPrefix + "Не удалось отправить результат на сервер"; + else if (msg.contains("Device is offline")) + msg = ctxPrefix + "Устройство не подключено"; + else if (msg.contains("bank_transaction_id is empty")) + msg = ctxPrefix + "Не удалось распознать ID транзакции"; + else if (msg.contains("Transaction not found")) + msg = ctxPrefix + "Транзакция не найдена"; + else if (msg.contains("Cannot reach home screen") || msg.contains("Cant open the home screen")) + msg = ctxPrefix + "Не удалось открыть приложение"; + else if (msg.contains("Card input screen not loaded")) + msg = ctxPrefix + "Экран ввода карты не загрузился"; + else if (msg.contains("Payment result not received")) + msg = ctxPrefix + "Результат платежа не получен (таймаут)"; + else if (msg.contains("Transfer failed")) + msg = ctxPrefix + "Перевод не удался"; + else if (msg.contains("PIN code") || msg.contains("Пин-код")) + msg = ctxPrefix + "Ошибка проверки пин-кода"; + else if (msg.contains("postTaskResult failed")) + msg = ctxPrefix + "Ошибка отправки результата задачи"; + else if (msg.contains("Network error") || msg.contains("network error")) { + QRegularExpression urlRe(R"((https?://[^\s\"]+))"); + QRegularExpression codeRe(R"(server replied:\s*(.+?)[\"\n])"); + QString url, code; + if (auto m = urlRe.match(msg); m.hasMatch()) url = m.captured(1); + if (auto m = codeRe.match(msg); m.hasMatch()) code = m.captured(1).trimmed(); + + // Преобразуем путь API в читаемое название + QString endpoint; + if (url.contains("/bank_profile")) endpoint = "обновление профиля банка"; + else if (url.contains("/material")) endpoint = "обновление карты/счёта"; + else if (url.contains("/task_result")) endpoint = "отправка результата задачи"; + else if (url.contains("/get_tasks")) endpoint = "получение задач"; + else if (url.contains("/device")) endpoint = "обновление устройства"; + else if (url.contains("/auth")) endpoint = "авторизация"; + else if (url.contains("/current_profile")) endpoint = "получение профиля"; + else if (url.contains("/monitoring")) endpoint = "отправка логов"; + else { + const int pathIdx = url.indexOf('/', url.indexOf("//") + 2); + endpoint = pathIdx > 0 ? url.mid(pathIdx) : url; + } + + msg = "Ошибка: " + endpoint; + if (!code.isEmpty()) msg += " [" + code + "]"; + msg = ctxPrefix + msg; + } + else if (msg.contains("App restarted")) + msg = ctxPrefix + "Приложение перезапущено, задача отменена"; + else if (msg.contains("Task expired")) + msg = ctxPrefix + "Задача просрочена"; + else if (msg.contains("Bank profile disabled") || msg.contains("Bank profile is disabled")) + msg = ctxPrefix + "Банковский профиль выключен"; + else if (msg.contains("Скриншот пустой")) + msg = ctxPrefix + "Скриншот устройства пустой"; + + const QString time = entry.timestamp.toLocalTime().toString("HH:mm:ss"); + const QString line = QStringLiteral("[%1] %2").arg(time, msg); + + auto *item = new QListWidgetItem(line, m_logList); + if (entry.level == "CRITICAL" || entry.level == "FATAL") { + item->setForeground(QColor("#ff6b6b")); + } else if (entry.level == "WARNING") { + item->setForeground(QColor("#ffa94d")); + } else { + item->setForeground(QColor("#cccccc")); + } + } } void MainWindow::resizeEvent(QResizeEvent *event) { diff --git a/views/MainWindow.h b/views/MainWindow.h index e3d03b0..671a8f7 100644 --- a/views/MainWindow.h +++ b/views/MainWindow.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -36,7 +37,10 @@ private: FileLogWindow *m_fileLogWindow = nullptr; QWidget *m_eventWindow = nullptr; + QListWidget *m_logList = nullptr; + void createDevicePage(); void deleteDevicePage(); + void updateLogPanel(); void resizeEvent(QResizeEvent *event) override; };