From a6509b5653bb5468490acd34ea313b36efb8f16e Mon Sep 17 00:00:00 2001 From: slava Date: Tue, 31 Mar 2026 13:08:02 +0700 Subject: [PATCH] - Add database migration for `screenshot` column in `general_logs`. - Implement `cleanupOldData` in `DatabaseManager` to remove outdated entries and optimize storage. - Introduce `unlockScreen` in `AdbUtils` and integrate it into common scripts for screen unlocking. - Enhance `GeneralLogDAO` to support screenshot storage and retrieval. - Expand `FileLogWindow` with screenshot viewer and horizontal layout. - Add device ID migration logic in `BankProfileDAO` and `MaterialDAO`. - Improve error handling in `EventHandler` by saving and logging screenshots for critical events. --- android/adb/AdbUtils.cpp | 30 +++++++++++ android/adb/AdbUtils.h | 2 + android/black/CommonScript.cpp | 3 ++ android/ozon/CommonScript.cpp | 3 ++ database/DatabaseManager.cpp | 47 ++++++++++++++++ database/DatabaseManager.h | 1 + database/dao/BankProfileDAO.cpp | 34 +++++++++--- database/dao/BankProfileDAO.h | 2 + database/dao/GeneralLogDAO.cpp | 23 +++++--- database/dao/GeneralLogDAO.h | 13 +++-- database/dao/MaterialDAO.cpp | 23 ++++++-- database/dao/MaterialDAO.h | 2 + main.cpp | 32 ++--------- services/DeviceScreener.cpp | 8 +++ services/EventHandler.cpp | 9 +++- views/log/FileLogWindow.cpp | 63 +++++++++++++++++++--- views/log/FileLogWindow.h | 7 +++ views/widget/loader/RegistrationWindow.cpp | 8 +-- 18 files changed, 245 insertions(+), 65 deletions(-) diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp index 8b86efb..16241bb 100644 --- a/android/adb/AdbUtils.cpp +++ b/android/adb/AdbUtils.cpp @@ -469,3 +469,33 @@ QSet AdbUtils::getListApps(const QString &deviceId) { } return apps; } + +void AdbUtils::unlockScreen(const QString &deviceId, const int screenWidth, const int screenHeight) { + const QString adb = adbPath(); + + // Проверяем, включён ли экран (mWakefulness=Awake) + QString powerState; + startProcess(adb, {"-s", deviceId, "shell", "dumpsys", "power"}, &powerState); + const bool isAwake = powerState.contains("mWakefulness=Awake"); + + if (!isAwake) { + // Включаем экран + startProcess(adb, {"-s", deviceId, "shell", "input", "keyevent", "KEYCODE_WAKEUP"}); + QThread::msleep(500); + qDebug() << "[AdbUtils] unlockScreen: woke up device" << deviceId; + } + + // Проверяем, залочен ли экран (mDreamingSleepToken / mShowingLockscreen) + QString wmState; + startProcess(adb, {"-s", deviceId, "shell", "dumpsys", "window"}, &wmState); + const bool isLocked = wmState.contains("mDreamingSleepToken") || wmState.contains("mShowingLockscreen=true") + || wmState.contains("StatusBar"); + + if (isLocked) { + // Свайп вверх для разблокировки + const int x = screenWidth / 2; + makeSwipe(deviceId, x, screenHeight * 3 / 4, x, screenHeight / 4); + QThread::msleep(500); + qDebug() << "[AdbUtils] unlockScreen: swiped to unlock" << deviceId; + } +} diff --git a/android/adb/AdbUtils.h b/android/adb/AdbUtils.h index bbd6629..20fe2a8 100644 --- a/android/adb/AdbUtils.h +++ b/android/adb/AdbUtils.h @@ -44,6 +44,8 @@ public: static bool goBack(const QString &deviceId); + static void unlockScreen(const QString &deviceId, int screenWidth, int screenHeight); + static QSet getListApps(const QString &deviceId); private: diff --git a/android/black/CommonScript.cpp b/android/black/CommonScript.cpp index 5ab4053..416893f 100644 --- a/android/black/CommonScript.cpp +++ b/android/black/CommonScript.cpp @@ -67,6 +67,9 @@ bool CommonScript::runAppAndGoToHomeScreen( const int width, const int height ) { + // Разблокируем экран перед запуском + AdbUtils::unlockScreen(deviceId, width, height); + // Убить приложение если запущено if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { qDebug() << "[Black] Process already running, pid:" << pid; diff --git a/android/ozon/CommonScript.cpp b/android/ozon/CommonScript.cpp index c71ce21..2f53b05 100644 --- a/android/ozon/CommonScript.cpp +++ b/android/ozon/CommonScript.cpp @@ -59,6 +59,9 @@ bool CommonScript::runAppAndGoToHomeScreen( const int width, const int height ) { + // Разблокируем экран перед запуском + AdbUtils::unlockScreen(deviceId, width, height); + // Запустить устройство if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { qDebug() << "Process already running, pid: " << pid; diff --git a/database/DatabaseManager.cpp b/database/DatabaseManager.cpp index 50af47e..5fa7ec7 100644 --- a/database/DatabaseManager.cpp +++ b/database/DatabaseManager.cpp @@ -212,6 +212,10 @@ void DatabaseManager::initSchema() { q.exec("CREATE INDEX IF NOT EXISTS idx_general_logs_id ON general_logs (id DESC)"); + // Миграция: добавляем колонку screenshot если её нет + q.exec("ALTER TABLE general_logs ADD COLUMN screenshot BLOB"); + // Ошибка = колонка уже существует — это нормально + q.exec(R"(CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT @@ -220,6 +224,49 @@ void DatabaseManager::initSchema() { db.commit(); } +void DatabaseManager::cleanupOldData(int days) { + QSqlDatabase db = database(); + QSqlQuery q(db); + + const QString cutoff = QDateTime::currentDateTimeUtc().addDays(-days).toString(Qt::ISODate); + + db.transaction(); + + // general_logs + q.prepare("DELETE FROM general_logs WHERE timestamp < :cutoff"); + q.bindValue(":cutoff", cutoff); + if (q.exec()) { + qDebug() << "[cleanup] general_logs: deleted" << q.numRowsAffected() << "rows older than" << days << "days"; + } + + // app_logs + q.prepare("DELETE FROM app_logs WHERE timestamp < :cutoff"); + q.bindValue(":cutoff", cutoff); + if (q.exec()) { + qDebug() << "[cleanup] app_logs: deleted" << q.numRowsAffected() << "rows older than" << days << "days"; + } + + // events (завершённые/ошибочные) + q.prepare("DELETE FROM events WHERE timestamp < :cutoff AND status IN ('complete', 'error')"); + q.bindValue(":cutoff", cutoff); + if (q.exec()) { + qDebug() << "[cleanup] events: deleted" << q.numRowsAffected() << "rows older than" << days << "days"; + } + + // transactions + q.prepare("DELETE FROM transactions WHERE timestamp < :cutoff"); + q.bindValue(":cutoff", cutoff); + if (q.exec()) { + qDebug() << "[cleanup] transactions: deleted" << q.numRowsAffected() << "rows older than" << days << "days"; + } + + db.commit(); + + // Освобождаем место + q.exec("VACUUM"); + qDebug() << "[cleanup] VACUUM done"; +} + void DatabaseManager::closeConnection() { QMutexLocker locker(&mutex); if (const QString name = connectionName(); connectionPool.contains(name)) { diff --git a/database/DatabaseManager.h b/database/DatabaseManager.h index bdeea3d..ea809d6 100644 --- a/database/DatabaseManager.h +++ b/database/DatabaseManager.h @@ -12,6 +12,7 @@ public: QSqlDatabase database(); void initSchema(); + void cleanupOldData(int days = 5); void closeConnection(); private: diff --git a/database/dao/BankProfileDAO.cpp b/database/dao/BankProfileDAO.cpp index 6aebc91..1e593bb 100644 --- a/database/dao/BankProfileDAO.cpp +++ b/database/dao/BankProfileDAO.cpp @@ -10,22 +10,24 @@ bool BankProfileDAO::upsertApplication(const BankProfileInfo &info) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - INSERT INTO bank_profiles (id, device_id, pin_code, name, code, package, status, comment, install, pin_code_checked) - VALUES (:id, :device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked) + INSERT INTO bank_profiles (device_id, pin_code, name, code, package, status, comment, install, pin_code_checked, + bank_profile_id, full_name, phone) + VALUES (:device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked, + :bank_profile_id, :full_name, :phone) ON CONFLICT(device_id, code) DO UPDATE SET - device_id = excluded.device_id, - pin_code = excluded.pin_code, + pin_code = CASE WHEN excluded.pin_code != '' THEN excluded.pin_code ELSE bank_profiles.pin_code END, name = excluded.name, - code = excluded.code, - package = excluded.package, + package = CASE WHEN excluded.package != '' THEN excluded.package ELSE bank_profiles.package END, status = excluded.status, comment = excluded.comment, install = excluded.install, - pin_code_checked = excluded.pin_code_checked + pin_code_checked = excluded.pin_code_checked, + bank_profile_id = CASE WHEN excluded.bank_profile_id != '' THEN excluded.bank_profile_id ELSE bank_profiles.bank_profile_id END, + full_name = CASE WHEN excluded.full_name != '' THEN excluded.full_name ELSE bank_profiles.full_name END, + phone = CASE WHEN excluded.phone != '' THEN excluded.phone ELSE bank_profiles.phone END )"); - query.bindValue(":id", info.id); query.bindValue(":device_id", info.deviceId); query.bindValue(":pin_code", info.pinCode); query.bindValue(":name", info.name); @@ -35,6 +37,9 @@ bool BankProfileDAO::upsertApplication(const BankProfileInfo &info) { query.bindValue(":comment", info.comment); query.bindValue(":install", info.install ? "yes" : "no"); query.bindValue(":pin_code_checked", info.pinCodeChecked ? "yes" : "no"); + query.bindValue(":bank_profile_id", info.bankProfileId); + query.bindValue(":full_name", info.fullName); + query.bindValue(":phone", info.phone); if (!query.exec()) { qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text(); @@ -367,3 +372,16 @@ bool BankProfileDAO::checkInstalledApps(const QString &deviceId, const QSet GeneralLogDAO::getLogs(int page, int pageSize, const QStr QList result; QSqlQuery q(DatabaseManager::instance().database()); q.prepare( - "SELECT id, level, source, message, timestamp FROM general_logs" + "SELECT id, level, source, message, timestamp, screenshot FROM general_logs" + buildWhereClause(levels) + " ORDER BY id DESC LIMIT :limit OFFSET :offset" ); @@ -50,7 +56,8 @@ QList GeneralLogDAO::getLogs(int page, int pageSize, const QStr e.level = q.value("level").toString(); e.source = q.value("source").toString(); e.message = q.value("message").toString(); - e.timestamp = DateUtils::fromUtcString(q.value("timestamp").toString()); + e.timestamp = DateUtils::fromUtcString(q.value("timestamp").toString()); + e.screenshot = q.value("screenshot").toByteArray(); result.append(e); } return result; diff --git a/database/dao/GeneralLogDAO.h b/database/dao/GeneralLogDAO.h index fb90ac8..9d36428 100644 --- a/database/dao/GeneralLogDAO.h +++ b/database/dao/GeneralLogDAO.h @@ -5,16 +5,19 @@ #include struct GeneralLogEntry { - int id = -1; - QString level; - QString source; - QString message; - QDateTime timestamp; + int id = -1; + QString level; + QString source; + QString message; + QDateTime timestamp; + QByteArray screenshot; }; class GeneralLogDAO { public: static bool insert(const QString &level, const QString &source, const QString &message); + static bool insert(const QString &level, const QString &source, const QString &message, + const QByteArray &screenshot); [[nodiscard]] static QList getLogs(int page, int pageSize, const QStringList &levels = {}); diff --git a/database/dao/MaterialDAO.cpp b/database/dao/MaterialDAO.cpp index 982a789..46e8c84 100644 --- a/database/dao/MaterialDAO.cpp +++ b/database/dao/MaterialDAO.cpp @@ -19,17 +19,18 @@ bool MaterialDAO::upsertAccount(const MaterialInfo &info) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - INSERT INTO materials (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency) - VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency) + INSERT INTO materials (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency, material_id) + VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency, :material_id) ON CONFLICT(device_id, app_code, last_numbers) DO UPDATE SET amount = excluded.amount, update_time = excluded.update_time, last_numbers = excluded.last_numbers, - card_number = excluded.card_number, + card_number = CASE WHEN excluded.card_number != '' THEN excluded.card_number ELSE materials.card_number END, status = excluded.status, description = excluded.description, - currency = excluded.currency + currency = excluded.currency, + material_id = CASE WHEN excluded.material_id != '' THEN excluded.material_id ELSE materials.material_id END )"); query.bindValue(":device_id", info.deviceId); @@ -41,6 +42,7 @@ bool MaterialDAO::upsertAccount(const MaterialInfo &info) { query.bindValue(":amount", info.amount); query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime)); query.bindValue(":status", materialStatusToString(info.status)); + query.bindValue(":material_id", info.materialId); if (!query.exec()) { qWarning() << "Ошибка при вставке/обновлении account:" << query.lastError().text(); @@ -289,3 +291,16 @@ MaterialInfo MaterialDAO::findByMaterialId(const QString &materialId) { return {}; } + +bool MaterialDAO::migrateDeviceId(const QString &oldDeviceId, const QString &newDeviceId) { + QSqlQuery query(DatabaseManager::instance().database()); + query.prepare("UPDATE materials SET device_id = :new_id WHERE device_id = :old_id"); + query.bindValue(":new_id", newDeviceId); + query.bindValue(":old_id", oldDeviceId); + if (!query.exec()) { + qWarning() << "[MaterialDAO] migrateDeviceId failed:" << query.lastError().text(); + return false; + } + qDebug() << "[MaterialDAO] migrated" << query.numRowsAffected() << "materials from" << oldDeviceId << "to" << newDeviceId; + return true; +} diff --git a/database/dao/MaterialDAO.h b/database/dao/MaterialDAO.h index 7b1ee11..f162647 100644 --- a/database/dao/MaterialDAO.h +++ b/database/dao/MaterialDAO.h @@ -36,4 +36,6 @@ public: [[nodiscard]] static QStringList getActiveMaterialIds(); [[nodiscard]] static QStringList getActiveMaterialIdsExcludingDevices(const QSet &excludeDevices); + + [[nodiscard]] static bool migrateDeviceId(const QString &oldDeviceId, const QString &newDeviceId); }; diff --git a/main.cpp b/main.cpp index bf1e2c3..36986c2 100644 --- a/main.cpp +++ b/main.cpp @@ -268,44 +268,18 @@ int main(int argc, char *argv[]) { qInstallMessageHandler(messageHandler); - // Проверяем наличие файла БД, если нет — предлагаем загрузить + // Создаём директорию для БД, если её нет (initSchema создаст файл автоматически) { QSettings settings("config.ini", QSettings::IniFormat); QString dbPath = settings.value("db/dbPath").toString(); if (dbPath.startsWith("~")) dbPath.replace(0, 1, QDir::homePath()); - - if (!QFile::exists(dbPath)) { - QMessageBox msgBox; - msgBox.setWindowTitle("База данных не найдена"); - msgBox.setText("Файл базы данных не найден:\n" + dbPath); - QPushButton *selectBtn = msgBox.addButton("Выбрать файл", QMessageBox::AcceptRole); - QPushButton *createBtn = msgBox.addButton("Создать новую", QMessageBox::ActionRole); - QPushButton *exitBtn = msgBox.addButton("Выход", QMessageBox::RejectRole); - msgBox.setDefaultButton(selectBtn); - msgBox.exec(); - - if (msgBox.clickedButton() == selectBtn) { - QString selected = QFileDialog::getOpenFileName( - nullptr, "Выберите файл базы данных", QDir::homePath(), "SQLite (*.db *.sqlite);;Все файлы (*)"); - if (selected.isEmpty()) { - return 0; - } - QDir().mkpath(QFileInfo(dbPath).absolutePath()); - if (!QFile::copy(selected, dbPath)) { - QMessageBox::critical(nullptr, "Ошибка", "Не удалось скопировать файл базы данных."); - return 1; - } - } else if (msgBox.clickedButton() == createBtn) { - // Просто продолжаем — initSchema создаст новую БД - } else { - return 0; - } - } + QDir().mkpath(QFileInfo(dbPath).absolutePath()); } // Миграции БД DatabaseManager::instance().initSchema(); + DatabaseManager::instance().cleanupOldData(5); dbReady.store(true); // 1. Если есть api_key — логинимся и проверяем/создаём desktop diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index e2af28c..ea06513 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -337,8 +337,16 @@ void DeviceScreener::start() { } else { // Маппинг с восстановленным устройством — только по name (точное совпадение) const DeviceInfo restored = DeviceDAO::findByName(device.name); + qDebug() << "[DeviceScreener] findByName(" << device.name << ") →" + << "id:" << restored.id << "apiId:" << restored.apiId + << "match:" << (!restored.id.isEmpty() && restored.id != device.id && !restored.apiId.isEmpty()); if (!restored.id.isEmpty() && restored.id != device.id && !restored.apiId.isEmpty()) { device.apiId = restored.apiId; + // Сначала создаём новое устройство (FK target), затем мигрируем, затем удаляем старое + DeviceDAO::upsertDevice(device); + const bool bpOk = BankProfileDAO::migrateDeviceId(restored.id, device.id); + const bool matOk = MaterialDAO::migrateDeviceId(restored.id, device.id); + qDebug() << "[DeviceScreener] migration result: bp=" << bpOk << "mat=" << matOk; DeviceDAO::deleteDevice(restored.id); qDebug() << "[DeviceScreener] matched restored device:" << restored.id << "->" << device.id << "apiId:" << device.apiId; diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index 4c2ffb5..26826a5 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -18,6 +18,7 @@ #include "dao/TransactionDAO.h" #include "db/BankProfileInfo.h" #include "adb/AdbUtils.h" +#include "dao/GeneralLogDAO.h" #include "net/NetworkService.h" #include "black/GetLastTransactionsScript.h" #include "black/LoginAndCheckAccountsScript.h" @@ -256,14 +257,20 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, } else { if (newStatus == EventStatus::Error) { // Сохраняем скриншот устройства при ошибке + QByteArray screenshot; if (!event.deviceId.isEmpty()) { const DeviceInfo device = DeviceDAO::getDeviceById(event.deviceId); const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial; - const QByteArray screenshot = AdbUtils::captureScreenshotBytes(adbSerial); + screenshot = AdbUtils::captureScreenshotBytes(adbSerial); if (!screenshot.isEmpty()) { EventDAO::updateScreenshot(event.id, screenshot); } } + // Дублируем ошибку в general_logs со скриншотом + const QString logMsg = QString("[%1] %2 bank=%3: %4") + .arg(eventTypeToString(event.type), event.externalId, event.bankName, result); + GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot); + sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result); EventDAO::markSentToServer(event.id); } diff --git a/views/log/FileLogWindow.cpp b/views/log/FileLogWindow.cpp index fed22b9..fede7c7 100644 --- a/views/log/FileLogWindow.cpp +++ b/views/log/FileLogWindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "dao/EventDAO.h" @@ -25,7 +26,7 @@ static constexpr int COL_MESSAGE = 4; FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) { setWindowTitle("Логи"); - resize(1100, 600); + resize(1200, 650); m_table = new QTableWidget(this); m_table->setColumnCount(5); @@ -40,6 +41,19 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) { m_table->setColumnWidth(COL_LEVEL, 80); m_table->setColumnWidth(COL_SOURCE, 250); + // Панель скриншота + m_screenshotLabel = new QLabel(this); + m_screenshotLabel->setAlignment(Qt::AlignCenter); + m_screenshotLabel->setMinimumWidth(300); + m_screenshotLabel->setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;"); + m_screenshotLabel->setText("Выберите запись\nдля просмотра скриншота"); + + auto *splitter = new QSplitter(Qt::Horizontal, this); + splitter->addWidget(m_table); + splitter->addWidget(m_screenshotLabel); + splitter->setStretchFactor(0, 3); + splitter->setStretchFactor(1, 1); + m_showAllCheck = new QCheckBox("Показать все (включая DEBUG/INFO)", this); m_prevBtn = new QPushButton("←", this); @@ -62,7 +76,7 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) { paginationLayout->addWidget(m_sendBtn); auto *mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(m_table, 1); + mainLayout->addWidget(splitter, 1); mainLayout->addLayout(paginationLayout); connect(m_prevBtn, &QPushButton::clicked, this, [this]() { @@ -79,6 +93,9 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) { m_page = 0; loadPage(); }); + connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) { + showScreenshot(row); + }); connect(m_sendBtn, &QPushButton::clicked, this, &FileLogWindow::sendLogToTelegram); loadPage(); @@ -96,11 +113,11 @@ void FileLogWindow::loadPage() { m_totalPages = qMax(1, (total + m_pageSize - 1) / m_pageSize); m_page = qBound(0, m_page, m_totalPages - 1); - const QList entries = GeneralLogDAO::getLogs(m_page, m_pageSize, levels); + m_entries = GeneralLogDAO::getLogs(m_page, m_pageSize, levels); - m_table->setRowCount(static_cast(entries.size())); - for (int i = 0; i < entries.size(); ++i) { - const auto &e = entries[i]; + m_table->setRowCount(static_cast(m_entries.size())); + for (int i = 0; i < m_entries.size(); ++i) { + const auto &e = m_entries[i]; m_table->setItem(i, COL_ID, new QTableWidgetItem(QString::number(e.id))); m_table->setItem(i, COL_TIME, new QTableWidgetItem( e.timestamp.toLocalTime().toString("dd.MM.yy HH:mm:ss"))); @@ -112,6 +129,25 @@ void FileLogWindow::loadPage() { updatePagination(); } +void FileLogWindow::showScreenshot(int row) { + if (row < 0 || row >= m_entries.size()) return; + const QByteArray &data = m_entries[row].screenshot; + if (data.isEmpty()) { + m_screenshotLabel->setText("Нет скриншота"); + return; + } + + QPixmap pixmap; + pixmap.loadFromData(data); + if (pixmap.isNull()) { + m_screenshotLabel->setText("Ошибка загрузки"); + return; + } + + m_screenshotLabel->setPixmap(pixmap.scaled( + m_screenshotLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); +} + void FileLogWindow::updatePagination() { m_pageLabel->setText(QString("Страница %1 из %2").arg(m_page + 1).arg(m_totalPages)); m_prevBtn->setEnabled(m_page > 0); @@ -175,6 +211,21 @@ void FileLogWindow::sendLogToTelegram() { } } + // 3.5. Экспортируем скриншоты из general_logs (ошибки скриптов) + const QList logErrors = GeneralLogDAO::getLogs(0, 100, {"WARNING", "CRITICAL", "FATAL"}); + for (const GeneralLogEntry &gl : logErrors) { + if (gl.screenshot.isEmpty()) continue; + const QString fileName = QString("screenshots/log_%1_%2.png") + .arg(gl.id) + .arg(gl.timestamp.toString("yyyyMMdd_HHmmss")); + QFile imgFile(tmpDir + "/" + fileName); + if (imgFile.open(QIODevice::WriteOnly)) { + imgFile.write(gl.screenshot); + imgFile.close(); + ++screenshotCount; + } + } + // 4. Создаём ZIP const QString zipPath = QDir::tempPath() + "/arc_logs.zip"; QFile::remove(zipPath); diff --git a/views/log/FileLogWindow.h b/views/log/FileLogWindow.h index 49f58a1..82af8ba 100644 --- a/views/log/FileLogWindow.h +++ b/views/log/FileLogWindow.h @@ -3,8 +3,11 @@ #include #include #include +#include #include +#include "dao/GeneralLogDAO.h" + class FileLogWindow final : public QDialog { Q_OBJECT @@ -13,6 +16,7 @@ public: private: QTableWidget *m_table; + QLabel *m_screenshotLabel; QLabel *m_pageLabel; QPushButton *m_prevBtn; QPushButton *m_nextBtn; @@ -23,8 +27,11 @@ private: int m_pageSize = 20; int m_totalPages = 1; + QList m_entries; + QStringList currentLevelFilter() const; void loadPage(); void updatePagination(); + void showScreenshot(int row); void sendLogToTelegram(); }; diff --git a/views/widget/loader/RegistrationWindow.cpp b/views/widget/loader/RegistrationWindow.cpp index 1526209..650ea27 100644 --- a/views/widget/loader/RegistrationWindow.cpp +++ b/views/widget/loader/RegistrationWindow.cpp @@ -19,10 +19,10 @@ RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) { auto *layout = new QVBoxLayout(this); - layout->addWidget(new QLabel("Введите API ключ:")); + layout->addWidget(new QLabel("Введите ваш идентификатор:")); QLineEdit *apiKeyEdit = new QLineEdit(); - apiKeyEdit->setPlaceholderText("API ключ"); + apiKeyEdit->setPlaceholderText("Идентификатор"); layout->addWidget(apiKeyEdit); QHBoxLayout *buttonLayout = new QHBoxLayout(); @@ -39,7 +39,7 @@ RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) { connect(loginButton, &QPushButton::clicked, this, [=]() { const QString apiKey = apiKeyEdit->text().trimmed(); if (apiKey.isEmpty()) { - QMessageBox::warning(this, "Ошибка", "Введите API ключ"); + QMessageBox::warning(this, "Ошибка", "Введите ваш идентификатор"); return; } @@ -66,7 +66,7 @@ RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) { emit onRegisteredSuccess(); } else if (result == 0) { SettingsDAO::remove("api_key"); - QMessageBox::critical(this, "Ошибка входа", "Неверный API ключ. Проверьте и попробуйте снова."); + QMessageBox::critical(this, "Ошибка входа", "Неверный идентификатор. Проверьте и попробуйте снова."); } else { QMessageBox::critical(this, "Ошибка подключения", "Нет подключения к интернету или сервер не отвечает.\n\nПожалуйста, попробуйте позже.");