// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-remoteproxy. * * nymea-remoteproxy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-remoteproxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-remoteproxy. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "terminalwindow.h" #include "utils.h" #include #include #include #include TerminalWindow::TerminalWindow(QObject *parent) : QObject(parent) { // Init view tabs m_tabs << ViewTunnelProxy; // Create main window m_mainWindow = initscr(); // Enable colors if available if (has_colors()) { start_color(); init_pair(1, COLOR_BLACK, COLOR_GREEN); init_pair(2, COLOR_BLACK, COLOR_RED); } // Configure behaviour cbreak(); noecho(); nonl();; nodelay(stdscr, TRUE); curs_set(FALSE); clear(); keypad(m_mainWindow, true); nodelay(m_mainWindow, true); getmaxyx(m_mainWindow, m_terminalSizeY, m_terminalSizeX); wrefresh(m_mainWindow); // Create header and content window m_headerWindow = newwin(m_headerHeight, m_terminalSizeX, 0, 0); m_contentWindow = newwin(m_terminalSizeY - m_headerHeight, m_terminalSizeX, m_headerHeight, 0); nodelay(m_headerWindow, TRUE); nodelay(m_contentWindow, TRUE); werase(m_headerWindow); werase(m_contentWindow); // Draw borders drawWindowBorder(m_headerWindow); //drawWindowBorder(m_contentWindow); scrollok(m_contentWindow, true); //box(m_headerWindow, 0 , 0); //box(m_contentWindow, 0 , 0); // Refresh windows wrefresh(m_headerWindow); wrefresh(m_contentWindow); refresh(); eventLoop(); } TerminalWindow::~TerminalWindow() { cleanup(); } void TerminalWindow::resizeWindow() { int terminalSizeX; int terminalSizeY; getmaxyx(stdscr, terminalSizeY, terminalSizeX); // Resize the window if size has changed if (m_terminalSizeX != terminalSizeX || m_terminalSizeY != terminalSizeY) { m_terminalSizeX = terminalSizeX; m_terminalSizeY = terminalSizeY; wresize(m_headerWindow, m_headerHeight, m_terminalSizeX); wresize(m_contentWindow, m_terminalSizeY - m_headerHeight, m_terminalSizeX); } } void TerminalWindow::drawWindowBorder(WINDOW *window) { int x, y, i; getmaxyx(window, y, x); // corners mvwprintw(window, 0, 0, "+"); mvwprintw(window, y - 1, 0, "+"); mvwprintw(window, 0, x - 1, "+"); mvwprintw(window, y - 1, x - 1, "+"); // sides for (i = 1; i < (y - 1); i++) { mvwprintw(window, i, 0, "|"); mvwprintw(window, i, x - 1, "|"); } // top and bottom for (i = 1; i < (x - 1); i++) { mvwprintw(window, 0, i, "-"); mvwprintw(window, y - 1, i, "-"); } } void TerminalWindow::moveTabRight() { int currentIndex = m_tabs.indexOf(m_view); if (currentIndex + 1 >= m_tabs.count()) { m_view = m_tabs.at(0); } else { m_view = m_tabs.at( currentIndex + 1); } } void TerminalWindow::moveTabLeft() { int currentIndex = m_tabs.indexOf(m_view); if (currentIndex - 1 < 0) { m_view = m_tabs.at(m_tabs.count() - 1); } else { m_view = m_tabs.at( currentIndex - 1); } } void TerminalWindow::paintHeader() { QString windowName; QString headerString; switch (m_view) { case ViewTunnelProxy: windowName = "-- TunnelProxy --"; headerString = QString(" Server: %1 (%2) | API: %3 | Total: %4 | Servers: %5 | Clients: %6 | %7 | %8") .arg(m_dataMap.value("serverName", "-").toString()) .arg(m_dataMap.value("serverVersion", "-").toString()) .arg(m_dataMap.value("apiVersion", "-").toString()) .arg(m_dataMap.value("tunnelProxyStatistic").toMap().value("totalClientCount", 0).toInt()) .arg(m_dataMap.value("tunnelProxyStatistic").toMap().value("serverConnectionsCount", 0).toInt()) .arg(m_dataMap.value("tunnelProxyStatistic").toMap().value("clientConnectionsCount", 0).toInt()) .arg(Utils::humanReadableTraffic(m_dataMap.value("tunnelProxyStatistic").toMap().value("troughput", 0).toInt()) + " / s", - 13) .arg(windowName); break; } int delta = m_terminalSizeX - headerString.count(); // Fill string for background color for (int i = 0; i < delta; i++) headerString.append(" "); //wattron(m_headerWindow, A_BOLD | COLOR_PAIR(1)); mvwprintw(m_headerWindow, 1, 2, "%s", headerString.toLatin1().constData()); //wattroff(m_headerWindow, A_BOLD | COLOR_PAIR(1)); } void TerminalWindow::paintContentClients() { if (m_clientHash.isEmpty()) return; int i = 1; foreach (const QVariant &clientVariant, m_clientHash.values()) { QVariantMap clientMap = clientVariant.toMap(); uint timeStamp = clientMap.value("timestamp").toUInt(); QString clientConnectionTime = QDateTime::fromMSecsSinceEpoch(timeStamp * 1000).toString("dd.MM.yyyy hh:mm:ss"); int rxDataCountBytes = clientMap.value("rxDataCount").toInt(); int txDataCountBytes = clientMap.value("txDataCount").toInt(); QString clientPrint = QString("%1 | %2 | %3 | RX: %4 | TX: %5 | %6 | %7 | %8") .arg(clientConnectionTime) .arg(clientMap.value("duration").toString()) .arg(clientMap.value("address").toString(), - 16) .arg(Utils::humanReadableTraffic(rxDataCountBytes), - 10) .arg(Utils::humanReadableTraffic(txDataCountBytes), - 10) .arg((clientMap.value("authenticated").toBool() ? "A" : "-")) .arg((clientMap.value("tunnelConnected").toBool() ? "T" : "-")) .arg(clientMap.value("name").toString(), -30); mvwprintw(m_contentWindow, i, 2, "%s", clientPrint.trimmed().toLatin1().constData()); i++; } } void TerminalWindow::paintContentTunnels() { int i = 1; foreach (const QVariant &tunnelVaiant, m_dataMap.value("proxyStatistic").toMap().value("tunnels").toList()) { QVariantMap tunnelMap = tunnelVaiant.toMap(); QVariantMap clientOne = m_clientHash.value(tunnelMap.value("clientOne").toString()); QVariantMap clientTwo = m_clientHash.value(tunnelMap.value("clientTwo").toString()); // Tunnel time uint timeStamp = tunnelMap.value("timestamp").toUInt(); QString tunnelConnectionTime = QDateTime::fromMSecsSinceEpoch(timeStamp * 1000).toString("dd.MM.yyyy hh:mm:ss"); QString tunnelPrint = QString("%1 | %2 | %3 | %4 | %5 (%6) <---> %7 (%8)") .arg(tunnelConnectionTime) .arg(Utils::getDurationString(timeStamp)) .arg(Utils::humanReadableTraffic(clientOne.value("rxDataCount").toInt() + clientOne.value("txDataCount").toInt()), - 10) .arg(clientOne.value("userName").toString()) .arg(clientOne.value("name").toString()) .arg(clientOne.value("address").toString()) .arg(clientTwo.value("name").toString()) .arg(clientTwo.value("address").toString()) ; mvwprintw(m_contentWindow, i, 2, "%s", tunnelPrint.toLatin1().constData()); i++; } } void TerminalWindow::paintContentTunnelProxy() { QVariantMap tunnelProxyMap = m_dataMap.value("tunnelProxyStatistic").toMap(); int i = 1; foreach (const QVariant &serverVariant, tunnelProxyMap.value("tunnelConnections").toList()) { QVariantMap serverMap = serverVariant.toMap(); uint timeStamp = serverMap.value("timestamp").toUInt(); QString serverConnectionTime = QDateTime::fromMSecsSinceEpoch(timeStamp * 1000).toString("dd.MM.yyyy hh:mm:ss"); int rxDataCountBytes = serverMap.value("rxDataCount").toInt(); int txDataCountBytes = serverMap.value("txDataCount").toInt(); QString serverLinePrint = QString("%1 | %2 | RX: %3 | TX: %4 | %5") .arg(serverConnectionTime) .arg(serverMap.value("address").toString(), - 16) .arg(Utils::humanReadableTraffic(rxDataCountBytes), - 10) .arg(Utils::humanReadableTraffic(txDataCountBytes), - 10) .arg(serverMap.value("name").toString(), -30); QVariantList clientList = serverMap.value("clientConnections").toList(); mvwaddch(m_contentWindow, i, 2, ACS_LTEE); mvwaddch(m_contentWindow, i, 3, ACS_HLINE); if (clientList.isEmpty()) { mvwaddch(m_contentWindow, i, 4, ACS_HLINE); } else { mvwaddch(m_contentWindow, i, 4, ACS_TTEE); } mvwaddch(m_contentWindow, i, 5, ACS_HLINE); mvwprintw(m_contentWindow, i, 6, "%s", serverLinePrint.trimmed().toLatin1().constData()); i++; for (int cc = 0; cc < clientList.count(); cc++) { QVariantMap clientMap = clientList.at(cc).toMap(); mvwaddch(m_contentWindow, i, 2, ACS_VLINE); if (cc >= clientList.count() - 1) { mvwaddch(m_contentWindow, i, 4, ACS_LLCORNER); } else { mvwaddch(m_contentWindow, i, 4, ACS_LTEE); } mvwaddch(m_contentWindow, i, 5, ACS_HLINE); QString clientLinePrint = QString("%1 | %2 | RX: %3 | TX: %4 | %5") .arg(QDateTime::fromMSecsSinceEpoch(clientMap.value("timestamp").toULongLong() * 1000).toString("dd.MM.yyyy hh:mm:ss")) .arg(clientMap.value("address").toString(), - 16) .arg(Utils::humanReadableTraffic(clientMap.value("rxDataCount").toInt()), - 10) .arg(Utils::humanReadableTraffic(clientMap.value("txDataCount").toInt()), - 10) .arg(clientMap.value("name").toString(), -30); mvwprintw(m_contentWindow, i, 6, "%s", clientLinePrint.trimmed().toLatin1().constData()); i++; } } } void TerminalWindow::cleanup() { clear(); delwin(m_headerWindow); delwin(m_contentWindow); delwin(m_mainWindow); endwin(); } void TerminalWindow::eventLoop() { werase(m_headerWindow); werase(m_contentWindow); resizeWindow(); int keyInteger = getch(); switch (keyInteger) { case KEY_LEFT: moveTabLeft(); break; case KEY_RIGHT: moveTabRight(); break; case 27: // Esc cleanup(); qDebug() << "Closing window monitor. Have a nice day!"; exit(0); break; default: break; } // Refresh header window paintHeader(); // Refresh content window switch (m_view) { case ViewTunnelProxy: paintContentTunnelProxy(); break; } switch (keyInteger) { case KEY_DOWN: scroll(m_contentWindow); scrl(1); break; case KEY_UP: scroll(m_contentWindow); scrl(-1); break; } // Draw borders drawWindowBorder(m_headerWindow); //drawWindowBorder(m_contentWindow); wrefresh(m_headerWindow); wrefresh(m_contentWindow); refresh(); // Reinvoke the method for the next event loop run //QMetaObject::invokeMethod(this, "eventLoop", Qt::QueuedConnection); QTimer::singleShot(100, this, &TerminalWindow::eventLoop); } void TerminalWindow::refreshWindow(const QVariantMap &dataMap) { m_dataMap = dataMap; QVariantMap statisticMap = m_dataMap.value("proxyStatistic").toMap(); m_clientHash.clear(); foreach (const QVariant &clientVariant, statisticMap.value("clients").toList()) { QVariantMap clientMap = clientVariant.toMap(); clientMap.insert("duration", Utils::getDurationString(clientMap.value("timestamp").toUInt())); m_clientHash.insert(clientMap.value("id").toString(), clientMap); } }