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 44KB

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