Files
gree-controller/web/index.html
T
2026-08-23 23:32:26 +02:00

301 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#151515" id="themeColorMeta">
<meta name="description" content="Local GREE air conditioner controller" data-i18n-content="meta.description">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/styles.css">
<script>
(() => {
const getCookie = name => document.cookie.split('; ').find(row => row.startsWith(`${name}=`))?.split('=')[1];
const requested = decodeURIComponent(getCookie('gree_controller_theme') || 'system');
const resolved = requested === 'light' || requested === 'dark'
? requested
: (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark');
document.documentElement.dataset.theme = resolved;
})();
</script>
<title>GREE Controller</title>
</head>
<body>
<header class="topbar">
<div class="brand">
<div><strong>GREE Controller</strong><small id="connectionLabel" data-i18n="status.connecting">Connecting…</small></div>
</div>
<div class="top-actions">
<select class="toolbar-select" id="languageSelect" data-i18n-aria="controls.language" aria-label="Language">
<option value="en">English</option>
</select>
<select class="toolbar-select theme-select" id="themeSelect" data-i18n-aria="controls.theme" aria-label="Theme">
<option value="system" data-i18n="theme.system">System</option>
<option value="light" data-i18n="theme.light">Light</option>
<option value="dark" data-i18n="theme.dark">Dark</option>
</select>
<button class="icon-button" id="refreshButton" data-i18n-title="actions.refresh" data-i18n-aria="actions.refresh" title="Refresh" aria-label="Refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.34 5.66"/><path d="M20 4v7h-7"/></svg></button>
<button class="primary compact" id="discoverButton" data-i18n="actions.discover">Discover</button>
</div>
</header>
<main>
<section class="view active" data-view="dashboard">
<div class="hero">
<div><span class="eyebrow" data-i18n="dashboard.home">Home</span><h1 data-i18n="dashboard.title">Comfort under control</h1><p id="summaryText" data-i18n="dashboard.loading">Loading devices…</p></div>
<div class="hero-temp" id="heroTemperature">--<small>°C</small></div>
</div>
<div class="metrics" id="metrics"></div>
<div class="panel house-climate" id="houseClimate"></div>
<div class="section-heading"><div><span class="eyebrow" data-i18n="nav.zones">Zones</span><h2 data-i18n="dashboard.quickThermostats">Quick thermostats</h2></div></div>
<div class="list-grid dashboard-zones" id="dashboardZones"></div>
<div class="section-heading"><div><span class="eyebrow" data-i18n="nav.devices">Devices</span><h2 data-i18n="dashboard.quickControl">Direct device control</h2></div></div>
<div class="device-grid" id="dashboardDevices"></div>
</section>
<section class="view" data-view="devices">
<div class="section-heading">
<div><span class="eyebrow" data-i18n="devices.lan">LAN network</span><h1 data-i18n="nav.devices">Devices</h1></div>
<button class="secondary" data-open="deviceDialog" data-i18n="devices.addManual">Add manually</button>
</div>
<div class="device-grid" id="deviceList"></div>
</section>
<section class="view" data-view="zones">
<div class="section-heading"><div><span class="eyebrow" data-i18n="zones.automation">Automation</span><h1 data-i18n="nav.zones">Zones</h1></div><button class="secondary" data-open="zoneDialog" data-i18n="zones.new">New zone</button></div>
<p class="lead" data-i18n="zones.description">Each zone is a fast thermostat. It normally follows the house heating/cooling mode and changes the AC setpoint instead of cycling power.</p>
<div class="list-grid" id="zoneList"></div>
</section>
<section class="view" data-view="schedules">
<div class="section-heading"><div><span class="eyebrow" data-i18n="schedules.calendar">Calendar</span><h1 data-i18n="nav.schedules">Schedules</h1></div><button class="secondary" data-open="scheduleDialog" data-i18n="actions.add">Add</button></div>
<div class="panel schedule-template-panel">
<div><h3 data-i18n="schedules.quickSetup">Quick schedule setup</h3><p class="field-note" data-i18n="schedules.quickSetupHint">Choose a zone and a ready-made daily rhythm. You can edit every generated entry afterwards.</p></div>
<select id="schedulePresetZone"></select>
<div class="template-grid">
<button class="secondary" data-schedule-template="family" data-i18n="schedules.templateFamily">Family</button>
<button class="secondary" data-schedule-template="child" data-i18n="schedules.templateChild">Child room</button>
<button class="secondary" data-schedule-template="bedroom" data-i18n="schedules.templateBedroom">Bedroom</button>
<button class="secondary" data-schedule-template="workday" data-i18n="schedules.templateWorkday">Workday</button>
<button class="secondary" data-schedule-template="always" data-i18n="schedules.templateAlways">Always comfort</button>
</div>
</div>
<div class="list-grid" id="scheduleList"></div>
</section>
<section class="view" data-view="automations">
<div class="section-heading"><div><span class="eyebrow" data-i18n="automations.rules">Rules</span><h1 data-i18n="nav.automations">Automations</h1></div><button class="secondary" data-open="automationDialog" data-i18n="actions.add">Add</button></div>
<div class="panel automation-hint"><strong data-i18n="automations.smartHintTitle">Use thermostat presets for daily comfort</strong><p data-i18n="automations.smartHint">Schedules and zone presets handle normal heating/cooling automatically. Keep automations for special exceptions so they do not fight the thermostat.</p></div>
<div class="list-grid" id="automationList"></div>
</section>
<section class="view" data-view="history">
<div class="section-heading"><div><span class="eyebrow" data-i18n="history.measurements">Measurements</span><h1 data-i18n="history.title">Climate history</h1></div></div>
<div class="panel chart-panel">
<div class="chart-toolbar">
<select id="historyZone" aria-label="History zone"></select>
<select id="historyHours">
<option value="6" data-i18n="history.6h">6 hours</option>
<option value="24" selected data-i18n="history.24h">24 hours</option>
<option value="168" data-i18n="history.7d">7 days</option>
<option value="720" data-i18n="history.30d">30 days</option>
</select>
<button class="secondary" id="historyRefresh" data-i18n="actions.show">Show</button>
</div>
<div class="history-summary" id="historySummary"></div>
</div>
<div class="panel chart-panel history-chart-card">
<div class="chart-title-row"><div><h3 data-i18n="history.temperatureOverview">Temperature overview</h3><p data-i18n="history.temperatureOverviewHint">GREE, room sensor, control temperature, targets and outdoor temperature.</p></div></div>
<div class="chart-wrap"><canvas id="historyTemperatureChart" width="1000" height="420"></canvas></div>
<div class="legend" id="historyTemperatureLegend"></div>
</div>
<div class="panel chart-panel history-chart-card">
<div class="chart-title-row"><div><h3 data-i18n="history.operationOverview">Operation overview</h3><p data-i18n="history.operationOverviewHint">Demand, power and fan activity over the same period.</p></div></div>
<div class="chart-wrap compact-chart"><canvas id="historyOperationChart" width="1000" height="300"></canvas></div>
<div class="legend" id="historyOperationLegend"></div>
</div>
</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="panel form-grid" id="settingsForm">
<h3 data-i18n="settings.controller">Controller</h3>
<label><span data-i18n="settings.clientId">Client identifier</span><input name="controller_id" required></label>
<label><span data-i18n="settings.pollInterval">Poll interval (s)</span><input type="number" name="poll_interval_seconds" min="2" max="3600" required></label>
<label><span data-i18n="settings.zoneInterval">Zone interval (s)</span><input type="number" name="zone_interval_seconds" min="2" max="3600" required></label>
<label><span data-i18n="settings.broadcast">Broadcast address</span><input name="discovery_broadcast" placeholder="255.255.255.255:7000" required></label>
<label><span data-i18n="settings.discoveryTimeout">Discovery timeout (ms)</span><input type="number" name="discovery_timeout_ms" min="300" max="30000" required></label>
<label class="check"><input type="checkbox" name="simulator_enabled"> <span data-i18n="settings.simulationMode">Simulation mode</span></label>
<hr>
<h3 data-i18n="settings.haSensorInput">Home Assistant sensor input</h3>
<p class="field-note wide" data-i18n="settings.haSensorInputHint">Optional. Used only when a room zone reads an external Home Assistant temperature sensor.</p>
<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://10.87.65.2.</p>
<div class="form-actions wide"><button type="button" class="secondary" id="haTest" data-i18n="settings.testHa">Test HA</button><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
<hr>
<h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3>
<p class="field-note wide" data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller integration in Home Assistant.</p>
<div class="wide 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>
</form>
<div class="panel system-panel" id="systemInfo"></div>
</section>
<section class="view" data-view="logs">
<div class="section-heading"><div><span class="eyebrow" data-i18n="logs.diagnostics">Diagnostics</span><h1 data-i18n="nav.logs">Events</h1></div><button class="secondary" id="logsRefresh" data-i18n="actions.refresh">Refresh</button></div>
<div class="panel log-list" id="logList"></div>
</section>
</main>
<nav class="bottom-nav" data-i18n-aria="nav.navigation" aria-label="Navigation">
<button class="active" data-nav="dashboard"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5"/><path d="M5.5 9.5V21h13V9.5"/><path d="M9 21v-7h6v7"/></svg></span><b data-i18n="nav.dashboard">Dashboard</b></button>
<button data-nav="devices"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h4"/></svg></span><b data-i18n="nav.devices">Devices</b></button>
<button data-nav="zones"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg></span><b data-i18n="nav.zones">Zones</b></button>
<button data-nav="history"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M4 19V5"/><path d="M4 19h16"/><path d="m7 15 4-4 3 2 5-6"/></svg></span><b data-i18n="nav.history">History</b></button>
<button class="desktop-nav-only" data-nav="schedules"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></svg></span><b data-i18n="nav.schedules">Schedules</b></button>
<button class="desktop-nav-only" data-nav="automations"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="m13 2-8 12h7l-1 8 8-12h-7z"/></svg></span><b data-i18n="nav.automations">Automations</b></button>
<button class="desktop-nav-only" data-nav="settings"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.86 2.86-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.08V21h-4v-.08A1.7 1.7 0 0 0 8.6 19.4a1.7 1.7 0 0 0-1.88.34l-.06.06-2.86-2.86.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.08-.4H3v-4h.08A1.7 1.7 0 0 0 4.6 8.6a1.7 1.7 0 0 0-.34-1.88l-.06-.06L7.06 3.8l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-.6 1.7 1.7 0 0 0 .4-1.08V3h4v.08A1.7 1.7 0 0 0 15.4 4.6a1.7 1.7 0 0 0 1.88-.34l.06-.06 2.86 2.86-.06.06A1.7 1.7 0 0 0 19.4 9c.36.3.7.65 1 1 .2.3.4.65.4 1v2c0 .35-.2.7-.4 1-.3.35-.64.7-1 1z"/></svg></span><b data-i18n="nav.settings">Settings</b></button>
<button class="desktop-nav-only" data-nav="logs"><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h10"/></svg></span><b data-i18n="nav.logs">Events</b></button>
<button data-nav="more"><span><svg viewBox="0 0 24 24" fill="currentColor" stroke="none" aria-hidden="true"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg></span><b data-i18n="nav.more">More</b></button>
</nav>
<dialog id="moreDialog" class="sheet">
<div class="dialog-head"><h2 data-i18n="nav.more">More</h2><button data-close>×</button></div>
<div class="menu-list">
<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="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>
</dialog>
<dialog id="deviceDialog">
<form method="dialog" id="deviceForm" class="dialog-form">
<div class="dialog-head"><h2 data-i18n="devices.add">Add device</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>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="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="zones.modePolicy">Mode policy</span><select name="mode_policy"><option value="house" data-i18n="zones.followHouse">Follow house mode</option><option value="cool" data-i18n="mode.cool">Cooling only</option><option value="heat" data-i18n="mode.heat">Heating only</option></select></label><label><span data-i18n="common.targetC">Manual target °C</span><input type="number" step="0.5" min="8" max="30" name="setpoint" value="23"></label></div>
<details class="profile-editor"><summary data-i18n="zones.profileTemperatures">Comfort / sleep / away temperatures</summary>
<div class="profile-grid"><strong data-i18n="mode.cool">Cooling</strong><label><span data-i18n="preset.comfort">Comfort</span><input type="number" step="0.5" name="cool_comfort_setpoint" value="23"></label><label><span data-i18n="preset.sleep">Sleep</span><input type="number" step="0.5" name="cool_sleep_setpoint" value="24.5"></label><label><span data-i18n="preset.away">Away</span><input type="number" step="0.5" name="cool_away_setpoint" value="27"></label></div>
<div class="profile-grid"><strong data-i18n="mode.heat">Heating</strong><label><span data-i18n="preset.comfort">Comfort</span><input type="number" step="0.5" name="heat_comfort_setpoint" value="21"></label><label><span data-i18n="preset.sleep">Sleep</span><input type="number" step="0.5" name="heat_sleep_setpoint" value="19"></label><label><span data-i18n="preset.away">Away</span><input type="number" step="0.5" name="heat_away_setpoint" value="17"></label></div>
</details>
<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>
<div class="two"><label><span data-i18n="zones.roomSensorWeight">Room sensor weight %</span><input type="number" step="5" min="0" max="100" name="external_sensor_weight_percent" value="40"></label><label><span data-i18n="zones.maxDifference">Max. sensor difference °C</span><input type="number" step="0.1" min="0.1" max="20" name="max_sensor_difference" value="3.0"></label></div>
<p class="field-note" data-i18n="zones.sensorHelp">The room sensor is assigned only to this zone. If it becomes unavailable, the controller falls back to the GREE sensor.</p>
</div>
<div class="two"><label><span data-i18n="zones.minAdjust">Minimum adjustment interval (s)</span><input type="number" min="15" name="min_adjust_seconds" value="120"></label><label><span data-i18n="zones.standbyOffset">Standby setpoint offset °C</span><input type="number" step="0.5" min="0.5" max="8" name="standby_offset_c" value="2"></label></div>
<label class="check"><input type="checkbox" name="smart_fan" checked> <span data-i18n="zones.smartFan">Smart fan assist</span></label>
<input type="hidden" name="min_on_seconds" value="180"><input type="hidden" name="min_off_seconds" value="180">
<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>
</dialog>
<dialog id="scheduleDialog">
<form method="dialog" id="scheduleForm" class="dialog-form">
<input type="hidden" name="id">
<div class="dialog-head"><h2 data-i18n="common.schedule">Schedule</h2><button type="button" data-close>×</button></div>
<label><span data-i18n="common.name">Name</span><input name="name" required data-i18n-placeholder="placeholder.night" placeholder="Night"></label>
<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="schedules.profile">Profile</span><select name="preset"><option value="comfort" data-i18n="preset.comfort">Comfort</option><option value="sleep" data-i18n="preset.sleep">Sleep</option><option value="away" data-i18n="preset.away">Away</option><option value="custom" data-i18n="preset.custom">Custom temperature</option></select></label>
<label id="scheduleSetpointField"><span data-i18n="common.temperatureC">Custom temperature °C</span><input type="number" name="setpoint" step="0.5" min="8" max="30" value="23"></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>
</dialog>
<dialog id="automationDialog">
<form method="dialog" id="automationForm" class="dialog-form">
<input type="hidden" name="id">
<div class="dialog-head"><h2 data-i18n="common.automation">Automation</h2><button type="button" data-close>×</button></div>
<label><span data-i18n="common.name">Name</span><input name="name" required data-i18n-placeholder="automations.namePlaceholder" placeholder="Emergency cooling"></label>
<label><span data-i18n="automations.trigger">Trigger</span><select name="trigger_kind"><option value="temperature_above" data-i18n="automations.tempAbove">Temperature above</option><option value="temperature_below" data-i18n="automations.tempBelow">Temperature below</option><option value="time" data-i18n="automations.time">Time</option></select></label>
<label><span data-i18n="automations.measurementDevice">Measurement device</span><select name="trigger_device_id"></select></label>
<div class="two"><label><span data-i18n="automations.thresholdC">Threshold °C</span><input type="number" step="0.1" name="threshold" value="27"></label><label><span data-i18n="automations.time">Time</span><input type="time" name="at_time" value="08:00"></label></div>
<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="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>
</dialog>
<dialog id="generatedTokenDialog" class="auth-dialog">
<div class="dialog-form">
<div class="dialog-head"><h2 data-i18n="settings.tokenCreated">Token created</h2><button type="button" data-close>×</button></div>
<p class="field-note" data-i18n="settings.tokenCreatedHint">Copy this token now. It will not be shown again.</p>
<label><span data-i18n="auth.token">Access token</span><input id="generatedAccessToken" class="mono" readonly></label>
<div class="form-actions"><button type="button" class="secondary" id="copyAccessToken" data-i18n="actions.copy">Copy</button><button type="button" class="primary" data-close data-i18n="actions.done">Done</button></div>
</div>
</dialog>
<dialog id="tokenDialog" class="auth-dialog">
<form method="dialog" id="tokenForm" class="dialog-form">
<div class="brand large"><div><strong>GREE Controller</strong><small data-i18n="auth.required">Authentication required</small></div></div>
<label><span data-i18n="auth.token">Access token</span><input type="password" name="token" required autocomplete="current-password"></label>
<button class="primary" type="submit" data-i18n="auth.connect">Connect</button>
</form>
</dialog>
<div id="toast" role="status" aria-live="polite"></div>
<script src="/app.js" defer></script>
</body>
</html>