v0.5.5
This commit is contained in:
+158
-38
@@ -213,6 +213,8 @@ function renderAll() {
|
||||
renderAccessTokens();
|
||||
fillSelects();
|
||||
renderSettings();
|
||||
renderNightSettings();
|
||||
renderHomeAssistantSettings();
|
||||
renderDebugOverlay();
|
||||
}
|
||||
|
||||
@@ -565,6 +567,7 @@ function zoneCard(zone, detailed = true) {
|
||||
const sensorDetails = zone.sensor_source === 'device'
|
||||
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
|
||||
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}`;
|
||||
const roomTemperature = zone.current_temperature ?? zone.device_temperature ?? device?.current_temperature;
|
||||
const target = Number(zone.effective_setpoint ?? zone.setpoint);
|
||||
const manual = zone.manual_preset || 'auto';
|
||||
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
|
||||
@@ -574,7 +577,7 @@ function zoneCard(zone, detailed = true) {
|
||||
: `<button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}"><span>${zone.enabled ? '✓' : '○'}</span>${esc(state)}</button>`;
|
||||
return `<article class="list-card zone-thermostat ${zone.demand ? 'demanding' : ''} ${zone.enabled ? '' : 'zone-disabled'}">
|
||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(zone.active_preset))}</p></div>${enabledControl}</div>
|
||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(zone.current_temperature)}</strong></div><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.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><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.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||
<div class="preset-row">
|
||||
${['auto','comfort','sleep','away'].map(preset=>`<button class="${manual===preset?'active':''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset==='sleep'?tr('zones.sleepNow'):zonePresetLabel(preset))}</button>`).join('')}
|
||||
</div>
|
||||
@@ -663,6 +666,7 @@ function renderLogRetention() {
|
||||
function renderSettings() {
|
||||
if (!app.settings) return;
|
||||
const form = $('#settingsForm');
|
||||
if (!form) return;
|
||||
form.controller_id.value = app.settings.controller_id || '';
|
||||
form.poll_interval_seconds.value = app.settings.poll_interval_seconds || 15;
|
||||
form.zone_interval_seconds.value = app.settings.zone_interval_seconds || 5;
|
||||
@@ -673,12 +677,6 @@ function renderSettings() {
|
||||
form.event_log_retention_days.value = app.settings.event_log_retention_days || 30;
|
||||
form.history_compaction_enabled.checked = app.settings.history_compaction_enabled !== false;
|
||||
form.suppress_device_beep.checked = !!app.settings.suppress_device_beep;
|
||||
form.night_mode_enabled.checked = !!app.settings.night_mode?.enabled;
|
||||
form.night_mode_start.value = app.settings.night_mode?.start_time || '22:00';
|
||||
form.night_mode_end.value = app.settings.night_mode?.end_time || '06:00';
|
||||
form.night_mode_max_fan_speed.value = String(app.settings.night_mode?.max_fan_speed || 1);
|
||||
form.night_mode_force_quiet.checked = app.settings.night_mode?.force_quiet !== false;
|
||||
form.night_mode_native_sleep.checked = app.settings.night_mode?.use_native_sleep !== false;
|
||||
form.influx_enabled.checked = !!app.settings.influxdb?.enabled;
|
||||
form.influx_version.value = String(app.settings.influxdb?.version || '2');
|
||||
form.influx_threshold_days.value = app.settings.influxdb?.history_threshold_days || 30;
|
||||
@@ -693,6 +691,27 @@ function renderSettings() {
|
||||
form.influx_token.placeholder = app.settings.influxdb?.token_configured ? tr('settings.secretSaved') : tr('settings.secretKeep');
|
||||
form.debug_overlay_enabled.checked = !!app.settings.debug?.overlay_enabled;
|
||||
form.debug_gree_frames.checked = !!app.settings.debug?.gree_frames;
|
||||
updateInfluxFields();
|
||||
renderLogRetention();
|
||||
$('#systemInfo').innerHTML = `<h3>${esc(tr('settings.systemState'))}</h3><div>${esc(tr('settings.version'))}: <strong>${esc(app.system.version || '—')}</strong></div><div>${esc(tr('settings.uptime'))}: <strong>${esc(formatDuration(app.system.uptime_seconds || 0))}</strong></div><div>${esc(tr('settings.apiAuth'))}: <strong>${esc(app.system.auth_required ? tr('settings.enabled') : tr('settings.disabled'))}</strong></div>`;
|
||||
}
|
||||
|
||||
function renderNightSettings() {
|
||||
if (!app.settings) return;
|
||||
const form = $('#nightModeForm');
|
||||
if (!form) return;
|
||||
form.night_mode_enabled.checked = !!app.settings.night_mode?.enabled;
|
||||
form.night_mode_start.value = app.settings.night_mode?.start_time || '22:00';
|
||||
form.night_mode_end.value = app.settings.night_mode?.end_time || '06:00';
|
||||
form.night_mode_max_fan_speed.value = String(app.settings.night_mode?.max_fan_speed || 1);
|
||||
form.night_mode_force_quiet.checked = app.settings.night_mode?.force_quiet !== false;
|
||||
form.night_mode_native_sleep.checked = app.settings.night_mode?.use_native_sleep !== false;
|
||||
}
|
||||
|
||||
function renderHomeAssistantSettings() {
|
||||
if (!app.settings) return;
|
||||
const form = $('#homeAssistantForm');
|
||||
if (!form) return;
|
||||
form.ha_url.value = app.settings.home_assistant?.url || '';
|
||||
form.ha_token.value = '';
|
||||
form.ha_token.placeholder = app.settings.home_assistant?.token_configured ? tr('settings.haTokenSaved') : tr('settings.haLongLivedToken');
|
||||
@@ -700,10 +719,8 @@ function renderSettings() {
|
||||
form.ha_outdoor_entity_id.value = app.settings.home_assistant?.outdoor_entity_id || '';
|
||||
form.ha_allow_invalid_tls.checked = !!app.settings.home_assistant?.allow_invalid_tls;
|
||||
form.outdoor_assist_enabled.checked = !!app.settings.outdoor_assist_enabled;
|
||||
updateInfluxFields();
|
||||
renderSensorAliases();
|
||||
renderLogRetention();
|
||||
$('#systemInfo').innerHTML = `<h3>${esc(tr('settings.systemState'))}</h3><div>${esc(tr('settings.version'))}: <strong>${esc(app.system.version || '—')}</strong></div><div>${esc(tr('settings.uptime'))}: <strong>${esc(formatDuration(app.system.uptime_seconds || 0))}</strong></div><div>${esc(tr('settings.apiAuth'))}: <strong>${esc(app.system.auth_required ? tr('settings.enabled') : tr('settings.disabled'))}</strong></div>`;
|
||||
renderAccessTokens();
|
||||
}
|
||||
|
||||
function updateInfluxFields() {
|
||||
@@ -747,7 +764,7 @@ function formatDuration(seconds) {
|
||||
return `${days ? `${days}d ` : ''}${hours}h ${minutes}m`;
|
||||
}
|
||||
|
||||
const VIEW_ROUTES = {dashboard:'/dashboard', devices:'/devices', zones:'/zones', schedules:'/schedules', automations:'/automations', simulation:'/simulation', settings:'/settings', logs:'/events'};
|
||||
const VIEW_ROUTES = {dashboard:'/dashboard', devices:'/devices', zones:'/zones', schedules:'/schedules', automations:'/automations', simulation:'/simulation', night:'/night-mode', homeassistant:'/home-assistant', settings:'/settings', logs:'/events'};
|
||||
const HISTORY_TABS = ['overview','zones','devices','sensors','custom'];
|
||||
|
||||
function currentHistoryPath() {
|
||||
@@ -772,7 +789,7 @@ function updateBrowserUrl(path, replace=false) {
|
||||
function showView(name, {push=true, scroll=true}={}) {
|
||||
app.currentView = name;
|
||||
$$('.view').forEach(view => view.classList.toggle('active', view.dataset.view === name));
|
||||
$$('.bottom-nav button').forEach(button => button.classList.toggle('active', button.dataset.nav === name || (button.dataset.nav === 'more' && ['schedules','automations','simulation','settings','logs'].includes(name))));
|
||||
$$('.bottom-nav button').forEach(button => button.classList.toggle('active', button.dataset.nav === name || (button.dataset.nav === 'more' && ['schedules','automations','simulation','night','homeassistant','settings','logs'].includes(name))));
|
||||
if (push) updateBrowserUrl(name === 'history' ? currentHistoryPath() : (VIEW_ROUTES[name] || '/dashboard'));
|
||||
if (scroll) window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
if (name === 'history') { renderHistoryNavigation(); loadHistory(); }
|
||||
@@ -1302,7 +1319,7 @@ function connectWebSocket() {
|
||||
else if (message.event === 'device.deleted') { app.devices=app.devices.filter(v=>v.id!==data.id); renderAll(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'devices.discovered') { (data.devices||[]).forEach(updateDevice); renderAll(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'zone.updated') { const i=app.zones.findIndex(v=>v.id===data.id); if(i>=0) app.zones[i]=data; else app.zones.push(data); renderSummary(); renderHouseClimate(); renderZones(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'settings.updated') { app.settings=data; app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderHouseClimate(); renderDebugOverlay(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'settings.updated') { app.settings=data; app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderNightSettings(); renderHomeAssistantSettings(); renderHouseClimate(); renderDebugOverlay(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'debug.settings') { app.settings = app.settings || {}; app.settings.debug = data; renderSettings(); renderDebugOverlay(); if(data.overlay_enabled) loadDebugBacklog(); }
|
||||
else if (message.event === 'outdoor.updated') { app.outdoorTemperature=Number.isFinite(Number(data.temperature))?Number(data.temperature):null; renderHouseClimate(); scheduleControlPlanLoad(); }
|
||||
else if (message.event === 'gree.frame') { if(app.settings?.debug?.overlay_enabled) debugLine('GREE', `${data.direction || '?'} ${data.protocol_version || ''}`, data.device_name || data.device_id || data.target || '', message.timestamp, data.payload); }
|
||||
@@ -1463,38 +1480,141 @@ $('#automationForm').addEventListener('submit', async event => {
|
||||
catch(error){toast(error.message,true);}
|
||||
});
|
||||
|
||||
function settingsBodyFromForm(form) {
|
||||
const raw=Object.fromEntries(new FormData(form));
|
||||
function currentSettingsBody() {
|
||||
const settings = app.settings || {};
|
||||
const influx = settings.influxdb || {};
|
||||
const ha = settings.home_assistant || {};
|
||||
return {
|
||||
controller_id:raw.controller_id, simulator_enabled:form.simulator_enabled.checked,
|
||||
poll_interval_seconds:Number(raw.poll_interval_seconds), zone_interval_seconds:Number(raw.zone_interval_seconds),
|
||||
discovery_timeout_ms:Number(raw.discovery_timeout_ms), discovery_broadcast:raw.discovery_broadcast,
|
||||
house_mode:app.settings?.house_mode||'cool', control_strategy:'setpoint', outdoor_assist_enabled:form.outdoor_assist_enabled.checked,
|
||||
history_retention_days:Number(raw.history_retention_days), history_compaction_enabled:form.history_compaction_enabled.checked,
|
||||
event_log_retention_days:Number(raw.event_log_retention_days),
|
||||
suppress_device_beep:form.suppress_device_beep.checked,
|
||||
night_mode:{
|
||||
enabled:form.night_mode_enabled.checked,start_time:raw.night_mode_start,end_time:raw.night_mode_end,
|
||||
max_fan_speed:Number(raw.night_mode_max_fan_speed),force_quiet:form.night_mode_force_quiet.checked,
|
||||
use_native_sleep:form.night_mode_native_sleep.checked,
|
||||
controller_id: settings.controller_id || 'gree-controller',
|
||||
simulator_enabled: !!settings.simulator_enabled,
|
||||
poll_interval_seconds: Number(settings.poll_interval_seconds || 15),
|
||||
zone_interval_seconds: Number(settings.zone_interval_seconds || 5),
|
||||
discovery_timeout_ms: Number(settings.discovery_timeout_ms || 3000),
|
||||
discovery_broadcast: settings.discovery_broadcast || '255.255.255.255:7000',
|
||||
house_mode: settings.house_mode || 'cool',
|
||||
control_strategy: 'setpoint',
|
||||
outdoor_assist_enabled: !!settings.outdoor_assist_enabled,
|
||||
history_retention_days: Number(settings.history_retention_days || 30),
|
||||
history_compaction_enabled: settings.history_compaction_enabled !== false,
|
||||
event_log_retention_days: Number(settings.event_log_retention_days || 30),
|
||||
suppress_device_beep: !!settings.suppress_device_beep,
|
||||
night_mode: {
|
||||
enabled: !!settings.night_mode?.enabled,
|
||||
start_time: settings.night_mode?.start_time || '22:00',
|
||||
end_time: settings.night_mode?.end_time || '06:00',
|
||||
max_fan_speed: Number(settings.night_mode?.max_fan_speed || 1),
|
||||
force_quiet: settings.night_mode?.force_quiet !== false,
|
||||
use_native_sleep: settings.night_mode?.use_native_sleep !== false,
|
||||
},
|
||||
influxdb:{
|
||||
enabled:form.influx_enabled.checked, version:raw.influx_version, url:raw.influx_url,
|
||||
database:raw.influx_database, username:raw.influx_username, password:raw.influx_password,
|
||||
org:raw.influx_org, bucket:raw.influx_bucket, token:raw.influx_token,
|
||||
history_threshold_days:Number(raw.influx_threshold_days),
|
||||
influxdb: {
|
||||
enabled: !!influx.enabled,
|
||||
version: String(influx.version || '2'),
|
||||
url: influx.url || '',
|
||||
database: influx.database || 'gree_controller',
|
||||
username: influx.username || '',
|
||||
password: '',
|
||||
org: influx.org || '',
|
||||
bucket: influx.bucket || 'gree_controller',
|
||||
token: '',
|
||||
history_threshold_days: Number(influx.history_threshold_days || 30),
|
||||
},
|
||||
debug: {
|
||||
overlay_enabled: !!settings.debug?.overlay_enabled,
|
||||
gree_frames: !!settings.debug?.gree_frames,
|
||||
},
|
||||
home_assistant: {
|
||||
url: ha.url || '',
|
||||
token: '',
|
||||
default_entity_id: ha.default_entity_id || '',
|
||||
outdoor_entity_id: ha.outdoor_entity_id || '',
|
||||
allow_invalid_tls: !!ha.allow_invalid_tls,
|
||||
sensor_aliases: {...(ha.sensor_aliases || {})},
|
||||
},
|
||||
debug:{overlay_enabled:form.debug_overlay_enabled.checked, gree_frames:form.debug_gree_frames.checked},
|
||||
home_assistant:{url:raw.ha_url,token:raw.ha_token,default_entity_id:raw.ha_entity_id,outdoor_entity_id:raw.ha_outdoor_entity_id,allow_invalid_tls:form.ha_allow_invalid_tls.checked,sensor_aliases:{...(app.sensorAliases||{})}},
|
||||
};
|
||||
}
|
||||
|
||||
async function saveSettingsForm(form, notify=true) {
|
||||
app.settings=await api('/api/settings',{method:'PUT',body:settingsBodyFromForm(form)}); app.sensorAliases={...(app.settings?.home_assistant?.sensor_aliases||{})}; renderSettings(); renderHouseClimate(); renderDebugOverlay(); scheduleControlPlanLoad(); if(app.settings?.debug?.overlay_enabled) loadDebugBacklog(); if(notify) toast(tr('common.saved')); return app.settings;
|
||||
function settingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = currentSettingsBody();
|
||||
body.controller_id = raw.controller_id;
|
||||
body.simulator_enabled = form.simulator_enabled.checked;
|
||||
body.poll_interval_seconds = Number(raw.poll_interval_seconds);
|
||||
body.zone_interval_seconds = Number(raw.zone_interval_seconds);
|
||||
body.discovery_timeout_ms = Number(raw.discovery_timeout_ms);
|
||||
body.discovery_broadcast = raw.discovery_broadcast;
|
||||
body.history_retention_days = Number(raw.history_retention_days);
|
||||
body.history_compaction_enabled = form.history_compaction_enabled.checked;
|
||||
body.event_log_retention_days = Number(raw.event_log_retention_days);
|
||||
body.suppress_device_beep = form.suppress_device_beep.checked;
|
||||
body.influxdb = {
|
||||
enabled: form.influx_enabled.checked, version: raw.influx_version, url: raw.influx_url,
|
||||
database: raw.influx_database, username: raw.influx_username, password: raw.influx_password,
|
||||
org: raw.influx_org, bucket: raw.influx_bucket, token: raw.influx_token,
|
||||
history_threshold_days: Number(raw.influx_threshold_days),
|
||||
};
|
||||
body.debug = {overlay_enabled: form.debug_overlay_enabled.checked, gree_frames: form.debug_gree_frames.checked};
|
||||
return body;
|
||||
}
|
||||
|
||||
function nightSettingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = currentSettingsBody();
|
||||
body.night_mode = {
|
||||
enabled: form.night_mode_enabled.checked,
|
||||
start_time: raw.night_mode_start,
|
||||
end_time: raw.night_mode_end,
|
||||
max_fan_speed: Number(raw.night_mode_max_fan_speed),
|
||||
force_quiet: form.night_mode_force_quiet.checked,
|
||||
use_native_sleep: form.night_mode_native_sleep.checked,
|
||||
};
|
||||
return body;
|
||||
}
|
||||
|
||||
function homeAssistantSettingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = currentSettingsBody();
|
||||
body.outdoor_assist_enabled = form.outdoor_assist_enabled.checked;
|
||||
body.home_assistant = {
|
||||
url: raw.ha_url,
|
||||
token: raw.ha_token,
|
||||
default_entity_id: raw.ha_entity_id,
|
||||
outdoor_entity_id: raw.ha_outdoor_entity_id,
|
||||
allow_invalid_tls: form.ha_allow_invalid_tls.checked,
|
||||
sensor_aliases: {...(app.sensorAliases || {})},
|
||||
};
|
||||
return body;
|
||||
}
|
||||
|
||||
async function saveRuntimeSettings(body, notify = true) {
|
||||
app.settings = await api('/api/settings', {method:'PUT', body});
|
||||
app.sensorAliases = {...(app.settings?.home_assistant?.sensor_aliases || {})};
|
||||
renderSettings();
|
||||
renderNightSettings();
|
||||
renderHomeAssistantSettings();
|
||||
renderHouseClimate();
|
||||
renderDebugOverlay();
|
||||
scheduleControlPlanLoad();
|
||||
if (app.settings?.debug?.overlay_enabled) loadDebugBacklog();
|
||||
if (notify) toast(tr('common.saved'));
|
||||
return app.settings;
|
||||
}
|
||||
|
||||
$('#settingsForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); try { await saveSettingsForm(event.currentTarget, true); } catch(error){toast(error.message,true);}
|
||||
event.preventDefault();
|
||||
try { await saveRuntimeSettings(settingsBodyFromForm(event.currentTarget), true); }
|
||||
catch(error) { toast(error.message, true); }
|
||||
});
|
||||
|
||||
$('#nightModeForm')?.addEventListener('submit', async event => {
|
||||
event.preventDefault();
|
||||
try { await saveRuntimeSettings(nightSettingsBodyFromForm(event.currentTarget), true); }
|
||||
catch(error) { toast(error.message, true); }
|
||||
});
|
||||
|
||||
$('#homeAssistantForm')?.addEventListener('submit', async event => {
|
||||
event.preventDefault();
|
||||
try { await saveRuntimeSettings(homeAssistantSettingsBodyFromForm(event.currentTarget), true); }
|
||||
catch(error) { toast(error.message, true); }
|
||||
});
|
||||
|
||||
$('#addSensorAlias')?.addEventListener('click', () => {
|
||||
@@ -1555,9 +1675,9 @@ $('#copyAccessToken').addEventListener('click', async () => {
|
||||
});
|
||||
|
||||
$('#haTest').addEventListener('click', async () => {
|
||||
const form=$('#settingsForm');
|
||||
const form=$('#homeAssistantForm');
|
||||
try {
|
||||
await saveSettingsForm(form, false);
|
||||
await saveRuntimeSettings(homeAssistantSettingsBodyFromForm(form), false);
|
||||
const entity = form.ha_entity_id.value || form.ha_outdoor_entity_id.value || null;
|
||||
const result=await api('/api/integrations/home-assistant/test',{method:'POST',body:{entity_id:entity}});
|
||||
toast(tr('toast.haTemperature',{temperature:result.temperature_c.toFixed(1)}));
|
||||
|
||||
+55
-33
@@ -135,6 +135,56 @@
|
||||
<div id="historyCharts" class="history-charts"></div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="view" data-view="night">
|
||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="night.eyebrow">Comfort</span><h1 data-i18n="nav.nightMode">Night mode</h1></div></div>
|
||||
<p class="lead" data-i18n="night.description">Configure quieter thermostat behavior for sleeping hours without mixing it with application settings.</p>
|
||||
<form class="settings-form standalone-settings-form" id="nightModeForm">
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.nightMode">Night mode</h3><p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can request Quiet on supported units.</p></div></div>
|
||||
<div class="settings-grid">
|
||||
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
||||
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start" value="22:00"></label>
|
||||
<label><span data-i18n="settings.nightEnd">End</span><input type="time" name="night_mode_end" value="06:00"></label>
|
||||
<label><span data-i18n="settings.nightMaxFan">Maximum fan speed</span><select name="night_mode_max_fan_speed"><option value="1" data-i18n="fan.low">Low</option><option value="2" data-i18n="fan.mediumLow">Medium-low</option><option value="3" data-i18n="fan.medium">Medium</option><option value="4" data-i18n="fan.mediumHigh">Medium-high</option><option value="5" data-i18n="fan.high">High</option></select></label>
|
||||
<label class="check"><input type="checkbox" name="night_mode_force_quiet"> <span data-i18n="settings.nightForceQuiet">Use Quiet when supported</span></label>
|
||||
<label class="check wide"><input type="checkbox" name="night_mode_native_sleep"> <span data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
||||
</div>
|
||||
</section>
|
||||
<div class="settings-save-bar"><span data-i18n="night.saveHint">Save night-mode changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="view" data-view="homeassistant">
|
||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="homeAssistant.eyebrow">Integration</span><h1 data-i18n="nav.homeAssistant">Home Assistant / Sensors</h1></div></div>
|
||||
<p class="lead" data-i18n="homeAssistant.description">Connection, temperature sensors, friendly aliases and access tokens for the Home Assistant integration.</p>
|
||||
<form class="settings-form standalone-settings-form" id="homeAssistantForm">
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haSensorInput">Home Assistant sensor input</h3><p data-i18n="settings.haSensorInputHint">Optional. Used when a room zone reads an external Home Assistant temperature sensor.</p></div></div>
|
||||
<div class="settings-grid">
|
||||
<label class="wide"><span>URL</span><input type="url" name="ha_url" placeholder="http://homeassistant.local:8123"></label>
|
||||
<label class="wide"><span data-i18n="settings.haLongLivedToken">Long-Lived Access Token</span><input type="password" name="ha_token" autocomplete="new-password" data-i18n-placeholder="settings.haTokenKeep" placeholder="Leave empty to keep the saved token"></label>
|
||||
<label class="wide"><span data-i18n="settings.defaultEntity">Default room entity_id</span><input name="ha_entity_id" placeholder="sensor.living_room_temperature"></label>
|
||||
<label class="wide"><span data-i18n="settings.outdoorEntity">Outdoor temperature entity_id</span><input name="ha_outdoor_entity_id" placeholder="sensor.outdoor_temperature"></label>
|
||||
<label class="check wide"><input type="checkbox" name="outdoor_assist_enabled"> <span data-i18n="settings.outdoorAssist">Use outdoor temperature as smart-control assist</span></label>
|
||||
<label class="check wide"><input type="checkbox" name="ha_allow_invalid_tls"> <span data-i18n="settings.allowInvalidTls">Allow invalid/self-signed HTTPS certificate</span></label>
|
||||
<p class="field-note wide warning-note" data-i18n="settings.allowInvalidTlsHint">Use only for a trusted local Home Assistant server, for example https://192.168.50.25.</p>
|
||||
<div class="wide sensor-alias-manager">
|
||||
<div class="settings-subhead"><div><strong data-i18n="settings.sensorAliases">Sensor aliases</strong><small data-i18n="settings.sensorAliasesHint">Friendly names are used in charts and selectors; the original entity_id remains unchanged.</small></div></div>
|
||||
<div id="sensorAliasList" class="sensor-alias-list"></div>
|
||||
<div class="sensor-alias-add"><input id="sensorAliasEntity" placeholder="sensor.gabinet_temperature"><input id="sensorAliasName" data-i18n-placeholder="settings.aliasPlaceholder" placeholder="Gabinet"><button type="button" class="secondary" id="addSensorAlias" data-i18n="actions.add">Add</button></div>
|
||||
</div>
|
||||
<div class="form-actions wide"><button type="button" class="secondary" id="haTest" data-i18n="settings.testHa">Test HA</button></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3><p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller integration in Home Assistant.</p></div></div>
|
||||
<div class="token-manager"><div id="accessTokenList" class="token-list"></div><div class="form-actions"><button type="button" class="primary" id="createAccessToken" data-i18n="settings.newToken">Create new token</button></div></div>
|
||||
</section>
|
||||
<div class="settings-save-bar"><span data-i18n="homeAssistant.saveHint">Save Home Assistant and sensor changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="view" data-view="settings">
|
||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="settings.system">System</span><h1 data-i18n="nav.settings">Settings</h1></div></div>
|
||||
<form class="settings-form" id="settingsForm">
|
||||
@@ -150,17 +200,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.nightMode">Night mode</h3><p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can request Quiet on supported units.</p></div></div>
|
||||
<div class="settings-grid">
|
||||
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
||||
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start" value="22:00"></label>
|
||||
<label><span data-i18n="settings.nightEnd">End</span><input type="time" name="night_mode_end" value="06:00"></label>
|
||||
<label><span data-i18n="settings.nightMaxFan">Maximum fan speed</span><select name="night_mode_max_fan_speed"><option value="1" data-i18n="fan.low">Low</option><option value="2" data-i18n="fan.mediumLow">Medium-low</option><option value="3" data-i18n="fan.medium">Medium</option><option value="4" data-i18n="fan.mediumHigh">Medium-high</option><option value="5" data-i18n="fan.high">High</option></select></label>
|
||||
<label class="check"><input type="checkbox" name="night_mode_force_quiet"> <span data-i18n="settings.nightForceQuiet">Use Quiet when supported</span></label>
|
||||
<label class="check wide"><input type="checkbox" name="night_mode_native_sleep"> <span data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.metricsAndLogs">Metrics and logs</h3><p data-i18n="settings.compactionHint">SQLite keeps recent data locally and compacts older samples to the resolution used by charts.</p></div></div>
|
||||
@@ -204,29 +244,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haSensorInput">Home Assistant sensor input</h3><p data-i18n="settings.haSensorInputHint">Optional. Used when a room zone reads an external Home Assistant temperature sensor.</p></div></div>
|
||||
<div class="settings-grid">
|
||||
<label class="wide"><span>URL</span><input type="url" name="ha_url" placeholder="http://homeassistant.local:8123"></label>
|
||||
<label class="wide"><span data-i18n="settings.haLongLivedToken">Long-Lived Access Token</span><input type="password" name="ha_token" autocomplete="new-password" data-i18n-placeholder="settings.haTokenKeep" placeholder="Leave empty to keep the saved token"></label>
|
||||
<label class="wide"><span data-i18n="settings.defaultEntity">Default room entity_id</span><input name="ha_entity_id" placeholder="sensor.living_room_temperature"></label>
|
||||
<label class="wide"><span data-i18n="settings.outdoorEntity">Outdoor temperature entity_id</span><input name="ha_outdoor_entity_id" placeholder="sensor.outdoor_temperature"></label>
|
||||
<label class="check wide"><input type="checkbox" name="outdoor_assist_enabled"> <span data-i18n="settings.outdoorAssist">Use outdoor temperature as smart-control assist</span></label>
|
||||
<label class="check wide"><input type="checkbox" name="ha_allow_invalid_tls"> <span data-i18n="settings.allowInvalidTls">Allow invalid/self-signed HTTPS certificate</span></label>
|
||||
<p class="field-note wide warning-note" data-i18n="settings.allowInvalidTlsHint">Use only for a trusted local Home Assistant server, for example https://192.168.50.25.</p>
|
||||
<div class="wide sensor-alias-manager">
|
||||
<div class="settings-subhead"><div><strong data-i18n="settings.sensorAliases">Sensor aliases</strong><small data-i18n="settings.sensorAliasesHint">Friendly names are used in charts and selectors; the original entity_id remains unchanged.</small></div></div>
|
||||
<div id="sensorAliasList" class="sensor-alias-list"></div>
|
||||
<div class="sensor-alias-add"><input id="sensorAliasEntity" placeholder="sensor.gabinet_temperature"><input id="sensorAliasName" data-i18n-placeholder="settings.aliasPlaceholder" placeholder="Gabinet"><button type="button" class="secondary" id="addSensorAlias" data-i18n="actions.add">Add</button></div>
|
||||
</div>
|
||||
<div class="form-actions wide"><button type="button" class="secondary" id="haTest" data-i18n="settings.testHa">Test HA</button></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3><p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller integration in Home Assistant.</p></div></div>
|
||||
<div class="token-manager"><div id="accessTokenList" class="token-list"></div><div class="form-actions"><button type="button" class="primary" id="createAccessToken" data-i18n="settings.newToken">Create new token</button></div></div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="panel settings-block">
|
||||
<div class="settings-block-head"><div><h3 data-i18n="settings.backup">Configuration backup</h3><p class="warning-note" data-i18n="settings.backupHint">Export/import application configuration. Exported files can contain GREE device keys plus Home Assistant and InfluxDB secrets; metrics and API access tokens are not included.</p></div></div>
|
||||
@@ -259,6 +279,8 @@
|
||||
<button data-go="schedules"><span data-i18n="nav.schedules">Schedules</span><span>›</span></button>
|
||||
<button data-go="automations"><span data-i18n="nav.automations">Automations</span><span>›</span></button>
|
||||
<button data-go="simulation"><span data-i18n="nav.simulation">Simulator</span><span>›</span></button>
|
||||
<button data-go="night"><span data-i18n="nav.nightMode">Night mode</span><span>›</span></button>
|
||||
<button data-go="homeassistant"><span data-i18n="nav.homeAssistant">Home Assistant / Sensors</span><span>›</span></button>
|
||||
<button data-go="settings"><span data-i18n="nav.settings">Settings</span><span>›</span></button>
|
||||
<button data-go="logs"><span data-i18n="nav.logsAndEvents">Events and logs</span><span>›</span></button>
|
||||
</div>
|
||||
|
||||
@@ -612,3 +612,12 @@ html[data-theme='light'] .simulation-metrics > div, html[data-theme='light'] .si
|
||||
.device-capability-buttons { display:flex; flex-wrap:wrap; gap:7px; }
|
||||
.device-capability-buttons button { min-height:34px; padding:7px 11px; border:1px solid var(--line); border-radius:11px; background:var(--surface-muted); color:var(--text); }
|
||||
.device-capability-buttons button.active { border-color:var(--accent); color:var(--accent); }
|
||||
|
||||
/* v0.5.5: compact, centered unit-feature controls */
|
||||
.device-capability-panel { justify-items:center; text-align:center; padding:11px 0 3px; }
|
||||
.device-capability-panel > small { text-align:center; }
|
||||
.device-capability-buttons { justify-content:center; align-items:center; gap:6px; max-width:100%; }
|
||||
.device-capability-buttons button { min-height:28px; padding:5px 9px; border-radius:10px; color:var(--muted); font-size:10px; line-height:1.1; background:transparent; }
|
||||
.device-capability-buttons button:hover { color:var(--text); background:var(--surface-muted); }
|
||||
.device-capability-buttons button.active { color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, var(--surface)); border-color:color-mix(in srgb, var(--accent) 45%, var(--line)); }
|
||||
.standalone-settings-form { max-width:1100px; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'gree-controller-v054';
|
||||
const CACHE = 'gree-controller-v055';
|
||||
const ASSETS = ['/', '/styles.css', '/app.js', '/favicon.svg', '/manifest.webmanifest', '/lang/index.json', '/lang/en.json'];
|
||||
self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)).then(() => self.skipWaiting())));
|
||||
self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim())));
|
||||
|
||||
Reference in New Issue
Block a user