You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rpcconsole.cpp 45KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216
  1. // Copyright (c) 2011-2016 The Starwels developers
  2. // Distributed under the MIT software license, see the accompanying
  3. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4. #if defined(HAVE_CONFIG_H)
  5. #include "config/starwels-config.h"
  6. #endif
  7. #include "rpcconsole.h"
  8. #include "ui_debugwindow.h"
  9. #include "bantablemodel.h"
  10. #include "clientmodel.h"
  11. #include "guiutil.h"
  12. #include "platformstyle.h"
  13. #include "chainparams.h"
  14. #include "netbase.h"
  15. #include "rpc/server.h"
  16. #include "rpc/client.h"
  17. #include "util.h"
  18. #include <openssl/crypto.h>
  19. #include <univalue.h>
  20. #ifdef ENABLE_WALLET
  21. #include <db_cxx.h>
  22. #include <wallet/wallet.h>
  23. #endif
  24. #include <QDesktopWidget>
  25. #include <QKeyEvent>
  26. #include <QMenu>
  27. #include <QMessageBox>
  28. #include <QScrollBar>
  29. #include <QSettings>
  30. #include <QSignalMapper>
  31. #include <QThread>
  32. #include <QTime>
  33. #include <QTimer>
  34. #include <QStringList>
  35. #if QT_VERSION < 0x050000
  36. #include <QUrl>
  37. #endif
  38. // TODO: add a scrollback limit, as there is currently none
  39. // TODO: make it possible to filter out categories (esp debug messages when implemented)
  40. // TODO: receive errors and debug messages through ClientModel
  41. const int CONSOLE_HISTORY = 50;
  42. const int INITIAL_TRAFFIC_GRAPH_MINS = 30;
  43. const QSize FONT_RANGE(4, 40);
  44. const char fontSizeSettingsKey[] = "consoleFontSize";
  45. const struct {
  46. const char *url;
  47. const char *source;
  48. } ICON_MAPPING[] = {
  49. {"cmd-request", ":/icons/tx_input"},
  50. {"cmd-reply", ":/icons/tx_output"},
  51. {"cmd-error", ":/icons/tx_output"},
  52. {"misc", ":/icons/tx_inout"},
  53. {nullptr, nullptr}
  54. };
  55. namespace {
  56. // don't add private key handling cmd's to the history
  57. const QStringList historyFilter = QStringList()
  58. << "importprivkey"
  59. << "importmulti"
  60. << "signmessagewithprivkey"
  61. << "signrawtransaction"
  62. << "walletpassphrase"
  63. << "walletpassphrasechange"
  64. << "encryptwallet";
  65. }
  66. /* Object for executing console RPC commands in a separate thread.
  67. */
  68. class RPCExecutor : public QObject
  69. {
  70. Q_OBJECT
  71. public Q_SLOTS:
  72. void request(const QString &command);
  73. Q_SIGNALS:
  74. void reply(int category, const QString &command);
  75. };
  76. /** Class for handling RPC timers
  77. * (used for e.g. re-locking the wallet after a timeout)
  78. */
  79. class QtRPCTimerBase: public QObject, public RPCTimerBase
  80. {
  81. Q_OBJECT
  82. public:
  83. QtRPCTimerBase(std::function<void(void)>& _func, int64_t millis):
  84. func(_func)
  85. {
  86. timer.setSingleShot(true);
  87. connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
  88. timer.start(millis);
  89. }
  90. ~QtRPCTimerBase() {}
  91. private Q_SLOTS:
  92. void timeout() { func(); }
  93. private:
  94. QTimer timer;
  95. std::function<void(void)> func;
  96. };
  97. class QtRPCTimerInterface: public RPCTimerInterface
  98. {
  99. public:
  100. ~QtRPCTimerInterface() {}
  101. const char *Name() { return "Qt"; }
  102. RPCTimerBase* NewTimer(std::function<void(void)>& func, int64_t millis)
  103. {
  104. return new QtRPCTimerBase(func, millis);
  105. }
  106. };
  107. #include "rpcconsole.moc"
  108. /**
  109. * Split shell command line into a list of arguments and optionally execute the command(s).
  110. * Aims to emulate \c bash and friends.
  111. *
  112. * - Command nesting is possible with parenthesis; for example: validateaddress(getnewaddress())
  113. * - Arguments are delimited with whitespace or comma
  114. * - Extra whitespace at the beginning and end and between arguments will be ignored
  115. * - Text can be "double" or 'single' quoted
  116. * - The backslash \c \ is used as escape character
  117. * - Outside quotes, any character can be escaped
  118. * - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
  119. * - Within single quotes, no escaping is possible and no special interpretation takes place
  120. *
  121. * @param[out] result stringified Result from the executed command(chain)
  122. * @param[in] strCommand Command line to split
  123. * @param[in] fExecute set true if you want the command to be executed
  124. * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive data
  125. */
  126. bool RPCConsole::RPCParseCommandLine(std::string &strResult, const std::string &strCommand, const bool fExecute, std::string * const pstrFilteredOut)
  127. {
  128. std::vector< std::vector<std::string> > stack;
  129. stack.push_back(std::vector<std::string>());
  130. enum CmdParseState
  131. {
  132. STATE_EATING_SPACES,
  133. STATE_EATING_SPACES_IN_ARG,
  134. STATE_EATING_SPACES_IN_BRACKETS,
  135. STATE_ARGUMENT,
  136. STATE_SINGLEQUOTED,
  137. STATE_DOUBLEQUOTED,
  138. STATE_ESCAPE_OUTER,
  139. STATE_ESCAPE_DOUBLEQUOTED,
  140. STATE_COMMAND_EXECUTED,
  141. STATE_COMMAND_EXECUTED_INNER
  142. } state = STATE_EATING_SPACES;
  143. std::string curarg;
  144. UniValue lastResult;
  145. unsigned nDepthInsideSensitive = 0;
  146. size_t filter_begin_pos = 0, chpos;
  147. std::vector<std::pair<size_t, size_t>> filter_ranges;
  148. auto add_to_current_stack = [&](const std::string& strArg) {
  149. if (stack.back().empty() && (!nDepthInsideSensitive) && historyFilter.contains(QString::fromStdString(strArg), Qt::CaseInsensitive)) {
  150. nDepthInsideSensitive = 1;
  151. filter_begin_pos = chpos;
  152. }
  153. // Make sure stack is not empty before adding something
  154. if (stack.empty()) {
  155. stack.push_back(std::vector<std::string>());
  156. }
  157. stack.back().push_back(strArg);
  158. };
  159. auto close_out_params = [&]() {
  160. if (nDepthInsideSensitive) {
  161. if (!--nDepthInsideSensitive) {
  162. assert(filter_begin_pos);
  163. filter_ranges.push_back(std::make_pair(filter_begin_pos, chpos));
  164. filter_begin_pos = 0;
  165. }
  166. }
  167. stack.pop_back();
  168. };
  169. std::string strCommandTerminated = strCommand;
  170. if (strCommandTerminated.back() != '\n')
  171. strCommandTerminated += "\n";
  172. for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos)
  173. {
  174. char ch = strCommandTerminated[chpos];
  175. switch(state)
  176. {
  177. case STATE_COMMAND_EXECUTED_INNER:
  178. case STATE_COMMAND_EXECUTED:
  179. {
  180. bool breakParsing = true;
  181. switch(ch)
  182. {
  183. case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break;
  184. default:
  185. if (state == STATE_COMMAND_EXECUTED_INNER)
  186. {
  187. if (ch != ']')
  188. {
  189. // append char to the current argument (which is also used for the query command)
  190. curarg += ch;
  191. break;
  192. }
  193. if (curarg.size() && fExecute)
  194. {
  195. // if we have a value query, query arrays with index and objects with a string key
  196. UniValue subelement;
  197. if (lastResult.isArray())
  198. {
  199. for(char argch: curarg)
  200. if (!std::isdigit(argch))
  201. throw std::runtime_error("Invalid result query");
  202. subelement = lastResult[atoi(curarg.c_str())];
  203. }
  204. else if (lastResult.isObject())
  205. subelement = find_value(lastResult, curarg);
  206. else
  207. throw std::runtime_error("Invalid result query"); //no array or object: abort
  208. lastResult = subelement;
  209. }
  210. state = STATE_COMMAND_EXECUTED;
  211. break;
  212. }
  213. // don't break parsing when the char is required for the next argument
  214. breakParsing = false;
  215. // pop the stack and return the result to the current command arguments
  216. close_out_params();
  217. // don't stringify the json in case of a string to avoid doublequotes
  218. if (lastResult.isStr())
  219. curarg = lastResult.get_str();
  220. else
  221. curarg = lastResult.write(2);
  222. // if we have a non empty result, use it as stack argument otherwise as general result
  223. if (curarg.size())
  224. {
  225. if (stack.size())
  226. add_to_current_stack(curarg);
  227. else
  228. strResult = curarg;
  229. }
  230. curarg.clear();
  231. // assume eating space state
  232. state = STATE_EATING_SPACES;
  233. }
  234. if (breakParsing)
  235. break;
  236. }
  237. case STATE_ARGUMENT: // In or after argument
  238. case STATE_EATING_SPACES_IN_ARG:
  239. case STATE_EATING_SPACES_IN_BRACKETS:
  240. case STATE_EATING_SPACES: // Handle runs of whitespace
  241. switch(ch)
  242. {
  243. case '"': state = STATE_DOUBLEQUOTED; break;
  244. case '\'': state = STATE_SINGLEQUOTED; break;
  245. case '\\': state = STATE_ESCAPE_OUTER; break;
  246. case '(': case ')': case '\n':
  247. if (state == STATE_EATING_SPACES_IN_ARG)
  248. throw std::runtime_error("Invalid Syntax");
  249. if (state == STATE_ARGUMENT)
  250. {
  251. if (ch == '(' && stack.size() && stack.back().size() > 0)
  252. {
  253. if (nDepthInsideSensitive) {
  254. ++nDepthInsideSensitive;
  255. }
  256. stack.push_back(std::vector<std::string>());
  257. }
  258. // don't allow commands after executed commands on baselevel
  259. if (!stack.size())
  260. throw std::runtime_error("Invalid Syntax");
  261. add_to_current_stack(curarg);
  262. curarg.clear();
  263. state = STATE_EATING_SPACES_IN_BRACKETS;
  264. }
  265. if ((ch == ')' || ch == '\n') && stack.size() > 0)
  266. {
  267. if (fExecute) {
  268. // Convert argument list to JSON objects in method-dependent way,
  269. // and pass it along with the method name to the dispatcher.
  270. JSONRPCRequest req;
  271. req.params = RPCConvertValues(stack.back()[0], std::vector<std::string>(stack.back().begin() + 1, stack.back().end()));
  272. req.strMethod = stack.back()[0];
  273. #ifdef ENABLE_WALLET
  274. // TODO: Move this logic to WalletModel
  275. if (!vpwallets.empty()) {
  276. // in Qt, use always the wallet with index 0 when running with multiple wallets
  277. QByteArray encodedName = QUrl::toPercentEncoding(QString::fromStdString(vpwallets[0]->GetName()));
  278. req.URI = "/wallet/"+std::string(encodedName.constData(), encodedName.length());
  279. }
  280. #endif
  281. lastResult = tableRPC.execute(req);
  282. }
  283. state = STATE_COMMAND_EXECUTED;
  284. curarg.clear();
  285. }
  286. break;
  287. case ' ': case ',': case '\t':
  288. if(state == STATE_EATING_SPACES_IN_ARG && curarg.empty() && ch == ',')
  289. throw std::runtime_error("Invalid Syntax");
  290. else if(state == STATE_ARGUMENT) // Space ends argument
  291. {
  292. add_to_current_stack(curarg);
  293. curarg.clear();
  294. }
  295. if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ',')
  296. {
  297. state = STATE_EATING_SPACES_IN_ARG;
  298. break;
  299. }
  300. state = STATE_EATING_SPACES;
  301. break;
  302. default: curarg += ch; state = STATE_ARGUMENT;
  303. }
  304. break;
  305. case STATE_SINGLEQUOTED: // Single-quoted string
  306. switch(ch)
  307. {
  308. case '\'': state = STATE_ARGUMENT; break;
  309. default: curarg += ch;
  310. }
  311. break;
  312. case STATE_DOUBLEQUOTED: // Double-quoted string
  313. switch(ch)
  314. {
  315. case '"': state = STATE_ARGUMENT; break;
  316. case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
  317. default: curarg += ch;
  318. }
  319. break;
  320. case STATE_ESCAPE_OUTER: // '\' outside quotes
  321. curarg += ch; state = STATE_ARGUMENT;
  322. break;
  323. case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
  324. if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
  325. curarg += ch; state = STATE_DOUBLEQUOTED;
  326. break;
  327. }
  328. }
  329. if (pstrFilteredOut) {
  330. if (STATE_COMMAND_EXECUTED == state) {
  331. assert(!stack.empty());
  332. close_out_params();
  333. }
  334. *pstrFilteredOut = strCommand;
  335. for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) {
  336. pstrFilteredOut->replace(i->first, i->second - i->first, "(…)");
  337. }
  338. }
  339. switch(state) // final state
  340. {
  341. case STATE_COMMAND_EXECUTED:
  342. if (lastResult.isStr())
  343. strResult = lastResult.get_str();
  344. else
  345. strResult = lastResult.write(2);
  346. case STATE_ARGUMENT:
  347. case STATE_EATING_SPACES:
  348. return true;
  349. default: // ERROR to end in one of the other states
  350. return false;
  351. }
  352. }
  353. void RPCExecutor::request(const QString &command)
  354. {
  355. try
  356. {
  357. std::string result;
  358. std::string executableCommand = command.toStdString() + "\n";
  359. if(!RPCConsole::RPCExecuteCommandLine(result, executableCommand))
  360. {
  361. Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
  362. return;
  363. }
  364. Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result));
  365. }
  366. catch (UniValue& objError)
  367. {
  368. try // Nice formatting for standard-format error
  369. {
  370. int code = find_value(objError, "code").get_int();
  371. std::string message = find_value(objError, "message").get_str();
  372. Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
  373. }
  374. catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
  375. { // Show raw JSON object
  376. Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write()));
  377. }
  378. }
  379. catch (const std::exception& e)
  380. {
  381. Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
  382. }
  383. }
  384. RPCConsole::RPCConsole(const PlatformStyle *_platformStyle, QWidget *parent) :
  385. QWidget(parent),
  386. ui(new Ui::RPCConsole),
  387. clientModel(0),
  388. historyPtr(0),
  389. platformStyle(_platformStyle),
  390. peersTableContextMenu(0),
  391. banTableContextMenu(0),
  392. consoleFontSize(0)
  393. {
  394. ui->setupUi(this);
  395. QSettings settings;
  396. if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) {
  397. // Restore failed (perhaps missing setting), center the window
  398. move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center());
  399. }
  400. ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME)));
  401. if (platformStyle->getImagesOnButtons()) {
  402. ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
  403. }
  404. ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
  405. ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontbigger"));
  406. ui->fontSmallerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontsmaller"));
  407. // Install event filter for up and down arrow
  408. ui->lineEdit->installEventFilter(this);
  409. ui->messagesWidget->installEventFilter(this);
  410. connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
  411. connect(ui->fontBiggerButton, SIGNAL(clicked()), this, SLOT(fontBigger()));
  412. connect(ui->fontSmallerButton, SIGNAL(clicked()), this, SLOT(fontSmaller()));
  413. connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph, SLOT(clear()));
  414. // set library version labels
  415. #ifdef ENABLE_WALLET
  416. ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0));
  417. #else
  418. ui->label_berkeleyDBVersion->hide();
  419. ui->berkeleyDBVersion->hide();
  420. #endif
  421. // Register RPC timer interface
  422. rpcTimerInterface = new QtRPCTimerInterface();
  423. // avoid accidentally overwriting an existing, non QTThread
  424. // based timer interface
  425. RPCSetTimerInterfaceIfUnset(rpcTimerInterface);
  426. setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
  427. ui->detailWidget->hide();
  428. ui->peerHeading->setText(tr("Select a peer to view detailed information."));
  429. consoleFontSize = settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize()).toInt();
  430. clear();
  431. }
  432. RPCConsole::~RPCConsole()
  433. {
  434. QSettings settings;
  435. settings.setValue("RPCConsoleWindowGeometry", saveGeometry());
  436. RPCUnsetTimerInterface(rpcTimerInterface);
  437. delete rpcTimerInterface;
  438. delete ui;
  439. }
  440. bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
  441. {
  442. if(event->type() == QEvent::KeyPress) // Special key handling
  443. {
  444. QKeyEvent *keyevt = static_cast<QKeyEvent*>(event);
  445. int key = keyevt->key();
  446. Qt::KeyboardModifiers mod = keyevt->modifiers();
  447. switch(key)
  448. {
  449. case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break;
  450. case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break;
  451. case Qt::Key_PageUp: /* pass paging keys to messages widget */
  452. case Qt::Key_PageDown:
  453. if(obj == ui->lineEdit)
  454. {
  455. QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt));
  456. return true;
  457. }
  458. break;
  459. case Qt::Key_Return:
  460. case Qt::Key_Enter:
  461. // forward these events to lineEdit
  462. if(obj == autoCompleter->popup()) {
  463. QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
  464. return true;
  465. }
  466. break;
  467. default:
  468. // Typing in messages widget brings focus to line edit, and redirects key there
  469. // Exclude most combinations and keys that emit no text, except paste shortcuts
  470. if(obj == ui->messagesWidget && (
  471. (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
  472. ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
  473. ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert)))
  474. {
  475. ui->lineEdit->setFocus();
  476. QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
  477. return true;
  478. }
  479. }
  480. }
  481. return QWidget::eventFilter(obj, event);
  482. }
  483. void RPCConsole::setClientModel(ClientModel *model)
  484. {
  485. clientModel = model;
  486. ui->trafficGraph->setClientModel(model);
  487. if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) {
  488. // Keep up to date with client
  489. setNumConnections(model->getNumConnections());
  490. connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
  491. setNumBlocks(model->getNumBlocks(), model->getLastBlockDate(), model->getVerificationProgress(nullptr), false);
  492. connect(model, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(setNumBlocks(int,QDateTime,double,bool)));
  493. updateNetworkState();
  494. connect(model, SIGNAL(networkActiveChanged(bool)), this, SLOT(setNetworkActive(bool)));
  495. updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent());
  496. connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64)));
  497. connect(model, SIGNAL(mempoolSizeChanged(long,size_t)), this, SLOT(setMempoolSize(long,size_t)));
  498. // set up peer table
  499. ui->peerWidget->setModel(model->getPeerTableModel());
  500. ui->peerWidget->verticalHeader()->hide();
  501. ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
  502. ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
  503. ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
  504. ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  505. ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
  506. ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
  507. ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);
  508. ui->peerWidget->horizontalHeader()->setStretchLastSection(true);
  509. // create peer table context menu actions
  510. QAction* disconnectAction = new QAction(tr("&Disconnect"), this);
  511. QAction* banAction1h = new QAction(tr("Ban for") + " " + tr("1 &hour"), this);
  512. QAction* banAction24h = new QAction(tr("Ban for") + " " + tr("1 &day"), this);
  513. QAction* banAction7d = new QAction(tr("Ban for") + " " + tr("1 &week"), this);
  514. QAction* banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this);
  515. // create peer table context menu
  516. peersTableContextMenu = new QMenu(this);
  517. peersTableContextMenu->addAction(disconnectAction);
  518. peersTableContextMenu->addAction(banAction1h);
  519. peersTableContextMenu->addAction(banAction24h);
  520. peersTableContextMenu->addAction(banAction7d);
  521. peersTableContextMenu->addAction(banAction365d);
  522. // Add a signal mapping to allow dynamic context menu arguments.
  523. // We need to use int (instead of int64_t), because signal mapper only supports
  524. // int or objects, which is okay because max bantime (1 year) is < int_max.
  525. QSignalMapper* signalMapper = new QSignalMapper(this);
  526. signalMapper->setMapping(banAction1h, 60*60);
  527. signalMapper->setMapping(banAction24h, 60*60*24);
  528. signalMapper->setMapping(banAction7d, 60*60*24*7);
  529. signalMapper->setMapping(banAction365d, 60*60*24*365);
  530. connect(banAction1h, SIGNAL(triggered()), signalMapper, SLOT(map()));
  531. connect(banAction24h, SIGNAL(triggered()), signalMapper, SLOT(map()));
  532. connect(banAction7d, SIGNAL(triggered()), signalMapper, SLOT(map()));
  533. connect(banAction365d, SIGNAL(triggered()), signalMapper, SLOT(map()));
  534. connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(banSelectedNode(int)));
  535. // peer table context menu signals
  536. connect(ui->peerWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showPeersTableContextMenu(const QPoint&)));
  537. connect(disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectSelectedNode()));
  538. // peer table signal handling - update peer details when selecting new node
  539. connect(ui->peerWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
  540. this, SLOT(peerSelected(const QItemSelection &, const QItemSelection &)));
  541. // peer table signal handling - update peer details when new nodes are added to the model
  542. connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged()));
  543. // peer table signal handling - cache selected node ids
  544. connect(model->getPeerTableModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(peerLayoutAboutToChange()));
  545. // set up ban table
  546. ui->banlistWidget->setModel(model->getBanTableModel());
  547. ui->banlistWidget->verticalHeader()->hide();
  548. ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
  549. ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
  550. ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection);
  551. ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  552. ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH);
  553. ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH);
  554. ui->banlistWidget->horizontalHeader()->setStretchLastSection(true);
  555. // create ban table context menu action
  556. QAction* unbanAction = new QAction(tr("&Unban"), this);
  557. // create ban table context menu
  558. banTableContextMenu = new QMenu(this);
  559. banTableContextMenu->addAction(unbanAction);
  560. // ban table context menu signals
  561. connect(ui->banlistWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showBanTableContextMenu(const QPoint&)));
  562. connect(unbanAction, SIGNAL(triggered()), this, SLOT(unbanSelectedNode()));
  563. // ban table signal handling - clear peer details when clicking a peer in the ban table
  564. connect(ui->banlistWidget, SIGNAL(clicked(const QModelIndex&)), this, SLOT(clearSelectedNode()));
  565. // ban table signal handling - ensure ban table is shown or hidden (if empty)
  566. connect(model->getBanTableModel(), SIGNAL(layoutChanged()), this, SLOT(showOrHideBanTableIfRequired()));
  567. showOrHideBanTableIfRequired();
  568. // Provide initial values
  569. ui->clientVersion->setText(model->formatFullVersion());
  570. ui->clientUserAgent->setText(model->formatSubVersion());
  571. ui->dataDir->setText(model->dataDir());
  572. ui->startupTime->setText(model->formatClientStartupTime());
  573. ui->networkName->setText(QString::fromStdString(Params().NetworkIDString()));
  574. //Setup autocomplete and attach it
  575. QStringList wordList;
  576. std::vector<std::string> commandList = tableRPC.listCommands();
  577. for (size_t i = 0; i < commandList.size(); ++i)
  578. {
  579. wordList << commandList[i].c_str();
  580. wordList << ("help " + commandList[i]).c_str();
  581. }
  582. wordList.sort();
  583. autoCompleter = new QCompleter(wordList, this);
  584. autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel);
  585. ui->lineEdit->setCompleter(autoCompleter);
  586. autoCompleter->popup()->installEventFilter(this);
  587. // Start thread to execute RPC commands.
  588. startExecutor();
  589. }
  590. if (!model) {
  591. // Client model is being set to 0, this means shutdown() is about to be called.
  592. // Make sure we clean up the executor thread
  593. Q_EMIT stopExecutor();
  594. thread.wait();
  595. }
  596. }
  597. static QString categoryClass(int category)
  598. {
  599. switch(category)
  600. {
  601. case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
  602. case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
  603. case RPCConsole::CMD_ERROR: return "cmd-error"; break;
  604. default: return "misc";
  605. }
  606. }
  607. void RPCConsole::fontBigger()
  608. {
  609. setFontSize(consoleFontSize+1);
  610. }
  611. void RPCConsole::fontSmaller()
  612. {
  613. setFontSize(consoleFontSize-1);
  614. }
  615. void RPCConsole::setFontSize(int newSize)
  616. {
  617. QSettings settings;
  618. //don't allow an insane font size
  619. if (newSize < FONT_RANGE.width() || newSize > FONT_RANGE.height())
  620. return;
  621. // temp. store the console content
  622. QString str = ui->messagesWidget->toHtml();
  623. // replace font tags size in current content
  624. str.replace(QString("font-size:%1pt").arg(consoleFontSize), QString("font-size:%1pt").arg(newSize));
  625. // store the new font size
  626. consoleFontSize = newSize;
  627. settings.setValue(fontSizeSettingsKey, consoleFontSize);
  628. // clear console (reset icon sizes, default stylesheet) and re-add the content
  629. float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value();
  630. clear(false);
  631. ui->messagesWidget->setHtml(str);
  632. ui->messagesWidget->verticalScrollBar()->setValue(oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum());
  633. }
  634. void RPCConsole::clear(bool clearHistory)
  635. {
  636. ui->messagesWidget->clear();
  637. if(clearHistory)
  638. {
  639. history.clear();
  640. historyPtr = 0;
  641. }
  642. ui->lineEdit->clear();
  643. ui->lineEdit->setFocus();
  644. // Add smoothly scaled icon images.
  645. // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
  646. for(int i=0; ICON_MAPPING[i].url; ++i)
  647. {
  648. ui->messagesWidget->document()->addResource(
  649. QTextDocument::ImageResource,
  650. QUrl(ICON_MAPPING[i].url),
  651. platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize*2, consoleFontSize*2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  652. }
  653. // Set default style sheet
  654. QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont());
  655. ui->messagesWidget->document()->setDefaultStyleSheet(
  656. QString(
  657. "table { }"
  658. "td.time { color: #808080; font-size: %2; padding-top: 3px; } "
  659. "td.message { font-family: %1; font-size: %2; white-space:pre-wrap; } "
  660. "td.cmd-request { color: #006060; } "
  661. "td.cmd-error { color: red; } "
  662. ".secwarning { color: red; }"
  663. "b { color: #006060; } "
  664. ).arg(fixedFontInfo.family(), QString("%1pt").arg(consoleFontSize))
  665. );
  666. #ifdef Q_OS_MAC
  667. QString clsKey = "(⌘)-L";
  668. #else
  669. QString clsKey = "Ctrl-L";
  670. #endif
  671. message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "<br>" +
  672. tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" +
  673. tr("Type <b>help</b> for an overview of available commands.")) +
  674. "<br><span class=\"secwarning\">" +
  675. tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.") +
  676. "</span>",
  677. true);
  678. }
  679. void RPCConsole::keyPressEvent(QKeyEvent *event)
  680. {
  681. if(windowType() != Qt::Widget && event->key() == Qt::Key_Escape)
  682. {
  683. close();
  684. }
  685. }
  686. void RPCConsole::message(int category, const QString &message, bool html)
  687. {
  688. QTime time = QTime::currentTime();
  689. QString timeString = time.toString();
  690. QString out;
  691. out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
  692. out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
  693. out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
  694. if(html)
  695. out += message;
  696. else
  697. out += GUIUtil::HtmlEscape(message, false);
  698. out += "</td></tr></table>";
  699. ui->messagesWidget->append(out);
  700. }
  701. void RPCConsole::updateNetworkState()
  702. {
  703. QString connections = QString::number(clientModel->getNumConnections()) + " (";
  704. connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / ";
  705. connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")";
  706. if(!clientModel->getNetworkActive()) {
  707. connections += " (" + tr("Network activity disabled") + ")";
  708. }
  709. ui->numberOfConnections->setText(connections);
  710. }
  711. void RPCConsole::setNumConnections(int count)
  712. {
  713. if (!clientModel)
  714. return;
  715. updateNetworkState();
  716. }
  717. void RPCConsole::setNetworkActive(bool networkActive)
  718. {
  719. updateNetworkState();
  720. }
  721. void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers)
  722. {
  723. if (!headers) {
  724. ui->numberOfBlocks->setText(QString::number(count));
  725. ui->lastBlockTime->setText(blockDate.toString());
  726. }
  727. }
  728. void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage)
  729. {
  730. ui->mempoolNumberTxs->setText(QString::number(numberOfTxs));
  731. if (dynUsage < 1000000)
  732. ui->mempoolSize->setText(QString::number(dynUsage/1000.0, 'f', 2) + " KB");
  733. else
  734. ui->mempoolSize->setText(QString::number(dynUsage/1000000.0, 'f', 2) + " MB");
  735. }
  736. void RPCConsole::on_lineEdit_returnPressed()
  737. {
  738. QString cmd = ui->lineEdit->text();
  739. if(!cmd.isEmpty())
  740. {
  741. std::string strFilteredCmd;
  742. try {
  743. std::string dummy;
  744. if (!RPCParseCommandLine(dummy, cmd.toStdString(), false, &strFilteredCmd)) {
  745. // Failed to parse command, so we cannot even filter it for the history
  746. throw std::runtime_error("Invalid command line");
  747. }
  748. } catch (const std::exception& e) {
  749. QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what()));
  750. return;
  751. }
  752. ui->lineEdit->clear();
  753. cmdBeforeBrowsing = QString();
  754. message(CMD_REQUEST, QString::fromStdString(strFilteredCmd));
  755. Q_EMIT cmdRequest(cmd);
  756. cmd = QString::fromStdString(strFilteredCmd);
  757. // Remove command, if already in history
  758. history.removeOne(cmd);
  759. // Append command to history
  760. history.append(cmd);
  761. // Enforce maximum history size
  762. while(history.size() > CONSOLE_HISTORY)
  763. history.removeFirst();
  764. // Set pointer to end of history
  765. historyPtr = history.size();
  766. // Scroll console view to end
  767. scrollToEnd();
  768. }
  769. }
  770. void RPCConsole::browseHistory(int offset)
  771. {
  772. // store current text when start browsing through the history
  773. if (historyPtr == history.size()) {
  774. cmdBeforeBrowsing = ui->lineEdit->text();
  775. }
  776. historyPtr += offset;
  777. if(historyPtr < 0)
  778. historyPtr = 0;
  779. if(historyPtr > history.size())
  780. historyPtr = history.size();
  781. QString cmd;
  782. if(historyPtr < history.size())
  783. cmd = history.at(historyPtr);
  784. else if (!cmdBeforeBrowsing.isNull()) {
  785. cmd = cmdBeforeBrowsing;
  786. }
  787. ui->lineEdit->setText(cmd);
  788. }
  789. void RPCConsole::startExecutor()
  790. {
  791. RPCExecutor *executor = new RPCExecutor();
  792. executor->moveToThread(&thread);
  793. // Replies from executor object must go to this object
  794. connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString)));
  795. // Requests from this object must go to executor
  796. connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString)));
  797. // On stopExecutor signal
  798. // - quit the Qt event loop in the execution thread
  799. connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit()));
  800. // - queue executor for deletion (in execution thread)
  801. connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection);
  802. // Default implementation of QThread::run() simply spins up an event loop in the thread,
  803. // which is what we want.
  804. thread.start();
  805. }
  806. void RPCConsole::on_tabWidget_currentChanged(int index)
  807. {
  808. if (ui->tabWidget->widget(index) == ui->tab_console)
  809. ui->lineEdit->setFocus();
  810. else if (ui->tabWidget->widget(index) != ui->tab_peers)
  811. clearSelectedNode();
  812. }
  813. void RPCConsole::on_openDebugLogfileButton_clicked()
  814. {
  815. GUIUtil::openDebugLogfile();
  816. }
  817. void RPCConsole::scrollToEnd()
  818. {
  819. QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
  820. scrollbar->setValue(scrollbar->maximum());
  821. }
  822. void RPCConsole::on_sldGraphRange_valueChanged(int value)
  823. {
  824. const int multiplier = 5; // each position on the slider represents 5 min
  825. int mins = value * multiplier;
  826. setTrafficGraphRange(mins);
  827. }
  828. QString RPCConsole::FormatBytes(quint64 bytes)
  829. {
  830. if(bytes < 1024)
  831. return QString(tr("%1 B")).arg(bytes);
  832. if(bytes < 1024 * 1024)
  833. return QString(tr("%1 KB")).arg(bytes / 1024);
  834. if(bytes < 1024 * 1024 * 1024)
  835. return QString(tr("%1 MB")).arg(bytes / 1024 / 1024);
  836. return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
  837. }
  838. void RPCConsole::setTrafficGraphRange(int mins)
  839. {
  840. ui->trafficGraph->setGraphRangeMins(mins);
  841. ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60));
  842. }
  843. void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
  844. {
  845. ui->lblBytesIn->setText(FormatBytes(totalBytesIn));
  846. ui->lblBytesOut->setText(FormatBytes(totalBytesOut));
  847. }
  848. void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected)
  849. {
  850. Q_UNUSED(deselected);
  851. if (!clientModel || !clientModel->getPeerTableModel() || selected.indexes().isEmpty())
  852. return;
  853. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.indexes().first().row());
  854. if (stats)
  855. updateNodeDetail(stats);
  856. }
  857. void RPCConsole::peerLayoutAboutToChange()
  858. {
  859. QModelIndexList selected = ui->peerWidget->selectionModel()->selectedIndexes();
  860. cachedNodeids.clear();
  861. for(int i = 0; i < selected.size(); i++)
  862. {
  863. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.at(i).row());
  864. cachedNodeids.append(stats->nodeStats.nodeid);
  865. }
  866. }
  867. void RPCConsole::peerLayoutChanged()
  868. {
  869. if (!clientModel || !clientModel->getPeerTableModel())
  870. return;
  871. const CNodeCombinedStats *stats = nullptr;
  872. bool fUnselect = false;
  873. bool fReselect = false;
  874. if (cachedNodeids.empty()) // no node selected yet
  875. return;
  876. // find the currently selected row
  877. int selectedRow = -1;
  878. QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes();
  879. if (!selectedModelIndex.isEmpty()) {
  880. selectedRow = selectedModelIndex.first().row();
  881. }
  882. // check if our detail node has a row in the table (it may not necessarily
  883. // be at selectedRow since its position can change after a layout change)
  884. int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.first());
  885. if (detailNodeRow < 0)
  886. {
  887. // detail node disappeared from table (node disconnected)
  888. fUnselect = true;
  889. }
  890. else
  891. {
  892. if (detailNodeRow != selectedRow)
  893. {
  894. // detail node moved position
  895. fUnselect = true;
  896. fReselect = true;
  897. }
  898. // get fresh stats on the detail node.
  899. stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
  900. }
  901. if (fUnselect && selectedRow >= 0) {
  902. clearSelectedNode();
  903. }
  904. if (fReselect)
  905. {
  906. for(int i = 0; i < cachedNodeids.size(); i++)
  907. {
  908. ui->peerWidget->selectRow(clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.at(i)));
  909. }
  910. }
  911. if (stats)
  912. updateNodeDetail(stats);
  913. }
  914. void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats)
  915. {
  916. // update the detail ui with latest node information
  917. QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " ");
  918. peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid));
  919. if (!stats->nodeStats.addrLocal.empty())
  920. peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
  921. ui->peerHeading->setText(peerAddrDetails);
  922. ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices));
  923. ui->peerLastSend->setText(stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never"));
  924. ui->peerLastRecv->setText(stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never"));
  925. ui->peerBytesSent->setText(FormatBytes(stats->nodeStats.nSendBytes));
  926. ui->peerBytesRecv->setText(FormatBytes(stats->nodeStats.nRecvBytes));
  927. ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected));
  928. ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingTime));
  929. ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingWait));
  930. ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.dMinPing));
  931. ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
  932. ui->peerVersion->setText(QString("%1").arg(QString::number(stats->nodeStats.nVersion)));
  933. ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
  934. ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound"));
  935. ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight)));
  936. ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No"));
  937. // This check fails for example if the lock was busy and
  938. // nodeStateStats couldn't be fetched.
  939. if (stats->fNodeStateStatsAvailable) {
  940. // Ban score is init to 0
  941. ui->peerBanScore->setText(QString("%1").arg(stats->nodeStateStats.nMisbehavior));
  942. // Sync height is init to -1
  943. if (stats->nodeStateStats.nSyncHeight > -1)
  944. ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight));
  945. else
  946. ui->peerSyncHeight->setText(tr("Unknown"));
  947. // Common height is init to -1
  948. if (stats->nodeStateStats.nCommonHeight > -1)
  949. ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight));
  950. else
  951. ui->peerCommonHeight->setText(tr("Unknown"));
  952. }
  953. ui->detailWidget->show();
  954. }
  955. void RPCConsole::resizeEvent(QResizeEvent *event)
  956. {
  957. QWidget::resizeEvent(event);
  958. }
  959. void RPCConsole::showEvent(QShowEvent *event)
  960. {
  961. QWidget::showEvent(event);
  962. if (!clientModel || !clientModel->getPeerTableModel())
  963. return;
  964. // start PeerTableModel auto refresh
  965. clientModel->getPeerTableModel()->startAutoRefresh();
  966. }
  967. void RPCConsole::hideEvent(QHideEvent *event)
  968. {
  969. QWidget::hideEvent(event);
  970. if (!clientModel || !clientModel->getPeerTableModel())
  971. return;
  972. // stop PeerTableModel auto refresh
  973. clientModel->getPeerTableModel()->stopAutoRefresh();
  974. }
  975. void RPCConsole::showPeersTableContextMenu(const QPoint& point)
  976. {
  977. QModelIndex index = ui->peerWidget->indexAt(point);
  978. if (index.isValid())
  979. peersTableContextMenu->exec(QCursor::pos());
  980. }
  981. void RPCConsole::showBanTableContextMenu(const QPoint& point)
  982. {
  983. QModelIndex index = ui->banlistWidget->indexAt(point);
  984. if (index.isValid())
  985. banTableContextMenu->exec(QCursor::pos());
  986. }
  987. void RPCConsole::disconnectSelectedNode()
  988. {
  989. if(!g_connman)
  990. return;
  991. // Get selected peer addresses
  992. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
  993. for(int i = 0; i < nodes.count(); i++)
  994. {
  995. // Get currently selected peer address
  996. NodeId id = nodes.at(i).data().toLongLong();
  997. // Find the node, disconnect it and clear the selected node
  998. if(g_connman->DisconnectNode(id))
  999. clearSelectedNode();
  1000. }
  1001. }
  1002. void RPCConsole::banSelectedNode(int bantime)
  1003. {
  1004. if (!clientModel || !g_connman)
  1005. return;
  1006. // Get selected peer addresses
  1007. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
  1008. for(int i = 0; i < nodes.count(); i++)
  1009. {
  1010. // Get currently selected peer address
  1011. NodeId id = nodes.at(i).data().toLongLong();
  1012. // Get currently selected peer address
  1013. int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id);
  1014. if(detailNodeRow < 0)
  1015. return;
  1016. // Find possible nodes, ban it and clear the selected node
  1017. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
  1018. if(stats) {
  1019. g_connman->Ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime);
  1020. }
  1021. }
  1022. clearSelectedNode();
  1023. clientModel->getBanTableModel()->refresh();
  1024. }
  1025. void RPCConsole::unbanSelectedNode()
  1026. {
  1027. if (!clientModel)
  1028. return;
  1029. // Get selected ban addresses
  1030. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address);
  1031. for(int i = 0; i < nodes.count(); i++)
  1032. {
  1033. // Get currently selected ban address
  1034. QString strNode = nodes.at(i).data().toString();
  1035. CSubNet possibleSubnet;
  1036. LookupSubNet(strNode.toStdString().c_str(), possibleSubnet);
  1037. if (possibleSubnet.IsValid() && g_connman)
  1038. {
  1039. g_connman->Unban(possibleSubnet);
  1040. clientModel->getBanTableModel()->refresh();
  1041. }
  1042. }
  1043. }
  1044. void RPCConsole::clearSelectedNode()
  1045. {
  1046. ui->peerWidget->selectionModel()->clearSelection();
  1047. cachedNodeids.clear();
  1048. ui->detailWidget->hide();
  1049. ui->peerHeading->setText(tr("Select a peer to view detailed information."));
  1050. }
  1051. void RPCConsole::showOrHideBanTableIfRequired()
  1052. {
  1053. if (!clientModel)
  1054. return;
  1055. bool visible = clientModel->getBanTableModel()->shouldShow();
  1056. ui->banlistWidget->setVisible(visible);
  1057. ui->banHeading->setVisible(visible);
  1058. }
  1059. void RPCConsole::setTabFocus(enum TabTypes tabType)
  1060. {
  1061. ui->tabWidget->setCurrentIndex(tabType);
  1062. }