- Add search.ts - header search with keyboard navigation - Debounced search with 300ms delay - Arrow key navigation through results - Escape to close, Enter to select - Library type icons and highlighting - Add collections.ts - collection and rule management - Rule CRUD operations (create, update, delete) - Rule testing functionality - Bulk collection operations - Add bookshelf.ts - book display and navigation - Library selection state management - Book viewing interactions - Pagination logic - Add linking.ts - book matching and manual linking - Search and match functionality - Manual link modal - Bulk auto-link and suggestions - Add api-explorer.ts - API testing interface - Request/response display - cURL command generation - History tracking - Add admin.ts - admin dashboard actions - Library scan triggers - System statistics display - Profile management - Add analytics.ts - analytics data loading - Chart.js integration - Daily reading minutes chart - Device usage and popular books display - Add queue.ts - sync queue management - Process pending items - Clear failed/all items - Filter by status, type, device - Add conflicts.ts - conflict resolution - Individual and bulk resolve operations - Winner device selection - Manual override inputs - Add docs.ts - documentation search - Lunr.js search integration - Sidebar toggle for mobile
178 lines
6.0 KiB
TypeScript
178 lines
6.0 KiB
TypeScript
import type { QueueItemResponse } from './types/api';
|
|
|
|
async function refreshQueue(): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/queue/all', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
renderQueueItems(data.items || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to refresh queue:', error);
|
|
}
|
|
}
|
|
|
|
async function processPendingItems(): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/queue/process', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success('Processing queue items');
|
|
}
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to process queue:', error);
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error('Failed to process queue');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function clearFailedItems(): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
if (!confirm('Are you sure you want to clear all failed items?')) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/queue/failed', {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success('Failed items cleared');
|
|
}
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to clear failed items:', error);
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error('Failed to clear items');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function clearAllItems(): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
if (!confirm('Are you sure you want to clear all queue items?')) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/queue/all', {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success('Queue cleared');
|
|
}
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to clear queue:', error);
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error('Failed to clear queue');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function retryQueueItem(itemId: string): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/queue/items/${itemId}/retry`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success('Item queued for retry');
|
|
}
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to retry item:', error);
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error('Failed to retry item');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deleteQueueItem(itemId: string): Promise<void> {
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/queue/items/${itemId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success('Item deleted');
|
|
}
|
|
refreshQueue();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to delete item:', error);
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error('Failed to delete item');
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderQueueItems(items: QueueItemResponse[]): void {
|
|
const container = document.getElementById('queue-items');
|
|
if (!container) return;
|
|
|
|
if (items.length === 0) {
|
|
container.innerHTML = '<p class="text-center p-4" style="color: var(--text-secondary)">Queue is empty</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items.map(item => `
|
|
<div class="p-3 rounded-lg border mb-2" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<p class="font-medium" style="color: var(--text-primary)">${item.media_title || 'Unknown'}</p>
|
|
<p class="text-sm" style="color: var(--text-secondary)">${item.status} - ${item.sync_type}</p>
|
|
<p class="text-xs" style="color: var(--text-secondary)">Attempts: ${item.attempts}/${item.max_attempts}</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
${item.status === 'failed' ? `<button onclick="window.retryQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Retry</button>` : ''}
|
|
<button onclick="window.deleteQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Delete</button>
|
|
</div>
|
|
</div>
|
|
${item.error_message ? `<p class="text-xs mt-2" style="color: var(--accent)">${item.error_message}</p>` : ''}
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
(window as any).refreshQueue = refreshQueue;
|
|
(window as any).processPendingItems = processPendingItems;
|
|
(window as any).clearFailedItems = clearFailedItems;
|
|
(window as any).clearAllItems = clearAllItems;
|
|
(window as any).retryQueueItem = retryQueueItem;
|
|
(window as any).deleteQueueItem = deleteQueueItem;
|