258 lines
9.6 KiB
C++
258 lines
9.6 KiB
C++
#include "FileLogWindow.h"
|
||
|
||
#include <QCoreApplication>
|
||
#include <QDir>
|
||
#include <QFile>
|
||
#include <QHBoxLayout>
|
||
#include <QHeaderView>
|
||
#include <QHttpMultiPart>
|
||
#include <QMessageBox>
|
||
#include <QNetworkAccessManager>
|
||
#include <QNetworkReply>
|
||
#include <QProcess>
|
||
#include <QTemporaryFile>
|
||
#include <QVBoxLayout>
|
||
|
||
#include "dao/EventDAO.h"
|
||
#include "dao/GeneralLogDAO.h"
|
||
#include "dao/SettingsDAO.h"
|
||
|
||
static constexpr int COL_ID = 0;
|
||
static constexpr int COL_TIME = 1;
|
||
static constexpr int COL_LEVEL = 2;
|
||
static constexpr int COL_SOURCE = 3;
|
||
static constexpr int COL_MESSAGE = 4;
|
||
|
||
FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
||
setWindowTitle("Логи");
|
||
resize(1100, 600);
|
||
|
||
m_table = new QTableWidget(this);
|
||
m_table->setColumnCount(5);
|
||
m_table->setHorizontalHeaderLabels({"#", "Время", "Уровень", "Источник", "Сообщение"});
|
||
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
m_table->verticalHeader()->hide();
|
||
m_table->horizontalHeader()->setStretchLastSection(true);
|
||
m_table->setColumnWidth(COL_ID, 45);
|
||
m_table->setColumnWidth(COL_TIME, 145);
|
||
m_table->setColumnWidth(COL_LEVEL, 80);
|
||
m_table->setColumnWidth(COL_SOURCE, 250);
|
||
|
||
m_showAllCheck = new QCheckBox("Показать все (включая DEBUG/INFO)", this);
|
||
|
||
m_prevBtn = new QPushButton("←", this);
|
||
m_nextBtn = new QPushButton("→", this);
|
||
m_pageLabel = new QLabel(this);
|
||
m_pageLabel->setAlignment(Qt::AlignCenter);
|
||
m_pageLabel->setMinimumWidth(140);
|
||
|
||
auto *refreshBtn = new QPushButton("Обновить", this);
|
||
m_sendBtn = new QPushButton("Отправить", this);
|
||
m_sendBtn->setStyleSheet("background-color: #0088cc; color: white; border-radius: 4px; padding: 4px 12px;");
|
||
|
||
auto *paginationLayout = new QHBoxLayout;
|
||
paginationLayout->addWidget(m_prevBtn);
|
||
paginationLayout->addWidget(m_pageLabel);
|
||
paginationLayout->addWidget(m_nextBtn);
|
||
paginationLayout->addStretch();
|
||
paginationLayout->addWidget(m_showAllCheck);
|
||
paginationLayout->addWidget(refreshBtn);
|
||
paginationLayout->addWidget(m_sendBtn);
|
||
|
||
auto *mainLayout = new QVBoxLayout(this);
|
||
mainLayout->addWidget(m_table, 1);
|
||
mainLayout->addLayout(paginationLayout);
|
||
|
||
connect(m_prevBtn, &QPushButton::clicked, this, [this]() {
|
||
if (m_page > 0) { --m_page; loadPage(); }
|
||
});
|
||
connect(m_nextBtn, &QPushButton::clicked, this, [this]() {
|
||
if (m_page < m_totalPages - 1) { ++m_page; loadPage(); }
|
||
});
|
||
connect(refreshBtn, &QPushButton::clicked, this, [this]() {
|
||
m_page = 0;
|
||
loadPage();
|
||
});
|
||
connect(m_showAllCheck, &QCheckBox::toggled, this, [this]() {
|
||
m_page = 0;
|
||
loadPage();
|
||
});
|
||
connect(m_sendBtn, &QPushButton::clicked, this, &FileLogWindow::sendLogToTelegram);
|
||
|
||
loadPage();
|
||
}
|
||
|
||
QStringList FileLogWindow::currentLevelFilter() const {
|
||
if (m_showAllCheck->isChecked())
|
||
return {};
|
||
return {"WARNING", "CRITICAL", "FATAL"};
|
||
}
|
||
|
||
void FileLogWindow::loadPage() {
|
||
const QStringList levels = currentLevelFilter();
|
||
const int total = GeneralLogDAO::getTotalCount(levels);
|
||
m_totalPages = qMax(1, (total + m_pageSize - 1) / m_pageSize);
|
||
m_page = qBound(0, m_page, m_totalPages - 1);
|
||
|
||
const QList<GeneralLogEntry> entries = GeneralLogDAO::getLogs(m_page, m_pageSize, levels);
|
||
|
||
m_table->setRowCount(static_cast<int>(entries.size()));
|
||
for (int i = 0; i < entries.size(); ++i) {
|
||
const auto &e = 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")));
|
||
m_table->setItem(i, COL_LEVEL, new QTableWidgetItem(e.level));
|
||
m_table->setItem(i, COL_SOURCE, new QTableWidgetItem(e.source));
|
||
m_table->setItem(i, COL_MESSAGE, new QTableWidgetItem(e.message));
|
||
}
|
||
|
||
updatePagination();
|
||
}
|
||
|
||
void FileLogWindow::updatePagination() {
|
||
m_pageLabel->setText(QString("Страница %1 из %2").arg(m_page + 1).arg(m_totalPages));
|
||
m_prevBtn->setEnabled(m_page > 0);
|
||
m_nextBtn->setEnabled(m_page < m_totalPages - 1);
|
||
}
|
||
|
||
void FileLogWindow::sendLogToTelegram() {
|
||
const QString logPath = QCoreApplication::applicationDirPath() + "/application.log";
|
||
if (!QFile::exists(logPath)) {
|
||
QMessageBox::warning(this, "Ошибка", "Файл application.log не найден");
|
||
return;
|
||
}
|
||
|
||
m_sendBtn->setEnabled(false);
|
||
m_sendBtn->setText("Подготовка...");
|
||
|
||
// Собираем временную папку для архива
|
||
const QString tmpDir = QDir::tempPath() + "/arc_logs_export";
|
||
QDir().mkpath(tmpDir);
|
||
|
||
// 1. Копируем application.log
|
||
QFile::remove(tmpDir + "/application.log");
|
||
QFile::copy(logPath, tmpDir + "/application.log");
|
||
|
||
// 2. Экспортируем events с ошибками в errors.txt
|
||
const QList<EventInfo> errors = EventDAO::getAllEvents(EventStatus::Error);
|
||
if (!errors.isEmpty()) {
|
||
QFile errFile(tmpDir + "/errors.txt");
|
||
if (errFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||
QTextStream out(&errFile);
|
||
for (const EventInfo &e : errors) {
|
||
out << "--- Event #" << e.id << " ---\n"
|
||
<< "Type: " << eventTypeToString(e.type) << "\n"
|
||
<< "Bank: " << e.bankName << "\n"
|
||
<< "Device: " << e.deviceId << "\n"
|
||
<< "Amount: " << e.amount << "\n"
|
||
<< "Phone: " << e.phone << "\n"
|
||
<< "Comment: " << e.comment << "\n"
|
||
<< "Time: " << e.timestamp.toString(Qt::ISODate) << "\n"
|
||
<< "JSON: " << e.json << "\n\n";
|
||
}
|
||
errFile.close();
|
||
}
|
||
}
|
||
|
||
// 3. Экспортируем скриншоты ошибок из events
|
||
QDir().mkpath(tmpDir + "/screenshots");
|
||
int screenshotCount = 0;
|
||
for (const EventInfo &e : errors) {
|
||
const QByteArray screenshot = EventDAO::getScreenshot(e.id);
|
||
if (screenshot.isEmpty()) continue;
|
||
const QString fileName = QString("screenshots/%1_%2_%3.png")
|
||
.arg(e.id)
|
||
.arg(eventTypeToString(e.type))
|
||
.arg(e.timestamp.toString("yyyyMMdd_HHmmss"));
|
||
QFile imgFile(tmpDir + "/" + fileName);
|
||
if (imgFile.open(QIODevice::WriteOnly)) {
|
||
imgFile.write(screenshot);
|
||
imgFile.close();
|
||
++screenshotCount;
|
||
}
|
||
}
|
||
|
||
// 4. Создаём ZIP
|
||
const QString zipPath = QDir::tempPath() + "/arc_logs.zip";
|
||
QFile::remove(zipPath);
|
||
|
||
QProcess zip;
|
||
zip.setWorkingDirectory(tmpDir);
|
||
zip.start("zip", QStringList() << "-r" << zipPath << ".");
|
||
zip.waitForFinished(30000);
|
||
|
||
// Чистим временную папку
|
||
QDir(tmpDir).removeRecursively();
|
||
|
||
if (zip.exitCode() != 0 || !QFile::exists(zipPath)) {
|
||
QMessageBox::warning(this, "Ошибка", "Не удалось создать архив");
|
||
m_sendBtn->setEnabled(true);
|
||
m_sendBtn->setText("Отправить");
|
||
return;
|
||
}
|
||
|
||
m_sendBtn->setText("Отправка...");
|
||
|
||
QFile *zipFile = new QFile(zipPath);
|
||
if (!zipFile->open(QIODevice::ReadOnly)) {
|
||
QMessageBox::warning(this, "Ошибка", "Не удалось открыть архив");
|
||
delete zipFile;
|
||
m_sendBtn->setEnabled(true);
|
||
m_sendBtn->setText("Отправить");
|
||
return;
|
||
}
|
||
|
||
// Telegram Bot API
|
||
const QString botToken = "8256716069:AAFgZRB_0Y6KTpqFQmCxIlZiWdY5m2dR2D8";
|
||
const QString chatId = "-5177086220";
|
||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||
const QString caption = "Logs from desktop: " + desktopId
|
||
+ "\nErrors: " + QString::number(errors.size())
|
||
+ "\nScreenshots: " + QString::number(screenshotCount);
|
||
|
||
const QUrl url("https://api.telegram.org/bot" + botToken + "/sendDocument");
|
||
|
||
auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||
|
||
QHttpPart chatPart;
|
||
chatPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="chat_id")");
|
||
chatPart.setBody(chatId.toUtf8());
|
||
multiPart->append(chatPart);
|
||
|
||
QHttpPart captionPart;
|
||
captionPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="caption")");
|
||
captionPart.setBody(caption.toUtf8());
|
||
multiPart->append(captionPart);
|
||
|
||
QHttpPart filePart;
|
||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/zip");
|
||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||
R"(form-data; name="document"; filename="arc_logs.zip")");
|
||
filePart.setBodyDevice(zipFile);
|
||
zipFile->setParent(multiPart);
|
||
multiPart->append(filePart);
|
||
|
||
auto *manager = new QNetworkAccessManager(this);
|
||
QNetworkReply *reply = manager->post(QNetworkRequest(url), multiPart);
|
||
multiPart->setParent(reply);
|
||
|
||
connect(reply, &QNetworkReply::finished, this, [this, reply, manager, zipPath]() {
|
||
reply->deleteLater();
|
||
manager->deleteLater();
|
||
QFile::remove(zipPath);
|
||
|
||
m_sendBtn->setEnabled(true);
|
||
m_sendBtn->setText("Отправить");
|
||
|
||
if (reply->error() != QNetworkReply::NoError) {
|
||
QMessageBox::warning(this, "Ошибка", "Не удалось отправить: " + reply->errorString());
|
||
return;
|
||
}
|
||
|
||
QMessageBox::information(this, "Готово", "Логи отправлены в Telegram");
|
||
});
|
||
}
|