feat(console): redesign the RPC reference as a novice-friendly command explorer
Replace the flat, terse command-reference table with a guided two-pane explorer built for users who don't know the RPC commands: - Search by name OR task: keyword synonyms let "balance" find getbalance, "send money" find sendtoaddress; results are ranked (name-prefix > name > keyword > desc). - Two panes (mirrors the portfolio editor): a category-grouped master list (mono names, a warning dot on consequential commands) + a rich detail pane with a plain-language explanation, a per-parameter type breakdown (string / number / json, optional dimmed), and a concrete example. - Insert into console (fills the input with a template to review + run) and, for no-parameter commands, Insert & run — with a confirm for destructive ones. Deferred via pending_submit_ so the modal needs no executor. - Keyboard: type to filter, Up/Down to move, Enter to insert; auto-focus and auto-select the top result. Data model: ConsoleCommandEntry gains details/example/keywords/destructive (C++17 defaults, so the ~60 un-enriched entries are untouched); ~20 common commands enriched and 18 consequential ones flagged. Command docs stay English (technical); the UI chrome + 7 category names are translated into all 8 languages (CJK subset rebuilt, +3 glyphs). Verified: two-pane renders on dark + light skins and at font_scale 1.5; detail pane shows explanation/params/example; danger dots + safety badge on destructive commands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "Kein Daemon",
|
||||
"console_not_connected": "Fehler: Nicht mit Daemon verbunden",
|
||||
"console_quit_note": "'quit'/'exit' werden hier nicht benötigt — schließen Sie einfach das Fenster.",
|
||||
"console_ref_cancel": "Abbrechen",
|
||||
"console_ref_destructive": "Folgenreich",
|
||||
"console_ref_example": "Beispiel",
|
||||
"console_ref_insert": "In Konsole einfügen",
|
||||
"console_ref_insert_run": "Einfügen & ausführen",
|
||||
"console_ref_no_match": "Keine Befehle gefunden.",
|
||||
"console_ref_no_params": "Erwartet keine Parameter.",
|
||||
"console_ref_optional": "optional",
|
||||
"console_ref_parameters": "Parameter",
|
||||
"console_ref_run": "Ausführen",
|
||||
"console_ref_run_confirm": "%s jetzt ausführen? Dies ist ein folgenreicher Befehl.",
|
||||
"console_ref_search_hint": "Nach Name oder Aufgabe suchen…",
|
||||
"console_ref_select_hint": "Wählen Sie einen Befehl, um zu sehen, was er tut.",
|
||||
"console_rpc_reference": "RPC-Befehlsreferenz",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "Konsolen-Scanline",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "Sin daemon",
|
||||
"console_not_connected": "Error: No conectado al daemon",
|
||||
"console_quit_note": "'quit'/'exit' no son necesarios aquí — simplemente cierra la ventana.",
|
||||
"console_ref_cancel": "Cancelar",
|
||||
"console_ref_destructive": "Delicado",
|
||||
"console_ref_example": "Ejemplo",
|
||||
"console_ref_insert": "Insertar en la consola",
|
||||
"console_ref_insert_run": "Insertar y ejecutar",
|
||||
"console_ref_no_match": "Ningún comando coincide.",
|
||||
"console_ref_no_params": "No requiere parámetros.",
|
||||
"console_ref_optional": "opcional",
|
||||
"console_ref_parameters": "Parámetros",
|
||||
"console_ref_run": "Ejecutar",
|
||||
"console_ref_run_confirm": "¿Ejecutar %s ahora? Es un comando con consecuencias.",
|
||||
"console_ref_search_hint": "Buscar por nombre o tarea…",
|
||||
"console_ref_select_hint": "Selecciona un comando para ver qué hace.",
|
||||
"console_rpc_reference": "Referencia de Comandos RPC",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "Líneas de consola",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "Pas de daemon",
|
||||
"console_not_connected": "Erreur : Non connecté au daemon",
|
||||
"console_quit_note": "'quit'/'exit' ne sont pas nécessaires ici — fermez simplement la fenêtre.",
|
||||
"console_ref_cancel": "Annuler",
|
||||
"console_ref_destructive": "Sensible",
|
||||
"console_ref_example": "Exemple",
|
||||
"console_ref_insert": "Insérer dans la console",
|
||||
"console_ref_insert_run": "Insérer et exécuter",
|
||||
"console_ref_no_match": "Aucune commande ne correspond.",
|
||||
"console_ref_no_params": "Ne prend aucun paramètre.",
|
||||
"console_ref_optional": "optionnel",
|
||||
"console_ref_parameters": "Paramètres",
|
||||
"console_ref_run": "Exécuter",
|
||||
"console_ref_run_confirm": "Exécuter %s maintenant ? C'est une commande à conséquences.",
|
||||
"console_ref_search_hint": "Rechercher par nom ou tâche…",
|
||||
"console_ref_select_hint": "Sélectionnez une commande pour voir ce qu'elle fait.",
|
||||
"console_rpc_reference": "Référence des commandes RPC",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "Scanline de la console",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "デーモンなし",
|
||||
"console_not_connected": "エラー:デーモンに接続されていません",
|
||||
"console_quit_note": "ここでは 'quit'/'exit' は不要です — ウィンドウを閉じるだけで構いません。",
|
||||
"console_ref_cancel": "キャンセル",
|
||||
"console_ref_destructive": "要注意",
|
||||
"console_ref_example": "例",
|
||||
"console_ref_insert": "コンソールに挿入",
|
||||
"console_ref_insert_run": "挿入して実行",
|
||||
"console_ref_no_match": "一致するコマンドはありません。",
|
||||
"console_ref_no_params": "パラメーターは不要です。",
|
||||
"console_ref_optional": "任意",
|
||||
"console_ref_parameters": "パラメーター",
|
||||
"console_ref_run": "実行",
|
||||
"console_ref_run_confirm": "%s を今すぐ実行しますか? 影響の大きいコマンドです。",
|
||||
"console_ref_search_hint": "名前または用途で検索…",
|
||||
"console_ref_select_hint": "コマンドを選ぶと内容が表示されます。",
|
||||
"console_rpc_reference": "RPCコマンドリファレンス",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "コンソールスキャンライン",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "데몬 없음",
|
||||
"console_not_connected": "오류: 데몬에 연결되지 않았습니다",
|
||||
"console_quit_note": "여기서는 'quit'/'exit'가 필요 없습니다 — 그냥 창을 닫으세요.",
|
||||
"console_ref_cancel": "취소",
|
||||
"console_ref_destructive": "주의",
|
||||
"console_ref_example": "예시",
|
||||
"console_ref_insert": "콘솔에 삽입",
|
||||
"console_ref_insert_run": "삽입 후 실행",
|
||||
"console_ref_no_match": "일치하는 명령이 없습니다.",
|
||||
"console_ref_no_params": "매개변수가 없습니다.",
|
||||
"console_ref_optional": "선택",
|
||||
"console_ref_parameters": "매개변수",
|
||||
"console_ref_run": "실행",
|
||||
"console_ref_run_confirm": "%s 을(를) 지금 실행할까요? 영향이 큰 명령입니다.",
|
||||
"console_ref_search_hint": "이름 또는 용도로 검색…",
|
||||
"console_ref_select_hint": "명령을 선택하면 설명이 표시됩니다.",
|
||||
"console_rpc_reference": "RPC 명령어 참조",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "콘솔 스캔라인",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "Sem daemon",
|
||||
"console_not_connected": "Erro: Não conectado ao daemon",
|
||||
"console_quit_note": "'quit'/'exit' não são necessários aqui — basta fechar a janela.",
|
||||
"console_ref_cancel": "Cancelar",
|
||||
"console_ref_destructive": "Sensível",
|
||||
"console_ref_example": "Exemplo",
|
||||
"console_ref_insert": "Inserir no console",
|
||||
"console_ref_insert_run": "Inserir e executar",
|
||||
"console_ref_no_match": "Nenhum comando corresponde.",
|
||||
"console_ref_no_params": "Não requer parâmetros.",
|
||||
"console_ref_optional": "opcional",
|
||||
"console_ref_parameters": "Parâmetros",
|
||||
"console_ref_run": "Executar",
|
||||
"console_ref_run_confirm": "Executar %s agora? Este é um comando com consequências.",
|
||||
"console_ref_search_hint": "Pesquisar por nome ou tarefa…",
|
||||
"console_ref_select_hint": "Selecione um comando para ver o que ele faz.",
|
||||
"console_rpc_reference": "Referência de Comandos RPC",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "Scanline do console",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "Нет daemon",
|
||||
"console_not_connected": "Ошибка: Не подключено к daemon",
|
||||
"console_quit_note": "Здесь не нужны 'quit'/'exit' — просто закройте окно.",
|
||||
"console_ref_cancel": "Отмена",
|
||||
"console_ref_destructive": "Осторожно",
|
||||
"console_ref_example": "Пример",
|
||||
"console_ref_insert": "Вставить в консоль",
|
||||
"console_ref_insert_run": "Вставить и выполнить",
|
||||
"console_ref_no_match": "Нет подходящих команд.",
|
||||
"console_ref_no_params": "Не требует параметров.",
|
||||
"console_ref_optional": "необязательно",
|
||||
"console_ref_parameters": "Параметры",
|
||||
"console_ref_run": "Выполнить",
|
||||
"console_ref_run_confirm": "Выполнить %s сейчас? Это ответственная команда.",
|
||||
"console_ref_search_hint": "Поиск по названию или задаче…",
|
||||
"console_ref_select_hint": "Выберите команду, чтобы увидеть, что она делает.",
|
||||
"console_rpc_reference": "Справочник RPC-команд",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "Скан-линия консоли",
|
||||
|
||||
@@ -229,6 +229,19 @@
|
||||
"console_no_daemon": "无守护进程",
|
||||
"console_not_connected": "错误:未连接到守护进程",
|
||||
"console_quit_note": "这里不需要 'quit'/'exit'——直接关闭窗口即可。",
|
||||
"console_ref_cancel": "取消",
|
||||
"console_ref_destructive": "谨慎",
|
||||
"console_ref_example": "示例",
|
||||
"console_ref_insert": "插入到控制台",
|
||||
"console_ref_insert_run": "插入并运行",
|
||||
"console_ref_no_match": "没有匹配的命令。",
|
||||
"console_ref_no_params": "无需参数。",
|
||||
"console_ref_optional": "可选",
|
||||
"console_ref_parameters": "参数",
|
||||
"console_ref_run": "运行",
|
||||
"console_ref_run_confirm": "立即运行 %s?这是一个有重大影响的命令。",
|
||||
"console_ref_search_hint": "按名称或用途搜索…",
|
||||
"console_ref_select_hint": "选择一个命令以查看其功能。",
|
||||
"console_rpc_reference": "RPC 命令参考",
|
||||
"console_rpc_trace": "RPC",
|
||||
"console_scanline": "控制台扫描线",
|
||||
|
||||
@@ -14,28 +14,50 @@ constexpr int CountOf(const ConsoleCommandEntry (&)[N])
|
||||
}
|
||||
|
||||
const ConsoleCommandEntry kControlCommands[] = {
|
||||
{"help", "List all commands, or get help for a specified command", "[\"command\"]"},
|
||||
{"getinfo", "Get general info about the node", ""},
|
||||
{"stop", "Stop the daemon", ""},
|
||||
{"help", "List all commands, or get help for a specified command", "[\"command\"]",
|
||||
"Lists every RPC command. Pass a command name to see the node's detailed help for just that one.",
|
||||
"help \"getinfo\"", "commands list documentation what available"},
|
||||
{"getinfo", "Get general info about the node", "",
|
||||
"A quick snapshot of the node: version, block height, connection count, difficulty and wallet balance.",
|
||||
"getinfo", "status version summary overview node health"},
|
||||
{"stop", "Stop the daemon", "",
|
||||
"Shuts the DragonX node down cleanly. The wallet stays disconnected until the node is started again.",
|
||||
"stop", "quit shutdown exit close halt turn off", true},
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kNetworkCommands[] = {
|
||||
{"getnetworkinfo", "Return P2P network state info", ""},
|
||||
{"getpeerinfo", "Get data about each connected peer", ""},
|
||||
{"getconnectioncount", "Get number of peer connections", ""},
|
||||
{"getnetworkinfo", "Return P2P network state info", "",
|
||||
"The node's networking state: protocol version, which networks are active, listening addresses and relay fee.",
|
||||
"getnetworkinfo", "network p2p connection status internet"},
|
||||
{"getpeerinfo", "Get data about each connected peer", "",
|
||||
"Detailed data for every peer you are connected to \xE2\x80\x94 address, version, ping time and bytes exchanged.",
|
||||
"getpeerinfo", "peers connections nodes who connected"},
|
||||
{"getconnectioncount", "Get number of peer connections", "",
|
||||
"How many peers the node is currently connected to. Zero usually means the node is still starting or offline.",
|
||||
"getconnectioncount", "peers connections count how many"},
|
||||
{"getnettotals", "Get network traffic statistics", ""},
|
||||
{"addnode", "Add, remove, or connect to a node", "\"node\" \"add|remove|onetry\""},
|
||||
{"setban", "Add or remove an IP/subnet from the ban list", "\"ip\" \"add|remove\" [bantime] [absolute]"},
|
||||
{"setban", "Add or remove an IP/subnet from the ban list", "\"ip\" \"add|remove\" [bantime] [absolute]",
|
||||
"Bans or unbans an IP or subnet from connecting. bantime is in seconds (0 = the default 24h); absolute treats it as a Unix timestamp.",
|
||||
"setban \"192.168.0.6\" \"add\" 86400", "ban block ip peer firewall reject", true},
|
||||
{"listbanned", "List all banned IPs/subnets", ""},
|
||||
{"clearbanned", "Clear all banned IPs", ""},
|
||||
{"clearbanned", "Clear all banned IPs", "",
|
||||
"Removes every entry from the ban list, letting all previously-banned peers connect again.",
|
||||
"clearbanned", "unban reset bans allow clear", true},
|
||||
{"ping", "Ping all peers to measure round-trip time", ""},
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kBlockchainCommands[] = {
|
||||
{"getblockchaininfo", "Get current blockchain state", ""},
|
||||
{"getblockcount", "Get number of blocks in longest chain", ""},
|
||||
{"getblockchaininfo", "Get current blockchain state", "",
|
||||
"State of the chain: height, best block hash, difficulty, verification progress and how far the node has synced.",
|
||||
"getblockchaininfo", "chain sync height status blockchain progress"},
|
||||
{"getblockcount", "Get number of blocks in longest chain", "",
|
||||
"The height of the longest chain \xE2\x80\x94 how many blocks the node has. Compare with the network to check sync.",
|
||||
"getblockcount", "height blocks how many chain length synced"},
|
||||
{"getbestblockhash", "Get hash of the tip block", ""},
|
||||
{"getblock", "Get block data for a given hash or height", "\"hash|height\" [verbosity]"},
|
||||
{"getblock", "Get block data for a given hash or height", "\"hash|height\" [verbosity]",
|
||||
"Returns a block by its hash or height. verbosity 0 = raw hex, 1 = decoded header + txids, 2 = full transactions.",
|
||||
"getblock \"0000000000abc123\" 1", "block details transactions header"},
|
||||
{"getblockhash", "Get block hash at a given height", "height"},
|
||||
{"getblockheader", "Get block header for a given hash", "\"hash\" [verbose]"},
|
||||
{"getdifficulty", "Get proof-of-work difficulty", ""},
|
||||
@@ -51,8 +73,12 @@ const ConsoleCommandEntry kBlockchainCommands[] = {
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kMiningCommands[] = {
|
||||
{"getmininginfo", "Get mining-related information", ""},
|
||||
{"setgenerate", "Turn mining on or off (true/false [threads])", "generate [genproclimit]"},
|
||||
{"getmininginfo", "Get mining-related information", "",
|
||||
"Mining status: current difficulty, estimated network hash rate and whether this node is generating blocks.",
|
||||
"getmininginfo", "mining hashrate difficulty generate"},
|
||||
{"setgenerate", "Turn mining on or off (true/false [threads])", "generate [genproclimit]",
|
||||
"Turns built-in CPU mining on or off. Pass true or false, and optionally the number of threads (-1 = all cores).",
|
||||
"setgenerate true 4", "mine mining generate cpu start stop", true},
|
||||
{"getgenerate", "Check if the node is mining", ""},
|
||||
{"getnetworkhashps", "Get estimated network hash rate", "[blocks] [height]"},
|
||||
{"getblocksubsidy", "Get block reward at a given height", "[height]"},
|
||||
@@ -61,39 +87,75 @@ const ConsoleCommandEntry kMiningCommands[] = {
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kWalletCommands[] = {
|
||||
{"getbalance", "Get wallet transparent balance", "[\"account\"] [minconf]"},
|
||||
{"z_gettotalbalance", "Get total transparent + shielded balance", "[minconf]"},
|
||||
{"z_getbalances", "Get all balances (transparent + shielded)", ""},
|
||||
{"getnewaddress", "Generate a new transparent address", ""},
|
||||
{"z_getnewaddress", "Generate a new shielded address", "[\"type\"]"},
|
||||
{"listaddresses", "List all transparent addresses", ""},
|
||||
{"z_listaddresses", "List all z-addresses", ""},
|
||||
{"sendtoaddress", "Send to a specific address", "\"address\" amount"},
|
||||
{"z_sendmany", "Send to multiple z/t-addresses with shielded support", "\"fromaddress\" [{\"address\":\"...\",\"amount\":...}]"},
|
||||
{"getbalance", "Get wallet transparent balance", "[\"account\"] [minconf]",
|
||||
"Your confirmed transparent (t-address) balance in DRGX. Shielded funds are NOT included \xE2\x80\x94 use z_gettotalbalance for everything.",
|
||||
"getbalance", "balance money funds how much transparent"},
|
||||
{"z_gettotalbalance", "Get total transparent + shielded balance", "[minconf]",
|
||||
"Your complete balance in DRGX \xE2\x80\x94 transparent plus shielded (private) funds together.",
|
||||
"z_gettotalbalance", "balance total money funds shielded private how much"},
|
||||
{"z_getbalances", "Get all balances (transparent + shielded)", "",
|
||||
"Breaks your balance down across each transparent and shielded address the wallet holds.",
|
||||
"z_getbalances", "balance breakdown addresses funds per address"},
|
||||
{"getnewaddress", "Generate a new transparent address", "",
|
||||
"Creates a fresh transparent (t-) address you can share to receive DRGX.",
|
||||
"getnewaddress", "receive new address deposit transparent create get paid"},
|
||||
{"z_getnewaddress", "Generate a new shielded address", "[\"type\"]",
|
||||
"Creates a fresh shielded (z-) address for receiving DRGX privately.",
|
||||
"z_getnewaddress", "receive new address private shielded create"},
|
||||
{"listaddresses", "List all transparent addresses", "",
|
||||
"Lists the transparent (t-) addresses in your wallet.",
|
||||
"listaddresses", "addresses list my accounts transparent"},
|
||||
{"z_listaddresses", "List all z-addresses", "",
|
||||
"Lists the shielded (z-) addresses in your wallet.",
|
||||
"z_listaddresses", "addresses list my shielded private"},
|
||||
{"sendtoaddress", "Send to a specific address", "\"address\" amount",
|
||||
"Sends DRGX to a transparent address. amount is in DRGX. Double-check the address \xE2\x80\x94 sent transactions cannot be reversed.",
|
||||
"sendtoaddress \"RyourRecipientAddr\" 1.5", "send pay transfer money spend", true},
|
||||
{"z_sendmany", "Send to multiple z/t-addresses with shielded support", "\"fromaddress\" [{\"address\":\"...\",\"amount\":...}]",
|
||||
"Sends DRGX from one address to one or more recipients, with shielded (private) support. Amounts are in DRGX.",
|
||||
"z_sendmany \"RfromAddr\" [{\"address\":\"zs1toAddr\",\"amount\":1.0}]", "send pay private shielded transfer money", true},
|
||||
{"z_shieldcoinbase", "Shield transparent coinbase funds to a z-address", "\"fromaddress\" \"tozaddress\" [fee] [limit]"},
|
||||
{"z_mergetoaddress", "Merge multiple UTXOs/notes to one address", "[\"fromaddress\",...] \"toaddress\" [fee] [limit]"},
|
||||
{"listtransactions", "List recent wallet transactions", "[\"account\"] [count] [from]"},
|
||||
{"listtransactions", "List recent wallet transactions", "[\"account\"] [count] [from]",
|
||||
"Your most recent wallet transactions, newest first \xE2\x80\x94 amounts, addresses and confirmations.",
|
||||
"listtransactions", "transactions history recent payments received sent"},
|
||||
{"listunspent", "List unspent transaction outputs", "[minconf] [maxconf]"},
|
||||
{"z_listunspent", "List unspent shielded notes", "[minconf] [maxconf]"},
|
||||
{"z_getoperationstatus", "Get status of async z operations", "[\"operationid\",...]"},
|
||||
{"z_getoperationresult", "Get result of completed z operations", "[\"operationid\",...]"},
|
||||
{"z_listoperationids", "List all async z operation IDs", ""},
|
||||
{"getwalletinfo", "Get wallet state info", ""},
|
||||
{"backupwallet", "Back up wallet to a file", "\"destination\""},
|
||||
{"dumpprivkey", "Dump private key for an address", "\"address\""},
|
||||
{"importprivkey", "Import a private key into the wallet", "\"privkey\" [\"label\"] [rescan]"},
|
||||
{"dumpwallet", "Dump all wallet keys to a file", "\"filename\""},
|
||||
{"importwallet", "Import wallet from a dump file", "\"filename\""},
|
||||
{"z_exportkey", "Export spending key for a z-address", "\"zaddr\""},
|
||||
{"z_importkey", "Import a z-address spending key", "\"zkey\" [rescan] [startheight]"},
|
||||
{"getwalletinfo", "Get wallet state info", "",
|
||||
"Wallet summary: balances, transaction count, key-pool size and whether the wallet is encrypted and locked.",
|
||||
"getwalletinfo", "wallet status info balance encrypted locked"},
|
||||
{"backupwallet", "Back up wallet to a file", "\"destination\"",
|
||||
"Saves a copy of your wallet.dat to the given path. Keep the backup somewhere safe and private.",
|
||||
"backupwallet \"/home/you/drgx-backup.dat\"", "backup save wallet copy protect"},
|
||||
{"dumpprivkey", "Dump private key for an address", "\"address\"",
|
||||
"Reveals the private key for an address. Anyone with this key can spend from the address \xE2\x80\x94 never share it.",
|
||||
"dumpprivkey \"RyourAddr\"", "private key export reveal secret spend", true},
|
||||
{"importprivkey", "Import a private key into the wallet", "\"privkey\" [\"label\"] [rescan]",
|
||||
"Imports a private key so the wallet can spend from its address, then rescans the chain for its history (can be slow).",
|
||||
"importprivkey \"Uxxxxxxxxxxxx\"", "import private key restore recover add", true},
|
||||
{"dumpwallet", "Dump all wallet keys to a file", "\"filename\"", "", "", "", true},
|
||||
{"importwallet", "Import wallet from a dump file", "\"filename\"", "", "", "", true},
|
||||
{"z_exportkey", "Export spending key for a z-address", "\"zaddr\"",
|
||||
"Reveals the spending key for a shielded address. Anyone with it can spend your private funds \xE2\x80\x94 keep it secret.",
|
||||
"z_exportkey \"zs1yourAddr\"", "private key export shielded secret spend", true},
|
||||
{"z_importkey", "Import a z-address spending key", "\"zkey\" [rescan] [startheight]", "", "", "", true},
|
||||
{"z_exportviewingkey", "Export viewing key for a z-address", "\"zaddr\""},
|
||||
{"z_importviewingkey", "Import a z-address viewing key", "\"vkey\" [rescan] [startheight]"},
|
||||
{"z_exportwallet", "Export all wallet keys (including z-keys) to file", "\"filename\""},
|
||||
{"signmessage", "Sign a message with an address key", "\"address\" \"message\""},
|
||||
{"z_importviewingkey", "Import a z-address viewing key", "\"vkey\" [rescan] [startheight]", "", "", "", true},
|
||||
{"z_exportwallet", "Export all wallet keys (including z-keys) to file", "\"filename\"", "", "", "", true},
|
||||
{"signmessage", "Sign a message with an address key", "\"address\" \"message\"",
|
||||
"Signs a message with the private key of one of your addresses, proving you control that address.",
|
||||
"signmessage \"RyourAddr\" \"hello world\"", "sign message prove ownership"},
|
||||
{"settxfee", "Set the transaction fee per kB", "amount"},
|
||||
{"walletpassphrase", "Unlock the wallet with passphrase", "\"passphrase\" timeout"},
|
||||
{"walletpassphrase", "Unlock the wallet with passphrase", "\"passphrase\" timeout",
|
||||
"Unlocks an encrypted wallet for the given number of seconds so you can send funds. Your passphrase is typed in plain text \xE2\x80\x94 be sure no one is watching.",
|
||||
"walletpassphrase \"your passphrase\" 60", "unlock passphrase password encrypted open", true},
|
||||
{"walletlock", "Lock the wallet", ""},
|
||||
{"encryptwallet", "Encrypt the wallet with a passphrase", "\"passphrase\""},
|
||||
{"encryptwallet", "Encrypt the wallet with a passphrase", "\"passphrase\"",
|
||||
"Encrypts the wallet with a passphrase. The node shuts down afterward and you will need the passphrase to send. Encryption cannot be undone \xE2\x80\x94 keep the passphrase safe.",
|
||||
"encryptwallet \"a strong passphrase\"", "encrypt password protect secure lock", true},
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kRawTransactionCommands[] = {
|
||||
@@ -107,13 +169,15 @@ const ConsoleCommandEntry kRawTransactionCommands[] = {
|
||||
};
|
||||
|
||||
const ConsoleCommandEntry kUtilityCommands[] = {
|
||||
{"validateaddress", "Validate a transparent address", "\"address\""},
|
||||
{"validateaddress", "Validate a transparent address", "\"address\"",
|
||||
"Checks whether a transparent address is well-formed and whether it belongs to your wallet.",
|
||||
"validateaddress \"RsomeAddr\"", "validate check address valid mine"},
|
||||
{"z_validateaddress", "Validate a z-address", "\"zaddr\""},
|
||||
{"estimatefee", "Estimate fee for a transaction", "nblocks"},
|
||||
{"verifymessage", "Verify a signed message", "\"address\" \"signature\" \"message\""},
|
||||
{"createmultisig", "Create a multisig address", "nrequired [\"key\",...]"},
|
||||
{"invalidateblock", "Mark a block as invalid", "\"hash\""},
|
||||
{"reconsiderblock", "Reconsider a previously invalidated block", "\"hash\""},
|
||||
{"invalidateblock", "Mark a block as invalid", "\"hash\"", "", "", "", true},
|
||||
{"reconsiderblock", "Reconsider a previously invalidated block", "\"hash\"", "", "", "", true},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -7,8 +7,14 @@ namespace ui {
|
||||
|
||||
struct ConsoleCommandEntry {
|
||||
const char* name;
|
||||
const char* desc;
|
||||
const char* params;
|
||||
const char* desc; // terse one-line summary (always set)
|
||||
const char* params; // parameter template, e.g. "\"address\" amount [comment]"
|
||||
// Optional novice-facing enrichment (C++17 aggregate defaults — entries that omit these keep
|
||||
// the empty/false fallbacks, and the command explorer falls back to `desc`):
|
||||
const char* details = ""; // longer plain-language explanation
|
||||
const char* example = ""; // one concrete, runnable example line
|
||||
const char* keywords = ""; // space-separated search synonyms ("balance money funds")
|
||||
bool destructive = false; // consequential/sensitive -> safety badge + run confirmation
|
||||
};
|
||||
|
||||
struct ConsoleCommandCategory {
|
||||
|
||||
@@ -284,6 +284,14 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec)
|
||||
}
|
||||
}
|
||||
|
||||
// Run a command the RPC-reference modal's "Insert & run" queued (the modal has no executor, so it
|
||||
// defers to here). Done before drain so the echoed "> cmd" line surfaces this frame.
|
||||
if (!pending_submit_.empty()) {
|
||||
std::string cmd;
|
||||
cmd.swap(pending_submit_);
|
||||
submitConsoleCommand(exec, cmd);
|
||||
}
|
||||
|
||||
// Pull passive log lines (daemon/xmrig output, or the lite diagnostics ring) and any
|
||||
// completed command results from the backend executor.
|
||||
exec.pollLogLines([this](const std::string& l, ConsoleChannel c) { addLine(l, c); });
|
||||
@@ -1439,54 +1447,52 @@ bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::s
|
||||
namespace {
|
||||
// True if any of a command's name/desc/params contains `filterLower` (already lowercased).
|
||||
// An empty filter matches everything.
|
||||
bool consoleCommandMatchesFilter(const char* name, const char* desc, const char* params,
|
||||
const std::string& filterLower)
|
||||
static bool lcContains(const char* s, const std::string& needleLower)
|
||||
{
|
||||
if (filterLower.empty()) return true;
|
||||
auto contains = [&filterLower](const char* s) {
|
||||
std::string v(s);
|
||||
std::transform(v.begin(), v.end(), v.begin(), ::tolower);
|
||||
return v.find(filterLower) != std::string::npos;
|
||||
};
|
||||
return contains(name) || contains(desc) || contains(params);
|
||||
std::string v(s);
|
||||
std::transform(v.begin(), v.end(), v.begin(), ::tolower);
|
||||
return v.find(needleLower) != std::string::npos;
|
||||
}
|
||||
static bool lcStartsWith(const char* s, const std::string& needleLower)
|
||||
{
|
||||
std::string v(s);
|
||||
std::transform(v.begin(), v.end(), v.begin(), ::tolower);
|
||||
return v.rfind(needleLower, 0) == 0;
|
||||
}
|
||||
|
||||
// Draw a command's parameter string into the current table cell, dimming optional [params].
|
||||
void drawConsoleCommandParams(const char* params)
|
||||
// Search relevance of a command for a lowercased query. -1 = no match; higher = better. Keywords
|
||||
// let novices find a command by intent ("balance" -> getbalance) even without the exact name.
|
||||
int consoleCommandRank(const ConsoleCommandEntry& cmd, const std::string& q)
|
||||
{
|
||||
using namespace material;
|
||||
const char* p = params;
|
||||
bool first = true;
|
||||
while (*p) {
|
||||
const char* bracketStart = strchr(p, '[');
|
||||
if (bracketStart) {
|
||||
// Draw the required part before the bracket.
|
||||
if (bracketStart > p) {
|
||||
if (!first) ImGui::SameLine(0, 0);
|
||||
std::string req(p, bracketStart);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", req.c_str());
|
||||
first = false;
|
||||
}
|
||||
const char* bracketEnd = strchr(bracketStart, ']');
|
||||
if (bracketEnd) {
|
||||
if (!first) ImGui::SameLine(0, 0);
|
||||
std::string opt(bracketStart, bracketEnd + 1);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", opt.c_str());
|
||||
first = false;
|
||||
p = bracketEnd + 1;
|
||||
} else {
|
||||
if (!first) ImGui::SameLine(0, 0);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", bracketStart);
|
||||
first = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!first) ImGui::SameLine(0, 0);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", p);
|
||||
first = false;
|
||||
break;
|
||||
}
|
||||
if (q.empty()) return 0;
|
||||
if (lcStartsWith(cmd.name, q)) return 100;
|
||||
if (lcContains(cmd.name, q)) return 60;
|
||||
if (cmd.keywords[0] && lcContains(cmd.keywords, q)) return 40;
|
||||
if (lcContains(cmd.desc, q)) return 25;
|
||||
if (cmd.details[0] && lcContains(cmd.details, q)) return 15;
|
||||
if (lcContains(cmd.params, q)) return 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Split a parameter template into top-level tokens, respecting quote/bracket nesting so a space
|
||||
// inside "..." or [{...}] doesn't split (e.g. `"address" [{"a":1}]` -> {`"address"`, `[{"a":1}]`}).
|
||||
std::vector<std::string> splitParamTemplate(const char* params)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
std::string tok;
|
||||
int depth = 0;
|
||||
char q = 0;
|
||||
for (const char* p = params; *p; ++p) {
|
||||
char c = *p;
|
||||
if (q) { tok += c; if (c == q) q = 0; continue; }
|
||||
if (c == '"' || c == '\'') { q = c; tok += c; continue; }
|
||||
if (c == '[' || c == '{') { depth++; tok += c; continue; }
|
||||
if (c == ']' || c == '}') { if (depth > 0) depth--; tok += c; continue; }
|
||||
if (c == ' ' && depth == 0) { if (!tok.empty()) { out.push_back(tok); tok.clear(); } continue; }
|
||||
tok += c;
|
||||
}
|
||||
if (!tok.empty()) out.push_back(tok);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Translate a command-category name from the static reference tables (English) for display. The
|
||||
@@ -1504,157 +1510,279 @@ const char* consoleCategoryLabel(const char* name)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConsoleTab::insertCommandToInput(const ConsoleCommandEntry& cmd)
|
||||
{
|
||||
// Fill the console input with the command (+ its param template) and close the modal — the user
|
||||
// reviews/edits it and presses Enter to run.
|
||||
if (cmd.params[0] != '\0')
|
||||
snprintf(input_buffer_, sizeof(input_buffer_), "%s %s", cmd.name, cmd.params);
|
||||
else {
|
||||
strncpy(input_buffer_, cmd.name, sizeof(input_buffer_) - 1);
|
||||
input_buffer_[sizeof(input_buffer_) - 1] = '\0';
|
||||
}
|
||||
command_search_[0] = '\0';
|
||||
run_confirm_cmd_ = nullptr;
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
|
||||
void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel)
|
||||
{
|
||||
using namespace material;
|
||||
float dp = Layout::dpiScale();
|
||||
ImFont* mono = Type().mono();
|
||||
|
||||
// Heading: command name, then category + a safety badge for consequential commands.
|
||||
ImGui::PushFont(Type().subtitle1());
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(IM_COL32(120, 190, 255, 255)), "%s", cmd.name);
|
||||
ImGui::PopFont();
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", catLabel);
|
||||
if (cmd.destructive) {
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
ImVec4 warn = ImGui::ColorConvertU32ToFloat4(Warning());
|
||||
ImGui::PushFont(Type().iconSmall());
|
||||
ImGui::TextColored(warn, ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, Layout::spacingXs());
|
||||
ImGui::TextColored(warn, "%s", TR("console_ref_destructive"));
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Plain-language explanation (falls back to the terse summary when not enriched).
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurface()), "%s",
|
||||
cmd.details[0] ? cmd.details : cmd.desc);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// Parameters — each token with a rough type tag; optional ([...]) tokens are dimmed.
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("console_ref_parameters"));
|
||||
std::vector<std::string> toks = splitParamTemplate(cmd.params);
|
||||
if (toks.empty()) {
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", TR("console_ref_no_params"));
|
||||
} else {
|
||||
ImGui::Indent(Layout::spacingMd());
|
||||
for (const std::string& t : toks) {
|
||||
bool optional = (!t.empty() && t.front() == '[');
|
||||
char c0 = t.empty() ? 0 : t[0];
|
||||
if (optional && t.size() >= 2) c0 = t[1];
|
||||
const char* type = (c0 == '"' || c0 == '\'') ? "string"
|
||||
: (c0 == '{' || c0 == '[') ? "json" : "number";
|
||||
ImGui::PushFont(mono);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(optional ? OnSurfaceMedium() : OnSurface()),
|
||||
"%s", t.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
if (optional)
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()),
|
||||
" %s \xC2\xB7 %s", type, TR("console_ref_optional"));
|
||||
else
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), " %s", type);
|
||||
}
|
||||
ImGui::Unindent(Layout::spacingMd());
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// Example (curated commands only).
|
||||
if (cmd.example[0]) {
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("console_ref_example"));
|
||||
ImGui::PushFont(mono);
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(IM_COL32(150, 200, 150, 255)), "%s", cmd.example);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::PopFont();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
}
|
||||
|
||||
// Actions (or the destructive run confirmation).
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (run_confirm_cmd_ == &cmd) {
|
||||
ImVec4 warn = ImGui::ColorConvertU32ToFloat4(Warning());
|
||||
ImGui::PushFont(Type().iconSmall());
|
||||
ImGui::TextColored(warn, ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, Layout::spacingXs());
|
||||
ImGui::TextColored(warn, TR("console_ref_run_confirm"), cmd.name);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
float cbw = 120.0f * dp;
|
||||
if (material::TactileButton(TR("console_ref_cancel"), ImVec2(cbw, 0))) run_confirm_cmd_ = nullptr;
|
||||
ImGui::SameLine();
|
||||
if (material::TactileButton(TR("console_ref_run"), ImVec2(cbw, 0))) {
|
||||
pending_submit_ = cmd.name;
|
||||
command_search_[0] = '\0';
|
||||
run_confirm_cmd_ = nullptr;
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
} else {
|
||||
if (material::TactileButton(TR("console_ref_insert"), ImVec2(200.0f * dp, 0)))
|
||||
insertCommandToInput(cmd);
|
||||
// "Insert & run" only for commands that take no parameters — running a template with
|
||||
// placeholders ("address", amount) would just error.
|
||||
if (cmd.params[0] == '\0') {
|
||||
ImGui::SameLine();
|
||||
if (material::TactileButton(TR("console_ref_insert_run"), ImVec2(140.0f * dp, 0))) {
|
||||
if (cmd.destructive) {
|
||||
run_confirm_cmd_ = &cmd;
|
||||
} else {
|
||||
pending_submit_ = cmd.name;
|
||||
command_search_[0] = '\0';
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConsoleTab::renderCommandsPopup()
|
||||
{
|
||||
using namespace material;
|
||||
|
||||
float popW = std::min(schema::UI().drawElement("tabs.console", "popup-max-width").size, ImGui::GetMainViewport()->Size.x * schema::UI().drawElement("tabs.console", "popup-width-ratio").size);
|
||||
material::OverlayDialogSpec ov;
|
||||
ov.title = TR("console_rpc_reference"); ov.p_open = &show_commands_popup_;
|
||||
ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading
|
||||
ov.cardWidth = popW; ov.cardBottomViewportRatio = 0.94f; // keep authored width
|
||||
// FIXED height (fills most of the viewport) instead of auto-height: the command list's
|
||||
// fill-height child reads GetContentRegionAvail() inside the dialog child, which is only stable
|
||||
// when the child is fixed-height. Auto-height made it self-referential and it slid content
|
||||
// off-screen after a monitor move (same class of bug as the Wallets modal).
|
||||
ov.cardHeight = ImGui::GetMainViewport()->Size.y * 0.84f / Layout::dpiScale();
|
||||
if (!material::BeginOverlayDialog(ov)) {
|
||||
return;
|
||||
}
|
||||
// Esc dismisses — this overlay is a plain Begin/BeginChild, not an ImGui popup, so it has no
|
||||
// built-in Esc handling (matches the other editor overlays).
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) show_commands_popup_ = false;
|
||||
float dp = Layout::dpiScale();
|
||||
|
||||
// Search filter — auto-focus on open so the user can type immediately (like the console input).
|
||||
material::OverlayDialogSpec ov;
|
||||
ov.title = TR("console_rpc_reference");
|
||||
ov.p_open = &show_commands_popup_;
|
||||
ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading
|
||||
ov.cardWidth = 960.0f; // wide enough for two panes
|
||||
ov.cardHeight = ImGui::GetMainViewport()->Size.y * 0.74f / dp; // fixed; both panes fill it
|
||||
ov.idSuffix = "cmdref";
|
||||
if (!material::BeginOverlayDialog(ov)) return;
|
||||
|
||||
// Esc dismisses (a first Esc cancels a pending run-confirm). Not built into the overlay.
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
if (run_confirm_cmd_) run_confirm_cmd_ = nullptr;
|
||||
else show_commands_popup_ = false;
|
||||
}
|
||||
|
||||
// Search box — auto-focus on open so the user can type immediately.
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
|
||||
ImGui::InputTextWithHint("##CmdSearch", TR("console_search_commands"), command_search_, sizeof(command_search_));
|
||||
bool searchChanged = ImGui::InputTextWithHint("##CmdSearch", TR("console_ref_search_hint"),
|
||||
command_search_, sizeof(command_search_));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
|
||||
std::string q(command_search_);
|
||||
std::transform(q.begin(), q.end(), q.begin(), ::tolower);
|
||||
const bool searching = !q.empty();
|
||||
|
||||
const auto& categories = consoleCommandCategories();
|
||||
|
||||
std::string filter(command_search_);
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
|
||||
ImGui::BeginChild("CmdListScroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - Layout::spacingXs()),
|
||||
false);
|
||||
|
||||
ImGui::PushFont(Type().caption());
|
||||
|
||||
// cmd-min-width is authored in logical px; popupInnerW (GetContentRegionAvail) is physical/DPI-
|
||||
// scaled, so scale the breakpoint to match — otherwise the 2-vs-3 column choice trips at the
|
||||
// wrong width on HiDPI.
|
||||
float cmdMinWidth = schema::UI().drawElement("tabs.console", "cmd-min-width").sizeOr(500.0f) * Layout::dpiScale();
|
||||
float popupInnerW = ImGui::GetContentRegionAvail().x;
|
||||
bool showParams = popupInnerW >= cmdMinWidth;
|
||||
int catIdx = 0;
|
||||
|
||||
for (const auto& cat : categories) {
|
||||
// Count matching commands in this category
|
||||
int matchCount = 0;
|
||||
if (filter.empty()) {
|
||||
matchCount = cat.count;
|
||||
} else {
|
||||
for (int i = 0; i < cat.count; i++) {
|
||||
if (consoleCommandMatchesFilter(cat.commands[i].name, cat.commands[i].desc,
|
||||
cat.commands[i].params, filter)) {
|
||||
matchCount++;
|
||||
}
|
||||
// Flat display order of (cat,idx): ranked when searching, category order when browsing. Drives
|
||||
// keyboard nav + auto-selection; the browse view still renders grouped headers below.
|
||||
std::vector<std::pair<int, int>> order;
|
||||
if (searching) {
|
||||
std::vector<std::pair<int, std::pair<int, int>>> scored; // (score, (cat,idx))
|
||||
for (int c = 0; c < (int)categories.size(); c++)
|
||||
for (int i = 0; i < categories[c].count; i++) {
|
||||
int s = consoleCommandRank(categories[c].commands[i], q);
|
||||
if (s >= 0) scored.push_back({s, {c, i}});
|
||||
}
|
||||
std::stable_sort(scored.begin(), scored.end(),
|
||||
[](const auto& a, const auto& b) { return a.first > b.first; });
|
||||
for (auto& s : scored) order.push_back(s.second);
|
||||
} else {
|
||||
for (int c = 0; c < (int)categories.size(); c++)
|
||||
for (int i = 0; i < categories[c].count; i++) order.push_back({c, i});
|
||||
}
|
||||
|
||||
// Keep a valid selection: reset to the top when the query changes or the current selection falls
|
||||
// out of the visible set, so the detail pane is always populated.
|
||||
int selPos = -1;
|
||||
for (int k = 0; k < (int)order.size(); k++)
|
||||
if (order[k].first == cmd_sel_cat_ && order[k].second == cmd_sel_idx_) { selPos = k; break; }
|
||||
if (searchChanged || selPos < 0) {
|
||||
if (!order.empty()) { cmd_sel_cat_ = order[0].first; cmd_sel_idx_ = order[0].second; selPos = 0; }
|
||||
else { cmd_sel_cat_ = cmd_sel_idx_ = -1; }
|
||||
}
|
||||
|
||||
// Keyboard nav: Up/Down move the selection, Enter inserts (disabled while a run-confirm shows).
|
||||
if (!run_confirm_cmd_ && !order.empty() && selPos >= 0) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && selPos + 1 < (int)order.size()) selPos++;
|
||||
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && selPos > 0) selPos--;
|
||||
cmd_sel_cat_ = order[selPos].first;
|
||||
cmd_sel_idx_ = order[selPos].second;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))
|
||||
insertCommandToInput(categories[cmd_sel_cat_].commands[cmd_sel_idx_]);
|
||||
}
|
||||
|
||||
// Two-pane body sized above the footer.
|
||||
float footerH = ImGui::GetFrameHeightWithSpacing() + Layout::spacingXs();
|
||||
float bodyH = std::max(120.0f, ImGui::GetContentRegionAvail().y - footerH);
|
||||
float contentW = ImGui::GetContentRegionAvail().x;
|
||||
float gap = Layout::spacingLg();
|
||||
float masterW = std::min(std::max(contentW * 0.34f, 280.0f * dp), 380.0f * dp);
|
||||
float detailW = contentW - masterW - gap;
|
||||
|
||||
ImFont* mono = Type().mono();
|
||||
ImU32 nameCol = IM_COL32(100, 180, 255, 255);
|
||||
float rowH = std::max(20.0f * dp, mono->LegacySize + 6.0f * dp);
|
||||
|
||||
// One master row: mono command name + a warning dot for consequential commands. Click selects
|
||||
// (drives the detail pane); double-click inserts.
|
||||
auto drawRow = [&](int c, int i) {
|
||||
const ConsoleCommandEntry& cmd = categories[c].commands[i];
|
||||
bool sel = (cmd_sel_cat_ == c && cmd_sel_idx_ == i);
|
||||
ImGui::PushID(c * 1000 + i);
|
||||
ImVec2 rmn = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Selectable("##cmdrow", sel, ImGuiSelectableFlags_SpanAvailWidth, ImVec2(0, rowH)))
|
||||
{ cmd_sel_cat_ = c; cmd_sel_idx_ = i; }
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) insertCommandToInput(cmd);
|
||||
}
|
||||
if (matchCount == 0) { catIdx++; continue; }
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
float tx = rmn.x + Layout::spacingSm();
|
||||
if (cmd.destructive) {
|
||||
float r = 3.0f * dp;
|
||||
dl->AddCircleFilled(ImVec2(tx + r, rmn.y + rowH * 0.5f), r, Warning());
|
||||
tx += r * 2.0f + Layout::spacingXs();
|
||||
}
|
||||
dl->AddText(mono, mono->LegacySize, ImVec2(tx, rmn.y + (rowH - mono->LegacySize) * 0.5f),
|
||||
sel ? OnSurface() : nameCol, cmd.name);
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
// Default-open only the first category (Control); collapse the rest
|
||||
ImGuiTreeNodeFlags headerFlags = (catIdx == 0) ? ImGuiTreeNodeFlags_DefaultOpen : 0;
|
||||
// When filtering, open all matching categories
|
||||
if (!filter.empty()) headerFlags = ImGuiTreeNodeFlags_DefaultOpen;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary()));
|
||||
// Show match count badge when filtering
|
||||
const char* catLabel = consoleCategoryLabel(cat.name);
|
||||
char headerLabel[128];
|
||||
if (!filter.empty()) {
|
||||
snprintf(headerLabel, sizeof(headerLabel), "%s (%d)", catLabel, matchCount);
|
||||
// MASTER pane.
|
||||
ImGui::BeginChild("##cmdMaster", ImVec2(masterW, bodyH), true);
|
||||
{
|
||||
if (order.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_ref_no_match"));
|
||||
} else if (searching) {
|
||||
for (auto& p : order) drawRow(p.first, p.second);
|
||||
} else {
|
||||
snprintf(headerLabel, sizeof(headerLabel), "%s", catLabel);
|
||||
}
|
||||
bool open = ImGui::CollapsingHeader(headerLabel, headerFlags);
|
||||
ImGui::PopStyleColor();
|
||||
catIdx++;
|
||||
|
||||
if (open) {
|
||||
float nameColW = schema::UI().drawElement("tabs.console", "cmd-name-col-width").size * Layout::hScale();
|
||||
float paramsColW = schema::UI().drawElement("tabs.console", "cmd-params-col-width").size * Layout::hScale();
|
||||
int numCols = showParams ? 3 : 2;
|
||||
char tableId[24];
|
||||
snprintf(tableId, sizeof(tableId), "##cmds%d", catIdx); // unique id per category table
|
||||
if (ImGui::BeginTable(tableId, numCols, ImGuiTableFlags_None)) {
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, nameColW);
|
||||
if (showParams)
|
||||
ImGui::TableSetupColumn("Parameters", ImGuiTableColumnFlags_WidthFixed, paramsColW);
|
||||
ImGui::TableSetupColumn("Desc", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
for (int i = 0; i < cat.count; i++) {
|
||||
const auto& cmd = cat.commands[i];
|
||||
if (!consoleCommandMatchesFilter(cmd.name, cmd.desc, cmd.params, filter))
|
||||
continue;
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
// Link-like blue for the command name. Readable on all skins: the RPC-reference
|
||||
// modal card is a dark glass panel regardless of theme, so no light-floor is needed.
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImGui::ColorConvertU32ToFloat4(IM_COL32(100, 180, 255, 255)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.2f, 0.4f, 0.6f, 0.3f));
|
||||
char selId[128];
|
||||
snprintf(selId, sizeof(selId), "%s##cmdRef", cmd.name);
|
||||
if (ImGui::Selectable(selId, false)) {
|
||||
if (cmd.params[0] != '\0') {
|
||||
snprintf(input_buffer_, sizeof(input_buffer_), "%s %s", cmd.name, cmd.params);
|
||||
} else {
|
||||
strncpy(input_buffer_, cmd.name, sizeof(input_buffer_) - 1);
|
||||
input_buffer_[sizeof(input_buffer_) - 1] = '\0';
|
||||
}
|
||||
// This overlay isn't an ImGui popup, so CloseCurrentPopup() is a no-op here —
|
||||
// close it the way the framework honors, revealing the inserted command.
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (cmd.params[0] != '\0')
|
||||
material::Tooltip(TR("console_click_insert_params"), cmd.name, cmd.params);
|
||||
else
|
||||
material::Tooltip(TR("console_click_insert"), cmd.name);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
if (showParams) {
|
||||
ImGui::TableNextColumn();
|
||||
drawConsoleCommandParams(cmd.params);
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextColored(
|
||||
ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()),
|
||||
"%s", cmd.desc);
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
for (int c = 0; c < (int)categories.size(); c++) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary()));
|
||||
bool open = ImGui::CollapsingHeader(consoleCategoryLabel(categories[c].name),
|
||||
ImGuiTreeNodeFlags_DefaultOpen);
|
||||
ImGui::PopStyleColor();
|
||||
if (open)
|
||||
for (int i = 0; i < categories[c].count; i++) drawRow(c, i);
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::EndChild();
|
||||
|
||||
|
||||
ImGui::SameLine(0, gap);
|
||||
|
||||
// DETAIL pane.
|
||||
ImGui::BeginChild("##cmdDetail", ImVec2(detailW, bodyH), true);
|
||||
if (cmd_sel_cat_ >= 0 && cmd_sel_cat_ < (int)categories.size() &&
|
||||
cmd_sel_idx_ >= 0 && cmd_sel_idx_ < categories[cmd_sel_cat_].count) {
|
||||
renderCommandDetail(categories[cmd_sel_cat_].commands[cmd_sel_idx_],
|
||||
consoleCategoryLabel(categories[cmd_sel_cat_].name));
|
||||
} else {
|
||||
ImVec2 av = ImGui::GetContentRegionAvail();
|
||||
ImGui::SetCursorPosY(av.y * 0.4f);
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_ref_select_hint"));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
// Footer.
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Close button (the filter is also reset when the modal next opens).
|
||||
if (material::TactileButton(TR("console_close"), ImVec2(-1, 0))) {
|
||||
command_search_[0] = '\0';
|
||||
run_confirm_cmd_ = nullptr;
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
|
||||
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ui {
|
||||
|
||||
class ConsoleCommandExecutor;
|
||||
struct ConsoleLogFilterCaps;
|
||||
struct ConsoleCommandEntry;
|
||||
|
||||
/**
|
||||
* @brief Console tab — a rich terminal shared by the full-node and lite variants.
|
||||
@@ -115,6 +116,8 @@ private:
|
||||
void addFormattedResult(const std::string& result, bool is_error);
|
||||
void renderStatusHeader(ConsoleCommandExecutor& exec);
|
||||
void renderCommandsPopup();
|
||||
void renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel); // right pane
|
||||
void insertCommandToInput(const ConsoleCommandEntry& cmd); // fill input + close the modal
|
||||
|
||||
// renderToolbar() draws the top bar; these are its sub-steps:
|
||||
void renderToolbar(ConsoleCommandExecutor& exec);
|
||||
@@ -183,9 +186,13 @@ private:
|
||||
// consumed by the renderer + hit-testing.
|
||||
mutable ConsoleLayout layout_;
|
||||
|
||||
// Commands popup
|
||||
// Commands popup (RPC command explorer)
|
||||
bool show_commands_popup_ = false;
|
||||
char command_search_[128] = {0}; // RPC-reference search filter (cleared when the modal opens)
|
||||
int cmd_sel_cat_ = -1; // detail-pane selection: category index into consoleCommandCategories()
|
||||
int cmd_sel_idx_ = -1; // detail-pane selection: command index within that category
|
||||
std::string pending_submit_; // command to run next frame (deferred so the modal needs no executor)
|
||||
const ConsoleCommandEntry* run_confirm_cmd_ = nullptr; // destructive "Insert & run" awaiting confirmation
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -1301,6 +1301,19 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["console_cat_wallet"] = "Wallet";
|
||||
strings_["console_cat_raw_transactions"] = "Raw Transactions";
|
||||
strings_["console_cat_utility"] = "Utility";
|
||||
strings_["console_ref_search_hint"] = "Search by name or task\xE2\x80\xA6";
|
||||
strings_["console_ref_parameters"] = "Parameters";
|
||||
strings_["console_ref_no_params"] = "Takes no parameters.";
|
||||
strings_["console_ref_optional"] = "optional";
|
||||
strings_["console_ref_example"] = "Example";
|
||||
strings_["console_ref_destructive"] = "Consequential";
|
||||
strings_["console_ref_run_confirm"] = "Run %s now? This is a consequential command.";
|
||||
strings_["console_ref_cancel"] = "Cancel";
|
||||
strings_["console_ref_run"] = "Run";
|
||||
strings_["console_ref_insert"] = "Insert into console";
|
||||
strings_["console_ref_insert_run"] = "Insert & run";
|
||||
strings_["console_ref_select_hint"] = "Select a command to see what it does.";
|
||||
strings_["console_ref_no_match"] = "No commands match.";
|
||||
|
||||
// --- Export All Keys Dialog ---
|
||||
strings_["export_keys_btn"] = "Export Keys";
|
||||
|
||||
Reference in New Issue
Block a user