Admin requests: paginated API & UI overhaul

Add a paginated Admin Requests API and fully refactor the admin requests UI to support filtering, sorting, pagination, and URL state.

- New API: src/app/api/admin/requests/route.ts implements paginated, searchable, filterable, and sortable request listing with proper relation includes and pagination metadata.
- Frontend: RecentRequestsTable rewritten to fetch via SWR (authenticatedFetcher), read/write URL query params, debounce search, support status/user filters, sortable columns, page size selector, and full pagination UI; added loading/error states and toast feedback for actions.
- Admin page updated to use Suspense and the new RecentRequestsTable (component now fetches its own data).
- Settings: deprecated single download-client PUT route now maps updates into the new multi-client format (download_clients JSON), logs deprecation, and invalidates download client manager; settings GET now reads multi-client config for backward compatibility.
- Processors: monitor-download and retry-failed-imports updated to use the download-client-manager and new PathMappingConfig shape for path mapping logic.
- Minor API/schema updates: request-with-torrent schema extended (indexerId, infoUrl, protocol) and setup complete no longer writes legacy path keys.
- Tests updated to reflect API and processor changes.

This change centralizes request management on the server, modernizes the UI for large datasets, and migrates download client settings toward a multi-client configuration while keeping backward compatibility.
This commit is contained in:
kikootwo
2026-02-02 10:24:09 -05:00
parent 2cda6decbe
commit aefc9ef667
15 changed files with 1258 additions and 361 deletions
+40 -11
View File
@@ -81,17 +81,46 @@ export async function GET(request: NextRequest) {
url: configMap.get('prowlarr_url') || '',
apiKey: maskValue('api_key', configMap.get('prowlarr_api_key')),
},
downloadClient: {
type: configMap.get('download_client_type') || 'qbittorrent',
url: configMap.get('download_client_url') || '',
username: configMap.get('download_client_username') || '',
password: maskValue('password', configMap.get('download_client_password')),
disableSSLVerify: configMap.get('download_client_disable_ssl_verify') === 'true',
seedingTimeMinutes: parseInt(configMap.get('seeding_time_minutes') || '0'),
remotePathMappingEnabled: configMap.get('download_client_remote_path_mapping_enabled') === 'true',
remotePath: configMap.get('download_client_remote_path') || '',
localPath: configMap.get('download_client_local_path') || '',
},
// downloadClient is populated from multi-client format for backward compatibility
// The DownloadTab component now uses DownloadClientManagement which reads from /api/admin/settings/download-clients
downloadClient: (() => {
// Try to read from new multi-client format first
const downloadClientsJson = configMap.get('download_clients');
if (downloadClientsJson) {
try {
const clients = JSON.parse(downloadClientsJson);
// Return the first enabled client for backward compatibility
const firstClient = clients.find((c: any) => c.enabled) || clients[0];
if (firstClient) {
return {
type: firstClient.type || 'qbittorrent',
url: firstClient.url || '',
username: firstClient.username || '',
password: maskValue('password', firstClient.password),
disableSSLVerify: firstClient.disableSSLVerify === true,
seedingTimeMinutes: parseInt(configMap.get('seeding_time_minutes') || '0'),
remotePathMappingEnabled: firstClient.remotePathMappingEnabled === true,
remotePath: firstClient.remotePath || '',
localPath: firstClient.localPath || '',
};
}
} catch {
// Fall through to legacy format
}
}
// Fall back to legacy flat keys
return {
type: configMap.get('download_client_type') || 'qbittorrent',
url: configMap.get('download_client_url') || '',
username: configMap.get('download_client_username') || '',
password: maskValue('password', configMap.get('download_client_password')),
disableSSLVerify: configMap.get('download_client_disable_ssl_verify') === 'true',
seedingTimeMinutes: parseInt(configMap.get('seeding_time_minutes') || '0'),
remotePathMappingEnabled: configMap.get('download_client_remote_path_mapping_enabled') === 'true',
remotePath: configMap.get('download_client_remote_path') || '',
localPath: configMap.get('download_client_local_path') || '',
};
})(),
paths: {
downloadDir: configMap.get('download_dir') || '/downloads',
mediaDir: configMap.get('media_dir') || '/media/audiobooks',