1
0
forked from BRT/arc

- Add log panel to the devices page and implement log parsing with enhanced context extraction for banks and devices. Remove unused bank synchronization logic and bank list actions.

This commit is contained in:
slava 2026-04-08 09:43:38 +07:00
parent 73461b93bf
commit 6d3f76feea
4 changed files with 191 additions and 23 deletions

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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 <QTimer>
#include <QCoreApplication>
@ -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<GeneralLogEntry> 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<BankProfileInfo> 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) {

View File

@ -1,4 +1,5 @@
#pragma once
#include <QListWidget>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QStackedWidget>
@ -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;
};