feat(diagnostics): add "Copy diagnostics" + "Open log folder" actions (Foundation QoL)

Settings (logging section) gains two support-friendly actions, now that the logging
foundation actually produces logs (W7-2):

- Open log folder: opens the config dir (Platform::openFolder) so users can find
  dragonx-debug.log / dragonx-crash.log.

- Copy diagnostics: copies a plaintext support snapshot to the clipboard via the new
  App::buildDiagnosticsReport() — version, build variant, platform, connection status,
  active wallet path + existence + size, encryption/lock state, sync heights, and (full-
  node) daemon status/running/crash-count/lastError, plus the log paths. No secrets.

Build-clean; ctest 1/1. Remaining QoL: persistent alert history, a daemon/RPC error
banner, and the W6-2 refresh-staleness badge. See docs/wallet-hardening.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 15:37:52 -05:00
co-authored by Claude Opus 4.8
parent 207f9074db
commit 940dd21464
5 changed files with 83 additions and 1 deletions
+57
View File
@@ -105,6 +105,7 @@
#include <unordered_set>
#include <fstream>
#include <filesystem>
#include <sstream>
#include <thread>
#include <chrono>
#include <exception>
@@ -5658,6 +5659,62 @@ void App::maybeFinishTransactionSendProgress()
if (addresses_dirty_ || network_refresh_.jobInProgress(Job::Addresses)) return;
send_progress_active_ = false;
}
std::string App::buildDiagnosticsReport()
{
std::ostringstream os;
os << "=== ObsidianDragon diagnostics ===\n";
os << "version: " << DRAGONX_VERSION << "\n";
#if DRAGONX_LITE_BUILD
os << "variant: Lite\n";
#else
os << "variant: Full-node\n";
#endif
#if defined(_WIN32)
os << "platform: windows\n";
#elif defined(__APPLE__)
os << "platform: macos\n";
#else
os << "platform: linux\n";
#endif
os << "connected: " << (state_.connected ? "yes" : "no") << "\n";
os << "status: " << connection_status_ << "\n";
const std::string activeWallet = settings_ ? settings_->getActiveWalletFile() : std::string("(none)");
os << "active wallet: " << activeWallet << "\n";
{
std::error_code ec;
const std::string wp = util::Platform::getDragonXDataDir() + "/" + activeWallet;
const bool present = std::filesystem::exists(wp, ec);
os << " path: " << wp << (present ? " [present" : " [MISSING");
if (present) { auto sz = std::filesystem::file_size(wp, ec); if (!ec) os << ", " << sz << " bytes"; }
os << "]\n";
}
os << "encryption: "
<< (state_.encryption_state_known
? (state_.encrypted ? (state_.locked ? "encrypted, locked" : "encrypted, unlocked") : "unencrypted")
: "unknown")
<< "\n";
os << "sync: block " << state_.sync.blocks << " / " << state_.sync.headers
<< (state_.sync.syncing ? " (syncing)" : "")
<< (state_.warming_up ? " (warming up)" : "") << "\n";
#if !DRAGONX_LITE_BUILD
os << "daemon status: " << daemon_status_ << "\n";
if (daemon_controller_) {
os << "daemon running: " << (daemon_controller_->isRunning() ? "yes" : "no")
<< ", crashes: " << daemon_controller_->crashCount() << "\n";
const std::string derr = daemon_controller_->lastError();
if (!derr.empty()) os << "daemon lastError: " << derr << "\n";
}
#endif
const std::string cfg = util::Platform::getObsidianDragonDir();
os << "log folder: " << cfg << "\n";
os << " " << cfg << "/dragonx-debug.log\n";
os << " " << cfg << "/dragonx-crash.log\n";
return os.str();
}
void App::restartDaemon()
{
if (!supportsFullNodeLifecycleActions()) {
+4
View File
@@ -147,6 +147,10 @@ public:
bool isLiteBuild() const { return wallet::isLiteBuild(walletCapabilities()); }
bool supportsEmbeddedDaemon() const { return wallet::supportsEmbeddedDaemon(walletCapabilities()); }
bool supportsFullNodeLifecycleActions() const { return wallet::supportsFullNodeLifecycleActions(walletCapabilities()); }
// W7 QoL: a plaintext support-diagnostics snapshot (version, variant, daemon/RPC/wallet/log state)
// for the "Copy diagnostics" action. Contains no secrets.
std::string buildDiagnosticsReport();
bool supportsSoloMining() const { return wallet::supportsSoloMining(walletCapabilities()); }
bool supportsPoolMining() const { return wallet::supportsPoolMining(walletCapabilities()); }
bool supportsLiteBackend() const { return wallet::supportsLiteBackend(walletCapabilities()); }
+15
View File
@@ -1351,6 +1351,21 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_verbose"));
}
// W7 QoL: quick diagnostics actions — open the log folder, and copy a plaintext support bundle
// (version, variant, daemon/RPC/wallet/log state) to the clipboard.
{
const float diagBtnW = (contentW - Layout::spacingMd()) * 0.5f;
if (TactileButton(TR("settings_open_log_folder"), ImVec2(diagBtnW, 0), S.resolveFont("button")))
dragonx::util::Platform::openFolder(dragonx::util::Platform::getObsidianDragonDir());
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_log_folder"));
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TR("settings_copy_diagnostics"), ImVec2(diagBtnW, 0), S.resolveFont("button"))) {
ImGui::SetClipboardText(app->buildDiagnosticsReport().c_str());
ui::Notifications::instance().info(TR("settings_diagnostics_copied"), 4.0f);
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_copy_diagnostics"));
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Tools & Actions... ---
+5
View File
@@ -1322,6 +1322,11 @@ void I18n::loadBuiltinEnglish()
strings_["loading_stall_body"] = "The daemon has been initializing for %.0fs. This can be normal after an update or on first launch (loading the block index or rescanning) — it will connect automatically once ready.";
strings_["loading_stall_hint"] = "Still stuck? Open Settings and use Restart Daemon, or check the Console for details.";
strings_["sb_plaintext_remote_blocked"] = "Refusing to send RPC credentials over plaintext to a remote host. Add rpcallowplaintext=1 to DRAGONX.conf to allow it, or enable TLS with rpctls=1.";
strings_["settings_open_log_folder"] = "Open log folder";
strings_["settings_copy_diagnostics"] = "Copy diagnostics";
strings_["settings_diagnostics_copied"] = "Diagnostics copied to clipboard";
strings_["tt_open_log_folder"] = "Open the folder containing the debug and crash logs";
strings_["tt_copy_diagnostics"] = "Copy a support snapshot (version, daemon/wallet/log state — no secrets) to the clipboard";
strings_["sb_dragonxd_running"] = "dragonxd running";
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";