From 35ec337c54c81bf3941a7c1b2998f28ed6898a6d Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Tue, 10 Feb 2026 15:04:21 +0100 Subject: [PATCH] Add copy ticket button to Job Details and improve cross-browser copy functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Added copy ticket button (⧉) next to ticket numbers in Job Details modal - Implemented robust cross-browser clipboard copy mechanism: 1. Modern navigator.clipboard API (works in HTTPS contexts) 2. Legacy document.execCommand('copy') fallback (works in older browsers) 3. Prompt fallback as last resort - Applied improved copy function to both Run Checks and Job Details pages - Copy now works directly in all browsers (Firefox, Edge, Chrome) without popup This eliminates the manual copy step in Edge that previously required a popup. Co-Authored-By: Claude Sonnet 4.5 --- .../src/templates/main/job_detail.html | 69 ++++++++++++++++++- .../src/templates/main/run_checks.html | 69 +++++++++++++++---- 2 files changed, 122 insertions(+), 16 deletions(-) diff --git a/containers/backupchecks/src/templates/main/job_detail.html b/containers/backupchecks/src/templates/main/job_detail.html index 99cba0c..17f0505 100644 --- a/containers/backupchecks/src/templates/main/job_detail.html +++ b/containers/backupchecks/src/templates/main/job_detail.html @@ -284,6 +284,60 @@ .replace(/'/g, "'"); } + // Cross-browser copy to clipboard function + function copyToClipboard(text, button) { + // Method 1: Modern Clipboard API (works in most browsers with HTTPS) + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text) + .then(function () { + showCopyFeedback(button); + }) + .catch(function () { + // Fallback to method 2 if clipboard API fails + fallbackCopy(text, button); + }); + } else { + // Method 2: Legacy execCommand method + fallbackCopy(text, button); + } + } + + function fallbackCopy(text, button) { + var textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.top = '0'; + textarea.style.left = '0'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + + try { + var successful = document.execCommand('copy'); + if (successful) { + showCopyFeedback(button); + } else { + // If execCommand fails, use prompt as last resort + window.prompt('Copy ticket number:', text); + } + } catch (err) { + // If all else fails, show prompt + window.prompt('Copy ticket number:', text); + } + + document.body.removeChild(textarea); + } + + function showCopyFeedback(button) { + if (!button) return; + var original = button.textContent; + button.textContent = '✓'; + setTimeout(function () { + button.textContent = original; + }, 800); + } + (function () { var currentRunId = null; @@ -319,12 +373,14 @@ html += '
Tickets
'; tickets.forEach(function (t) { var status = t.resolved_at ? 'Resolved' : 'Active'; + var ticketCode = (t.ticket_code || '').toString(); html += '
' + '
' + '
' + '
' + '🎫' + - '' + escapeHtml(t.ticket_code || '') + '' + + '' + escapeHtml(ticketCode) + '' + + '' + '' + status + '' + '
' + '
' + @@ -371,7 +427,16 @@ ev.preventDefault(); var action = btn.getAttribute('data-action'); var id = btn.getAttribute('data-id'); - if (!action || !id) return; + if (!action) return; + + if (action === 'copy-ticket') { + var code = btn.getAttribute('data-code') || ''; + if (!code) return; + copyToClipboard(code, btn); + return; + } + + if (!id) return; if (action === 'resolve-ticket') { if (!confirm('Mark ticket as resolved?')) return; apiJson('/api/tickets/' + encodeURIComponent(id) + '/resolve', {method: 'POST', body: '{}'}) diff --git a/containers/backupchecks/src/templates/main/run_checks.html b/containers/backupchecks/src/templates/main/run_checks.html index 4d06a0b..0d49309 100644 --- a/containers/backupchecks/src/templates/main/run_checks.html +++ b/containers/backupchecks/src/templates/main/run_checks.html @@ -447,6 +447,60 @@ function escapeHtml(s) { .replace(/'/g, "'"); } + // Cross-browser copy to clipboard function + function copyToClipboard(text, button) { + // Method 1: Modern Clipboard API (works in most browsers with HTTPS) + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text) + .then(function () { + showCopyFeedback(button); + }) + .catch(function () { + // Fallback to method 2 if clipboard API fails + fallbackCopy(text, button); + }); + } else { + // Method 2: Legacy execCommand method + fallbackCopy(text, button); + } + } + + function fallbackCopy(text, button) { + var textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.top = '0'; + textarea.style.left = '0'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + + try { + var successful = document.execCommand('copy'); + if (successful) { + showCopyFeedback(button); + } else { + // If execCommand fails, use prompt as last resort + window.prompt('Copy ticket number:', text); + } + } catch (err) { + // If all else fails, show prompt + window.prompt('Copy ticket number:', text); + } + + document.body.removeChild(textarea); + } + + function showCopyFeedback(button) { + if (!button) return; + var original = button.textContent; + button.textContent = '✓'; + setTimeout(function () { + button.textContent = original; + }, 800); + } + function getSelectedJobIds() { var cbs = table.querySelectorAll('tbody .rc_row_cb'); var ids = []; @@ -840,20 +894,7 @@ table.addEventListener('change', function (e) { if (action === 'copy-ticket') { var code = btn.getAttribute('data-code') || ''; if (!code) return; - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(code) - .then(function () { - var original = btn.textContent; - btn.textContent = '✓'; - setTimeout(function () { btn.textContent = original; }, 800); - }) - .catch(function () { - // Fallback: select/copy via prompt - window.prompt('Copy ticket number:', code); - }); - } else { - window.prompt('Copy ticket number:', code); - } + copyToClipboard(code, btn); return; }