1
0
forked from BRT/arc
arc/views/log/FileLogWindow.cpp

317 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <QPixmap>
#include <QVBoxLayout>
#include "dao/EventDAO.h"
#include "dao/GeneralLogDAO.h"
#include "dao/SettingsDAO.h"
#include "log/LogDetailWindow.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(1200, 650);
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_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);
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(splitter, 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_table, &QTableWidget::cellClicked, this, [this](int row, int) {
showScreenshot(row);
});
connect(m_table, &QTableWidget::cellDoubleClicked, this, [this](int row, int) {
if (row >= 0 && row < m_entries.size()) {
auto *detail = new LogDetailWindow(m_entries[row], this);
detail->setAttribute(Qt::WA_DeleteOnClose);
detail->show();
}
});
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);
m_entries = GeneralLogDAO::getLogs(m_page, m_pageSize, levels);
m_table->setRowCount(static_cast<int>(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")));
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::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);
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;
}
}
// 3.5. Экспортируем скриншоты из general_logs (ошибки скриптов)
const QList<GeneralLogEntry> 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);
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");
});
}