v0.3.5
This commit is contained in:
+76
-10
@@ -211,16 +211,16 @@ function deviceCard(device, detailed = false) {
|
||||
const fans = [0,1,3,5];
|
||||
const error = device.last_error
|
||||
? `<small title="${esc(device.last_error)}">${esc(device.last_error)}</small>`
|
||||
: `<small>${esc(device.ip)}:${esc(device.port)} · ${device.simulated ? tr('devices.simulator') : `V${device.protocol_version}`}</small>`;
|
||||
: `<small>${esc(device.ip)}:${esc(device.port)} · ${device.simulated ? tr('devices.simulator') : device.protocol_version === 2 ? 'V2 GCM' : device.protocol_version === 1 ? 'V1 ECB' : tr('devices.protocolAuto')}</small>`;
|
||||
return `<article class="device-card ${device.power ? '' : 'off'}" data-device-card="${esc(device.id)}">
|
||||
<div class="device-head">
|
||||
<div class="device-title"><h3>${esc(device.name)}</h3><p><span class="status ${device.online ? 'online' : ''}">${esc(tr(device.online ? 'status.online' : 'status.offline'))}</span> · ${esc(device.model || device.mac)}</p></div>
|
||||
<button class="power-button ${device.power ? 'on' : ''}" data-action="power" data-device="${esc(device.id)}" aria-label="${esc(tr('devices.power'))}">⏻</button>
|
||||
</div>
|
||||
<div class="temperature-control">
|
||||
<button data-action="temperature" data-delta="-0.5" data-device="${esc(device.id)}">−</button>
|
||||
<button data-action="temperature" data-delta="-1" data-device="${esc(device.id)}">−</button>
|
||||
<div class="target-temp">${Number(device.target_temperature).toFixed(1)}<small>°C</small></div>
|
||||
<button data-action="temperature" data-delta="0.5" data-device="${esc(device.id)}">+</button>
|
||||
<button data-action="temperature" data-delta="1" data-device="${esc(device.id)}">+</button>
|
||||
</div>
|
||||
<div class="current-line">${esc(tr('devices.currentTemperature'))}: <strong>${fmtTemp(device.current_temperature)}</strong>${device.outdoor_temperature == null ? '' : ` · ${esc(tr('devices.outdoor'))} ${fmtTemp(device.outdoor_temperature)}`}</div>
|
||||
<div class="mode-row">${modes.map(mode => `<button class="${device.mode === mode ? 'active' : ''}" data-action="mode" data-value="${mode}" data-device="${esc(device.id)}">${esc(modeLabel(mode))}</button>`).join('')}</div>
|
||||
@@ -230,7 +230,7 @@ function deviceCard(device, detailed = false) {
|
||||
<button class="${device.quiet ? 'active' : ''}" data-action="toggle" data-field="quiet" data-device="${esc(device.id)}">${esc(tr('devices.quiet'))}</button>
|
||||
<button class="${device.turbo ? 'active' : ''}" data-action="toggle" data-field="turbo" data-device="${esc(device.id)}">${esc(tr('devices.turbo'))}</button>
|
||||
</div>
|
||||
${detailed ? `<div class="card-footer">${error}<div class="card-menu"><button data-action="poll" data-device="${esc(device.id)}">${esc(tr('actions.read'))}</button>${device.simulated ? '' : `<button data-action="bind" data-device="${esc(device.id)}">${esc(tr('actions.bind'))}</button>`}<button class="danger" data-action="delete-device" data-device="${esc(device.id)}">${esc(tr('actions.delete'))}</button></div></div>` : ''}
|
||||
${detailed ? `<div class="card-footer">${error}<div class="card-menu"><button data-action="poll" data-device="${esc(device.id)}">${esc(tr('actions.read'))}</button><button data-action="rename-device" data-device="${esc(device.id)}">${esc(tr('devices.rename'))}</button>${device.simulated ? '' : `<button data-action="bind" data-device="${esc(device.id)}">${esc(tr('actions.bind'))}</button>`}<button class="danger" data-action="delete-device" data-device="${esc(device.id)}">${esc(tr('actions.delete'))}</button></div></div>` : ''}
|
||||
</article>`;
|
||||
}
|
||||
|
||||
@@ -267,8 +267,12 @@ function renderZones() {
|
||||
return `<article class="list-card">
|
||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zoneStrategyLabel(zone))}${zone.ha_entity_id ? ` · ${esc(zone.ha_entity_id)}` : ''}</p></div><span class="badge ${zone.enabled ? 'active' : ''}">${esc(state)}</span></div>
|
||||
<div class="card-stats"><div class="card-stat"><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(zone.current_temperature)}</strong></div><div class="card-stat"><small>${esc(tr('common.target'))}</small><strong>${fmtTemp(zone.setpoint)}</strong></div><div class="card-stat"><small>${esc(tr('zones.demand'))}</small><strong>${esc(tr(zone.demand ? 'common.on' : 'common.off'))}</strong></div></div>
|
||||
<div class="zone-quick-control">
|
||||
<div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact">${Number(zone.setpoint).toFixed(1)}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div>
|
||||
<div class="mode-row"><button class="${zone.mode === 'heat' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${zone.mode === 'cool' ? 'active' : ''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
|
||||
</div>
|
||||
<div class="sensor-detail">${esc(sensorDetails)}</div>
|
||||
<div class="card-footer"><small>${esc(modeLabel(zone.mode))} · ${esc(tr('zones.hysteresis').replace(' °C','').toLowerCase())} ${Number(zone.hysteresis).toFixed(1)}°C</small><div class="card-menu"><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></div>
|
||||
<div class="card-footer"><small>${esc(tr('zones.quickHint'))}</small><div class="card-menu"><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></div>
|
||||
</article>`;
|
||||
}).join('') : `<div class="empty"><strong>${esc(tr('zones.emptyTitle'))}</strong>${esc(tr('zones.emptyText'))}</div>`;
|
||||
}
|
||||
@@ -361,6 +365,35 @@ function updateDevice(device) {
|
||||
if (index >= 0) app.devices[index] = device; else app.devices.push(device);
|
||||
}
|
||||
|
||||
async function sendZoneControl(id, patch) {
|
||||
try {
|
||||
const zone = await api(`/api/zones/${encodeURIComponent(id)}/control`, {method:'POST', body:patch});
|
||||
const index = app.zones.findIndex(item => item.id === zone.id);
|
||||
if (index >= 0) app.zones[index] = zone; else app.zones.push(zone);
|
||||
renderSummary(); renderZones();
|
||||
} catch (error) { toast(error.message, true); }
|
||||
}
|
||||
|
||||
function showDiscoveryNames(ids) {
|
||||
const wanted = new Set(Array.isArray(ids) ? ids : []);
|
||||
const devices = app.devices.filter(device => wanted.has(device.id));
|
||||
if (!devices.length) return;
|
||||
const list = $('#discoveryNamesList');
|
||||
list.innerHTML = devices.map(device => `
|
||||
<label class="discovery-name-row">
|
||||
<span><strong>${esc(device.model || 'GREE')}</strong><small>${esc(device.ip)} · ${esc(device.mac)} · ${device.protocol_version === 2 ? 'V2 GCM' : 'V1 ECB'}</small></span>
|
||||
<input data-device-id="${esc(device.id)}" maxlength="80" required value="${esc(device.name)}">
|
||||
</label>`).join('');
|
||||
openDialog('discoveryNamesDialog');
|
||||
}
|
||||
|
||||
function populateDeviceRename(id) {
|
||||
const device = app.devices.find(item => item.id === id); if (!device) return;
|
||||
const form = $('#renameDeviceForm'); form.reset();
|
||||
form.id.value = device.id; form.name.value = device.name; form.protocol_version.value = String(device.protocol_version ?? 0);
|
||||
openDialog('renameDeviceDialog');
|
||||
}
|
||||
|
||||
async function deleteEntity(type, id, labelKey) {
|
||||
if (!confirm(tr('confirm.delete', {label: tr(labelKey)}))) return;
|
||||
try { await api(`/api/${type}/${encodeURIComponent(id)}`, {method:'DELETE'}); await loadBootstrap(); toast(tr('common.removed')); }
|
||||
@@ -502,13 +535,16 @@ document.addEventListener('click', async event => {
|
||||
const action = button.dataset.action; if (!action) return;
|
||||
const device = app.devices.find(v => v.id === button.dataset.device);
|
||||
if (action === 'power' && device) return sendDeviceCommand(device.id, {power:!device.power});
|
||||
if (action === 'temperature' && device) return sendDeviceCommand(device.id, {target_temperature:clamp(Number(device.target_temperature)+Number(button.dataset.delta),8,32)});
|
||||
if (action === 'temperature' && device) return sendDeviceCommand(device.id, {target_temperature:clamp(Number(device.target_temperature)+Number(button.dataset.delta),8,30)});
|
||||
if (action === 'mode' && device) return sendDeviceCommand(device.id, {mode:button.dataset.value, power:true});
|
||||
if (action === 'fan' && device) return sendDeviceCommand(device.id, {fan_speed:Number(button.dataset.value)});
|
||||
if (action === 'toggle' && device) return sendDeviceCommand(device.id, {[button.dataset.field]:!device[button.dataset.field]});
|
||||
if (action === 'poll' && device) { try { button.disabled=true; updateDevice(await api(`/api/devices/${encodeURIComponent(device.id)}/poll`,{method:'POST'})); renderAll(); toast(tr('devices.readDone')); } catch(e){toast(e.message,true);} finally{button.disabled=false;} return; }
|
||||
if (action === 'bind' && device) { try { button.disabled=true; updateDevice(await api(`/api/devices/${encodeURIComponent(device.id)}/bind`,{method:'POST'})); renderAll(); toast(tr('devices.bound')); } catch(e){toast(e.message,true);} finally{button.disabled=false;} return; }
|
||||
if (action === 'rename-device' && device) return populateDeviceRename(device.id);
|
||||
if (action === 'delete-device') return deleteEntity('devices', button.dataset.device, 'label.device');
|
||||
if (action === 'zone-temperature') { const zone=app.zones.find(v=>v.id===button.dataset.id); if(zone) return sendZoneControl(zone.id,{setpoint:clamp(Number(zone.setpoint)+Number(button.dataset.delta),8,30)}); }
|
||||
if (action === 'zone-mode') return sendZoneControl(button.dataset.id,{mode:button.dataset.value});
|
||||
if (action === 'edit-zone') return populateZone(button.dataset.id);
|
||||
if (action === 'delete-zone') return deleteEntity('zones', button.dataset.id, 'label.zone');
|
||||
if (action === 'edit-schedule') return populateSchedule(button.dataset.id);
|
||||
@@ -528,10 +564,10 @@ document.addEventListener('click', async event => {
|
||||
});
|
||||
|
||||
$('#refreshButton').addEventListener('click', () => loadBootstrap(true));
|
||||
$('#discoverButton').addEventListener('click', async event => {
|
||||
const button=event.currentTarget; button.disabled=true; button.textContent=tr('actions.discovering');
|
||||
try { const result=await api('/api/discovery',{method:'POST',body:{}}); await loadBootstrap(); toast(tr('toast.found',{count:result.count})); }
|
||||
catch(error){toast(error.message,true);} finally{button.disabled=false;button.textContent=tr('actions.discover');}
|
||||
$('#discoverButton').addEventListener('click', () => {
|
||||
const form=$('#discoverForm'); form.reset();
|
||||
form.protocol_version.value='0'; form.passes.value='3'; form.timeout_ms.value=String(Math.max(6000, Number(app.settings?.discovery_timeout_ms || 3000)));
|
||||
openDialog('discoverDialog');
|
||||
});
|
||||
$('#historyRefresh').addEventListener('click', loadHistory);
|
||||
$('#historyDevice').addEventListener('change', loadHistory);
|
||||
@@ -546,6 +582,36 @@ $('#tokenForm').addEventListener('submit', event => {
|
||||
localStorage.setItem('gree_controller_token', app.token); if (app.ws) app.ws.close(); loadBootstrap();
|
||||
});
|
||||
|
||||
$('#discoverForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, raw=Object.fromEntries(new FormData(form));
|
||||
const submit=form.querySelector('button[type=submit]'); submit.disabled=true; submit.textContent=tr('actions.discovering');
|
||||
try {
|
||||
const result=await api('/api/discovery',{method:'POST',body:{protocol_version:Number(raw.protocol_version),passes:Number(raw.passes),timeout_ms:Number(raw.timeout_ms)}});
|
||||
form.closest('dialog').close(); await loadBootstrap(); toast(tr('toast.found',{count:result.count})); showDiscoveryNames(result.new_device_ids || []);
|
||||
} catch(error){toast(error.message,true);} finally {submit.disabled=false;submit.textContent=tr('actions.discover');}
|
||||
});
|
||||
|
||||
$('#discoveryNamesForm').addEventListener('submit', async event => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const inputs = $$('input[data-device-id]', form);
|
||||
try {
|
||||
const updated = await Promise.all(inputs.map(input => api(`/api/devices/${encodeURIComponent(input.dataset.deviceId)}`, {method:'PATCH', body:{name:input.value.trim()}})));
|
||||
updated.forEach(updateDevice);
|
||||
form.closest('dialog').close();
|
||||
renderAll();
|
||||
toast(tr('common.saved'));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
});
|
||||
|
||||
$('#renameDeviceForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, raw=Object.fromEntries(new FormData(form));
|
||||
try {
|
||||
const device=await api(`/api/devices/${encodeURIComponent(raw.id)}`,{method:'PATCH',body:{name:raw.name.trim(),protocol_version:Number(raw.protocol_version)}});
|
||||
updateDevice(device); form.closest('dialog').close(); renderAll(); toast(tr('common.saved'));
|
||||
} catch(error){toast(error.message,true);}
|
||||
});
|
||||
|
||||
$('#deviceForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, data=Object.fromEntries(new FormData(form));
|
||||
data.port=Number(data.port); data.protocol_version=Number(data.protocol_version); data.simulated=form.simulated.checked;
|
||||
|
||||
+35
-4
@@ -150,20 +150,51 @@
|
||||
<label><span data-i18n="common.name">Name</span><input name="name" required data-i18n-placeholder="placeholder.livingRoom" placeholder="Living room"></label>
|
||||
<label><span>MAC / CID</span><input name="mac" required placeholder="AABBCCDDEEFF"></label>
|
||||
<label><span data-i18n="devices.ipAddress">IP address</span><input name="ip" required placeholder="192.168.1.50"></label>
|
||||
<div class="two"><label><span>Port</span><input type="number" name="port" value="7000" min="1" max="65535"></label><label><span data-i18n="devices.protocol">Protocol</span><select name="protocol_version"><option value="1">V1 AES-ECB</option><option value="2">V2 AES-GCM</option></select></label></div>
|
||||
<div class="two"><label><span>Port</span><input type="number" name="port" value="7000" min="1" max="65535"></label><label><span data-i18n="devices.protocol">Protocol</span><select name="protocol_version"><option value="0" data-i18n="devices.protocolAuto">Auto (V1 + V2)</option><option value="1">V1 AES-ECB</option><option value="2">V2 AES-GCM</option></select></label></div>
|
||||
<label><span data-i18n="devices.keyOptional">Key (optional)</span><input name="key" data-i18n-placeholder="devices.keyPlaceholder" placeholder="Leave empty for automatic bind"></label>
|
||||
<label class="check"><input type="checkbox" name="simulated"> <span data-i18n="devices.simulated">Simulated device</span></label>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit" data-i18n="actions.add">Add</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
|
||||
<dialog id="discoverDialog">
|
||||
<form method="dialog" id="discoverForm" class="dialog-form">
|
||||
<div class="dialog-head"><h2 data-i18n="discovery.title">Discover GREE devices</h2><button type="button" data-close>×</button></div>
|
||||
<p class="field-note" data-i18n="discovery.help">Auto searches both AES-ECB and AES-GCM devices. Multiple passes improve discovery when several Wi-Fi modules answer the same broadcast.</p>
|
||||
<label><span data-i18n="discovery.protocol">Discovery protocol</span><select name="protocol_version"><option value="0" data-i18n="devices.protocolAuto">Auto (V1 + V2)</option><option value="1">V1 AES-ECB</option><option value="2">V2 AES-GCM</option></select></label>
|
||||
<div class="two"><label><span data-i18n="discovery.passes">Scan passes</span><input type="number" name="passes" min="1" max="10" value="3"></label><label><span data-i18n="discovery.timeout">Total scan time (ms)</span><input type="number" name="timeout_ms" min="500" max="30000" value="6000"></label></div>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit" data-i18n="actions.discover">Discover</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="discoveryNamesDialog">
|
||||
<form method="dialog" id="discoveryNamesForm" class="dialog-form">
|
||||
<div class="dialog-head"><h2 data-i18n="discovery.nameDevices">Name discovered devices</h2><button type="button" data-close>×</button></div>
|
||||
<p class="field-note" data-i18n="discovery.nameDevicesHelp">Give each new unit a friendly room name. The technical model and MAC remain available in diagnostics.</p>
|
||||
<div id="discoveryNamesList" class="discovery-name-list"></div>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.later">Later</button><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="renameDeviceDialog">
|
||||
<form method="dialog" id="renameDeviceForm" class="dialog-form">
|
||||
<input type="hidden" name="id">
|
||||
<div class="dialog-head"><h2 data-i18n="devices.rename">Rename device</h2><button type="button" data-close>×</button></div>
|
||||
<label><span data-i18n="common.name">Name</span><input name="name" required maxlength="80"></label>
|
||||
<label><span data-i18n="devices.protocol">Protocol</span><select name="protocol_version"><option value="0" data-i18n="devices.protocolAuto">Auto (V1 + V2)</option><option value="1">V1 AES-ECB</option><option value="2">V2 AES-GCM</option></select></label>
|
||||
<p class="field-note" data-i18n="devices.protocolChangeHint">Changing protocol clears the saved device key and performs a new bind on the next request.</p>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="zoneDialog">
|
||||
<form method="dialog" id="zoneForm" class="dialog-form">
|
||||
<input type="hidden" name="id">
|
||||
<div class="dialog-head"><h2 data-i18n="common.zone">Zone</h2><button type="button" data-close>×</button></div>
|
||||
<label><span data-i18n="common.name">Name</span><input name="name" required data-i18n-placeholder="placeholder.livingRoom" placeholder="Living room"></label>
|
||||
<label><span data-i18n="common.device">Device</span><select name="device_id" required></select></label>
|
||||
<div class="two"><label><span data-i18n="common.mode">Mode</span><select name="mode"><option value="cool" data-i18n="mode.cool">Cooling</option><option value="heat" data-i18n="mode.heat">Heating</option></select></label><label><span data-i18n="common.targetC">Target °C</span><input type="number" step="0.1" min="8" max="32" name="setpoint" value="23"></label></div>
|
||||
<div class="two"><label><span data-i18n="common.mode">Mode</span><select name="mode"><option value="cool" data-i18n="mode.cool">Cooling</option><option value="heat" data-i18n="mode.heat">Heating</option></select></label><label><span data-i18n="common.targetC">Target °C</span><input type="number" step="0.1" min="8" max="30" name="setpoint" value="23"></label></div>
|
||||
<div class="two"><label><span data-i18n="zones.hysteresis">Hysteresis °C</span><input type="number" step="0.1" min="0.1" max="5" name="hysteresis" value="0.6"></label><label><span data-i18n="zones.source">Temperature strategy</span><select name="sensor_source"><option value="combined" selected data-i18n="zones.combinedSensor">GREE + room sensor</option><option value="device" data-i18n="zones.greeSensor">GREE only</option><option value="home_assistant" data-i18n="zones.externalSensor">Room sensor only</option></select></label></div>
|
||||
<div id="externalSensorFields">
|
||||
<label><span data-i18n="zones.roomSensorEntity">Room sensor entity_id</span><input name="ha_entity_id" placeholder="sensor.living_room_temperature"></label>
|
||||
@@ -184,7 +215,7 @@
|
||||
<label><span data-i18n="common.zone">Zone</span><select name="zone_id" required></select></label>
|
||||
<fieldset><legend data-i18n="schedules.weekdays">Weekdays</legend><div class="days"><label><input type="checkbox" name="weekday" value="1" checked><span data-day="1">Mon</span></label><label><input type="checkbox" name="weekday" value="2" checked><span data-day="2">Tue</span></label><label><input type="checkbox" name="weekday" value="3" checked><span data-day="3">Wed</span></label><label><input type="checkbox" name="weekday" value="4" checked><span data-day="4">Thu</span></label><label><input type="checkbox" name="weekday" value="5" checked><span data-day="5">Fri</span></label><label><input type="checkbox" name="weekday" value="6"><span data-day="6">Sat</span></label><label><input type="checkbox" name="weekday" value="7"><span data-day="7">Sun</span></label></div></fieldset>
|
||||
<div class="two"><label><span data-i18n="common.from">From</span><input type="time" name="start_time" value="22:00" required></label><label><span data-i18n="common.to">To</span><input type="time" name="end_time" value="06:00" required></label></div>
|
||||
<label><span data-i18n="common.temperatureC">Temperature °C</span><input type="number" name="setpoint" step="0.1" min="8" max="32" value="23" required></label>
|
||||
<label><span data-i18n="common.temperatureC">Temperature °C</span><input type="number" name="setpoint" step="0.1" min="8" max="30" value="23" required></label>
|
||||
<label class="check"><input type="checkbox" name="enabled" checked> <span data-i18n="common.enabled">Enabled</span></label>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
@@ -201,7 +232,7 @@
|
||||
<hr><h3 data-i18n="automations.action">Action</h3>
|
||||
<label><span data-i18n="common.device">Device</span><select name="action_device_id" required></select></label>
|
||||
<div class="two"><label><span data-i18n="common.power">Power</span><select name="action_power"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="actions.turnOn">Turn on</option><option value="false" data-i18n="actions.turnOff">Turn off</option></select></label><label><span data-i18n="common.mode">Mode</span><select name="action_mode"><option value="" data-i18n="actions.noChange">No change</option><option value="cool" data-i18n="mode.cool">Cooling</option><option value="heat" data-i18n="mode.heat">Heating</option><option value="dry" data-i18n="mode.dry">Dry</option><option value="fan" data-i18n="mode.fan">Fan</option><option value="auto" data-i18n="mode.auto">Auto</option></select></label></div>
|
||||
<div class="two"><label><span data-i18n="common.targetC">Target °C</span><input type="number" step="0.1" min="8" max="32" name="action_target_temperature"></label><label><span data-i18n="common.cooldownSeconds">Cooldown (s)</span><input type="number" min="30" name="cooldown_seconds" value="300"></label></div>
|
||||
<div class="two"><label><span data-i18n="common.targetC">Target °C</span><input type="number" step="0.1" min="8" max="30" name="action_target_temperature"></label><label><span data-i18n="common.cooldownSeconds">Cooldown (s)</span><input type="number" min="30" name="cooldown_seconds" value="300"></label></div>
|
||||
<label class="check"><input type="checkbox" name="enabled" checked> <span data-i18n="common.enabled">Enabled</span></label>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
|
||||
@@ -232,3 +232,16 @@ legend { padding: 0 5px; color: var(--muted); font-size: 11px; }
|
||||
.token-row { align-items: stretch; flex-direction: column; }
|
||||
.token-row button { width: 100%; }
|
||||
}
|
||||
|
||||
/* Zone quick controls */
|
||||
.zone-quick-control { display: grid; gap: 4px; margin-top: 10px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface-muted); }
|
||||
.temperature-control.compact { padding: 4px 0 7px; gap: 16px; }
|
||||
.temperature-control.compact button { width: 38px; height: 38px; }
|
||||
.target-temp.compact { min-width: 92px; font-size: 32px; }
|
||||
.zone-quick-control .mode-row { padding-bottom: 2px; }
|
||||
|
||||
.discovery-name-list { display:grid; gap:12px; }
|
||||
.discovery-name-row { display:grid; gap:8px; padding:12px 0; border-bottom:1px solid var(--border); }
|
||||
.discovery-name-row:last-child { border-bottom:0; }
|
||||
.discovery-name-row span { display:grid; gap:2px; }
|
||||
.discovery-name-row small { color:var(--muted); overflow-wrap:anywhere; }
|
||||
|
||||
Reference in New Issue
Block a user