299 lines
12 KiB
HTML
Executable File
299 lines
12 KiB
HTML
Executable File
<!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>
|
|
<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>
|
|
<div id="projects-list">
|
|
<script src="js/dashboard.js"></script>
|
|
</div>
|
|
<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>
|
|
|
|
</div>
|
|
|
|
<!-- 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()">×</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()">×</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> |