hm_ai_qc_report_tool/templates/index.html
nickviljoen f21e41afc3 v1.2.0: Add Docker deployment, simplify auth to local login, production config
- Add Dockerfile, docker-compose.yml, .dockerignore for containerised deployment
- Add deploy/ scripts (deploy.sh, nginx/apache configs, password generator)
- Replace MSAL/Azure AD auth with local username/password authentication
- Add login.html template
- Simplify app.py, middleware, and auth routes for production use
- Update gunicorn_config.py and wsgi.py for Docker/production
- Update templates to work with new auth and URL prefix handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:37:53 +02:00

231 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QC Report Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">QC Report Dashboard</span>
<div id="userInfo" class="d-flex align-items-center" style="display: none !important;">
<span id="userName" class="text-white me-3"></span>
<button id="logoutBtn" class="btn btn-sm btn-outline-light">Logout</button>
</div>
</div>
</nav>
<!-- Authentication Loading State -->
<div id="authLoading" class="container mt-5" style="display: none;">
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted">Authenticating...</p>
</div>
</div>
</div>
<!-- Authentication Required Screen -->
<div id="authRequired" class="container mt-5" style="display: none;">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow text-center">
<div class="card-body p-5">
<i class="bi bi-shield-lock" style="font-size: 4rem; color: #0d6efd;"></i>
<h3 class="mt-4 mb-3">Authentication Required</h3>
<p class="text-muted mb-4">
Please sign in with your Microsoft account to access the HM QC Report Dashboard
</p>
<button id="loginBtn" class="btn btn-primary btn-lg">
<i class="bi bi-microsoft"></i> Sign in with Microsoft
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content (show only when authenticated) -->
<div id="mainContent" style="display: none;">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow">
<div class="card-body p-5">
<h2 class="card-title text-center mb-4">Search QC Reports</h2>
<p class="text-muted text-center mb-4">
Enter a job number to view all QC reports and quality checks
</p>
<form id="searchForm">
<div class="mb-3">
<label for="jobNumber" class="form-label">Job Number</label>
<input
type="text"
class="form-control form-control-lg"
id="jobNumber"
placeholder="e.g., 10107-06, 1022A, 9000_10107-06"
required
autocomplete="off"
>
<div class="form-text">
Enter the reference/job number from the filename
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg" id="searchBtn">
<span id="searchBtnText">Search Reports</span>
<span id="searchBtnSpinner" class="spinner-border spinner-border-sm ms-2 d-none" role="status" aria-hidden="true"></span>
</button>
</div>
</form>
<!-- Progress Bar -->
<div id="progressContainer" class="mt-4 d-none">
<div class="card border-primary">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="spinner-border text-primary me-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="flex-grow-1">
<h6 class="mb-1" id="progressStatus">Searching for campaign...</h6>
<small class="text-muted" id="progressDetail">Please wait while we search through the campaigns folder</small>
</div>
</div>
<div class="progress" style="height: 8px;">
<div id="progressBar"
class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
role="progressbar"
style="width: 0%"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
</div>
</div>
<div id="searchResults" class="mt-4 d-none"></div>
</div>
</div>
<div class="text-center mt-4">
<p class="text-muted">
<small>Reports are retrieved from Box.com</small>
</p>
</div>
</div>
</div>
</div>
</div><!-- End mainContent -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script>
// Progress bar update function
function updateProgress(percent, status, detail) {
const progressBar = document.getElementById('progressBar');
const progressStatus = document.getElementById('progressStatus');
const progressDetail = document.getElementById('progressDetail');
progressBar.style.width = `${percent}%`;
progressBar.setAttribute('aria-valuenow', percent);
progressStatus.textContent = status;
progressDetail.textContent = detail;
}
document.getElementById('searchForm').addEventListener('submit', async (e) => {
e.preventDefault();
const jobNumber = document.getElementById('jobNumber').value.trim();
const searchBtn = document.getElementById('searchBtn');
const searchBtnText = document.getElementById('searchBtnText');
const searchBtnSpinner = document.getElementById('searchBtnSpinner');
const resultsDiv = document.getElementById('searchResults');
const progressContainer = document.getElementById('progressContainer');
if (!jobNumber) {
showError('Please enter a job number');
return;
}
// Show loading state
searchBtn.disabled = true;
searchBtnSpinner.classList.remove('d-none');
searchBtnText.textContent = 'Searching...';
resultsDiv.classList.add('d-none');
progressContainer.classList.remove('d-none');
// Simulate progress updates (since we don't have real-time updates from backend)
updateProgress(10, 'Connecting to Box...', 'Authenticating with Box API');
setTimeout(() => {
updateProgress(30, 'Searching campaigns folder...', `Looking for campaign ${jobNumber}`);
}, 500);
try {
const response = await fetch((window.BASE_URL || '') + '/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ job_number: jobNumber })
});
// Update progress while waiting for response
updateProgress(60, 'Processing results...', 'Retrieving QC reports from campaign folder');
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Search failed');
}
if (data.reports && data.reports.length > 0) {
// Show success state
updateProgress(100, 'Success!', `Found ${data.reports.length} QC report(s). Redirecting to dashboard...`);
// Wait a moment before redirecting so user sees the success message
setTimeout(() => {
window.location.href = `${window.BASE_URL || ''}/dashboard/${encodeURIComponent(jobNumber)}`;
}, 800);
} else {
progressContainer.classList.add('d-none');
showError(data.message || 'No reports found for this job number');
}
} catch (error) {
console.error('Search error:', error);
progressContainer.classList.add('d-none');
showError(error.message || 'Failed to search for reports');
} finally {
// Reset button state (only if not redirecting)
if (progressContainer.classList.contains('d-none')) {
searchBtn.disabled = false;
searchBtnSpinner.classList.add('d-none');
searchBtnText.textContent = 'Search Reports';
}
}
});
function showError(message) {
const resultsDiv = document.getElementById('searchResults');
resultsDiv.innerHTML = `
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle"></i> ${message}
</div>
`;
resultsDiv.classList.remove('d-none');
}
</script>
</body>
</html>