Files
Abschluss-Projekt/overview-training.html
2025-12-02 12:50:22 +01:00

457 lines
20 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="stylesheet" href="globals.css" />
<link rel="stylesheet" href="styleguide.css" />
<link rel="stylesheet" href="style.css" />
<style>
#projects-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: flex-start;
align-items: flex-start;
}
.dataset-card {
flex: 0 0 auto;
}
</style>
</head>
<body onload="pollStatus()">
<div>
<div id="header">
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" style="cursor: pointer;"
src="./media/logo.png" alt="Logo"></icon>
<label id="project-title-label"
style="display: block; text-align: left; font-weight: bold; font-size: x-large;">Project</label>
<div class="button-row">
<!-- Training Notification Bell -->
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
🔔
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
text-align: center; font-weight: bold;">0</span>
</button>
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
Training Project</button>
<button id="seed-db-btn" class="button">
Seed Database
<div class="loader" id="loader" style="display: none"></div>
</button>
<button id="settings-btn" onclick="window.openSettingsModal()" class="button" title="Einstellungen" style="padding: 8px 12px; margin-left: 10px;">⚙️</button>
</div>
</div>
</button>
<script>
document.getElementById('seed-db-btn').addEventListener('click', function () {
const elLoader = document.getElementById("loader")
elLoader.style.display = "inherit"
fetch('/api/seed')
.finally(() => {
// Instead of hiding loader immediately, poll /api/update-status until done
function pollStatus() {
fetch('/api/update-status')
.then(res => res.json())
.then(status => {
if (status && status.running) {
// Still running, poll again after short delay
setTimeout(pollStatus, 5000);
} else {
elLoader.style.display = "none";
}
})
.catch(() => {
elLoader.style.display = "none";
});
}
pollStatus();
})
});
// Show loader if backend is still processing on page load
function pollStatus() {
const elLoader = document.getElementById("loader");
fetch('/api/update-status')
.then(res => res.json())
.then(status => {
if (status && status.running) {
elLoader.style.display = "inherit";
setTimeout(pollStatus, 5000);
} else {
elLoader.style.display = "none";
}
})
.catch(() => {
elLoader.style.display = "none";
});
}
</script>
<script>
// Declare urlParams and projectId once
const urlParams = new URLSearchParams(window.location.search);
const projectId = urlParams.get('id');
// Set project title in header
fetch('/api/training-projects')
.then(res => res.json())
.then(projects => {
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
if (project) {
const titleLabel = document.getElementById('project-title-label');
if (titleLabel) titleLabel.textContent = '/' + project.title;
}
});
// Render trainings
function renderTrainings(trainings) {
const list = document.getElementById('projects-list');
list.innerHTML = '';
if (!Array.isArray(trainings) || trainings.length === 0) {
list.innerHTML = '<div style="color:#009eac;padding:16px;">No trainings found for this project.</div>';
return;
}
trainings.forEach(training => {
const card = document.createElement('div');
card.className = 'dataset-card';
card.style = 'border:1px solid #009eac;padding:12px;margin:8px;border-radius:8px;background:#eaf7fa;position:relative;min-width:320px;min-height:120px;display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;';
// Info section (left)
const infoDiv = document.createElement('div');
infoDiv.style = 'flex:1; text-align:left;';
infoDiv.innerHTML = `<b>${training.exp_name || 'Training'}</b><br>Epochs: ${training.max_epoch}<br>Depth: ${training.depth}<br>Width: ${training.width}<br>Activation: ${training.activation || training.act || ''}`;
// Buttons section (right)
const btnDiv = document.createElement('div');
btnDiv.style = 'display:flex;flex-direction:column;align-items:flex-end;gap:8px;min-width:160px;';
// Start Training button
const startBtn = document.createElement('button');
startBtn.textContent = 'Start YOLOX Training';
startBtn.style = 'background:#009eac;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
startBtn.onclick = function() {
startBtn.disabled = true;
fetch('/api/start-yolox-training', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ project_id: projectId, training_id: training.id })
})
.then(res => res.json())
.then(result => {
alert(result.message || 'Training started');
startBtn.disabled = false;
})
.catch(() => {
alert('Failed to start training');
startBtn.disabled = false;
});
};
btnDiv.appendChild(startBtn);
// View Training Details button
const detailsBtn = document.createElement('button');
detailsBtn.textContent = 'View Training Details';
detailsBtn.style = 'background:#666;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
detailsBtn.onclick = function() {
// Navigate to edit-training page in read-only mode
window.location.href = `/edit-training.html?training_id=${training.id}&readonly=true`;
};
btnDiv.appendChild(detailsBtn);
// Remove button
const removeBtn = document.createElement('button');
removeBtn.textContent = 'Remove';
removeBtn.style = 'background:#ff4d4f;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
removeBtn.onclick = function() {
if (confirm('Are you sure you want to delete this training?')) {
fetch(`/api/trainings/${training.id}`, { method: 'DELETE' })
.then(res => res.json())
.then(result => {
alert(result.message || 'Training deleted');
fetchTrainings(); // Refresh list
})
.catch(() => alert('Failed to delete training'));
}
};
btnDiv.appendChild(removeBtn);
card.appendChild(infoDiv);
card.appendChild(btnDiv);
list.appendChild(card);
});
// Modal for log display
if (!document.getElementById('log-modal')) {
const modal = document.createElement('div');
modal.id = 'log-modal';
modal.style = 'display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.5);z-index:9999;justify-content:center;align-items:center;';
modal.innerHTML = `<div style="background:#fff;padding:24px;border-radius:8px;max-width:800px;width:90vw;max-height:80vh;overflow:auto;position:relative;"><pre id='log-content' style='font-size:13px;white-space:pre-wrap;word-break:break-all;max-height:60vh;overflow:auto;background:#f7f7f7;padding:12px;border-radius:6px;'></pre><button id='close-log-modal' style='position:absolute;top:8px;right:8px;background:#009eac;color:#fff;border:none;border-radius:4px;padding:6px 12px;cursor:pointer;'>Close</button></div>`;
document.body.appendChild(modal);
document.getElementById('close-log-modal').onclick = function() {
modal.style.display = 'none';
};
}
// Function to show log modal and poll log
function showLogModal(trainingId) {
const modal = document.getElementById('log-modal');
const logContent = document.getElementById('log-content');
modal.style.display = 'flex';
function fetchLog() {
fetch(`/api/training-log?project_id=${projectId}&training_id=${trainingId}`)
.then(res => res.json())
.then(data => {
logContent.textContent = data.log || 'No log found.';
})
.catch(() => {
logContent.textContent = 'Failed to fetch log.';
});
}
fetchLog();
// Poll every 5 seconds while modal is open
let poller = setInterval(() => {
if (modal.style.display === 'flex') fetchLog();
else clearInterval(poller);
}, 5000);
}
}
// Fetch trainings for project
function fetchTrainings() {
if (!projectId) return;
fetch(`/api/trainings?project_id=${projectId}`)
.then(res => res.json())
.then(trainings => {
renderTrainings(trainings);
});
}
window.addEventListener('DOMContentLoaded', fetchTrainings);
</script>
<div style="padding: 16px; text-align: left;">
<button id="create-new-training-btn" class="button" style="background:#009eac;color:white;">
+ Create New Training
</button>
</div>
<div id="projects-list"></div>
</div>
<script>
// Create New Training button handler
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('create-new-training-btn').addEventListener('click', function() {
if (!projectId) {
alert('No project selected');
return;
}
// Navigate to edit-training page to configure new training parameters
// This will reuse existing project details and class mappings
window.location.href = `/edit-training.html?project_id=${projectId}`;
});
});
</script>
<!-- Settings Modal -->
<div id="settings-modal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 600px; max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h2>Einstellungen</h2>
<button class="close-btn" onclick="window.closeSettingsModal()">&times;</button>
</div>
<div class="modal-body">
<!-- Label Studio Settings -->
<div class="settings-section">
<h3>Label Studio</h3>
<div class="form-group">
<label for="labelstudio-url">API URL:</label>
<input type="text" id="labelstudio-url" placeholder="http://192.168.1.19:8080">
</div>
<div class="form-group">
<label for="labelstudio-token">API Token:</label>
<input type="password" id="labelstudio-token" placeholder="API Token">
</div>
<button id="test-labelstudio-btn" class="button">
Verbindung testen
<div class="loader" id="test-ls-loader" style="display: none"></div>
</button>
<div id="labelstudio-status" class="status-message"></div>
</div>
<!-- YOLOX Settings -->
<div class="settings-section">
<h3>YOLOX</h3>
<div class="form-group">
<label for="yolox-path">Installation Path:</label>
<input type="text" id="yolox-path" placeholder="C:/YOLOX">
</div>
<div class="form-group">
<label for="yolox-venv-path">Virtual Environment Path:</label>
<input type="text" id="yolox-venv-path" placeholder="/path/to/venv/bin/activate">
</div>
<div class="form-group">
<label for="yolox-output-path">Output Folder:</label>
<input type="text" id="yolox-output-path" placeholder="./backend">
<small style="display: block; margin-top: 4px; color: #666;">Folder for experiment files and JSON files</small>
</div>
<div class="form-group">
<label for="yolox-data-dir">Data Directory:</label>
<input type="text" id="yolox-data-dir" placeholder="/home/kitraining/To_Annotate/">
<small style="display: block; margin-top: 4px; color: #666;">Path where training images are located</small>
</div>
<button id="test-yolox-btn" class="button">
Pfad überprüfen
<div class="loader" id="test-yolox-loader" style="display: none"></div>
</button>
<div id="yolox-status" class="status-message"></div>
</div>
<!-- Save Button -->
<div class="settings-section">
<button id="save-settings-btn" class="button-red">Einstellungen speichern</button>
<div id="save-status" class="status-message"></div>
</div>
</div>
</div>
</div>
<!-- Training Status Modal -->
<div id="training-status-modal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h2>Training Status</h2>
<button class="close-btn" onclick="toggleTrainingModal()">&times;</button>
</div>
<div class="modal-body">
<!-- Current Training -->
<div class="settings-section" id="current-training-section" style="display: none;">
<h3 style="color: #009eac;">Current Training</h3>
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
<!-- Populated by JS -->
</div>
</div>
<!-- Queued Trainings -->
<div class="settings-section" id="queue-section" style="display: none;">
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
<!-- Populated by JS -->
</div>
</div>
<!-- No trainings message -->
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
No trainings running or queued.
</div>
</div>
</div>
</div>
<script src="js/settings.js"></script>
<script>
// Training status polling
let trainingStatusPoller = null;
function toggleTrainingModal() {
const modal = document.getElementById('training-status-modal');
if (modal.style.display === 'none') {
modal.style.display = 'flex';
updateTrainingStatus(); // Immediate update
} else {
modal.style.display = 'none';
}
}
function updateTrainingStatus() {
fetch('/api/training-status')
.then(res => res.json())
.then(data => {
const bell = document.getElementById('training-bell');
const badge = document.getElementById('bell-badge');
const currentSection = document.getElementById('current-training-section');
const queueSection = document.getElementById('queue-section');
const noTrainingsMsg = document.getElementById('no-trainings-msg');
const totalCount = (data.current ? 1 : 0) + data.queue.length;
// Update bell appearance
if (totalCount > 0) {
bell.style.background = '#009eac';
badge.style.display = 'block';
badge.textContent = totalCount;
} else {
bell.style.background = '#999';
badge.style.display = 'none';
}
// Update modal content
if (data.current) {
currentSection.style.display = 'block';
noTrainingsMsg.style.display = 'none';
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
document.getElementById('current-training-info').innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<strong>${data.current.name || 'Training'}</strong>
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
</div>
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
</div>
<div style="font-size: 14px; color: #666;">
Epoch ${data.current.iteration} / ${data.current.max_epoch}
</div>
`;
} else {
currentSection.style.display = 'none';
}
if (data.queue.length > 0) {
queueSection.style.display = 'block';
noTrainingsMsg.style.display = 'none';
document.getElementById('queue-count').textContent = data.queue.length;
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
<div style="font-size: 13px; color: #666; margin-top: 4px;">
${t.max_epoch} epochs • Waiting...
</div>
</div>
`).join('');
} else {
queueSection.style.display = 'none';
}
if (totalCount === 0) {
noTrainingsMsg.style.display = 'block';
}
})
.catch(err => console.error('Failed to fetch training status:', err));
}
// Poll every 5 seconds
window.addEventListener('DOMContentLoaded', function() {
updateTrainingStatus();
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
});
// Stop polling when page unloads
window.addEventListener('beforeunload', function() {
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
});
</script>
</body>
</html>