diff --git a/frontend.tgz b/frontend.tgz
new file mode 100644
index 0000000..e032bd2
Binary files /dev/null and b/frontend.tgz differ
diff --git a/frontend/static/app.js b/frontend/static/app.js
index 28978de..a9dee35 100644
--- a/frontend/static/app.js
+++ b/frontend/static/app.js
@@ -11,6 +11,7 @@ let activeSession = null;
let latestReading = null;
let setpoints = [];
let chartInstance = null;
+let cardsInitialized = false;
async function api(path, options = {}) {
const resp = await fetch(path, options);
@@ -28,46 +29,33 @@ function statusClass(connected) {
return connected ? 'ok' : 'danger';
}
-function renderProbeCards(reading, setpoints) {
+function createProbeCards() {
const container = document.getElementById('probe-cards');
container.innerHTML = '';
- const probes = reading?.probes || [];
- const unit = reading?.unit || 'F';
-
for (let i = 0; i < 4; i++) {
- const probe = probes[i] || { id: i + 1, name: DEFAULT_PROBE_NAMES[i], temperature: null, connected: false };
- const setpoint = setpoints.find(s => s.probe_number === (i + 1));
-
const card = document.createElement('div');
- card.className = `probe-card ${probe.connected ? 'connected' : ''}`;
-
- const tempDisplay = probe.connected
- ? `
${probe.temperature.toFixed(1)}°${unit}
`
- : `---
`;
-
- const targetDisplay = setpoint
- ? `Target: ${setpoint.label || ''} ${setpoint.target_temp.toFixed(0)}°${unit}
`
- : `No target set
`;
+ card.className = 'probe-card';
+ card.dataset.probe = i + 1;
card.innerHTML = `
- ${targetDisplay}
- ${tempDisplay}
+ No target set
+ ---
-
-
+
+
`;
@@ -75,7 +63,7 @@ function renderProbeCards(reading, setpoints) {
container.appendChild(card);
}
- // Wire up preset selectors and save buttons.
+ // Wire up preset selectors and save buttons once.
document.querySelectorAll('.preset-select').forEach(sel => {
sel.addEventListener('change', () => {
const probe = sel.dataset.probe;
@@ -95,6 +83,63 @@ function renderProbeCards(reading, setpoints) {
await saveSetpoint(probe, label, target);
});
});
+
+ cardsInitialized = true;
+}
+
+function updateProbeCards(reading, setpoints) {
+ const probes = reading?.probes || [];
+ const unit = reading?.unit || 'F';
+
+ for (let i = 0; i < 4; i++) {
+ const card = document.querySelector(`.probe-card[data-probe="${i + 1}"]`);
+ if (!card) continue;
+
+ const probe = probes[i] || { id: i + 1, name: DEFAULT_PROBE_NAMES[i], temperature: null, connected: false };
+ const setpoint = setpoints.find(s => s.probe_number === (i + 1));
+
+ card.classList.toggle('connected', probe.connected);
+ card.querySelector('.name').textContent = probe.name || DEFAULT_PROBE_NAMES[i];
+
+ const targetEl = card.querySelector('.target');
+ if (setpoint) {
+ targetEl.textContent = `Target: ${setpoint.label ? setpoint.label + ' ' : ''}${setpoint.target_temp.toFixed(0)}°${unit}`;
+ } else {
+ targetEl.textContent = 'No target set';
+ }
+
+ const tempEl = card.querySelector('.temp');
+ if (probe.connected) {
+ tempEl.classList.remove('disconnected');
+ tempEl.innerHTML = `${probe.temperature.toFixed(1)}°${unit}`;
+ } else {
+ tempEl.classList.add('disconnected');
+ tempEl.textContent = '---';
+ }
+
+ card.querySelector('.status').textContent = probe.connected ? 'Connected' : 'Disconnected';
+
+ // Only update setpoint placeholders, never overwrite values while typing.
+ const customInput = card.querySelector('.custom-target');
+ const labelInput = card.querySelector('.target-label');
+ const presetSelect = card.querySelector('.preset-select');
+ if (!customInput.matches(':focus') && !labelInput.matches(':focus')) {
+ customInput.placeholder = `Target °${unit}`;
+ if (!customInput.value && setpoint) {
+ customInput.value = setpoint.target_temp;
+ }
+ if (!labelInput.value && setpoint) {
+ labelInput.value = setpoint.label || '';
+ }
+ // Update preset option labels with current unit.
+ Array.from(presetSelect.options).forEach((opt, idx) => {
+ if (idx > 0 && idx <= PRESETS.length) {
+ const p = PRESETS[idx - 1];
+ opt.textContent = `${p.label} (${p.temp}°${unit})`;
+ }
+ });
+ }
+ }
}
async function saveSetpoint(probeNumber, label, targetTemp) {
@@ -105,7 +150,8 @@ async function saveSetpoint(probeNumber, label, targetTemp) {
body: JSON.stringify({ probe_number: probeNumber, label, target_temp: targetTemp }),
});
await loadSetpoints();
- await refreshDashboard();
+ // Do not call refreshDashboard here; it would clear the input the user just used.
+ // The next periodic poll will pick up the new setpoint from /api/status.
}
function renderStatusBar(reading) {
@@ -161,13 +207,12 @@ async function refreshDashboard() {
const status = await api('/api/status');
latestReading = status.latest_reading;
- // Add probe names from active session if we had stored them; for now use CYD names.
- if (latestReading) {
- // The CYD returns probe names; pass them through.
+ if (!cardsInitialized) {
+ createProbeCards();
}
+ updateProbeCards(status.latest_reading, setpoints);
renderStatusBar(status.latest_reading);
- renderProbeCards(status.latest_reading, setpoints);
document.getElementById('last-updated').textContent = `Last updated: ${formatTime(new Date().toISOString())}`;
} catch (e) {
console.error('Refresh failed:', e);
@@ -257,16 +302,12 @@ async function init() {
initChart();
await loadSessions();
await loadSetpoints();
+ // First call creates the cards; subsequent calls only update values.
await refreshDashboard();
await refreshChart();
- setInterval(async () => {
- await refreshDashboard();
- }, 5000);
-
- setInterval(async () => {
- if (activeSession) await refreshChart();
- }, 30000);
+ setInterval(refreshDashboard, 5000);
+ setInterval(() => { if (activeSession) refreshChart(); }, 30000);
}
init();