improved history page

This commit is contained in:
Xoconoch
2025-06-03 22:55:35 -06:00
parent c2e526be60
commit adf90df678
6 changed files with 90 additions and 15 deletions

View File

@@ -1 +0,0 @@
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg CACHE_BUST=$(date +%s) --tag cooldockerizer93/spotizerr:dev .

View File

@@ -1 +0,0 @@
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg CACHE_BUST=$(date +%s) --tag cooldockerizer93/spotizerr:latest .

View File

@@ -160,6 +160,28 @@ def _log_task_to_history(task_id, final_status_str, error_msg=None):
logger.warning(f"History: No task_info found for task_id {task_id}. Cannot log to history.")
return
# Determine service_used and quality_profile
main_service_name = str(task_info.get('main', 'Unknown')).capitalize() # e.g. Spotify, Deezer from their respective .env values
fallback_service_name = str(task_info.get('fallback', '')).capitalize()
service_used_str = main_service_name
if task_info.get('fallback') and fallback_service_name: # Check if fallback was configured
# Try to infer actual service used if possible, otherwise show configured.
# This part is a placeholder for more accurate determination if deezspot gives explicit feedback.
# For now, we assume 'main' was used unless an error hints otherwise.
# A more robust solution would involve deezspot callback providing this.
service_used_str = f"{main_service_name} (Fallback: {fallback_service_name})"
# If error message indicates fallback, we could try to parse it.
# e.g. if error_msg and "fallback" in error_msg.lower(): service_used_str = f"{fallback_service_name} (Used Fallback)"
# Determine quality profile (primarily from the 'quality' field)
# 'quality' usually holds the primary service's quality (e.g., spotifyQuality, deezerQuality)
quality_profile_str = str(task_info.get('quality', 'N/A'))
# Get convertTo and bitrate
convert_to_str = str(task_info.get('convertTo', '')) # Empty string if None or not present
bitrate_str = str(task_info.get('bitrate', '')) # Empty string if None or not present
# Extract Spotify ID from item URL if possible
spotify_id = None
item_url = task_info.get('url', '')
@@ -185,7 +207,11 @@ def _log_task_to_history(task_id, final_status_str, error_msg=None):
'timestamp_added': task_info.get('created_at', time.time()),
'timestamp_completed': last_status_obj.get('timestamp', time.time()) if last_status_obj else time.time(),
'original_request_json': json.dumps(task_info.get('original_request', {})),
'last_status_obj_json': json.dumps(last_status_obj if last_status_obj else {})
'last_status_obj_json': json.dumps(last_status_obj if last_status_obj else {}),
'service_used': service_used_str,
'quality_profile': quality_profile_str,
'convert_to': convert_to_str if convert_to_str else None, # Store None if empty string
'bitrate': bitrate_str if bitrate_str else None # Store None if empty string
}
add_entry_to_history(history_entry)
except Exception as e:

View File

@@ -29,7 +29,11 @@ def init_history_db():
timestamp_added REAL,
timestamp_completed REAL,
original_request_json TEXT,
last_status_obj_json TEXT
last_status_obj_json TEXT,
service_used TEXT,
quality_profile TEXT,
convert_to TEXT,
bitrate TEXT
)
""")
conn.commit()
@@ -51,7 +55,8 @@ def add_entry_to_history(history_data: dict):
'task_id', 'download_type', 'item_name', 'item_artist', 'item_album',
'item_url', 'spotify_id', 'status_final', 'error_message',
'timestamp_added', 'timestamp_completed', 'original_request_json',
'last_status_obj_json'
'last_status_obj_json', 'service_used', 'quality_profile',
'convert_to', 'bitrate'
]
# Ensure all keys are present, filling with None if not
for key in required_keys:
@@ -66,14 +71,17 @@ def add_entry_to_history(history_data: dict):
task_id, download_type, item_name, item_artist, item_album,
item_url, spotify_id, status_final, error_message,
timestamp_added, timestamp_completed, original_request_json,
last_status_obj_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
last_status_obj_json, service_used, quality_profile,
convert_to, bitrate
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
history_data['task_id'], history_data['download_type'], history_data['item_name'],
history_data['item_artist'], history_data['item_album'], history_data['item_url'],
history_data['spotify_id'], history_data['status_final'], history_data['error_message'],
history_data['timestamp_added'], history_data['timestamp_completed'],
history_data['original_request_json'], history_data['last_status_obj_json']
history_data['original_request_json'], history_data['last_status_obj_json'],
history_data['service_used'], history_data['quality_profile'],
history_data['convert_to'], history_data['bitrate']
))
conn.commit()
logger.info(f"Added/Updated history for task_id: {history_data['task_id']}, status: {history_data['status_final']}")
@@ -131,7 +139,8 @@ def get_history_entries(limit=25, offset=0, sort_by='timestamp_completed', sort_
# Validate sort_by and sort_order to prevent SQL injection
valid_sort_columns = [
'task_id', 'download_type', 'item_name', 'item_artist', 'item_album',
'item_url', 'status_final', 'timestamp_added', 'timestamp_completed'
'item_url', 'status_final', 'timestamp_added', 'timestamp_completed',
'service_used', 'quality_profile', 'convert_to', 'bitrate'
]
if sort_by not in valid_sort_columns:
sort_by = 'timestamp_completed' # Default sort
@@ -175,7 +184,11 @@ if __name__ == '__main__':
'timestamp_added': time.time() - 3600,
'timestamp_completed': time.time(),
'original_request_json': json.dumps({'param1': 'value1'}),
'last_status_obj_json': json.dumps({'status': 'complete', 'message': 'Finished!'})
'last_status_obj_json': json.dumps({'status': 'complete', 'message': 'Finished!'}),
'service_used': 'Spotify (Primary)',
'quality_profile': 'NORMAL',
'convert_to': None,
'bitrate': None
}
add_entry_to_history(sample_data_complete)
@@ -192,7 +205,11 @@ if __name__ == '__main__':
'timestamp_added': time.time() - 7200,
'timestamp_completed': time.time() - 60,
'original_request_json': json.dumps({'param2': 'value2'}),
'last_status_obj_json': json.dumps({'status': 'error', 'error': 'Network issue'})
'last_status_obj_json': json.dumps({'status': 'error', 'error': 'Network issue'}),
'service_used': 'Deezer',
'quality_profile': 'MP3_320',
'convert_to': 'mp3',
'bitrate': '320'
}
add_entry_to_history(sample_data_error)
@@ -210,7 +227,11 @@ if __name__ == '__main__':
'timestamp_added': time.time() - 3600,
'timestamp_completed': time.time() + 100, # Updated completion time
'original_request_json': json.dumps({'param1': 'value1', 'new_param': 'added'}),
'last_status_obj_json': json.dumps({'status': 'complete', 'message': 'Finished! With update.'})
'last_status_obj_json': json.dumps({'status': 'complete', 'message': 'Finished! With update.'}),
'service_used': 'Spotify (Deezer Fallback)',
'quality_profile': 'HIGH',
'convert_to': 'flac',
'bitrate': None
}
add_entry_to_history(updated_data_complete)

View File

@@ -41,10 +41,11 @@ document.addEventListener('DOMContentLoaded', () => {
totalEntries = data.total_count;
currentPage = Math.floor(offset / limit) + 1;
updatePagination();
updateSortIndicators();
} catch (error) {
console.error('Error fetching history:', error);
if (historyTableBody) {
historyTableBody.innerHTML = '<tr><td colspan="7">Error loading history.</td></tr>';
historyTableBody.innerHTML = '<tr><td colspan="9">Error loading history.</td></tr>';
}
}
}
@@ -54,7 +55,7 @@ document.addEventListener('DOMContentLoaded', () => {
historyTableBody.innerHTML = ''; // Clear existing rows
if (!entries || entries.length === 0) {
historyTableBody.innerHTML = '<tr><td colspan="7">No history entries found.</td></tr>';
historyTableBody.innerHTML = '<tr><td colspan="9">No history entries found.</td></tr>';
return;
}
@@ -63,6 +64,19 @@ document.addEventListener('DOMContentLoaded', () => {
row.insertCell().textContent = entry.item_name || 'N/A';
row.insertCell().textContent = entry.item_artist || 'N/A';
row.insertCell().textContent = entry.download_type ? entry.download_type.charAt(0).toUpperCase() + entry.download_type.slice(1) : 'N/A';
row.insertCell().textContent = entry.service_used || 'N/A';
// Construct Quality display string
let qualityDisplay = entry.quality_profile || 'N/A';
if (entry.convert_to) {
qualityDisplay = `${entry.convert_to.toUpperCase()}`;
if (entry.bitrate) {
qualityDisplay += ` ${entry.bitrate}k`;
}
qualityDisplay += ` (${entry.quality_profile || 'Original'})`;
} else if (entry.bitrate) { // Case where convert_to might not be set, but bitrate is (e.g. for OGG Vorbis quality settings)
qualityDisplay = `${entry.bitrate}k (${entry.quality_profile || 'Profile'})`;
}
row.insertCell().textContent = qualityDisplay;
const statusCell = row.insertCell();
statusCell.textContent = entry.status_final || 'N/A';
@@ -91,7 +105,7 @@ document.addEventListener('DOMContentLoaded', () => {
errorDetailsDiv = document.createElement('div');
errorDetailsDiv.className = 'error-details';
const newCell = row.insertCell(); // This will append to the end of the row
newCell.colSpan = 7; // Span across all columns
newCell.colSpan = 9; // Span across all columns
newCell.appendChild(errorDetailsDiv);
// Visually, this new cell will be after the 'Details' button cell.
// To make it appear as part of the status cell or below the row, more complex DOM manipulation or CSS would be needed.
@@ -122,6 +136,10 @@ document.addEventListener('DOMContentLoaded', () => {
`Album: ${entry.item_album || 'N/A'}\n` +
`URL: ${entry.item_url}\n` +
`Spotify ID: ${entry.spotify_id || 'N/A'}\n` +
`Service Used: ${entry.service_used || 'N/A'}\n` +
`Quality Profile (Original): ${entry.quality_profile || 'N/A'}\n` +
`ConvertTo: ${entry.convert_to || 'N/A'}\n` +
`Bitrate: ${entry.bitrate ? entry.bitrate + 'k' : 'N/A'}\n` +
`Status: ${entry.status_final}\n` +
`Error: ${entry.error_message || 'None'}\n` +
`Added: ${new Date(entry.timestamp_added * 1000).toLocaleString()}\n` +
@@ -146,6 +164,16 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
function updateSortIndicators() {
document.querySelectorAll('th[data-sort]').forEach(headerCell => {
const th = headerCell as HTMLElement;
th.classList.remove('sort-asc', 'sort-desc');
if (th.dataset.sort === currentSortBy) {
th.classList.add(currentSortOrder === 'ASC' ? 'sort-asc' : 'sort-desc');
}
});
}
prevButton?.addEventListener('click', () => fetchHistory(currentPage - 1));
nextButton?.addEventListener('click', () => fetchHistory(currentPage + 1));
limitSelect?.addEventListener('change', (e) => {

View File

@@ -46,6 +46,8 @@
<th data-sort="item_name">Name</th>
<th data-sort="item_artist">Artist</th>
<th data-sort="download_type">Type</th>
<th data-sort="service_used">Service</th>
<th data-sort="quality_profile">Quality</th>
<th data-sort="status_final">Status</th>
<th data-sort="timestamp_added">Date Added</th>
<th data-sort="timestamp_completed">Date Completed/Ended</th>