2025-08-05 14:38:54 +04:00

382 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PLC Generation Web Application</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.upload-area {
border: 2px dashed #007bff;
border-radius: 10px;
padding: 40px;
text-align: center;
background-color: #f8f9fa;
transition: all 0.3s ease;
}
.upload-area:hover {
border-color: #0056b3;
background-color: #e3f2fd;
}
.upload-area.dragover {
border-color: #28a745;
background-color: #d4edda;
}
.header-icon {
font-size: 3rem;
color: #007bff;
margin-bottom: 20px;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
padding: 12px 30px;
font-size: 1.1rem;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.btn-success {
padding: 12px 30px;
font-size: 1.1rem;
}
.alert {
display: none;
}
.file-info {
background-color: #e9ecef;
border-radius: 5px;
padding: 10px;
margin-top: 10px;
display: none;
}
.project-card {
border: 2px solid #dee2e6;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.project-card:hover {
border-color: #007bff;
background-color: #f8f9fa;
}
.project-card.selected {
border-color: #28a745;
background-color: #d4edda;
}
.project-icon {
font-size: 2rem;
color: #007bff;
margin-bottom: 10px;
}
.tab-content {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="text-center mb-5">
<i class="fas fa-microchip header-icon"></i>
<h1 class="display-4 mb-3">PLC Generation Web Application</h1>
<p class="lead text-muted">Select a predefined project or upload your Excel file to generate PLC projects automatically</p>
</div>
<!-- Alert Messages -->
<div id="alertContainer"></div>
<!-- Navigation Tabs -->
<ul class="nav nav-tabs" id="generationTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="predefined-tab" data-bs-toggle="tab" data-bs-target="#predefined" type="button" role="tab">
<i class="fas fa-project-diagram me-2"></i>Projects
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="custom-tab" data-bs-toggle="tab" data-bs-target="#custom" type="button" role="tab">
<i class="fas fa-upload me-2"></i>Custom Upload
</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content" id="generationTabContent">
<!-- Predefined Projects Tab -->
<div class="tab-pane fade show active" id="predefined" role="tabpanel">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-list me-2"></i>Select a Predefined Project
</h5>
<p class="text-muted">Choose from one of the available projects below:</p>
<div class="row" id="projectSelection">
{% for key, project in predefined_projects.items() %}
<div class="col-md-6">
<div class="project-card" data-project-key="{{ key }}">
<div class="text-center">
<i class="fas fa-microchip project-icon"></i>
<h6 class="fw-bold">{{ project.display_name }}</h6>
<small class="text-muted">{{ project.name }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="d-grid mt-4">
<button type="button" class="btn btn-success btn-lg" id="generatePredefinedBtn" disabled>
<i class="fas fa-rocket me-2"></i>Generate Selected Project
</button>
</div>
</div>
</div>
</div>
<!-- Custom Upload Tab -->
<div class="tab-pane fade" id="custom" role="tabpanel">
<div class="card shadow">
<div class="card-body">
<form id="uploadForm" enctype="multipart/form-data">
<div class="mb-4">
<label for="projectName" class="form-label">
<i class="fas fa-project-diagram me-2"></i>Project Name
</label>
<input type="text" class="form-control form-control-lg" id="projectName" name="project_name"
placeholder="Enter project name (e.g., MTN6_MCM01_UL1_UL3)" required>
<div class="form-text">Enter a unique name for your PLC project</div>
</div>
<div class="mb-4">
<label for="excelFile" class="form-label">
<i class="fas fa-file-excel me-2"></i>Excel File
</label>
<div class="upload-area" id="uploadArea">
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
<h5>Drag & Drop your Excel file here</h5>
<p class="text-muted">or click to browse files</p>
<input type="file" class="form-control" id="excelFile" name="excel_file"
accept=".xlsx,.xlsm" required style="display: none;">
</div>
<div class="file-info" id="fileInfo">
<i class="fas fa-file-excel text-success me-2"></i>
<span id="fileName"></span>
<span class="text-muted" id="fileSize"></span>
</div>
<div class="form-text">Upload .xlsx or .xlsm files only (max 100MB)</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
<i class="fas fa-rocket me-2"></i>Generate PLC Project
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Recent Jobs -->
<div class="card shadow mt-4" id="recentJobs" style="display: none;">
<div class="card-header">
<h5><i class="fas fa-history me-2"></i>Recent Jobs</h5>
</div>
<div class="card-body">
<div id="jobsList"></div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// File upload handling
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('excelFile');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const uploadForm = document.getElementById('uploadForm');
const submitBtn = document.getElementById('submitBtn');
const alertContainer = document.getElementById('alertContainer');
// Project selection handling
const projectCards = document.querySelectorAll('.project-card');
const generatePredefinedBtn = document.getElementById('generatePredefinedBtn');
let selectedProject = null;
// Project card selection
projectCards.forEach(card => {
card.addEventListener('click', () => {
// Remove selection from all cards
projectCards.forEach(c => c.classList.remove('selected'));
// Add selection to clicked card
card.classList.add('selected');
selectedProject = card.dataset.projectKey;
// Enable generate button
generatePredefinedBtn.disabled = false;
});
});
// Generate predefined project
generatePredefinedBtn.addEventListener('click', async () => {
if (!selectedProject) {
showAlert('Please select a project first', 'warning');
return;
}
generatePredefinedBtn.disabled = true;
generatePredefinedBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Starting Generation...';
try {
const response = await fetch('/generate_predefined', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
project_key: selectedProject
})
});
const result = await response.json();
if (response.ok) {
showAlert(`Generation started! Redirecting to job status...`, 'success');
setTimeout(() => {
window.location.href = `/job/${result.job_id}`;
}, 2000);
} else {
showAlert(`Error: ${result.error}`, 'danger');
generatePredefinedBtn.disabled = false;
generatePredefinedBtn.innerHTML = '<i class="fas fa-rocket me-2"></i>Generate Selected Project';
}
} catch (error) {
showAlert(`Network error: ${error.message}`, 'danger');
generatePredefinedBtn.disabled = false;
generatePredefinedBtn.innerHTML = '<i class="fas fa-rocket me-2"></i>Generate Selected Project';
}
});
// Drag and drop handlers for custom upload
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
showFileInfo(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
showFileInfo(e.target.files[0]);
}
});
function showFileInfo(file) {
fileName.textContent = file.name;
fileSize.textContent = `(${(file.size / 1024 / 1024).toFixed(2)} MB)`;
fileInfo.style.display = 'block';
}
function showAlert(message, type = 'info') {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
alertContainer.appendChild(alertDiv);
}
// Form submission for custom upload
uploadForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(uploadForm);
// Disable submit button
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Uploading...';
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (response.ok) {
showAlert(`Upload successful! Redirecting to job status...`, 'success');
setTimeout(() => {
window.location.href = `/job/${result.job_id}`;
}, 2000);
} else {
showAlert(`Error: ${result.error}`, 'danger');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-rocket me-2"></i>Generate PLC Project';
}
} catch (error) {
showAlert(`Network error: ${error.message}`, 'danger');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-rocket me-2"></i>Generate PLC Project';
}
});
// Load recent jobs from localStorage
function loadRecentJobs() {
const jobs = JSON.parse(localStorage.getItem('plcJobs') || '[]');
if (jobs.length > 0) {
document.getElementById('recentJobs').style.display = 'block';
const jobsList = document.getElementById('jobsList');
jobsList.innerHTML = jobs.map(job => `
<div class="d-flex justify-content-between align-items-center p-2 border-bottom">
<div>
<strong>${job.name}</strong>
<small class="text-muted d-block">${job.date}</small>
</div>
<a href="/job/${job.id}" class="btn btn-sm btn-outline-primary">View Status</a>
</div>
`).join('');
}
}
// Save job to localStorage
function saveJob(jobId, projectName) {
const jobs = JSON.parse(localStorage.getItem('plcJobs') || '[]');
jobs.unshift({
id: jobId,
name: projectName,
date: new Date().toLocaleString()
});
// Keep only last 5 jobs
jobs.splice(5);
localStorage.setItem('plcJobs', JSON.stringify(jobs));
}
// Load recent jobs on page load
loadRecentJobs();
</script>
</body>
</html>