Add HiFi API instance health check to Settings

New "Check All Instances" button in Settings > Downloads > HiFi
shows each configured instance with color-coded status: green for
fully working (can download), orange for search-only (downloads
fail), red for offline/SSL error/timeout. Helps users understand
why HiFi downloads aren't working when community instances go down.
This commit is contained in:
Broque Thomas 2026-04-08 09:58:55 -07:00
parent 6180135f94
commit e1f7b8f5cc
3 changed files with 85 additions and 0 deletions

View file

@ -31226,6 +31226,47 @@ def hifi_status():
return jsonify({"available": False, "error": str(e)})
@app.route('/api/hifi/instances', methods=['GET'])
def hifi_instances():
"""Check availability of all HiFi API instances."""
import requests as req
try:
hifi = soulseek_client.hifi
instances = list(hifi._instances)
results = []
for url in instances:
entry = {'url': url, 'status': 'unknown', 'version': None, 'can_search': False, 'can_download': False}
try:
# Check root for version
r = req.get(f'{url}/', timeout=5, headers={'Accept': 'application/json'})
if r.ok:
data = r.json()
entry['version'] = data.get('version')
entry['status'] = 'online'
# Check search
sr = req.get(f'{url}/search', params={'s': 'test', 'limit': 1}, timeout=5)
entry['can_search'] = sr.ok
# Check track (download capability)
tr = req.get(f'{url}/track', params={'id': '1550546', 'quality': 'LOSSLESS'}, timeout=5)
entry['can_download'] = tr.ok
if not tr.ok:
entry['download_error'] = f'HTTP {tr.status_code}'
else:
entry['status'] = f'error (HTTP {r.status_code})'
except req.exceptions.SSLError:
entry['status'] = 'ssl_error'
except req.exceptions.ConnectTimeout:
entry['status'] = 'timeout'
except req.exceptions.ConnectionError:
entry['status'] = 'offline'
except Exception as e:
entry['status'] = f'error ({type(e).__name__})'
results.append(entry)
return jsonify({'instances': results, 'active': hifi._get_instance()})
except Exception as e:
return jsonify({'error': str(e)}), 500
# ===================================================================
# DEEZER DOWNLOAD ENDPOINTS
# ===================================================================

View file

@ -4825,6 +4825,11 @@
Free lossless downloads via community-run hifi-api instances. No authentication or subscription needed.
</div>
</div>
<div class="form-group">
<label>API Instances:</label>
<button class="test-button" onclick="checkHiFiInstances()" id="hifi-instances-check-btn" style="margin-bottom: 8px;">Check All Instances</button>
<div id="hifi-instances-panel" style="display: none;"></div>
</div>
</div>
<!-- Deezer Download Settings (shown only when deezer_dl mode is selected) -->

View file

@ -7738,6 +7738,45 @@ async function testHiFiConnection() {
}
}
async function checkHiFiInstances() {
const panel = document.getElementById('hifi-instances-panel');
const btn = document.getElementById('hifi-instances-check-btn');
if (!panel) return;
panel.style.display = 'block';
panel.innerHTML = '<div style="color: rgba(255,255,255,0.4); font-size: 0.85em; padding: 8px 0;">Checking instances...</div>';
if (btn) { btn.disabled = true; btn.textContent = 'Checking...'; }
try {
const resp = await fetch('/api/hifi/instances');
const data = await resp.json();
if (!data.instances || data.instances.length === 0) {
panel.innerHTML = '<div style="color: #ff9800; font-size: 0.85em;">No instances configured.</div>';
return;
}
const _statusIcon = (inst) => {
if (inst.can_download) return '<span style="color:#4caf50">● Download</span>';
if (inst.can_search) return '<span style="color:#ff9800">● Search only</span>';
if (inst.status === 'online') return '<span style="color:#ff9800">● Online (limited)</span>';
if (inst.status === 'ssl_error') return '<span style="color:#f44336">● SSL error</span>';
if (inst.status === 'timeout') return '<span style="color:#f44336">● Timeout</span>';
if (inst.status === 'offline') return '<span style="color:#f44336">● Offline</span>';
return `<span style="color:#f44336">● ${escapeHtml(inst.status)}</span>`;
};
panel.innerHTML = data.instances.map(inst => {
const isActive = inst.url === data.active;
const ver = inst.version ? ` v${inst.version}` : '';
const activeTag = isActive ? ' <span style="color:rgb(var(--accent-rgb));font-weight:600;font-size:0.75em;">(ACTIVE)</span>' : '';
return `<div style="display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid rgba(255,255,255,0.04);font-size:0.82em;">
<span style="color:rgba(255,255,255,0.6);font-family:monospace;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(inst.url)}${ver}${activeTag}</span>
<span style="flex-shrink:0;margin-left:12px;">${_statusIcon(inst)}</span>
</div>`;
}).join('');
} catch (e) {
panel.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error checking instances: ${escapeHtml(e.message)}</div>`;
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Check All Instances'; }
}
}
async function testDeezerDownloadConnection() {
const statusEl = document.getElementById('deezer-download-status');
if (!statusEl) return;