Compare commits
3 Commits
328b2a3ee8
...
2be8fe65d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2be8fe65d8 | ||
|
|
a0c1628461 | ||
|
|
06615a3541 |
@@ -1499,6 +1499,54 @@ BITCOIN_DATADIR = "/run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node"
|
||||
_btc_sync_cache: tuple[float, dict | None] = (0.0, None)
|
||||
_BTC_SYNC_CACHE_TTL = 5 # seconds
|
||||
|
||||
_btc_version_cache: tuple[float, dict | None] = (0.0, None)
|
||||
_BTC_VERSION_CACHE_TTL = 60 # seconds — version doesn't change at runtime
|
||||
|
||||
|
||||
def _parse_bitcoin_subversion(subversion: str) -> str:
|
||||
"""Parse a subversion string like '/Bitcoin Knots:27.1.0/' into 'v27.1.0'.
|
||||
|
||||
Examples:
|
||||
'/Bitcoin Knots:27.1.0/' → 'v27.1.0'
|
||||
'/Satoshi:27.0.0/' → 'v27.0.0'
|
||||
'/Bitcoin Knots:27.1.0(bip110)/' → 'v27.1.0'
|
||||
Falls back to the raw subversion string if parsing fails.
|
||||
"""
|
||||
m = re.search(r":(\d+\.\d+(?:\.\d+)*)", subversion)
|
||||
if m:
|
||||
return "v" + m.group(1)
|
||||
return subversion
|
||||
|
||||
|
||||
def _get_bitcoin_version_info() -> dict | None:
|
||||
"""Call bitcoin-cli getnetworkinfo and return parsed JSON, or None on error.
|
||||
|
||||
Results are cached for _BTC_VERSION_CACHE_TTL seconds since the version
|
||||
does not change while the service is running.
|
||||
"""
|
||||
global _btc_version_cache
|
||||
now = time.monotonic()
|
||||
cached_at, cached_val = _btc_version_cache
|
||||
if now - cached_at < _BTC_VERSION_CACHE_TTL:
|
||||
return cached_val
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["bitcoin-cli", f"-datadir={BITCOIN_DATADIR}", "getnetworkinfo"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
_btc_version_cache = (now, None)
|
||||
return None
|
||||
info = json.loads(result.stdout)
|
||||
_btc_version_cache = (now, info)
|
||||
return info
|
||||
except Exception:
|
||||
_btc_version_cache = (now, None)
|
||||
return None
|
||||
|
||||
|
||||
def _get_bitcoin_sync_info() -> dict | None:
|
||||
"""Call bitcoin-cli getblockchaininfo and return parsed JSON, or None on error.
|
||||
@@ -1548,6 +1596,23 @@ async def api_bitcoin_sync():
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/bitcoin/version")
|
||||
async def api_bitcoin_version():
|
||||
"""Return the version string of the active bitcoind implementation."""
|
||||
loop = asyncio.get_event_loop()
|
||||
info = await loop.run_in_executor(None, _get_bitcoin_version_info)
|
||||
if info is None:
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={"error": "bitcoin-cli unavailable or bitcoind not running"},
|
||||
)
|
||||
subversion = info.get("subversion", "")
|
||||
return {
|
||||
"version": _parse_bitcoin_subversion(subversion),
|
||||
"subversion": subversion,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/services")
|
||||
async def api_services():
|
||||
cfg = load_config()
|
||||
@@ -1668,6 +1733,11 @@ async def api_services():
|
||||
service_data["sync_progress"] = sync_progress
|
||||
service_data["sync_blocks"] = sync_blocks
|
||||
service_data["sync_headers"] = sync_headers
|
||||
if unit == "bitcoind.service":
|
||||
ver_info = await loop.run_in_executor(None, _get_bitcoin_version_info)
|
||||
if ver_info is not None:
|
||||
subversion = ver_info.get("subversion", "")
|
||||
service_data["bitcoin_version"] = _parse_bitcoin_subversion(subversion)
|
||||
return service_data
|
||||
|
||||
results = await asyncio.gather(*[get_status(s) for s in services])
|
||||
@@ -1940,6 +2010,12 @@ async def api_service_detail(unit: str, icon: str | None = None):
|
||||
service_detail["sync_progress"] = sync_progress
|
||||
service_detail["sync_blocks"] = sync_blocks
|
||||
service_detail["sync_headers"] = sync_headers
|
||||
if unit == "bitcoind.service":
|
||||
loop = asyncio.get_event_loop()
|
||||
ver_info = await loop.run_in_executor(None, _get_bitcoin_version_info)
|
||||
if ver_info is not None:
|
||||
subversion = ver_info.get("subversion", "")
|
||||
service_detail["bitcoin_version"] = _parse_bitcoin_subversion(subversion)
|
||||
return service_detail
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,13 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tile-version {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
||||
@@ -131,10 +131,12 @@ function buildTile(svc) {
|
||||
var pct = Math.round((svc.sync_progress || 0) * 100);
|
||||
var id = tileId(svc);
|
||||
var eta = _calcBtcEta(id, svc.sync_progress || 0);
|
||||
var versionLabel = svc.bitcoin_version ? '<div class="tile-version">' + escHtml(svc.bitcoin_version) + '</div>' : '';
|
||||
tile.innerHTML =
|
||||
'<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'">' +
|
||||
'<div class="tile-icon-fallback" style="display:none">?</div>' +
|
||||
'<div class="tile-name">' + escHtml(svc.name) + '</div>' +
|
||||
versionLabel +
|
||||
'<div class="tile-sync-container">' +
|
||||
'<div class="tile-sync-label">\u23F3 Syncing Timechain</div>' +
|
||||
'<div class="tile-sync-bar-row">' +
|
||||
@@ -150,7 +152,8 @@ function buildTile(svc) {
|
||||
return tile;
|
||||
}
|
||||
|
||||
tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div><div class="tile-status"><span class="status-dot ' + sc + '"></span><span class="status-text">' + st + '</span></div>';
|
||||
var versionLabel = svc.bitcoin_version ? '<div class="tile-version">' + escHtml(svc.bitcoin_version) + '</div>' : '';
|
||||
tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div>' + versionLabel + '<div class="tile-status"><span class="status-dot ' + sc + '"></span><span class="status-text">' + st + '</span></div>';
|
||||
|
||||
tile.style.cursor = "pointer";
|
||||
tile.addEventListener("click", function() {
|
||||
@@ -204,6 +207,21 @@ function updateTiles(services) {
|
||||
if (fill) fill.style.width = pct + "%";
|
||||
if (pctEl) pctEl.textContent = pct + "%";
|
||||
if (etaEl) etaEl.textContent = etaText;
|
||||
// Update or insert version label
|
||||
if (svc.bitcoin_version) {
|
||||
var syncVerEl = tile.querySelector(".tile-version");
|
||||
if (syncVerEl) {
|
||||
syncVerEl.textContent = svc.bitcoin_version;
|
||||
} else {
|
||||
var syncNameEl = tile.querySelector(".tile-name");
|
||||
if (syncNameEl) {
|
||||
var newSyncVerEl = document.createElement("div");
|
||||
newSyncVerEl.className = "tile-version";
|
||||
newSyncVerEl.textContent = svc.bitcoin_version;
|
||||
syncNameEl.insertAdjacentElement("afterend", newSyncVerEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// IBD finished or not syncing — if tile had sync layout rebuild it normally
|
||||
if (tile.querySelector(".tile-sync-container")) {
|
||||
@@ -218,6 +236,21 @@ function updateTiles(services) {
|
||||
var text = tile.querySelector(".status-text");
|
||||
if (dot) dot.className = "status-dot " + sc;
|
||||
if (text) text.textContent = st;
|
||||
// Update or insert version label for bitcoind tiles
|
||||
if (svc.bitcoin_version) {
|
||||
var verEl = tile.querySelector(".tile-version");
|
||||
if (verEl) {
|
||||
verEl.textContent = svc.bitcoin_version;
|
||||
} else {
|
||||
var nameEl = tile.querySelector(".tile-name");
|
||||
if (nameEl) {
|
||||
var newVerEl = document.createElement("div");
|
||||
newVerEl.className = "tile-version";
|
||||
newVerEl.textContent = svc.bitcoin_version;
|
||||
nameEl.insertAdjacentElement("afterend", newVerEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user