improved history page
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg CACHE_BUST=$(date +%s) --tag cooldockerizer93/spotizerr:dev .
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg CACHE_BUST=$(date +%s) --tag cooldockerizer93/spotizerr:latest .
|
|
||||||
@@ -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.")
|
logger.warning(f"History: No task_info found for task_id {task_id}. Cannot log to history.")
|
||||||
return
|
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
|
# Extract Spotify ID from item URL if possible
|
||||||
spotify_id = None
|
spotify_id = None
|
||||||
item_url = task_info.get('url', '')
|
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_added': task_info.get('created_at', time.time()),
|
||||||
'timestamp_completed': last_status_obj.get('timestamp', time.time()) if last_status_obj else 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', {})),
|
'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)
|
add_entry_to_history(history_entry)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ def init_history_db():
|
|||||||
timestamp_added REAL,
|
timestamp_added REAL,
|
||||||
timestamp_completed REAL,
|
timestamp_completed REAL,
|
||||||
original_request_json TEXT,
|
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()
|
conn.commit()
|
||||||
@@ -51,7 +55,8 @@ def add_entry_to_history(history_data: dict):
|
|||||||
'task_id', 'download_type', 'item_name', 'item_artist', 'item_album',
|
'task_id', 'download_type', 'item_name', 'item_artist', 'item_album',
|
||||||
'item_url', 'spotify_id', 'status_final', 'error_message',
|
'item_url', 'spotify_id', 'status_final', 'error_message',
|
||||||
'timestamp_added', 'timestamp_completed', 'original_request_json',
|
'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
|
# Ensure all keys are present, filling with None if not
|
||||||
for key in required_keys:
|
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,
|
task_id, download_type, item_name, item_artist, item_album,
|
||||||
item_url, spotify_id, status_final, error_message,
|
item_url, spotify_id, status_final, error_message,
|
||||||
timestamp_added, timestamp_completed, original_request_json,
|
timestamp_added, timestamp_completed, original_request_json,
|
||||||
last_status_obj_json
|
last_status_obj_json, service_used, quality_profile,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
convert_to, bitrate
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
history_data['task_id'], history_data['download_type'], history_data['item_name'],
|
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['item_artist'], history_data['item_album'], history_data['item_url'],
|
||||||
history_data['spotify_id'], history_data['status_final'], history_data['error_message'],
|
history_data['spotify_id'], history_data['status_final'], history_data['error_message'],
|
||||||
history_data['timestamp_added'], history_data['timestamp_completed'],
|
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()
|
conn.commit()
|
||||||
logger.info(f"Added/Updated history for task_id: {history_data['task_id']}, status: {history_data['status_final']}")
|
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
|
# Validate sort_by and sort_order to prevent SQL injection
|
||||||
valid_sort_columns = [
|
valid_sort_columns = [
|
||||||
'task_id', 'download_type', 'item_name', 'item_artist', 'item_album',
|
'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:
|
if sort_by not in valid_sort_columns:
|
||||||
sort_by = 'timestamp_completed' # Default sort
|
sort_by = 'timestamp_completed' # Default sort
|
||||||
@@ -175,7 +184,11 @@ if __name__ == '__main__':
|
|||||||
'timestamp_added': time.time() - 3600,
|
'timestamp_added': time.time() - 3600,
|
||||||
'timestamp_completed': time.time(),
|
'timestamp_completed': time.time(),
|
||||||
'original_request_json': json.dumps({'param1': 'value1'}),
|
'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)
|
add_entry_to_history(sample_data_complete)
|
||||||
|
|
||||||
@@ -192,7 +205,11 @@ if __name__ == '__main__':
|
|||||||
'timestamp_added': time.time() - 7200,
|
'timestamp_added': time.time() - 7200,
|
||||||
'timestamp_completed': time.time() - 60,
|
'timestamp_completed': time.time() - 60,
|
||||||
'original_request_json': json.dumps({'param2': 'value2'}),
|
'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)
|
add_entry_to_history(sample_data_error)
|
||||||
|
|
||||||
@@ -210,7 +227,11 @@ if __name__ == '__main__':
|
|||||||
'timestamp_added': time.time() - 3600,
|
'timestamp_added': time.time() - 3600,
|
||||||
'timestamp_completed': time.time() + 100, # Updated completion time
|
'timestamp_completed': time.time() + 100, # Updated completion time
|
||||||
'original_request_json': json.dumps({'param1': 'value1', 'new_param': 'added'}),
|
'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)
|
add_entry_to_history(updated_data_complete)
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
totalEntries = data.total_count;
|
totalEntries = data.total_count;
|
||||||
currentPage = Math.floor(offset / limit) + 1;
|
currentPage = Math.floor(offset / limit) + 1;
|
||||||
updatePagination();
|
updatePagination();
|
||||||
|
updateSortIndicators();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching history:', error);
|
console.error('Error fetching history:', error);
|
||||||
if (historyTableBody) {
|
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
|
historyTableBody.innerHTML = ''; // Clear existing rows
|
||||||
if (!entries || entries.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +64,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
row.insertCell().textContent = entry.item_name || 'N/A';
|
row.insertCell().textContent = entry.item_name || 'N/A';
|
||||||
row.insertCell().textContent = entry.item_artist || '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.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();
|
const statusCell = row.insertCell();
|
||||||
statusCell.textContent = entry.status_final || 'N/A';
|
statusCell.textContent = entry.status_final || 'N/A';
|
||||||
@@ -91,7 +105,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
errorDetailsDiv = document.createElement('div');
|
errorDetailsDiv = document.createElement('div');
|
||||||
errorDetailsDiv.className = 'error-details';
|
errorDetailsDiv.className = 'error-details';
|
||||||
const newCell = row.insertCell(); // This will append to the end of the row
|
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);
|
newCell.appendChild(errorDetailsDiv);
|
||||||
// Visually, this new cell will be after the 'Details' button cell.
|
// 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.
|
// 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` +
|
`Album: ${entry.item_album || 'N/A'}\n` +
|
||||||
`URL: ${entry.item_url}\n` +
|
`URL: ${entry.item_url}\n` +
|
||||||
`Spotify ID: ${entry.spotify_id || 'N/A'}\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` +
|
`Status: ${entry.status_final}\n` +
|
||||||
`Error: ${entry.error_message || 'None'}\n` +
|
`Error: ${entry.error_message || 'None'}\n` +
|
||||||
`Added: ${new Date(entry.timestamp_added * 1000).toLocaleString()}\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));
|
prevButton?.addEventListener('click', () => fetchHistory(currentPage - 1));
|
||||||
nextButton?.addEventListener('click', () => fetchHistory(currentPage + 1));
|
nextButton?.addEventListener('click', () => fetchHistory(currentPage + 1));
|
||||||
limitSelect?.addEventListener('change', (e) => {
|
limitSelect?.addEventListener('change', (e) => {
|
||||||
|
|||||||
@@ -46,6 +46,8 @@
|
|||||||
<th data-sort="item_name">Name</th>
|
<th data-sort="item_name">Name</th>
|
||||||
<th data-sort="item_artist">Artist</th>
|
<th data-sort="item_artist">Artist</th>
|
||||||
<th data-sort="download_type">Type</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="status_final">Status</th>
|
||||||
<th data-sort="timestamp_added">Date Added</th>
|
<th data-sort="timestamp_added">Date Added</th>
|
||||||
<th data-sort="timestamp_completed">Date Completed/Ended</th>
|
<th data-sort="timestamp_completed">Date Completed/Ended</th>
|
||||||
|
|||||||
Reference in New Issue
Block a user