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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  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(boost::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. boost::function<void(void)> func;
  95. };
  96. class QtRPCTimerInterface: public RPCTimerInterface
  97. {
  98. public:
  99. ~QtRPCTimerInterface() {}
  100. const char *Name() { return "Qt"; }
  101. RPCTimerBase* NewTimer(boost::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. #ifdef Q_OS_MAC
  654. QString clsKey = "(⌘)-L";
  655. #else
  656. QString clsKey = "Ctrl-L";
  657. #endif
  658. message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "<br>" +
  659. tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" +
  660. tr("Type <b>help</b> for an overview of available commands.")) +
  661. "<br><span class=\"secwarning\">" +
  662. 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.") +
  663. "</span>",
  664. true);
  665. }
  666. void RPCConsole::keyPressEvent(QKeyEvent *event)
  667. {
  668. if(windowType() != Qt::Widget && event->key() == Qt::Key_Escape)
  669. {
  670. close();
  671. }
  672. }
  673. void RPCConsole::message(int category, const QString &message, bool html)
  674. {
  675. QTime time = QTime::currentTime();
  676. QString timeString = time.toString();
  677. QString out;
  678. out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
  679. out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
  680. out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
  681. if(html)
  682. out += message;
  683. else
  684. out += GUIUtil::HtmlEscape(message, false);
  685. out += "</td></tr></table>";
  686. ui->messagesWidget->append(out);
  687. }
  688. void RPCConsole::updateNetworkState()
  689. {
  690. QString connections = QString::number(clientModel->getNumConnections()) + " (";
  691. connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / ";
  692. connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")";
  693. if(!clientModel->getNetworkActive()) {
  694. connections += " (" + tr("Network activity disabled") + ")";
  695. }
  696. ui->numberOfConnections->setText(connections);
  697. }
  698. void RPCConsole::setNumConnections(int count)
  699. {
  700. if (!clientModel)
  701. return;
  702. updateNetworkState();
  703. }
  704. void RPCConsole::setNetworkActive(bool networkActive)
  705. {
  706. updateNetworkState();
  707. }
  708. void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers)
  709. {
  710. if (!headers) {
  711. ui->numberOfBlocks->setText(QString::number(count));
  712. ui->lastBlockTime->setText(blockDate.toString());
  713. }
  714. }
  715. void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage)
  716. {
  717. ui->mempoolNumberTxs->setText(QString::number(numberOfTxs));
  718. if (dynUsage < 1000000)
  719. ui->mempoolSize->setText(QString::number(dynUsage/1000.0, 'f', 2) + " KB");
  720. else
  721. ui->mempoolSize->setText(QString::number(dynUsage/1000000.0, 'f', 2) + " MB");
  722. }
  723. void RPCConsole::on_lineEdit_returnPressed()
  724. {
  725. QString cmd = ui->lineEdit->text();
  726. if(!cmd.isEmpty())
  727. {
  728. std::string strFilteredCmd;
  729. try {
  730. std::string dummy;
  731. if (!RPCParseCommandLine(dummy, cmd.toStdString(), false, &strFilteredCmd)) {
  732. // Failed to parse command, so we cannot even filter it for the history
  733. throw std::runtime_error("Invalid command line");
  734. }
  735. } catch (const std::exception& e) {
  736. QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what()));
  737. return;
  738. }
  739. ui->lineEdit->clear();
  740. cmdBeforeBrowsing = QString();
  741. message(CMD_REQUEST, QString::fromStdString(strFilteredCmd));
  742. Q_EMIT cmdRequest(cmd);
  743. cmd = QString::fromStdString(strFilteredCmd);
  744. // Remove command, if already in history
  745. history.removeOne(cmd);
  746. // Append command to history
  747. history.append(cmd);
  748. // Enforce maximum history size
  749. while(history.size() > CONSOLE_HISTORY)
  750. history.removeFirst();
  751. // Set pointer to end of history
  752. historyPtr = history.size();
  753. // Scroll console view to end
  754. scrollToEnd();
  755. }
  756. }
  757. void RPCConsole::browseHistory(int offset)
  758. {
  759. // store current text when start browsing through the history
  760. if (historyPtr == history.size()) {
  761. cmdBeforeBrowsing = ui->lineEdit->text();
  762. }
  763. historyPtr += offset;
  764. if(historyPtr < 0)
  765. historyPtr = 0;
  766. if(historyPtr > history.size())
  767. historyPtr = history.size();
  768. QString cmd;
  769. if(historyPtr < history.size())
  770. cmd = history.at(historyPtr);
  771. else if (!cmdBeforeBrowsing.isNull()) {
  772. cmd = cmdBeforeBrowsing;
  773. }
  774. ui->lineEdit->setText(cmd);
  775. }
  776. void RPCConsole::startExecutor()
  777. {
  778. RPCExecutor *executor = new RPCExecutor();
  779. executor->moveToThread(&thread);
  780. // Replies from executor object must go to this object
  781. connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString)));
  782. // Requests from this object must go to executor
  783. connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString)));
  784. // On stopExecutor signal
  785. // - quit the Qt event loop in the execution thread
  786. connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit()));
  787. // - queue executor for deletion (in execution thread)
  788. connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection);
  789. // Default implementation of QThread::run() simply spins up an event loop in the thread,
  790. // which is what we want.
  791. thread.start();
  792. }
  793. void RPCConsole::on_tabWidget_currentChanged(int index)
  794. {
  795. if (ui->tabWidget->widget(index) == ui->tab_console)
  796. ui->lineEdit->setFocus();
  797. else if (ui->tabWidget->widget(index) != ui->tab_peers)
  798. clearSelectedNode();
  799. }
  800. void RPCConsole::on_openDebugLogfileButton_clicked()
  801. {
  802. GUIUtil::openDebugLogfile();
  803. }
  804. void RPCConsole::scrollToEnd()
  805. {
  806. QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
  807. scrollbar->setValue(scrollbar->maximum());
  808. }
  809. void RPCConsole::on_sldGraphRange_valueChanged(int value)
  810. {
  811. const int multiplier = 5; // each position on the slider represents 5 min
  812. int mins = value * multiplier;
  813. setTrafficGraphRange(mins);
  814. }
  815. QString RPCConsole::FormatBytes(quint64 bytes)
  816. {
  817. if(bytes < 1024)
  818. return QString(tr("%1 B")).arg(bytes);
  819. if(bytes < 1024 * 1024)
  820. return QString(tr("%1 KB")).arg(bytes / 1024);
  821. if(bytes < 1024 * 1024 * 1024)
  822. return QString(tr("%1 MB")).arg(bytes / 1024 / 1024);
  823. return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
  824. }
  825. void RPCConsole::setTrafficGraphRange(int mins)
  826. {
  827. ui->trafficGraph->setGraphRangeMins(mins);
  828. ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60));
  829. }
  830. void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
  831. {
  832. ui->lblBytesIn->setText(FormatBytes(totalBytesIn));
  833. ui->lblBytesOut->setText(FormatBytes(totalBytesOut));
  834. }
  835. void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected)
  836. {
  837. Q_UNUSED(deselected);
  838. if (!clientModel || !clientModel->getPeerTableModel() || selected.indexes().isEmpty())
  839. return;
  840. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.indexes().first().row());
  841. if (stats)
  842. updateNodeDetail(stats);
  843. }
  844. void RPCConsole::peerLayoutAboutToChange()
  845. {
  846. QModelIndexList selected = ui->peerWidget->selectionModel()->selectedIndexes();
  847. cachedNodeids.clear();
  848. for(int i = 0; i < selected.size(); i++)
  849. {
  850. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.at(i).row());
  851. cachedNodeids.append(stats->nodeStats.nodeid);
  852. }
  853. }
  854. void RPCConsole::peerLayoutChanged()
  855. {
  856. if (!clientModel || !clientModel->getPeerTableModel())
  857. return;
  858. const CNodeCombinedStats *stats = NULL;
  859. bool fUnselect = false;
  860. bool fReselect = false;
  861. if (cachedNodeids.empty()) // no node selected yet
  862. return;
  863. // find the currently selected row
  864. int selectedRow = -1;
  865. QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes();
  866. if (!selectedModelIndex.isEmpty()) {
  867. selectedRow = selectedModelIndex.first().row();
  868. }
  869. // check if our detail node has a row in the table (it may not necessarily
  870. // be at selectedRow since its position can change after a layout change)
  871. int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.first());
  872. if (detailNodeRow < 0)
  873. {
  874. // detail node disappeared from table (node disconnected)
  875. fUnselect = true;
  876. }
  877. else
  878. {
  879. if (detailNodeRow != selectedRow)
  880. {
  881. // detail node moved position
  882. fUnselect = true;
  883. fReselect = true;
  884. }
  885. // get fresh stats on the detail node.
  886. stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
  887. }
  888. if (fUnselect && selectedRow >= 0) {
  889. clearSelectedNode();
  890. }
  891. if (fReselect)
  892. {
  893. for(int i = 0; i < cachedNodeids.size(); i++)
  894. {
  895. ui->peerWidget->selectRow(clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.at(i)));
  896. }
  897. }
  898. if (stats)
  899. updateNodeDetail(stats);
  900. }
  901. void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats)
  902. {
  903. // update the detail ui with latest node information
  904. QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " ");
  905. peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid));
  906. if (!stats->nodeStats.addrLocal.empty())
  907. peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
  908. ui->peerHeading->setText(peerAddrDetails);
  909. ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices));
  910. ui->peerLastSend->setText(stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never"));
  911. ui->peerLastRecv->setText(stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never"));
  912. ui->peerBytesSent->setText(FormatBytes(stats->nodeStats.nSendBytes));
  913. ui->peerBytesRecv->setText(FormatBytes(stats->nodeStats.nRecvBytes));
  914. ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected));
  915. ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingTime));
  916. ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingWait));
  917. ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.dMinPing));
  918. ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
  919. ui->peerVersion->setText(QString("%1").arg(QString::number(stats->nodeStats.nVersion)));
  920. ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
  921. ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound"));
  922. ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight)));
  923. ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No"));
  924. // This check fails for example if the lock was busy and
  925. // nodeStateStats couldn't be fetched.
  926. if (stats->fNodeStateStatsAvailable) {
  927. // Ban score is init to 0
  928. ui->peerBanScore->setText(QString("%1").arg(stats->nodeStateStats.nMisbehavior));
  929. // Sync height is init to -1
  930. if (stats->nodeStateStats.nSyncHeight > -1)
  931. ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight));
  932. else
  933. ui->peerSyncHeight->setText(tr("Unknown"));
  934. // Common height is init to -1
  935. if (stats->nodeStateStats.nCommonHeight > -1)
  936. ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight));
  937. else
  938. ui->peerCommonHeight->setText(tr("Unknown"));
  939. }
  940. ui->detailWidget->show();
  941. }
  942. void RPCConsole::resizeEvent(QResizeEvent *event)
  943. {
  944. QWidget::resizeEvent(event);
  945. }
  946. void RPCConsole::showEvent(QShowEvent *event)
  947. {
  948. QWidget::showEvent(event);
  949. if (!clientModel || !clientModel->getPeerTableModel())
  950. return;
  951. // start PeerTableModel auto refresh
  952. clientModel->getPeerTableModel()->startAutoRefresh();
  953. }
  954. void RPCConsole::hideEvent(QHideEvent *event)
  955. {
  956. QWidget::hideEvent(event);
  957. if (!clientModel || !clientModel->getPeerTableModel())
  958. return;
  959. // stop PeerTableModel auto refresh
  960. clientModel->getPeerTableModel()->stopAutoRefresh();
  961. }
  962. void RPCConsole::showPeersTableContextMenu(const QPoint& point)
  963. {
  964. QModelIndex index = ui->peerWidget->indexAt(point);
  965. if (index.isValid())
  966. peersTableContextMenu->exec(QCursor::pos());
  967. }
  968. void RPCConsole::showBanTableContextMenu(const QPoint& point)
  969. {
  970. QModelIndex index = ui->banlistWidget->indexAt(point);
  971. if (index.isValid())
  972. banTableContextMenu->exec(QCursor::pos());
  973. }
  974. void RPCConsole::disconnectSelectedNode()
  975. {
  976. if(!g_connman)
  977. return;
  978. // Get selected peer addresses
  979. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
  980. for(int i = 0; i < nodes.count(); i++)
  981. {
  982. // Get currently selected peer address
  983. NodeId id = nodes.at(i).data().toLongLong();
  984. // Find the node, disconnect it and clear the selected node
  985. if(g_connman->DisconnectNode(id))
  986. clearSelectedNode();
  987. }
  988. }
  989. void RPCConsole::banSelectedNode(int bantime)
  990. {
  991. if (!clientModel || !g_connman)
  992. return;
  993. // Get selected peer addresses
  994. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
  995. for(int i = 0; i < nodes.count(); i++)
  996. {
  997. // Get currently selected peer address
  998. NodeId id = nodes.at(i).data().toLongLong();
  999. // Get currently selected peer address
  1000. int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id);
  1001. if(detailNodeRow < 0)
  1002. return;
  1003. // Find possible nodes, ban it and clear the selected node
  1004. const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
  1005. if(stats) {
  1006. g_connman->Ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime);
  1007. }
  1008. }
  1009. clearSelectedNode();
  1010. clientModel->getBanTableModel()->refresh();
  1011. }
  1012. void RPCConsole::unbanSelectedNode()
  1013. {
  1014. if (!clientModel)
  1015. return;
  1016. // Get selected ban addresses
  1017. QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address);
  1018. for(int i = 0; i < nodes.count(); i++)
  1019. {
  1020. // Get currently selected ban address
  1021. QString strNode = nodes.at(i).data().toString();
  1022. CSubNet possibleSubnet;
  1023. LookupSubNet(strNode.toStdString().c_str(), possibleSubnet);
  1024. if (possibleSubnet.IsValid() && g_connman)
  1025. {
  1026. g_connman->Unban(possibleSubnet);
  1027. clientModel->getBanTableModel()->refresh();
  1028. }
  1029. }
  1030. }
  1031. void RPCConsole::clearSelectedNode()
  1032. {
  1033. ui->peerWidget->selectionModel()->clearSelection();
  1034. cachedNodeids.clear();
  1035. ui->detailWidget->hide();
  1036. ui->peerHeading->setText(tr("Select a peer to view detailed information."));
  1037. }
  1038. void RPCConsole::showOrHideBanTableIfRequired()
  1039. {
  1040. if (!clientModel)
  1041. return;
  1042. bool visible = clientModel->getBanTableModel()->shouldShow();
  1043. ui->banlistWidget->setVisible(visible);
  1044. ui->banHeading->setVisible(visible);
  1045. }
  1046. void RPCConsole::setTabFocus(enum TabTypes tabType)
  1047. {
  1048. ui->tabWidget->setCurrentIndex(tabType);
  1049. }