feat(mining): persistent start/stop error caption + timeout-aware pool status

- A mining start/stop/pool failure now sets a persistent App::mining_last_error_
  (cleared on the next attempt), rendered as a red caption with "Press Mine to
  try again" below the Mine button (plus a Dismiss, and a Windows-Defender
  "How to fix" on an AV block), so a crashed/blocked/failed miner is no longer
  just a button that "did nothing" once its toast faded. Two more hardcoded
  English strings (pool-payout-invalid, AV-blocked) moved to i18n.
- Pool connection status is now a persistent "Status" row in the pool stats
  card (PoolMiningState.running_since / last_error): it flips from "Connecting…"
  to "Can't reach pool — check the URL" after a 30s timeout instead of sitting
  on red forever, and no longer vanishes once logs arrive. The old transient
  banner in mining_tab.cpp was removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 12:09:49 -05:00
co-authored by Claude Opus 4.8
parent 15c1181aef
commit 8fe20f6385
8 changed files with 104 additions and 29 deletions
+21 -2
View File
@@ -1233,14 +1233,18 @@ void App::update()
if (xmrig_manager_ && state_.pool_mining.xmrig_running
&& xmrig_manager_->getState() == daemon::XmrigManager::State::Error) {
state_.pool_mining.xmrig_running = false;
state_.pool_mining.running_since = 0;
state_.pool_mining.last_error.clear();
state_.pool_mining.hashrate_10s = 0.0;
state_.pool_mining.hashrate_60s = 0.0;
state_.pool_mining.hashrate_15m = 0.0;
pool_starting_.store(false, std::memory_order_relaxed);
pool_threads_target_.store(0, std::memory_order_relaxed); // that miner is gone
const std::string err = xmrig_manager_->getLastError();
ui::Notifications::instance().error(err.empty() ? TR("appx_miner_stopped_unexpectedly")
: (std::string(TR("appx_miner_stopped_prefix")) + err));
const std::string mmsg = err.empty() ? std::string(TR("appx_miner_stopped_unexpectedly"))
: (std::string(TR("appx_miner_stopped_prefix")) + err);
setMiningError(mmsg); // persist: a miner that crashed/was killed otherwise just "did nothing"
ui::Notifications::instance().error(mmsg);
}
// Poll xmrig stats every ~2 seconds (use a simple toggle)
@@ -1273,6 +1277,19 @@ void App::update()
ps.threads_active = xs.threads_active;
ps.pool_hashrate = xs.pool_hashrate;
ps.log_lines = xmrig_manager_->getRecentLines(30);
if (ps.running_since == 0) ps.running_since = std::time(nullptr);
// While not yet connected, surface the newest error-looking log line so "Connecting" can flip
// to a real "can't reach pool" after a timeout instead of sitting on red forever.
ps.last_error.clear();
if (!ps.connected) {
for (auto it = ps.log_lines.rbegin(); it != ps.log_lines.rend(); ++it) {
std::string l = *it;
std::transform(l.begin(), l.end(), l.begin(), [](unsigned char c){ return (char)std::tolower(c); });
if (l.find("error") != std::string::npos || l.find("refused") != std::string::npos ||
l.find("timed out") != std::string::npos || l.find("unreachable") != std::string::npos ||
l.find("dns") != std::string::npos) { ps.last_error = *it; break; }
}
}
// Record hashrate sample for the chart
ps.hashrate_history.push_back(ps.hashrate_10s);
@@ -1283,6 +1300,8 @@ void App::update()
}
} else if (xmrig_manager_ && !xmrig_manager_->isRunning()) {
state_.pool_mining.xmrig_running = false;
state_.pool_mining.running_since = 0;
state_.pool_mining.last_error.clear();
pool_starting_.store(false, std::memory_order_relaxed); // stopped / never came up
}