Expand debug info with library stats, service status, config, and configurable log output
This commit is contained in:
parent
fc05412a13
commit
f9d80606e3
3 changed files with 268 additions and 32 deletions
119
web_server.py
119
web_server.py
|
|
@ -3839,12 +3839,26 @@ def get_system_stats():
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_check(fn, default=False):
|
||||||
|
"""Safely evaluate a check function, returning default on any error."""
|
||||||
|
try:
|
||||||
|
return fn()
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/debug-info')
|
@app.route('/api/debug-info')
|
||||||
def get_debug_info():
|
def get_debug_info():
|
||||||
"""Collect system diagnostics for troubleshooting support requests."""
|
"""Collect system diagnostics for troubleshooting support requests."""
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import psutil
|
import psutil
|
||||||
|
import time
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
log_lines = request.args.get('lines', 20, type=int)
|
||||||
|
log_lines = max(10, min(log_lines, 500))
|
||||||
|
log_source = request.args.get('log', 'app')
|
||||||
|
|
||||||
info = {}
|
info = {}
|
||||||
|
|
||||||
|
|
@ -3854,6 +3868,11 @@ def get_debug_info():
|
||||||
info['python'] = sys.version.split()[0]
|
info['python'] = sys.version.split()[0]
|
||||||
info['docker'] = os.path.exists('/.dockerenv')
|
info['docker'] = os.path.exists('/.dockerenv')
|
||||||
|
|
||||||
|
# Uptime
|
||||||
|
start_time = getattr(app, 'start_time', time.time())
|
||||||
|
uptime_seconds = time.time() - start_time
|
||||||
|
info['uptime'] = str(timedelta(seconds=int(uptime_seconds)))
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
download_path = config_manager.get('soulseek.download_path', './downloads')
|
download_path = config_manager.get('soulseek.download_path', './downloads')
|
||||||
transfer_folder = config_manager.get('soulseek.transfer_path', './Transfer')
|
transfer_folder = config_manager.get('soulseek.transfer_path', './Transfer')
|
||||||
|
|
@ -3881,6 +3900,8 @@ def get_debug_info():
|
||||||
'media_server_connected': media_server_cache.get('connected', False),
|
'media_server_connected': media_server_cache.get('connected', False),
|
||||||
'soulseek_connected': soulseek_cache.get('connected', False),
|
'soulseek_connected': soulseek_cache.get('connected', False),
|
||||||
'download_source': config_manager.get('download_source.mode', 'soulseek'),
|
'download_source': config_manager.get('download_source.mode', 'soulseek'),
|
||||||
|
'tidal_connected': _safe_check(lambda: bool(tidal_client and tidal_client.is_authenticated())),
|
||||||
|
'qobuz_connected': _safe_check(lambda: bool(qobuz_enrichment_worker and qobuz_enrichment_worker.client and qobuz_enrichment_worker.client.is_authenticated())),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enrichment workers
|
# Enrichment workers
|
||||||
|
|
@ -3891,6 +3912,68 @@ def get_debug_info():
|
||||||
workers[name] = 'paused' if config_manager.get(paused_key, False) else 'active'
|
workers[name] = 'paused' if config_manager.get(paused_key, False) else 'active'
|
||||||
info['enrichment_workers'] = workers
|
info['enrichment_workers'] = workers
|
||||||
|
|
||||||
|
# Library stats
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
lib_stats = db.get_statistics()
|
||||||
|
info['library'] = {
|
||||||
|
'artists': lib_stats.get('artists', 0),
|
||||||
|
'albums': lib_stats.get('albums', 0),
|
||||||
|
'tracks': lib_stats.get('tracks', 0),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
info['library'] = {'artists': 0, 'albums': 0, 'tracks': 0}
|
||||||
|
|
||||||
|
# Watchlist count
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
info['watchlist_count'] = db.get_watchlist_count()
|
||||||
|
except Exception:
|
||||||
|
info['watchlist_count'] = 0
|
||||||
|
|
||||||
|
# Automation count
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
automations = db.get_automations()
|
||||||
|
info['automations'] = {
|
||||||
|
'total': len(automations),
|
||||||
|
'enabled': len([a for a in automations if a.get('enabled', False)]),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
info['automations'] = {'total': 0, 'enabled': 0}
|
||||||
|
|
||||||
|
# Active downloads & syncs (use list() snapshots to avoid RuntimeError from concurrent mutation)
|
||||||
|
try:
|
||||||
|
active_downloads = len([bid for bid, bd in list(download_batches.items()) if bd.get('phase') == 'downloading'])
|
||||||
|
except Exception:
|
||||||
|
active_downloads = 0
|
||||||
|
active_syncs = 0
|
||||||
|
try:
|
||||||
|
for pid, ss in list(sync_states.items()):
|
||||||
|
if ss.get('status') == 'syncing':
|
||||||
|
active_syncs += 1
|
||||||
|
for uh, st in list(youtube_playlist_states.items()):
|
||||||
|
if st.get('phase') == 'syncing':
|
||||||
|
active_syncs += 1
|
||||||
|
for pid, st in list(tidal_discovery_states.items()):
|
||||||
|
if st.get('phase') == 'syncing':
|
||||||
|
active_syncs += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
info['active_downloads'] = active_downloads
|
||||||
|
info['active_syncs'] = active_syncs
|
||||||
|
|
||||||
|
# Config settings relevant to troubleshooting
|
||||||
|
info['config'] = {
|
||||||
|
'source_mode': config_manager.get('download_source.mode', 'soulseek'),
|
||||||
|
'quality_profile': config_manager.get('download_source.quality_profile', 'default'),
|
||||||
|
'organization_template': config_manager.get('organization.folder_template', ''),
|
||||||
|
'post_processing_enabled': config_manager.get('post_processing.enabled', True),
|
||||||
|
'acoustid_enabled': bool(config_manager.get('acoustid.api_key', '')),
|
||||||
|
'auto_scan_enabled': config_manager.get('watchlist.auto_scan', False),
|
||||||
|
'm3u_export_enabled': config_manager.get('m3u.enabled', False),
|
||||||
|
}
|
||||||
|
|
||||||
# Database size
|
# Database size
|
||||||
db_path = os.path.join('database', 'music_library.db')
|
db_path = os.path.join('database', 'music_library.db')
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
|
|
@ -3899,22 +3982,50 @@ def get_debug_info():
|
||||||
else:
|
else:
|
||||||
info['database_size'] = 'not found'
|
info['database_size'] = 'not found'
|
||||||
|
|
||||||
# Memory
|
# Memory & CPU
|
||||||
process = psutil.Process(os.getpid())
|
process = psutil.Process(os.getpid())
|
||||||
mem = process.memory_info()
|
mem = process.memory_info()
|
||||||
info['memory_usage'] = f"{mem.rss / (1024 * 1024):.0f} MB"
|
info['memory_usage'] = f"{mem.rss / (1024 * 1024):.0f} MB"
|
||||||
|
info['system_memory'] = f"{psutil.virtual_memory().percent}%"
|
||||||
|
try:
|
||||||
|
info['cpu_percent'] = f"{process.cpu_percent(interval=0.1):.1f}%"
|
||||||
|
except Exception:
|
||||||
|
info['cpu_percent'] = 'unknown'
|
||||||
|
info['thread_count'] = process.num_threads()
|
||||||
|
|
||||||
# Recent log lines
|
# Log lines
|
||||||
log_path = os.path.join('logs', 'app.log')
|
log_map = {
|
||||||
|
'app': os.path.join('logs', 'app.log'),
|
||||||
|
'acoustid': os.path.join('logs', 'acoustid.log'),
|
||||||
|
'post_processing': os.path.join('logs', 'post_processing.log'),
|
||||||
|
'source_reuse': os.path.join('logs', 'source_reuse.log'),
|
||||||
|
}
|
||||||
|
log_path = log_map.get(log_source, log_map['app'])
|
||||||
|
info['log_source'] = log_source
|
||||||
|
info['log_lines_requested'] = log_lines
|
||||||
info['recent_logs'] = []
|
info['recent_logs'] = []
|
||||||
if os.path.exists(log_path):
|
if os.path.exists(log_path):
|
||||||
try:
|
try:
|
||||||
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
|
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
info['recent_logs'] = [line.rstrip() for line in lines[-20:]]
|
info['recent_logs'] = [line.rstrip() for line in lines[-log_lines:]]
|
||||||
except Exception:
|
except Exception:
|
||||||
info['recent_logs'] = ['(could not read log file)']
|
info['recent_logs'] = ['(could not read log file)']
|
||||||
|
|
||||||
|
# Available log files
|
||||||
|
info['available_logs'] = []
|
||||||
|
logs_dir = 'logs'
|
||||||
|
if os.path.isdir(logs_dir):
|
||||||
|
for fname in sorted(os.listdir(logs_dir)):
|
||||||
|
if fname.endswith('.log'):
|
||||||
|
fpath = os.path.join(logs_dir, fname)
|
||||||
|
size_kb = os.path.getsize(fpath) / 1024
|
||||||
|
info['available_logs'].append({
|
||||||
|
'name': fname.replace('.log', ''),
|
||||||
|
'file': fname,
|
||||||
|
'size': f"{size_kb:.0f} KB" if size_kb < 1024 else f"{size_kb/1024:.1f} MB",
|
||||||
|
})
|
||||||
|
|
||||||
return jsonify(info)
|
return jsonify(info)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1726,50 +1726,115 @@ function initializeDocsPage() {
|
||||||
});
|
});
|
||||||
nav.innerHTML = navHTML;
|
nav.innerHTML = navHTML;
|
||||||
|
|
||||||
// Add debug info button to sidebar header
|
// Add debug info panel to sidebar header
|
||||||
const sidebarHeader = document.querySelector('.docs-sidebar-header');
|
const sidebarHeader = document.querySelector('.docs-sidebar-header');
|
||||||
if (sidebarHeader) {
|
if (sidebarHeader) {
|
||||||
const debugBtn = document.createElement('button');
|
const debugWrap = document.createElement('div');
|
||||||
debugBtn.className = 'docs-debug-button';
|
debugWrap.className = 'docs-debug-wrap';
|
||||||
debugBtn.innerHTML = '📋 Copy Debug Info';
|
debugWrap.innerHTML = `
|
||||||
|
<button class="docs-debug-button">📋 Copy Debug Info</button>
|
||||||
|
<div class="docs-debug-options">
|
||||||
|
<div class="docs-debug-row">
|
||||||
|
<label>Log lines</label>
|
||||||
|
<select class="docs-debug-select" id="debug-log-lines">
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100" selected>100</option>
|
||||||
|
<option value="200">200</option>
|
||||||
|
<option value="500">500</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="docs-debug-row">
|
||||||
|
<label>Log source</label>
|
||||||
|
<select class="docs-debug-select" id="debug-log-source">
|
||||||
|
<option value="app">app.log</option>
|
||||||
|
<option value="post_processing">post_processing.log</option>
|
||||||
|
<option value="acoustid">acoustid.log</option>
|
||||||
|
<option value="source_reuse">source_reuse.log</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
sidebarHeader.appendChild(debugWrap);
|
||||||
|
|
||||||
|
const debugBtn = debugWrap.querySelector('.docs-debug-button');
|
||||||
debugBtn.onclick = async () => {
|
debugBtn.onclick = async () => {
|
||||||
|
const logLines = document.getElementById('debug-log-lines').value;
|
||||||
|
const logSource = document.getElementById('debug-log-source').value;
|
||||||
try {
|
try {
|
||||||
debugBtn.textContent = 'Collecting...';
|
debugBtn.textContent = 'Collecting...';
|
||||||
const resp = await fetch('/api/debug-info');
|
const resp = await fetch(`/api/debug-info?lines=${logLines}&log=${logSource}`);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
|
const ck = '\u2713';
|
||||||
|
const ex = '\u2717';
|
||||||
let text = 'SoulSync Debug Info\n';
|
let text = 'SoulSync Debug Info\n';
|
||||||
text += '===================================\n\n';
|
text += '═══════════════════════════════════\n\n';
|
||||||
text += `Version: ${data.version}\n`;
|
|
||||||
text += `OS: ${data.os}${data.docker ? ' (Docker)' : ''}\n`;
|
|
||||||
text += `Python: ${data.python}\n\n`;
|
|
||||||
|
|
||||||
text += '-- Services --\n';
|
text += '── System ──\n';
|
||||||
text += `Music Source: ${data.services?.music_source || 'unknown'}\n`;
|
text += `Version: ${data.version}\n`;
|
||||||
text += `Spotify: ${data.services?.spotify_connected ? 'Connected' : 'Disconnected'}${data.services?.spotify_rate_limited ? ' (Rate Limited)' : ''}\n`;
|
text += `OS: ${data.os}${data.docker ? ' (Docker)' : ''}\n`;
|
||||||
text += `Media Server: ${data.services?.media_server_type || 'none'} (${data.services?.media_server_connected ? 'Connected' : 'Disconnected'})\n`;
|
text += `Python: ${data.python}\n`;
|
||||||
text += `Soulseek: ${data.services?.soulseek_connected ? 'Connected' : 'Disconnected'}\n`;
|
text += `Uptime: ${data.uptime || 'unknown'}\n`;
|
||||||
text += `Download Source: ${data.services?.download_source || 'unknown'}\n\n`;
|
text += `Memory: ${data.memory_usage || '?'} (system: ${data.system_memory || '?'})\n`;
|
||||||
|
text += `CPU: ${data.cpu_percent || '?'}\n`;
|
||||||
|
text += `Threads: ${data.thread_count || '?'}\n\n`;
|
||||||
|
|
||||||
text += '-- Paths --\n';
|
text += '── Services ──\n';
|
||||||
text += `Download: ${data.paths?.download_path || '(not set)'} ${data.paths?.download_path_exists ? '\u2713 exists' : '\u2717 missing'}${data.paths?.download_path_writable ? ' \u2713 writable' : ' \u2717 not writable'}\n`;
|
text += `Music Source: ${data.services?.music_source || 'unknown'}\n`;
|
||||||
text += `Transfer: ${data.paths?.transfer_folder || '(not set)'} ${data.paths?.transfer_folder_exists ? '\u2713 exists' : '\u2717 missing'}${data.paths?.transfer_folder_writable ? ' \u2713 writable' : ' \u2717 not writable'}\n`;
|
text += `Spotify: ${data.services?.spotify_connected ? ck + ' Connected' : ex + ' Disconnected'}${data.services?.spotify_rate_limited ? ' (RATE LIMITED)' : ''}\n`;
|
||||||
text += `Staging: ${data.paths?.staging_folder || '(not set)'} ${data.paths?.staging_folder_exists ? '\u2713 exists' : '\u2717 missing'}\n\n`;
|
text += `Media Server: ${data.services?.media_server_type || 'none'} ${data.services?.media_server_connected ? ck + ' Connected' : ex + ' Disconnected'}\n`;
|
||||||
|
text += `Soulseek: ${data.services?.soulseek_connected ? ck + ' Connected' : ex + ' Disconnected'}\n`;
|
||||||
|
text += `Tidal: ${data.services?.tidal_connected ? ck + ' Connected' : ex + ' Disconnected'}\n`;
|
||||||
|
text += `Qobuz: ${data.services?.qobuz_connected ? ck + ' Connected' : ex + ' Disconnected'}\n`;
|
||||||
|
text += `Download Mode: ${data.services?.download_source || 'unknown'}\n\n`;
|
||||||
|
|
||||||
text += '-- Enrichment Workers --\n';
|
text += '── Library ──\n';
|
||||||
if (data.enrichment_workers) {
|
text += `Artists: ${data.library?.artists?.toLocaleString() || '0'}\n`;
|
||||||
Object.entries(data.enrichment_workers).forEach(([name, status]) => {
|
text += `Albums: ${data.library?.albums?.toLocaleString() || '0'}\n`;
|
||||||
text += `${name}: ${status}\n`;
|
text += `Tracks: ${data.library?.tracks?.toLocaleString() || '0'}\n`;
|
||||||
});
|
text += `Database: ${data.database_size || 'unknown'}\n`;
|
||||||
|
text += `Watchlist: ${data.watchlist_count || 0} artists\n`;
|
||||||
|
text += `Automations: ${data.automations?.enabled || 0} enabled / ${data.automations?.total || 0} total\n\n`;
|
||||||
|
|
||||||
|
text += '── Active ──\n';
|
||||||
|
text += `Downloads: ${data.active_downloads || 0}\n`;
|
||||||
|
text += `Syncs: ${data.active_syncs || 0}\n\n`;
|
||||||
|
|
||||||
|
text += '── Paths ──\n';
|
||||||
|
const pathStatus = (exists, writable) => exists ? (writable ? ck + ' ok' : ck + ' exists ' + ex + ' not writable') : ex + ' missing';
|
||||||
|
text += `Download: ${data.paths?.download_path || '(not set)'} [${pathStatus(data.paths?.download_path_exists, data.paths?.download_path_writable)}]\n`;
|
||||||
|
text += `Transfer: ${data.paths?.transfer_folder || '(not set)'} [${pathStatus(data.paths?.transfer_folder_exists, data.paths?.transfer_folder_writable)}]\n`;
|
||||||
|
text += `Staging: ${data.paths?.staging_folder || '(not set)'} [${data.paths?.staging_folder_exists ? ck + ' ok' : ex + ' missing'}]\n\n`;
|
||||||
|
|
||||||
|
text += '── Config ──\n';
|
||||||
|
if (data.config) {
|
||||||
|
text += `Source Mode: ${data.config.source_mode || 'unknown'}\n`;
|
||||||
|
text += `Quality Profile: ${data.config.quality_profile || 'default'}\n`;
|
||||||
|
text += `Folder Template: ${data.config.organization_template || '(default)'}\n`;
|
||||||
|
text += `Post-Processing: ${data.config.post_processing_enabled ? 'enabled' : 'disabled'}\n`;
|
||||||
|
text += `AcoustID: ${data.config.acoustid_enabled ? 'enabled' : 'disabled'}\n`;
|
||||||
|
text += `Auto Scan: ${data.config.auto_scan_enabled ? 'enabled' : 'disabled'}\n`;
|
||||||
|
text += `M3U Export: ${data.config.m3u_export_enabled ? 'enabled' : 'disabled'}\n`;
|
||||||
}
|
}
|
||||||
text += '\n';
|
text += '\n';
|
||||||
|
|
||||||
text += `Database: ${data.database_size || 'unknown'}\n`;
|
text += '── Enrichment Workers ──\n';
|
||||||
text += `Memory: ${data.memory_usage || 'unknown'}\n\n`;
|
if (data.enrichment_workers) {
|
||||||
|
const active = [], paused = [];
|
||||||
|
Object.entries(data.enrichment_workers).forEach(([name, status]) => {
|
||||||
|
(status === 'active' ? active : paused).push(name);
|
||||||
|
});
|
||||||
|
text += `Active: ${active.length > 0 ? active.join(', ') : 'none'}\n`;
|
||||||
|
text += `Paused: ${paused.length > 0 ? paused.join(', ') : 'none'}\n`;
|
||||||
|
}
|
||||||
|
text += '\n';
|
||||||
|
|
||||||
text += '-- Recent Logs (last 20 lines) --\n';
|
text += `── Logs: ${data.log_source || 'app'}.log (last ${data.recent_logs?.length || 0} lines) ──\n`;
|
||||||
if (data.recent_logs?.length) {
|
if (data.recent_logs?.length) {
|
||||||
data.recent_logs.forEach(line => { text += line + '\n'; });
|
data.recent_logs.forEach(line => { text += line + '\n'; });
|
||||||
|
} else {
|
||||||
|
text += '(no log lines)\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
|
|
@ -1785,7 +1850,6 @@ function initializeDocsPage() {
|
||||||
setTimeout(() => { debugBtn.innerHTML = '📋 Copy Debug Info'; }, 2000);
|
setTimeout(() => { debugBtn.innerHTML = '📋 Copy Debug Info'; }, 2000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sidebarHeader.appendChild(debugBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build content
|
// Build content
|
||||||
|
|
|
||||||
|
|
@ -34942,6 +34942,67 @@ tr.tag-diff-same {
|
||||||
border-color: rgb(var(--accent-rgb));
|
border-color: rgb(var(--accent-rgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docs-debug-wrap {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-wrap .docs-debug-button {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-row {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-row label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgba(255, 255, 255, 0.35);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-select {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-size: 11.5px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
font-family: 'SF Pro Text', -apple-system, sans-serif;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='rgba(255,255,255,0.4)'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
padding-right: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-select:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(var(--accent-rgb), 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-debug-select option {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
/* Toast help link */
|
/* Toast help link */
|
||||||
.toast-help-link {
|
.toast-help-link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue