diff --git a/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.spec.ts b/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.spec.ts index fa6361d4bb..74e88db0ad 100644 --- a/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.spec.ts +++ b/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.spec.ts @@ -167,6 +167,10 @@ describe('BulkActionsService', () => { 'Content-Disposition', `attachment; filename="${expectedFilename}"`, ); + expect(mockResponse.setHeader).toHaveBeenCalledWith( + 'Access-Control-Expose-Headers', + 'Content-Disposition', + ); expect(mockResponse.setHeader).toHaveBeenCalledWith( 'Transfer-Encoding', 'chunked', diff --git a/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.ts b/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.ts index 9581cca9ee..77a901ab64 100644 --- a/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.ts +++ b/redisinsight/api/src/modules/bulk-actions/bulk-actions.service.ts @@ -75,6 +75,7 @@ export class BulkActionsService { 'Content-Disposition', `attachment; filename="bulk-delete-report-${timestamp}.txt"`, ); + res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition'); res.setHeader('Transfer-Encoding', 'chunked'); // Attach the response stream to the bulk action diff --git a/redisinsight/ui/src/pages/browser/components/virtual-tree/components/Node/styles.module.scss b/redisinsight/ui/src/pages/browser/components/virtual-tree/components/Node/styles.module.scss index 669119cf24..4bb478feed 100644 --- a/redisinsight/ui/src/pages/browser/components/virtual-tree/components/Node/styles.module.scss +++ b/redisinsight/ui/src/pages/browser/components/virtual-tree/components/Node/styles.module.scss @@ -64,10 +64,6 @@ .nodeSelected { border-left-color: var(--browserComponentActive) !important; background-color: var(--browserComponentActive) !important; - - .nodeContent * { - color: var(--euiColorFullShade) !important; - } } .nodeIcon { diff --git a/redisinsight/ui/src/utils/dom/triggerDownloadFromUrl.ts b/redisinsight/ui/src/utils/dom/triggerDownloadFromUrl.ts index c41f7daa5f..479b4e724f 100644 --- a/redisinsight/ui/src/utils/dom/triggerDownloadFromUrl.ts +++ b/redisinsight/ui/src/utils/dom/triggerDownloadFromUrl.ts @@ -1,12 +1,41 @@ +import { CustomHeaders } from 'uiSrc/constants/api' + /** - * Triggers a file download from a URL by creating a temporary link element + * Triggers a file download from a URL using fetch with proper headers + * This is necessary for Electron app where window ID authentication is required * @param url The full URL to download from */ -export const triggerDownloadFromUrl = (url: string): void => { +export const triggerDownloadFromUrl = async (url: string): Promise => { + const headers: Record = {} + + // Add window ID header for Electron app authentication + if (window.windowId) { + headers[CustomHeaders.WindowId] = window.windowId + } + + const response = await fetch(url, { headers }) + + if (!response.ok) { + throw new Error(`Download failed: ${response.statusText}`) + } + + // Extract filename from Content-Disposition header + const contentDisposition = response.headers.get('content-disposition') || '' + const filenameMatch = contentDisposition.match(/filename="?([^";\n]+)"?/) + const filename = filenameMatch?.[1] || 'download' + + // Convert response to blob and trigger download + const blob = await response.blob() + const blobUrl = URL.createObjectURL(blob) + const link = document.createElement('a') - link.href = url + link.href = blobUrl + link.download = filename link.style.display = 'none' document.body.appendChild(link) link.click() document.body.removeChild(link) + + // Clean up the blob URL + URL.revokeObjectURL(blobUrl) }