Arrêt de génération et reclassification optionnelle depuis l'interface
- POST /api/stop : SIGTERM au pipeline (groupe isolé via start_new_session), SIGKILL du groupe après 15 s ; forwardé au worker en mode deux machines - « Régénérer » n'implique plus la reclassification : le DTM en cache est réutilisé tant que la méthode ne change pas ; nouvelle case « Reclasser le sol » (--force-classification) et rappel de la classification existante - Tâche marquée « Interrompu » (returncode négatif ou nul) au lieu d'Échec
This commit is contained in:
@ -1216,6 +1216,10 @@ _HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<label class="gen-opt">
|
||||
<input type="checkbox" id="genRegen"> Régénérer les tuiles existantes
|
||||
</label>
|
||||
<label class="gen-opt" title="Par défaut la classification du sol existante est conservée (DTM réutilisé). Cochez pour la relancer sur toutes les tuiles ; choisir une autre méthode ci-dessous reclassifie déjà les tuiles concernées.">
|
||||
<input type="checkbox" id="genReclass"> Reclasser le sol
|
||||
</label>
|
||||
<div class="gen-hint" id="genClassInfo" hidden></div>
|
||||
<label class="gen-opt col" title="Toutes pré-sélectionnées — Ctrl+clic pour modifier la sélection">
|
||||
<span>Visualisations :</span>
|
||||
<select id="genViz" class="gen-select" multiple size="8">
|
||||
@ -1250,6 +1254,7 @@ _HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<div class="queue-head">
|
||||
<span class="panel-title">File de génération</span>
|
||||
<span id="genQueueCount"></span>
|
||||
<button id="genStop" class="icon-btn" title="Arrêter la génération en cours" hidden>■</button>
|
||||
<button id="genQueueClear" class="icon-btn" title="Retirer les tâches terminées">Vider</button>
|
||||
</div>
|
||||
<div id="genQueueBody"></div>
|
||||
@ -1638,6 +1643,7 @@ body.zooming .ltile { will-change: transform; }
|
||||
.gen-opt.col { flex-direction: column; align-items: stretch; gap: 4px; }
|
||||
.gen-opt.col > span { color: var(--muted); font-size: 11px; }
|
||||
.gen-opt select { flex: 1; min-width: 0; }
|
||||
.gen-hint { color: var(--muted); font-size: 11px; padding: 0 2px; }
|
||||
.gen-run { display: flex; gap: 8px; margin-top: 2px; }
|
||||
.gen-run #genGo { flex: 1; }
|
||||
|
||||
@ -1658,7 +1664,7 @@ body.zooming .ltile { will-change: transform; }
|
||||
background: var(--accent); animation: gen-pulse 1.1s ease-in-out infinite;
|
||||
}
|
||||
.gen-task.done .gen-task-dot { background: #39d98a; }
|
||||
.gen-task.failed .gen-task-dot { background: var(--danger); }
|
||||
.gen-task.failed .gen-task-dot, .gen-task.stopped .gen-task-dot { background: var(--danger); }
|
||||
@keyframes gen-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
|
||||
.gen-task-label {
|
||||
font-size: 12px; flex: 1; min-width: 120px;
|
||||
@ -1667,7 +1673,7 @@ body.zooming .ltile { will-change: transform; }
|
||||
.gen-task-state { font-size: 11px; white-space: nowrap; }
|
||||
.gen-task.running .gen-task-state { color: var(--accent); }
|
||||
.gen-task.done .gen-task-state { color: #39d98a; }
|
||||
.gen-task.failed .gen-task-state { color: var(--danger); }
|
||||
.gen-task.failed .gen-task-state, .gen-task.stopped .gen-task-state { color: var(--danger); }
|
||||
.gen-task-time {
|
||||
font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@ -2404,6 +2410,7 @@ function regenerateCurrentTile() {
|
||||
document.getElementById('genRegen').checked = true;
|
||||
genGo.style.display = '';
|
||||
showGenBar('1 tuile à régénérer — choisissez les options puis lancez');
|
||||
updateGenClassInfo();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@ -2419,12 +2426,15 @@ const genMsg = document.getElementById('genMsg');
|
||||
const genGo = document.getElementById('genGo');
|
||||
const genCancel = document.getElementById('genCancel');
|
||||
const genRegen = document.getElementById('genRegen');
|
||||
const genReclass = document.getElementById('genReclass');
|
||||
const genClassInfo = document.getElementById('genClassInfo');
|
||||
const genClass = document.getElementById('genClass');
|
||||
const genIgnClasses = document.getElementById('genIgnClasses');
|
||||
const genViz = document.getElementById('genViz');
|
||||
const genBareEarth = document.getElementById('genBareEarth');
|
||||
const genQueue = document.getElementById('genQueue');
|
||||
const genBadge = document.getElementById('genBadge');
|
||||
const genStop = document.getElementById('genStop');
|
||||
const genQueueBody = document.getElementById('genQueueBody');
|
||||
const genQueueClear = document.getElementById('genQueueClear');
|
||||
const genQueueCount = document.getElementById('genQueueCount');
|
||||
@ -2442,6 +2452,24 @@ function showGenBar(msg) {
|
||||
genBar.classList.add('show');
|
||||
}
|
||||
|
||||
// Classification déjà présente sur les tuiles visées : guide le choix
|
||||
// « Reclasser le sol » (décoché = la classification existante est conservée).
|
||||
function updateGenClassInfo() {
|
||||
if (!genClassInfo) return;
|
||||
const methods = new Set();
|
||||
for (const pair of (pendingTiles || [])) {
|
||||
for (const t of TILES) {
|
||||
if (t.col === pair[0] && t.row === pair[1] && t.meta && t.meta.method)
|
||||
methods.add(t.meta.method);
|
||||
}
|
||||
}
|
||||
if (!methods.size) { genClassInfo.hidden = true; return; }
|
||||
genClassInfo.textContent = 'Classification existante : ' +
|
||||
[...methods].map(methodLabel).join(', ') +
|
||||
' — conservée si « Reclasser le sol » est décoché et que la méthode ne change pas';
|
||||
genClassInfo.hidden = false;
|
||||
}
|
||||
|
||||
fetch('/api/status').then(r => {
|
||||
apiAvailable = r.ok;
|
||||
if (!apiAvailable) hideExportTab();
|
||||
@ -2512,6 +2540,7 @@ map.on('mouseup', e => {
|
||||
if (drawRect) { drawRect.remove(); drawRect = null; }
|
||||
pendingTiles = (d.cells || []).map(c => [c.col, c.row]);
|
||||
pendingCompleteAll = false; // zone dessinée : le scope global est levé
|
||||
updateGenClassInfo();
|
||||
(d.cells || []).forEach(c => ghostCells.push(
|
||||
L.polygon(c.corners, { color: '#e94560', weight: 1, dashArray: '4 4', fillOpacity: 0.08, pane: 'genDraw' }).addTo(map)
|
||||
));
|
||||
@ -2563,6 +2592,7 @@ function toggleGenSel(t) {
|
||||
pendingRegen = true;
|
||||
pendingCompleteAll = false;
|
||||
updateGenSelBar();
|
||||
updateGenClassInfo();
|
||||
}
|
||||
|
||||
function setGenSelMode(on) {
|
||||
@ -2623,6 +2653,7 @@ btnComplete.addEventListener('click', () => {
|
||||
}).then(r => r.json()).then(d => {
|
||||
pendingTiles = (d.cells || []).map(c => [c.col, c.row]);
|
||||
pendingCompleteAll = true;
|
||||
updateGenClassInfo();
|
||||
(d.cells || []).forEach(c => ghostCells.push(
|
||||
L.polygon(c.corners, { color: '#e94560', weight: 1, dashArray: '4 4', fillOpacity: 0.08, pane: 'genDraw' }).addTo(map)
|
||||
));
|
||||
@ -2652,7 +2683,8 @@ genGo.addEventListener('click', () => {
|
||||
all_missing: pendingCompleteAll,
|
||||
ground_class: genClass ? genClass.value : 'ign',
|
||||
ign_classes: genIgnClasses ? genIgnClasses.value : 'sol',
|
||||
bare_earth: genBareEarth ? genBareEarth.checked : false })
|
||||
bare_earth: genBareEarth ? genBareEarth.checked : false,
|
||||
reclassify: genReclass ? genReclass.checked : false })
|
||||
}).then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.detail || ('HTTP ' + r.status))))
|
||||
.then(() => {
|
||||
const verb = isRegen ? 'régénération'
|
||||
@ -2669,6 +2701,7 @@ genCancel.addEventListener('click', () => {
|
||||
genBar.classList.remove('show');
|
||||
clearGhosts();
|
||||
pendingTiles = []; pendingRegen = false; pendingCompleteAll = false;
|
||||
updateGenClassInfo();
|
||||
clearGenSel();
|
||||
if (genMode) exitGenMode();
|
||||
});
|
||||
@ -2943,6 +2976,8 @@ function genTaskEl(t) {
|
||||
: 'En cours…';
|
||||
} else if (t.state === 'done') {
|
||||
state.textContent = 'Terminé';
|
||||
} else if (t.state === 'stopped') {
|
||||
state.textContent = 'Interrompu';
|
||||
} else {
|
||||
state.textContent = 'Échec (code ' + t.returncode + ')';
|
||||
}
|
||||
@ -2988,6 +3023,7 @@ function renderGenQueue() {
|
||||
const running = genTasks.filter(t => t.state === 'running').length;
|
||||
genBadge.hidden = running === 0;
|
||||
genBadge.textContent = running > 0 ? String(running) : '';
|
||||
if (genStop) { genStop.hidden = running === 0; genStop.disabled = false; }
|
||||
genQueueCount.textContent = genTasks.length
|
||||
? (running ? running + ' en cours / ' + genTasks.length : genTasks.length + ' tâche(s)')
|
||||
: '';
|
||||
@ -3008,7 +3044,10 @@ function finishRunningTasks(d) {
|
||||
let changed = false;
|
||||
for (const t of genTasks) {
|
||||
if (t.state !== 'running') continue;
|
||||
t.state = d.returncode === 0 ? 'done' : 'failed';
|
||||
// returncode négatif = tué par /api/stop ; null = serveur relancé en plein
|
||||
// run (job disparu) : dans les deux cas la tâche est interrompue, pas échouée.
|
||||
t.state = d.returncode === 0 ? 'done'
|
||||
: (d.returncode === null || d.returncode < 0) ? 'stopped' : 'failed';
|
||||
t.returncode = d.returncode;
|
||||
changed = true;
|
||||
}
|
||||
@ -3154,6 +3193,19 @@ genQueueClear.addEventListener('click', () => {
|
||||
saveGenTasks(); renderGenQueue();
|
||||
});
|
||||
|
||||
// Arrêt de la génération en cours : SIGTERM au pipeline côté serveur, qui
|
||||
// nettoie ses workers et ses processus PDAL ; le sondage marque ensuite la
|
||||
// tâche « Interrompu » (returncode négatif).
|
||||
if (genStop) genStop.addEventListener('click', () => {
|
||||
genStop.disabled = true;
|
||||
fetch('/api/stop', { method: 'POST' })
|
||||
.then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.detail || ('HTTP ' + r.status))))
|
||||
.catch(msg => {
|
||||
genStop.disabled = false;
|
||||
showGenBar('Erreur arrêt : ' + msg);
|
||||
});
|
||||
});
|
||||
|
||||
renderGenQueue();
|
||||
// Reprend le suivi si une tâche tournait avant un rechargement de la page,
|
||||
// adopte une génération lancée hors interface (API directe), ou réaffiche le
|
||||
@ -3175,7 +3227,8 @@ else fetch('/api/status').then(r => r.ok ? r.json() : null).then(s => {
|
||||
genTasks.push({
|
||||
id: Date.now(), label: 'Dernière génération',
|
||||
time: now, started: s.started,
|
||||
state: s.returncode === 0 ? 'done' : 'failed', returncode: s.returncode,
|
||||
state: s.returncode === 0 ? 'done' : s.returncode < 0 ? 'stopped' : 'failed',
|
||||
returncode: s.returncode,
|
||||
log: [], logOpen: false, tiles: s.tiles,
|
||||
});
|
||||
saveGenTasks(); renderGenQueue();
|
||||
|
||||
Reference in New Issue
Block a user