mainwindow.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #include <include/ui/mainwindow.h>
  2. #include <QDateTime>
  3. #include <QDebug>
  4. #include <QLayout>
  5. #include <QScrollBar>
  6. #include <QString>
  7. #include <QTextEdit>
  8. #include <QTextStream>
  9. #include <headers/util.hpp>
  10. #include <vector>
  11. #include "ui_mainwindow.h"
  12. /**
  13. * Helper functions
  14. */
  15. namespace {
  16. void infoMessageBox(QString text, QString title = "KYGUI") {
  17. QMessageBox box;
  18. box.setWindowTitle(title);
  19. box.setText(text);
  20. box.setButtonText(0, "Close");
  21. box.exec();
  22. }
  23. bool isSameEvent(QString event1, QString event2) {
  24. auto event_size =
  25. event1.size() > event2.size() ? event2.size() : event1.size();
  26. auto similarity_standard = event_size * 0.67;
  27. auto similarity_index = 0;
  28. for (auto i = 0; i < event_size; i++) {
  29. if (event1[i] == event2[i]) {
  30. similarity_index++;
  31. }
  32. }
  33. return similarity_index > similarity_standard;
  34. }
  35. int getLikeEventNum(QString event, QList<QString> events) {
  36. auto i = events.size() - 1;
  37. auto hits = 0;
  38. bool is_same_event = false;
  39. auto incoming_event = event.remove(0, 11);
  40. do {
  41. auto existing_event = events[i];
  42. if ((is_same_event =
  43. isSameEvent(incoming_event, existing_event.remove(0, 11)))) {
  44. i--;
  45. hits++;
  46. }
  47. } while (is_same_event && i > -1);
  48. return hits;
  49. }
  50. QString getTime() { return QDateTime::currentDateTime().toString("hh:mm:ss"); }
  51. QString timestampPrefix() {
  52. return QDateTime::currentDateTime().toString("hh:mm:ss") + " - ";
  53. }
  54. /**
  55. * @brief createProcessListItem
  56. * @param process
  57. * @return
  58. */
  59. QStandardItem* createProcessListItem(Process process) {
  60. return new QStandardItem(
  61. QString("%0 requested for execution. ID: %1\nStatus: %2\nTime: %3 "
  62. "Done: %4\n Errors: %5")
  63. .arg(process.name)
  64. .arg(process.id)
  65. .arg(ProcessNames[process.state - 1])
  66. .arg(process.start)
  67. .arg(process.end)
  68. .arg(process.error));
  69. }
  70. /**
  71. * @brief createEventListItem
  72. * @param event
  73. * @return
  74. */
  75. QStandardItem* createEventListItem(QString event) {
  76. return new QStandardItem(event);
  77. }
  78. } // namespace
  79. /**
  80. *\mainpage The KYGUI application interface begins with the MainWindow
  81. * @brief MainWindow::MainWindow
  82. * @param argc
  83. * @param argv
  84. * @param parent
  85. */
  86. MainWindow::MainWindow(int argc, char** argv, QWidget* parent)
  87. : QMainWindow(parent),
  88. cli_argc(argc),
  89. cli_argv(argv),
  90. ui(new Ui::MainWindow),
  91. arg_ui(new ArgDialog),
  92. q_client(nullptr) {
  93. m_event_model = new QStandardItemModel(this);
  94. m_process_model = new QStandardItemModel(this);
  95. q_client = new Client(this, cli_argc, cli_argv);
  96. message_parser.init(this);
  97. ui->setupUi(this);
  98. this->setWindowTitle("KYGUI");
  99. setStyleSheet(
  100. "QListView { font: 87 11pt \"Noto Sans\"; background-color: #2f535f;"
  101. "color: rgb(131, "
  102. "148, 150); font-weight: 700; background-color: rgb(29, 51, 59);color: "
  103. "rgb(223, 252, 255);}");
  104. setConnectScreen();
  105. connect(ui->connect, &QPushButton::clicked, this, &MainWindow::connectClient);
  106. ui->eventList->setModel(m_event_model);
  107. ui->processList->setModel(m_process_model);
  108. }
  109. /**
  110. * @brief MainWindow::~MainWindow
  111. */
  112. MainWindow::~MainWindow() {
  113. delete q_client;
  114. delete ui;
  115. delete arg_ui;
  116. delete m_event_model;
  117. }
  118. /**
  119. * @brief MainWindow::setConnectScreen
  120. * @param visible
  121. */
  122. void MainWindow::setConnectScreen(bool visible) {
  123. if (visible) {
  124. ui->startScreen->setMaximumSize(1366, 825);
  125. ui->startScreen->setMinimumSize(1366, 825);
  126. ui->connect->setMaximumSize(1366, 725);
  127. ui->connect->setMinimumSize(1366, 725);
  128. ui->kyConfig->setMaximumSize(1366, 75);
  129. ui->kyConfig->setMinimumSize(1366, 75);
  130. QFile file(QCoreApplication::applicationDirPath() + "/config/config.json");
  131. file.open(QIODevice::ReadOnly | QFile::ReadOnly);
  132. QString config_json = QString::fromUtf8(file.readAll());
  133. ui->kyConfig->setText(config_json);
  134. qDebug() << "Set config json: \n" << ui->kyConfig->toPlainText();
  135. file.close();
  136. } else {
  137. ui->connect->hide();
  138. ui->kyConfig->hide();
  139. ui->startScreen->setVisible(false);
  140. }
  141. }
  142. /**
  143. * @brief MainWindow::connectClient
  144. */
  145. void MainWindow::connectClient() {
  146. m_config = getConfigObject(ui->kyConfig->toPlainText());
  147. QString file_path = m_config.at("fileDirectory");
  148. if (file_path != NULL) {
  149. arg_ui->setFilePath(file_path);
  150. }
  151. setConnectScreen(false);
  152. qDebug() << "Connecting to KServer";
  153. QObject::connect(q_client, &Client::messageReceived, this,
  154. &MainWindow::updateMessages);
  155. QProgressBar* progressBar = ui->progressBar;
  156. q_client->start();
  157. for (int i = 1; i < 101; i++) {
  158. progressBar->setValue(i);
  159. }
  160. QPushButton* send_message_button =
  161. this->findChild<QPushButton*>("sendMessage");
  162. // Handle mouse
  163. QObject::connect(send_message_button, &QPushButton::clicked, this, [this]() {
  164. q_client->sendMessage(escapeMessage(ui->inputText->toPlainText()));
  165. ui->inputText->clear();
  166. });
  167. QObject::connect(
  168. ui->appList,
  169. static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
  170. this, [this]() {
  171. QString app_name = ui->appList->currentText();
  172. // TODO: I know, it's awful. Fix this
  173. q_client->setSelectedApp(std::vector<QString>{{app_name}});
  174. });
  175. QPushButton* disconnect_button = this->findChild<QPushButton*>("disconnect");
  176. QObject::connect(disconnect_button, &QPushButton::clicked, this, [this]() {
  177. q_client->closeConnection();
  178. QApplication::exit(9);
  179. });
  180. QObject::connect(ui->execute, &QPushButton::clicked, this,
  181. [this]() { q_client->execute(); });
  182. QObject::connect(ui->addArgs, &QPushButton::clicked, this, [this]() {
  183. if (ui->appList->count() == 0) {
  184. QMessageBox::warning(this, tr("Args"),
  185. tr("Please connect to the KServer and retrieve a "
  186. "list of available processes."));
  187. } else {
  188. arg_ui->show();
  189. }
  190. });
  191. QObject::connect(
  192. arg_ui, &ArgDialog::taskRequestReady, this,
  193. [this](Task* task) {
  194. auto mask = q_client->getSelectedApp();
  195. if (mask > -1) {
  196. qDebug() << "Scheduling a task";
  197. task->setArgument("mask", mask);
  198. q_client->scheduleTask(task);
  199. }
  200. });
  201. QObject::connect(ui->tasks, &QPushButton::clicked, this, [this]() {
  202. // TODO: Change this to a complete implementation
  203. q_client->sendMessage("scheduler");
  204. });
  205. QObject::connect(ui->viewConsole, &QPushButton::clicked, this,
  206. [this]() { console_ui.show(); });
  207. QObject::connect(
  208. ui->processList, &QListView::clicked, this,
  209. [this](const QModelIndex& index) {
  210. auto process = m_processes.at(index.row());
  211. QString process_info_text =
  212. m_processes.at(index.row()).name.toUtf8() + "\n";
  213. process_info_text += "Execution requested at " +
  214. process.start.toUtf8() + "\n" +
  215. "Is currently in a state of: " +
  216. ProcessNames[process.state - 1].toUtf8();
  217. if (process.end.size() > 0 || process.id == "Scheduled task") {
  218. process_info_text += "\n\nResult: \n" + process.result;
  219. }
  220. infoMessageBox(process_info_text, "Process");
  221. });
  222. QObject::connect(ui->eventList, &QListView::clicked, this,
  223. [this](const QModelIndex& index) {
  224. auto event = m_events.at(index.row());
  225. infoMessageBox(event, "Event");
  226. });
  227. QObject::connect(m_event_model, &QAbstractItemModel::rowsAboutToBeInserted,
  228. this, [this]() {
  229. QScrollBar* bar = ui->eventList->verticalScrollBar();
  230. if (bar->value() == bar->maximum()) {
  231. m_view_states.eventViewBottom = true;
  232. }
  233. });
  234. QObject::connect(m_event_model, &QAbstractItemModel::rowsInserted, this,
  235. [this]() {
  236. if (m_view_states.eventViewBottom) {
  237. ui->eventList->scrollToBottom();
  238. }
  239. });
  240. QTimer* timer = new QTimer(this);
  241. connect(timer, &QTimer::timeout, q_client, &Client::ping);
  242. timer->start(10000);
  243. }
  244. /**
  245. * @brief MainWindow::updateMessages
  246. * @param s
  247. */
  248. void MainWindow::updateMessages(int t, const QString& message, StringVec v) {
  249. QString timestamp_prefix = timestampPrefix();
  250. if (t == MESSAGE_UPDATE_TYPE) { // Normal message
  251. qDebug() << "Updating message area";
  252. message_parser.handleMessage(message, v);
  253. console_ui.updateText(message);
  254. } else if (t == COMMANDS_UPDATE_TYPE) { // Received app list from server
  255. qDebug() << "Updating commands";
  256. QString default_app = configValue("defaultApp", m_config);
  257. message_parser.handleCommands(v, default_app);
  258. if (message == "New Session") { // Session has started
  259. ui->led->setState(true);
  260. arg_ui->setConfig(configValue("instagram", m_config));
  261. if (configBoolValue("schedulerMode", std::ref(m_config))) {
  262. arg_ui->show();
  263. }
  264. }
  265. } else if (t == PROCESS_REQUEST_TYPE) { // Sent execution request to server
  266. qDebug() << "Updating process list";
  267. m_processes.push_back(Process{.name = v.at(1),
  268. .state = ProcessState::PENDING,
  269. .start = getTime(),
  270. .id = v.at(2)});
  271. int row = 0;
  272. for (const auto& process : m_processes) {
  273. m_process_model->setItem(row, createProcessListItem(process));
  274. row++;
  275. }
  276. } else if (t == EVENT_UPDATE_TYPE) { // Received event from server
  277. QString event_message = message_parser.handleEventMessage(message, v);
  278. if (m_events.size() > 1) { // Group repeating event messages
  279. auto last_event = m_events[m_events.size() - 1];
  280. if (isSameEvent(message, last_event.remove(0, 11))) {
  281. m_consecutive_events++;
  282. auto count = getLikeEventNum(event_message, m_events);
  283. QString clean_event_message =
  284. event_message + " (" + QString::number(count) + ")";
  285. m_events.push_back(event_message);
  286. m_event_model->setItem(m_event_model->rowCount() - 1,
  287. createEventListItem(clean_event_message));
  288. return; // It was not a unique message, we can return
  289. }
  290. m_consecutive_events = 0;
  291. }
  292. if (isKEvent<QString>(message,
  293. Event::TASK_SCHEDULED)) { // Event was scheduled task
  294. event_message += ". Details:\n" + parseTaskInfo(v);
  295. }
  296. m_events.push_back(event_message);
  297. m_event_model->setItem(m_event_model->rowCount(),
  298. createEventListItem(event_message));
  299. } else {
  300. qDebug() << "Unknown update type. Cannot update UI";
  301. }
  302. }
  303. /**
  304. * @brief MainWindow::parseTaskInfo
  305. * @param v
  306. * @return
  307. */
  308. QString MainWindow::parseTaskInfo(StringVec v) {
  309. QString task_info{};
  310. if (q_client == nullptr) {
  311. qDebug() << "Can't parse when not connected";
  312. return task_info;
  313. }
  314. auto size = v.size();
  315. if (size < 3) {
  316. qDebug() << "Not enough arguments to parse task information";
  317. } else {
  318. auto error = size == 4;
  319. task_info += " UUID - " + v.at(0) + "\n ID - " + v.at(1) + "\n APP - " +
  320. q_client->getAppName(std::stoi(v.at(2).toUtf8().constData())) +
  321. "\n ENV - " + (v.at(3));
  322. if (error) {
  323. task_info += "\n !ERROR! - " + v.at(3);
  324. }
  325. }
  326. return task_info;
  327. }
  328. /**
  329. * MessageParser
  330. *
  331. * \note We use the MessageParser class to do the heavy lifting of parsing
  332. * incoming messages and updating the UI accordingly
  333. */
  334. /**
  335. * @brief MainWindow::MessageParser::init
  336. * @param window
  337. */
  338. void MainWindow::MessageParser::init(MainWindow* window) {
  339. this->window = window;
  340. }
  341. /**
  342. * @brief MainWindow::MessageParser::handleCommands
  343. * @param commands
  344. * @param default_command
  345. */
  346. void MainWindow::MessageParser::handleCommands(StringVec commands,
  347. QString default_command) {
  348. QComboBox* app_list = window->ui->appList;
  349. app_list->clear();
  350. int app_index = 0;
  351. for (const auto& s : commands) {
  352. app_list->addItem(s);
  353. if (s.toLower() == default_command.toLower()) {
  354. window->ui->appList->setCurrentIndex(app_index);
  355. }
  356. app_index++;
  357. }
  358. }
  359. /**
  360. * @brief MainWindow::MessageParser::handleMessage
  361. * @param message
  362. * @param v
  363. */
  364. void MainWindow::MessageParser::handleMessage(QString message, StringVec v) {
  365. auto simple_message = timestampPrefix() + parseMessage(message, v);
  366. window->ui->messages->append(simple_message);
  367. }
  368. /**
  369. * @brief MainWindow::MessageParser::parseMessage
  370. * @param message
  371. * @param v
  372. * @return
  373. */
  374. QString MainWindow::MessageParser::parseMessage(const QString& message,
  375. StringVec v) {
  376. QString simplified_message{};
  377. if (isMessage(message.toUtf8())) {
  378. simplified_message += "Message: " + getMessage(message.toUtf8());
  379. } else if (isEvent(message.toUtf8())) {
  380. simplified_message += "Event: " + getEvent(message.toUtf8());
  381. } else if (isOperation(message.toUtf8())) {
  382. simplified_message += "Operation: ";
  383. simplified_message += getOperation(message.toUtf8()).c_str();
  384. }
  385. return simplified_message;
  386. }
  387. /**
  388. * @brief MainWindow::MessageParser::updateProcessResult
  389. * @param id
  390. * @param result
  391. * @param error
  392. */
  393. void MainWindow::MessageParser::updateProcessResult(
  394. QString id, QString result,
  395. bool error = false) { // We need to start matching processes with a
  396. // unique identifier
  397. for (int i = window->m_processes.size() - 1; i >= 0; i--) {
  398. if (window->m_processes.at(i).id == id) {
  399. window->m_processes.at(i).end = getTime();
  400. window->m_processes.at(i).state =
  401. !error ? ProcessState::SUCCEEDED : ProcessState::FAILED;
  402. window->m_processes.at(i).result = result;
  403. window->m_process_model->setItem(
  404. i, 0, createProcessListItem(window->m_processes.at(i)));
  405. return;
  406. }
  407. }
  408. // If we didn't return, it's a new process:
  409. }
  410. /**
  411. * @brief MainWindow::MessageParser::handleEventMessage
  412. * @param message
  413. * @param v
  414. * @return
  415. */
  416. QString MainWindow::MessageParser::handleEventMessage(QString message,
  417. StringVec v) {
  418. QString event_message = timestampPrefix();
  419. if (!v.empty()) {
  420. if (v.size() == 1) {
  421. event_message += message + "\n" + v.at(0);
  422. } else {
  423. event_message += message;
  424. if (message == "Process Result") {
  425. auto error = v.size() > 3 ? true : false;
  426. event_message += "\n";
  427. auto app_name = window->q_client->getAppName(
  428. std::stoi(v.at(0).toUtf8().constData()));
  429. auto process_it = std::find_if(
  430. window->m_processes.begin(), window->m_processes.end(),
  431. [v](const Process& process) { return process.id == v.at(1); });
  432. if (process_it != window->m_processes.end()) {
  433. updateProcessResult(v.at(1), v.at(2), error);
  434. } else { // new process, from scheduled task
  435. Process new_process{
  436. .name = app_name,
  437. .state = !error ? ProcessState::SUCCEEDED : ProcessState::FAILED,
  438. .start = getTime(),
  439. .id = "Scheduled task",
  440. .error = error ? v.at(3) : "No errors reported"};
  441. if (v.count() > 2 && !v.at(2).isEmpty()) {
  442. new_process.result = v.at(2);
  443. new_process.end = new_process.start;
  444. }
  445. window->m_processes.push_back(new_process);
  446. window->m_process_model->setItem(window->m_process_model->rowCount(),
  447. createProcessListItem(new_process));
  448. }
  449. event_message += app_name;
  450. event_message += ": ";
  451. event_message += v.at(2);
  452. if (error) {
  453. event_message += "\n Error: " + v.at(3);
  454. }
  455. } else if (QString::compare(message, "Message Received") == 0) {
  456. event_message += "\n" + v.at(1) + ": " + v.at(2);
  457. }
  458. }
  459. } else {
  460. event_message += message;
  461. }
  462. return event_message;
  463. }