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
parent 207f9074db
commit 940dd21464
5 changed files with 83 additions and 1 deletions

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()) {