feat: multi-model modes (A/B/C/D, default B) + box counting + manual-mode banner
ci / smoke (push) Canceled after 0s

This commit is contained in:
andrew committed 2026-09-11 14:40:57 +07:00
1 parent 52d3127302
commit 6e4af50646
14 files changed
+819 -128

No files matched your search

+175 -5
View File
@@ -167,10 +167,73 @@
font-weight: 600;
border-radius: var(--radius-md);
}
/* Non-blocking mode banner (fixed top, status stays visible below) */
.mode-banner {
position: sticky;
top: 0;
z-index: 500;
display: none;
align-items: center;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
background-color: rgba(255, 204, 0, 0.15);
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
}
.mode-banner .btn {
padding: 6px 14px;
font-size: 13px;
font-weight: 700;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
}
.mode-banner .btn-yes {
background-color: var(--accent-green);
color: #fff;
}
.mode-banner .btn-no {
background-color: var(--bg-base);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.model-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 16px;
font-size: 13px;
color: var(--text-secondary);
}
.model-row select {
padding: 6px 10px;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
background-color: var(--bg-base);
color: var(--text-primary);
font-size: 13px;
}
</style>
{% endblock %}
{% block content %}
<!-- Non-blocking manual-mode prompt: status/counts stay visible below -->
<div id="modeBanner" class="mode-banner">
<span>🟡 Mode OTOMATIS aktif. Alihkan ke MANUAL untuk kontrol tombol batch?</span>
<button class="btn btn-yes" onclick="switchToManual()">Ya, Manual</button>
<button class="btn btn-no" onclick="dismissModeBanner()">Tidak, Tetap Otomatis</button>
</div>
<div class="operator-container">
<div class="operator-card">
<h1 class="operator-title"><i class="fa-solid fa-boxes-packing"></i> Kontrol Pemuatan Karung</h1>
@@ -192,6 +255,14 @@
<div class="info-label">Waktu Mulai</div>
<div class="info-val" id="opStartTime">--:--:--</div>
</div>
<div class="info-item">
<div class="info-label">Karung Masuk</div>
<div class="info-val" id="opSackCount">0</div>
</div>
<div class="info-item">
<div class="info-label">Box Masuk</div>
<div class="info-val" id="opBoxCount">0</div>
</div>
</div>
<!-- Action Button -->
@@ -203,6 +274,16 @@
<i class="fa-solid fa-square"></i> SELESAI / AKHIRI BATCH
</button>
</div>
<!-- Model mode -->
<div class="model-row">
<span>Model:</span>
<strong id="opModelMode">--</strong>
<select id="modelModeSelect" onchange="setModelMode(this.value)">
<option value="">Ganti mode…</option>
</select>
</div>
<p class="operator-subtitle" style="margin-top:6px;font-size:12px">Ganti mode model berlaku setelah service direstart.</p>
</div>
</div>
@@ -239,6 +320,86 @@
document.getElementById(id).style.display = 'none';
}
// --- Non-blocking manual-mode prompt (banner, status stays visible) ---
async function loadModeBanner() {
try {
const res = await fetch('/api/batch/mode');
const data = await res.json();
if (data.mode === 'auto' && !sessionStorage.getItem('mode_banner_dismissed')) {
document.getElementById('modeBanner').style.display = 'flex';
}
} catch (e) {
console.error('Mode load error:', e);
}
}
function dismissModeBanner() {
sessionStorage.setItem('mode_banner_dismissed', '1');
document.getElementById('modeBanner').style.display = 'none';
}
async function switchToManual() {
try {
const res = await fetch('/api/batch/mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'manual' })
});
const data = await res.json();
if (data.success) {
dismissModeBanner();
} else {
alert('Gagal alih mode: ' + (data.error || 'Terjadi kesalahan'));
}
} catch (e) {
alert('Gagal terhubung ke server');
}
}
// --- Model mode selector ---
async function loadModelModes() {
try {
const res = await fetch('/api/model-modes');
const data = await res.json();
if (!data.success) return;
const sel = document.getElementById('modelModeSelect');
data.modes.forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.id + ' — ' + m.description;
sel.appendChild(opt);
});
} catch (e) {
console.error('Model modes load error:', e);
}
}
async function setModelMode(mode) {
if (!mode) return;
if (!confirm('Ganti mode model ke ' + mode + '? Berlaku setelah service karung-counter direstart.')) {
document.getElementById('modelModeSelect').value = '';
return;
}
try {
const res = await fetch('/api/batch/mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model_mode: mode })
});
const data = await res.json();
if (data.success) {
document.getElementById('opModelMode').textContent = data.model_mode;
alert('Mode model tersimpan: ' + data.model_mode + '. Restart service untuk berlaku.');
} else {
alert('Gagal ganti mode: ' + (data.error || 'Terjadi kesalahan'));
}
} catch (e) {
alert('Gagal terhubung ke server');
} finally {
document.getElementById('modelModeSelect').value = '';
}
}
function confirmStartBatch() {
document.getElementById('modalStart').style.display = 'flex';
}
@@ -301,15 +462,22 @@
const text = document.getElementById('statusText');
const batchNum = document.getElementById('opBatchNum');
const startTime = document.getElementById('opStartTime');
const sackCount = document.getElementById('opSackCount');
const boxCount = document.getElementById('opBoxCount');
const modelMode = document.getElementById('opModelMode');
const btnStart = document.getElementById('btnStartBatch');
const btnStop = document.getElementById('btnStopBatch');
if (data.model_mode) modelMode.textContent = data.model_mode;
sackCount.textContent = data.count || 0;
boxCount.textContent = data.box_count || 0;
if (data.success && data.batch_number) {
isBatchActive = true;
badge.className = 'status-badge-box status-active';
icon.className = 'fa-solid fa-circle-play';
text.textContent = 'Status: Sedang Memuat (Batch #' + data.batch_number + ')';
batchNum.textContent = '#' + data.batch_number;
if (data.start_time) {
const st = new Date(data.start_time);
@@ -317,7 +485,7 @@
} else {
startTime.textContent = '--:--:--';
}
btnStart.style.display = 'none';
btnStop.style.display = 'flex';
} else {
@@ -325,10 +493,10 @@
badge.className = 'status-badge-box status-idle';
icon.className = 'fa-solid fa-circle-pause';
text.textContent = 'Status: Standby (Siap Memuat)';
batchNum.textContent = '--';
startTime.textContent = '--:--:--';
btnStart.style.display = 'flex';
btnStop.style.display = 'none';
}
@@ -339,5 +507,7 @@
setInterval(pollStatus, 2000);
pollStatus();
loadModeBanner();
loadModelModes();
</script>
{% endblock %}