From 62094f4dfa4d2616a0dc5f81a64919e6b31f04be Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Fri, 13 Mar 2026 15:21:19 +0000 Subject: [PATCH] Move document history to separate history.html page - history.html: standalone page with My Documents table + auth - js/history.js: renderHistory, loadHistory, deleteHistoryJob logic - js/app-history.js: MSAL auth init for history.html - index.html: remove history section, add 'My Documents' link in header - js/app.js: show historyLink after auth, open job from ?job_id= URL param - deploy.sh: include history.html in deploy Co-Authored-By: Claude Sonnet 4.6 --- deploy.sh | 3 +- history.html | 68 ++++++++++++++++++++++++ index.html | 12 +---- js/app-history.js | 110 +++++++++++++++++++++++++++++++++++++++ js/app.js | 129 +++------------------------------------------- js/history.js | 116 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 304 insertions(+), 134 deletions(-) create mode 100644 history.html create mode 100644 js/app-history.js create mode 100644 js/history.js diff --git a/deploy.sh b/deploy.sh index f99881c..90ac447 100755 --- a/deploy.sh +++ b/deploy.sh @@ -158,12 +158,13 @@ sudo mkdir -p "${WEB_DIR}" # Clean old frontend files (but preserve uploads, results, .env, logs) log "Cleaning old frontend files..." -sudo rm -f "${WEB_DIR}/index.html" +sudo rm -f "${WEB_DIR}/index.html" "${WEB_DIR}/history.html" sudo rm -rf "${WEB_DIR}/css" "${WEB_DIR}/js" sudo rm -f "${WEB_DIR}/api.php" "${WEB_DIR}/auth.php" # Copy frontend files sudo cp "${REPO_DIR}/index.html" "${WEB_DIR}/" +sudo cp "${REPO_DIR}/history.html" "${WEB_DIR}/" sudo cp -r "${REPO_DIR}/css" "${WEB_DIR}/" sudo cp -r "${REPO_DIR}/js" "${WEB_DIR}/" diff --git a/history.html b/history.html new file mode 100644 index 0000000..3b5d3b0 --- /dev/null +++ b/history.html @@ -0,0 +1,68 @@ + + + + + + My Documents — PDF Accessibility Checker + + + + + + + + + + + + + +
+
+
+
+

Enterprise PDF Accessibility Checker

+

Comprehensive WCAG 2.1 compliance validation with AI-powered analysis

+
+
+ ⬆ New Check + + + +
+
+
+
+ +
+
+ +
+
+ + + + + + + diff --git a/index.html b/index.html index 3727c57..6462d85 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,7 @@

Comprehensive WCAG 2.1 compliance validation with AI-powered analysis

+ @@ -48,17 +49,6 @@
- - -

Upload PDF Document

diff --git a/js/app-history.js b/js/app-history.js new file mode 100644 index 0000000..8ece3a7 --- /dev/null +++ b/js/app-history.js @@ -0,0 +1,110 @@ +/* MSAL auth + init for history.html */ + +const msalConfig = { + auth: { + clientId: '', + authority: '', + redirectUri: window.location.origin + window.location.pathname + }, + cache: { cacheLocation: 'localStorage', storeAuthStateInCookie: false } +}; + +let msalInstance = null; +window.msalToken = null; + +function initMsal() { + const el = document.getElementById('msalConfig'); + if (!el) return; + const tenantId = el.dataset.tenantId; + const clientId = el.dataset.clientId; + const redirectUri = el.dataset.redirectUri; + if (!tenantId || !clientId) return; + + msalConfig.auth.clientId = clientId; + msalConfig.auth.authority = `https://login.microsoftonline.com/${tenantId}`; + if (redirectUri) msalConfig.auth.redirectUri = redirectUri; + + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/@azure/msal-browser@2/lib/msal-browser.min.js'; + script.onload = () => { + msalInstance = new msal.PublicClientApplication(msalConfig); + msalInstance.initialize().then(handleMsalRedirect); + }; + document.head.appendChild(script); +} + +async function handleMsalRedirect() { + try { + const response = await msalInstance.handleRedirectPromise(); + if (response) { + window.msalToken = response.accessToken; + showAuthenticatedUI(response.account); + return; + } + } catch (e) { console.error('MSAL redirect error:', e); } + + const accounts = msalInstance.getAllAccounts(); + if (accounts.length > 0) { + try { + const tokenResponse = await msalInstance.acquireTokenSilent({ scopes: ['User.Read'], account: accounts[0] }); + window.msalToken = tokenResponse.accessToken; + showAuthenticatedUI(accounts[0]); + } catch (e) { showLoginUI(); } + } else { + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + showAuthenticatedUI(null); + } else { + showLoginUI(); + } + } +} + +function showLoginUI() { + const overlay = document.getElementById('authOverlay'); + if (overlay) overlay.classList.add('active'); +} + +function showAuthenticatedUI(account) { + const overlay = document.getElementById('authOverlay'); + if (overlay) overlay.classList.remove('active'); + + const userInfo = document.getElementById('userInfo'); + if (userInfo && account) userInfo.textContent = account.name || account.username; + + const logoutBtn = document.getElementById('logoutBtn'); + if (logoutBtn) logoutBtn.style.display = 'inline-block'; + + const historySection = document.getElementById('historySection'); + if (historySection) historySection.style.display = ''; + + loadHistory(); +} + +async function loginWithMicrosoft() { + if (!msalInstance) return; + try { await msalInstance.loginRedirect({ scopes: ['User.Read'] }); } + catch (e) { console.error('Login failed:', e); alert('Login failed. Please try again.'); } +} + +function logout() { + if (msalInstance) msalInstance.logoutRedirect(); +} + +function toggleDarkMode() { + const isDark = document.body.classList.toggle('dark-mode'); + localStorage.setItem('darkMode', isDark ? '1' : '0'); + document.getElementById('themeToggle').textContent = isDark ? 'Light' : 'Dark'; +} + +function loadTheme() { + if (localStorage.getItem('darkMode') === '1') { + document.body.classList.add('dark-mode'); + const btn = document.getElementById('themeToggle'); + if (btn) btn.textContent = 'Light'; + } +} + +document.addEventListener('DOMContentLoaded', () => { + loadTheme(); + initMsal(); +}); diff --git a/js/app.js b/js/app.js index be2a784..30e987c 100644 --- a/js/app.js +++ b/js/app.js @@ -97,129 +97,14 @@ function showAuthenticatedUI(account) { const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) logoutBtn.style.display = 'inline-block'; - // Show history section and load jobs - const historySection = document.getElementById('historySection'); - if (historySection) historySection.style.display = ''; - loadHistory(); -} + // Show My Documents link in header + const historyLink = document.getElementById('historyLink'); + if (historyLink) historyLink.style.display = 'inline-block'; -// ─── Document History ──────────────────────────────────────────────────────── - -async function loadHistory() { - const wrap = document.getElementById('historyTableWrap'); - if (!wrap) return; - - try { - const data = await apiCall('list'); - const jobs = data?.data?.jobs || data?.jobs || []; - renderHistory(jobs); - } catch (e) { - console.error('[history] failed to load:', e); - } -} - -function renderHistory(jobs) { - const wrap = document.getElementById('historyTableWrap'); - const empty = document.getElementById('historyEmpty'); - - if (!jobs.length) { - if (empty) empty.style.display = ''; - // Remove table if it existed - const old = wrap.querySelector('table'); - if (old) old.remove(); - return; - } - if (empty) empty.style.display = 'none'; - - const rows = jobs.map(j => { - const score = j.score != null ? j.score : '—'; - const grade = j.grade || '—'; - const scoreClass = j.score >= 90 ? 'history-score-a' - : j.score >= 70 ? 'history-score-b' - : j.score != null ? 'history-score-f' : ''; - const status = j.status === 'completed' ? 'Done' - : 'Pending'; - const critical = j.critical_count ?? 0; - const errors = j.error_count ?? 0; - const date = j.uploaded_at ? j.uploaded_at.replace('T', ' ').substring(0, 16) : '—'; - const name = escapeHtml(j.original_filename || j.job_id); - - const openBtn = j.status === 'completed' - ? `` - : ''; - const htmlBtn = j.status === 'completed' - ? `HTML` - : ''; - const pdfBtn = j.status === 'completed' - ? `PDF` - : ''; - const jsonBtn = j.status === 'completed' - ? `JSON` - : ''; - const deleteBtn = ``; - - return ` - ${name} - ${date} - ${status} - ${score}${j.score != null ? '/100' : ''} ${grade} - ${critical > 0 ? `${critical} crit` : ''} ${errors > 0 ? `${errors} err` : ''}${!critical && !errors ? '✓ Clean' : ''} - ${openBtn}${htmlBtn}${pdfBtn}${jsonBtn}${deleteBtn} - `; - }).join(''); - - // Replace or create table - const old = wrap.querySelector('table'); - if (old) old.remove(); - - const table = document.createElement('table'); - table.className = 'history-table'; - table.setAttribute('aria-label', 'Document history'); - table.innerHTML = ` - - Document - Date - Status - Score - Issues - Actions - - ${rows}`; - wrap.appendChild(table); -} - -function escapeHtml(str) { - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); -} - -async function deleteHistoryJob(jobId, btn) { - if (!confirm('Delete this document and its report?')) return; - btn.disabled = true; - try { - const formData = new FormData(); - formData.append('job_id', jobId); - const data = await apiCall('delete', { method: 'POST', body: formData }); - if (data.success) { - btn.closest('tr').remove(); - // Show empty state if no rows left - const tbody = document.querySelector('.history-table tbody'); - if (tbody && !tbody.querySelector('tr')) { - document.querySelector('.history-table').remove(); - const empty = document.getElementById('historyEmpty'); - if (empty) empty.style.display = ''; - } - } else { - alert('Delete failed: ' + (data.error || 'Unknown error')); - btn.disabled = false; - } - } catch (e) { - alert('Delete failed.'); - btn.disabled = false; - } + // If URL has ?job_id= open that report directly + const params = new URLSearchParams(window.location.search); + const jobId = params.get('job_id'); + if (jobId) openHistoryJob(jobId); } async function openHistoryJob(jobId) { diff --git a/js/history.js b/js/history.js new file mode 100644 index 0000000..aa74a59 --- /dev/null +++ b/js/history.js @@ -0,0 +1,116 @@ +/* Document history table — used on history.html */ + +async function loadHistory() { + const wrap = document.getElementById('historyTableWrap'); + if (!wrap) return; + + try { + const data = await apiCall('list'); + const jobs = data?.data?.jobs || data?.jobs || []; + renderHistory(jobs); + } catch (e) { + console.error('[history] failed to load:', e); + } +} + +function renderHistory(jobs) { + const wrap = document.getElementById('historyTableWrap'); + const empty = document.getElementById('historyEmpty'); + + if (!jobs.length) { + if (empty) empty.style.display = ''; + const old = wrap.querySelector('table'); + if (old) old.remove(); + return; + } + if (empty) empty.style.display = 'none'; + + const rows = jobs.map(j => { + const score = j.score != null ? j.score : '—'; + const grade = j.grade || '—'; + const scoreClass = j.score >= 90 ? 'history-score-a' + : j.score >= 70 ? 'history-score-b' + : j.score != null ? 'history-score-f' : ''; + const status = j.status === 'completed' + ? 'Done' + : 'Pending'; + const critical = j.critical_count ?? 0; + const errors = j.error_count ?? 0; + const date = j.uploaded_at ? j.uploaded_at.replace('T', ' ').substring(0, 16) : '—'; + const name = escapeHtml(j.original_filename || j.job_id); + + const openBtn = j.status === 'completed' + ? `Open` + : ''; + const htmlBtn = j.status === 'completed' + ? `HTML` + : ''; + const pdfBtn = j.status === 'completed' + ? `PDF` + : ''; + const jsonBtn = j.status === 'completed' + ? `JSON` + : ''; + const deleteBtn = ``; + + return ` + ${name} + ${date} + ${status} + ${score}${j.score != null ? '/100' : ''} ${grade} + ${critical > 0 ? `${critical} crit` : ''} ${errors > 0 ? `${errors} err` : ''}${!critical && !errors && j.status === 'completed' ? '✓ Clean' : ''} + ${openBtn}${htmlBtn}${pdfBtn}${jsonBtn}${deleteBtn} + `; + }).join(''); + + const old = wrap.querySelector('table'); + if (old) old.remove(); + + const table = document.createElement('table'); + table.className = 'history-table'; + table.setAttribute('aria-label', 'Document history'); + table.innerHTML = ` + + Document + Date + Status + Score + Issues + Actions + + ${rows}`; + wrap.appendChild(table); +} + +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +async function deleteHistoryJob(jobId, btn) { + if (!confirm('Delete this document and its report?')) return; + btn.disabled = true; + try { + const formData = new FormData(); + formData.append('job_id', jobId); + const data = await apiCall('delete', { method: 'POST', body: formData }); + if (data.success) { + btn.closest('tr').remove(); + const tbody = document.querySelector('.history-table tbody'); + if (tbody && !tbody.querySelector('tr')) { + document.querySelector('.history-table').remove(); + const empty = document.getElementById('historyEmpty'); + if (empty) empty.style.display = ''; + } + } else { + alert('Delete failed: ' + (data.error || 'Unknown error')); + btn.disabled = false; + } + } catch (e) { + alert('Delete failed.'); + btn.disabled = false; + } +}