update chức năng ndvi time seriese

This commit is contained in:
Victor Phan
2025-12-22 07:20:41 +07:00
parent 46da481029
commit f3ff5d81a6
12 changed files with 3558 additions and 66 deletions
+788
View File
@@ -0,0 +1,788 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Batch Processing - Land Classification</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1600px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.content {
padding: 30px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
.section {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
}
.section h2 {
color: #667eea;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: 600;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 1em;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-right: 10px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.batch-item {
background: white;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
border-left: 4px solid #667eea;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 20px;
}
.batch-item.completed {
border-left-color: #28a745;
}
.batch-item.failed {
border-left-color: #dc3545;
}
.batch-item.running {
border-left-color: #ffc107;
}
.progress {
height: 25px;
background: #e0e0e0;
border-radius: 12px;
overflow: hidden;
margin-top: 10px;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 0.9em;
}
.alert {
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.alert-info {
background: #d1ecf1;
border-left: 4px solid #0c5460;
color: #0c5460;
}
.alert-success {
background: #d4edda;
border-left: 4px solid #155724;
color: #155724;
}
.jobs-list {
max-height: 500px;
overflow-y: auto;
}
#batchMap {
height: 400px;
border-radius: 10px;
margin-top: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Batch Processing</h1>
<p>Xử lý nhiều khu vực cùng lúc với model đã train</p>
</div>
<div style="background: white; padding: 15px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; border-bottom: 2px solid #e0e0e0;">
<a href="/" style="padding: 10px 20px; background: #667eea; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🏠 Trang Chủ</a>
<a href="/training" style="padding: 10px 20px; background: #f093fb; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🎓 Training</a>
<a href="/prediction" style="padding: 10px 20px; background: #4facfe; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🗺️ Prediction</a>
<a href="/batch" style="padding: 10px 20px; background: #764ba2; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🚀 Batch Processing (Active)</a>
<a href="/ndvi" style="padding: 10px 20px; background: #2ecc71; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🌿 NDVI Analysis</a>
</div>
<div class="content">
<!-- Configuration Section -->
<div class="section">
<h2>⚙️ Cấu hình Batch</h2>
<div class="form-group">
<label for="batchModelSelect">Model:</label>
<select id="batchModelSelect">
<option value="">Đang tải...</option>
</select>
</div>
<div class="form-group">
<label for="batchName">Tên khu vực:</label>
<input type="text" id="batchName" placeholder="Ví dụ: Khu vực A">
</div>
<div class="form-group">
<label>Bbox (từ bản đồ hoặc nhập thủ công):</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="number" id="batchMinLon" placeholder="Min Lon" step="0.0001">
<input type="number" id="batchMinLat" placeholder="Min Lat" step="0.0001">
<input type="number" id="batchMaxLon" placeholder="Max Lon" step="0.0001">
<input type="number" id="batchMaxLat" placeholder="Max Lat" step="0.0001">
</div>
</div>
<div class="form-group">
<label>Thời gian:</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="date" id="batchStartDate" value="2023-03-01">
<input type="date" id="batchEndDate" value="2023-05-31">
</div>
</div>
<button class="btn btn-primary" onclick="addBatchItem()">
Thêm vào Batch
</button>
<!-- Map for selecting bbox -->
<div id="batchMap"></div>
</div>
<!-- Batch Queue Section -->
<div class="section">
<h2>📋 Batch Queue (<span id="queueCount">0</span> items)</h2>
<div id="batchQueue" class="jobs-list">
<p style="text-align: center; color: #666;">Chưa có item nào. Thêm khu vực từ bên trái.</p>
</div>
<div style="margin-top: 20px;">
<button class="btn btn-success" onclick="startBatch()" id="startBatchBtn" disabled>
🚀 Start Batch Processing
</button>
<button class="btn btn-danger" onclick="clearBatchQueue()">
🗑️ Clear Queue
</button>
</div>
</div>
<!-- Status Section -->
<div class="section" style="grid-column: 1 / -1;">
<h2>📊 Batch Status</h2>
<div id="batchStatus" style="display: none;">
<div class="alert alert-info">
<p><strong>Batch ID:</strong> <span id="currentBatchId"></span></p>
<p><strong>Status:</strong> Queued: <span id="statusQueued">0</span> | Running: <span id="statusRunning">0</span> | Completed: <span id="statusCompleted">0</span> | Failed: <span id="statusFailed">0</span></p>
</div>
</div>
<div id="runningJobs" class="jobs-list">
<!-- Running jobs will appear here -->
</div>
</div>
<!-- Results Section -->
<div class="section" style="grid-column: 1 / -1;">
<h2>✅ Completed Results</h2>
<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
<button class="btn btn-primary" onclick="loadAllBatchResults()" style="padding: 8px 20px;">
🔄 Refresh Results
</button>
<button class="btn btn-success" onclick="downloadAllResults()" style="padding: 8px 20px;">
📦 Download All (Bulk)
</button>
</div>
<div id="completedResults" class="jobs-list">
<p style="text-align: center; color: #666;">Chưa có kết quả nào</p>
</div>
</div>
</div>
</div>
<!-- Modal for large preview -->
<div id="previewModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 10000; padding: 20px;">
<div style="position: relative; height: 100%; display: flex; align-items: center; justify-content: center;">
<button onclick="closePreviewModal()" style="position: absolute; top: 20px; right: 20px; background: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 18px; font-weight: bold;">
✕ Close
</button>
<img id="previewImage" style="max-width: 90%; max-height: 90%; border-radius: 10px;">
</div>
</div>
<!-- Scripts -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<script>
let map, drawnItems, drawControl;
let batchQueue = [];
let currentBatchId = null;
let statusCheckInterval = null;
// Initialize map
function initMap() {
map = L.map('batchMap').setView([9.5, 105.9], 9);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
drawControl = new L.Control.Draw({
draw: {
rectangle: true,
polygon: false,
circle: false,
marker: false,
polyline: false,
circlemarker: false
},
edit: {
featureGroup: drawnItems,
remove: true
}
});
map.addControl(drawControl);
map.on(L.Draw.Event.CREATED, function(event) {
drawnItems.clearLayers();
const layer = event.layer;
drawnItems.addLayer(layer);
const bounds = layer.getBounds();
document.getElementById('batchMinLon').value = bounds.getWest().toFixed(4);
document.getElementById('batchMinLat').value = bounds.getSouth().toFixed(4);
document.getElementById('batchMaxLon').value = bounds.getEast().toFixed(4);
document.getElementById('batchMaxLat').value = bounds.getNorth().toFixed(4);
});
}
// Load models
async function loadModels() {
try {
const response = await fetch('/api/models/list');
const data = await response.json();
const select = document.getElementById('batchModelSelect');
select.innerHTML = '<option value="">Chọn model...</option>';
data.models.filter(m => m.filename.endsWith('.joblib')).forEach(model => {
const option = document.createElement('option');
option.value = model.filename;
option.textContent = `${model.filename} - ${model.created}`;
select.appendChild(option);
});
if (data.models.length > 0) {
select.value = data.models[0].filename;
}
} catch (error) {
console.error('Error loading models:', error);
}
}
// Add item to batch queue
function addBatchItem() {
const name = document.getElementById('batchName').value;
const minLon = parseFloat(document.getElementById('batchMinLon').value);
const minLat = parseFloat(document.getElementById('batchMinLat').value);
const maxLon = parseFloat(document.getElementById('batchMaxLon').value);
const maxLat = parseFloat(document.getElementById('batchMaxLat').value);
const startDate = document.getElementById('batchStartDate').value;
const endDate = document.getElementById('batchEndDate').value;
if (!name || isNaN(minLon) || isNaN(minLat) || isNaN(maxLon) || isNaN(maxLat)) {
alert('❌ Vui lòng điền đầy đủ thông tin!');
return;
}
const item = {
name,
min_lon: minLon,
min_lat: minLat,
max_lon: maxLon,
max_lat: maxLat,
start_date: startDate,
end_date: endDate,
max_scenes: 12,
cloud_cover: 30,
resolution: 20
};
batchQueue.push(item);
updateBatchQueueDisplay();
// Clear form
document.getElementById('batchName').value = '';
drawnItems.clearLayers();
}
// Update batch queue display
function updateBatchQueueDisplay() {
const queueDiv = document.getElementById('batchQueue');
const countSpan = document.getElementById('queueCount');
countSpan.textContent = batchQueue.length;
if (batchQueue.length === 0) {
queueDiv.innerHTML = '<p style="text-align: center; color: #666;">Chưa có item nào. Thêm khu vực từ bên trái.</p>';
document.getElementById('startBatchBtn').disabled = true;
return;
}
document.getElementById('startBatchBtn').disabled = false;
queueDiv.innerHTML = batchQueue.map((item, idx) => `
<div class="batch-item">
<div>
<strong>${item.name}</strong><br>
<small>Bbox: (${item.min_lon.toFixed(2)}, ${item.min_lat.toFixed(2)}) → (${item.max_lon.toFixed(2)}, ${item.max_lat.toFixed(2)})</small><br>
<small>Time: ${item.start_date}${item.end_date}</small>
</div>
<button class="btn btn-danger" style="padding: 5px 15px;" onclick="removeBatchItem(${idx})">
</button>
</div>
`).join('');
}
// Remove item from queue
function removeBatchItem(index) {
batchQueue.splice(index, 1);
updateBatchQueueDisplay();
}
// Clear batch queue
function clearBatchQueue() {
if (!confirm('Xóa tất cả items trong queue?')) return;
batchQueue = [];
updateBatchQueueDisplay();
}
// Start batch processing
async function startBatch() {
const modelFilename = document.getElementById('batchModelSelect').value;
if (!modelFilename) {
alert('❌ Vui lòng chọn model!');
return;
}
if (batchQueue.length === 0) {
alert('❌ Batch queue trống!');
return;
}
const config = {
model_filename: modelFilename,
items: batchQueue,
auto_retry: true,
max_retries: 3
};
try {
const response = await fetch('/api/batch/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
if (response.ok) {
currentBatchId = result.batch_id;
document.getElementById('currentBatchId').textContent = currentBatchId;
document.getElementById('batchStatus').style.display = 'block';
// Clear local queue
batchQueue = [];
updateBatchQueueDisplay();
// Start monitoring
startStatusCheck();
alert(`✅ Đã bắt đầu batch processing với ${result.total_jobs} jobs!`);
} else {
throw new Error(result.detail || 'Lỗi khi bắt đầu batch');
}
} catch (error) {
console.error('Error starting batch:', error);
alert('❌ Lỗi: ' + error.message);
}
}
// Check batch status
async function checkBatchStatus() {
try {
const response = await fetch('/api/batch/status');
const status = await response.json();
// Update status counts
document.getElementById('statusQueued').textContent = status.queue.queued;
document.getElementById('statusRunning').textContent = status.queue.running;
document.getElementById('statusCompleted').textContent = status.queue.completed;
document.getElementById('statusFailed').textContent = status.queue.failed;
// Update running jobs
const runningDiv = document.getElementById('runningJobs');
if (status.jobs.running.length > 0) {
runningDiv.innerHTML = status.jobs.running.map(job => {
const outputFile = job.result?.output_file || '';
const pngFile = job.result?.png_file || '';
const outputFilename = outputFile ? outputFile.split('/').pop() : '';
const pngFilename = pngFile ? pngFile.split('/').pop() : '';
return `
<div class="batch-item running">
<div style="flex: 1;">
<strong>${job.name}</strong> - <span style="color: #ffc107;">Running</span><br>
<small>Job ID: ${job.job_id}</small>
<div class="progress">
<div class="progress-bar" style="width: ${job.progress}%">${job.progress}%</div>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; min-width: 200px; flex-shrink: 0;">
<button class="btn btn-success" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadResult('${outputFilename}')"
${outputFilename ? '' : 'disabled'}>
💾 Download GeoTIFF
</button>
<button class="btn btn-primary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadPNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🖼️ Download PNG
</button>
<button class="btn btn-secondary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="viewLargePNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🔍 View Preview
</button>
</div>
</div>
`;
}).join('');
} else {
runningDiv.innerHTML = '<p style="text-align: center; color: #666;">Không có job nào đang chạy</p>';
}
// Update completed results
const completedDiv = document.getElementById('completedResults');
if (status.jobs.recent_completed.length > 0) {
completedDiv.innerHTML = status.jobs.recent_completed.map(job => {
const outputFile = job.result?.output_file || '';
const pngFile = job.result?.png_file || '';
const outputFilename = outputFile ? outputFile.split('/').pop() : '';
const pngFilename = pngFile ? pngFile.split('/').pop() : '';
return `
<div class="batch-item completed">
<div style="flex: 1;">
<strong>${job.name}</strong> - <span style="color: #28a745;">✓ Completed</span><br>
<small>Job ID: ${job.job_id}</small><br>
<small>Completed: ${new Date(job.completed_at).toLocaleString()}</small><br>
<small><strong>Shape:</strong> ${job.result?.shape ? job.result.shape.join(' x ') : 'N/A'}</small><br>
<small><strong>Classes:</strong> ${job.result?.unique_classes ? job.result.unique_classes.join(', ') : 'N/A'}</small><br>
<small><strong>Features:</strong> ${job.result?.n_features || 'N/A'}</small><br>
<small><strong>Output:</strong> ${outputFilename || 'N/A'}</small><br>
<div style="margin-top: 10px;">
<img src="/api/predictions/preview/${pngFilename}"
style="max-width: 100%; max-height: 300px; border-radius: 5px; cursor: pointer; ${pngFilename ? '' : 'display:none;'}"
onclick="viewLargePNG('${pngFilename}')"
title="Click để xem lớn hơn"
onerror="this.style.display='none'">
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; min-width: 200px; flex-shrink: 0;">
<button class="btn btn-success" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadResult('${outputFilename}')"
${outputFilename ? '' : 'disabled'}>
💾 Download GeoTIFF
</button>
<button class="btn btn-primary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadPNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🖼️ Download PNG
</button>
<button class="btn btn-secondary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="viewLargePNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🔍 View Preview
</button>
</div>
</div>
`;
}).join('');
} else {
completedDiv.innerHTML = '<p style="text-align: center; color: #666;">Chưa có kết quả nào</p>';
}
// Show failed jobs if any
if (status.jobs.recent_failed.length > 0) {
const failedHTML = status.jobs.recent_failed.map(job => {
const outputFile = job.result?.output_file || '';
const pngFile = job.result?.png_file || '';
const outputFilename = outputFile ? outputFile.split('/').pop() : '';
const pngFilename = pngFile ? pngFile.split('/').pop() : '';
return `
<div class="batch-item failed">
<div style="flex: 1;">
<strong>${job.name}</strong> - <span style="color: #dc3545;">✗ Failed</span><br>
<small>Job ID: ${job.job_id}</small><br>
<small style="color: #dc3545;">${job.error || 'Unknown error'}</small>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; min-width: 200px; flex-shrink: 0;">
<button class="btn btn-success" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadResult('${outputFilename}')"
${outputFilename ? '' : 'disabled'}>
💾 Download GeoTIFF
</button>
<button class="btn btn-primary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="downloadPNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🖼️ Download PNG
</button>
<button class="btn btn-secondary" style="padding: 10px 20px; margin: 0; white-space: nowrap;"
onclick="viewLargePNG('${pngFilename}')"
${pngFilename ? '' : 'disabled'}>
🔍 View Preview
</button>
</div>
</div>
`;
}).join('');
completedDiv.innerHTML += '<h3 style="margin-top: 20px; color: #dc3545;">❌ Failed Jobs</h3>' + failedHTML;
}
// Stop checking if all done
if (status.queue.running === 0 && status.queue.queued === 0 && currentBatchId) {
stopStatusCheck();
alert('✅ Batch processing hoàn thành!');
}
} catch (error) {
console.error('Error checking batch status:', error);
}
}
// Download result
function downloadResult(filename) {
window.location.href = `/api/predictions/download/${filename}`;
}
// Download PNG
function downloadPNG(filename) {
window.location.href = `/api/predictions/preview/${filename}`;
}
// View large PNG in new window
function viewLargePNG(filename) {
const modal = document.getElementById('previewModal');
const img = document.getElementById('previewImage');
img.src = `/api/predictions/preview/${filename}`;
modal.style.display = 'block';
}
// Close preview modal
function closePreviewModal() {
document.getElementById('previewModal').style.display = 'none';
}
// Load all batch results
async function loadAllBatchResults() {
try {
const response = await fetch('/api/batch/status');
const status = await response.json();
const completedDiv = document.getElementById('completedResults');
// Combine recent_completed from status
const allCompleted = status.jobs.recent_completed || [];
if (allCompleted.length === 0) {
completedDiv.innerHTML = '<p style="text-align: center; color: #666;">Chưa có kết quả nào</p>';
return;
}
completedDiv.innerHTML = allCompleted.map(job => `
<div class="batch-item completed">
<div style="flex: 1;">
<strong>${job.name}</strong> - <span style="color: #28a745;">✓ Completed</span><br>
<small>Job ID: ${job.job_id}</small><br>
<small>Completed: ${new Date(job.completed_at).toLocaleString()}</small><br>
${job.result ? `
<small><strong>Shape:</strong> ${job.result.shape.join(' x ')}</small><br>
<small><strong>Classes:</strong> ${job.result.unique_classes.join(', ')}</small><br>
<small><strong>Features:</strong> ${job.result.n_features}</small><br>
<small><strong>Model:</strong> ${job.result.model_used}</small><br>
${job.result.png_file ? `
<div style="margin-top: 10px;">
<img src="/api/predictions/preview/${job.result.png_file.split('/').pop()}"
style="max-width: 100%; border-radius: 5px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2);"
onclick="viewLargePNG('${job.result.png_file.split('/').pop()}')"
title="Click để xem lớn hơn">
</div>
` : ''}
` : ''}
</div>
<div style="display: flex; flex-direction: column; gap: 5px; min-width: 200px;">
${job.result && job.result.output_file ? `
<button class="btn btn-success" style="padding: 8px 20px;" onclick="downloadResult('${job.result.output_file.split('/').pop()}')">
💾 Download GeoTIFF
</button>
${job.result.png_file ? `
<button class="btn btn-primary" style="padding: 8px 20px;" onclick="downloadPNG('${job.result.png_file.split('/').pop()}')">
🖼️ Download PNG
</button>
<button class="btn btn-secondary" style="padding: 8px 20px;" onclick="viewLargePNG('${job.result.png_file.split('/').pop()}')">
🔍 View Preview
</button>
` : ''}
` : ''}
</div>
</div>
`).join('');
} catch (error) {
console.error('Error loading batch results:', error);
}
}
// Download all results as ZIP (placeholder)
function downloadAllResults() {
alert('💡 Tính năng download tất cả batch results sẽ được thêm trong phiên bản tiếp theo.\\nHiện tại vui lòng download từng file riêng lẻ.');
}
// Start/stop status monitoring
function startStatusCheck() {
if (statusCheckInterval) clearInterval(statusCheckInterval);
statusCheckInterval = setInterval(checkBatchStatus, 3000);
}
function stopStatusCheck() {
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
statusCheckInterval = null;
}
}
// Initialize on load
window.onload = function() {
initMap();
loadModels();
};
// Cleanup on unload
window.onbeforeunload = function() {
stopStatusCheck();
};
</script>
</body>
</html>