This commit is contained in:
Mateusz Gruszczyński
2026-08-30 09:50:03 +02:00
parent 2383526a83
commit fbc66b0c26
22 changed files with 2911 additions and 1632 deletions
+71 -44
View File
@@ -18,15 +18,32 @@
</div>
<div class="toolbar-runtime-status" id="toolbarRuntimeStatus" aria-live="polite">0/0</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"></button>
<div class="toolbar-picker" data-i18n-title="controls.language" title="Language">
<span class="toolbar-picker-value" id="languageLabel" aria-hidden="true">EN</span>
<span class="toolbar-picker-chevron" aria-hidden="true"></span>
<select class="toolbar-picker-select" id="languageSelect" data-i18n-aria="controls.language" aria-label="Language">
<option value="en">English</option>
</select>
</div>
<div class="toolbar-picker" data-i18n-title="controls.theme" title="Theme">
<span class="toolbar-picker-value theme-picker-value" id="themeIcon" aria-hidden="true"></span>
<span class="toolbar-picker-chevron" aria-hidden="true"></span>
<select class="toolbar-picker-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>
</div>
<button class="icon-button toolbar-icon-control" id="refreshButton" data-i18n-title="actions.refresh" data-i18n-aria="actions.refresh" title="Refresh" aria-label="Refresh"></button>
</div>
<div class="connection-banner" id="connectionBanner" role="status" aria-live="polite" hidden>
<span class="connection-banner-dot" aria-hidden="true"></span>
<div><strong id="connectionBannerTitle">Disconnected</strong><span id="connectionBannerText">Live updates are unavailable. Displayed data may be stale.</span></div>
<button class="secondary" type="button" id="connectionRetry" data-i18n="actions.retry">Retry</button>
</div>
<div class="simulation-mode-banner" id="simulationModeBanner" role="status" aria-live="polite" hidden>
<strong data-i18n="simulation.bannerTitle">SIMULATION MODE</strong>
<span data-i18n="simulation.bannerText">Simulation is enabled. Verify the mode before relying on controller output.</span>
</div>
</header>
@@ -158,7 +175,7 @@
<div class="panel chart-panel history-toolbar-panel">
<div class="chart-toolbar">
<div id="historyContextControls" class="history-context-controls"></div>
<label class="history-range-control"><span data-i18n="history.range">Range</span><select id="historyHours" aria-label="History range">
<label class="history-range-control"><span data-i18n="history.range">Range</span><select id="historyHours" data-i18n-aria="history.range" aria-label="History range">
<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>
@@ -228,28 +245,26 @@
<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">
<div class="settings-mode-tabs" role="tablist" data-i18n-aria="settings.settingsSections" aria-label="Settings sections">
<button type="button" class="active" role="tab" aria-selected="true" data-settings-tab="app" data-i18n="settings.applicationSettings">Application</button>
<button type="button" role="tab" aria-selected="false" data-settings-tab="gree" data-i18n="settings.greeSettings">GREE</button>
</div>
<form class="settings-form" id="settingsForm" novalidate>
<div class="settings-pane" data-settings-pane="app">
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.controller">Controller</h3><p data-i18n="settings.controllerHint">Runtime and LAN discovery settings.</p></div></div>
<div class="settings-block-head"><div><h3 data-i18n="settings.applicationRuntime">Application runtime</h3><p data-i18n="settings.applicationRuntimeHint">Application-wide behavior that does not change GREE transport or discovery.</p></div></div>
<div class="settings-grid">
<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>
<label class="check wide"><input type="checkbox" name="simulator_enabled"> <span data-i18n="settings.simulationMode">Simulation mode</span></label>
<p class="field-note wide warning-note" data-i18n="settings.simulationModeHint">When enabled, a prominent warning is shown throughout the application.</p>
</div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="notifications.title">Notifications</h3><p data-i18n="notifications.hint">Send important events or only problems/anomalies.</p></div></div>
<div class="settings-grid">
<label class="check wide"><input type="checkbox" name="notifications_enabled"> <span data-i18n="notifications.enable">Enable notifications</span></label>
<label><span data-i18n="notifications.mode">Mode</span><select name="notifications_mode"><option value="problems" data-i18n="notifications.problems">Errors and anomalies only</option><option value="important" data-i18n="notifications.important">Important events + problems</option></select></label>
<label><span data-i18n="notifications.provider">Provider</span><select name="notifications_provider"><option value="pushover">Pushover</option><option value="slack">Slack</option><option value="discord">Discord</option></select></label>
<div class="wide notification-provider" data-notification-provider="pushover"><label><span data-i18n="notifications.pushoverToken">Pushover application token</span><input type="password" name="pushover_app_token" autocomplete="new-password" placeholder="Leave empty to keep saved secret"></label><label><span data-i18n="notifications.pushoverUser">Pushover user/group key</span><input type="password" name="pushover_user_key" autocomplete="new-password" placeholder="Leave empty to keep saved secret"></label></div>
<div class="wide notification-provider" data-notification-provider="pushover"><label><span data-i18n="notifications.pushoverToken">Pushover application token</span><input type="password" name="pushover_app_token" autocomplete="new-password" data-i18n-placeholder="settings.secretKeep" placeholder="Leave empty to keep saved secret"></label><label><span data-i18n="notifications.pushoverUser">Pushover user/group key</span><input type="password" name="pushover_user_key" autocomplete="new-password" data-i18n-placeholder="settings.secretKeep" placeholder="Leave empty to keep saved secret"></label></div>
<label class="wide notification-provider" data-notification-provider="slack"><span data-i18n="notifications.slackWebhook">Slack incoming webhook</span><input type="password" name="slack_webhook_url" autocomplete="new-password" placeholder="https://hooks.slack.com/services/…"></label>
<label class="wide notification-provider" data-notification-provider="discord"><span data-i18n="notifications.discordWebhook">Discord webhook</span><input type="password" name="discord_webhook_url" autocomplete="new-password" placeholder="https://discord.com/api/webhooks/…"></label>
<label><span data-i18n="notifications.cooldown">Cooldown (s)</span><input type="number" name="notification_cooldown_seconds" min="30" max="86400" value="300"></label>
@@ -272,7 +287,6 @@
<div class="form-actions wide"><button type="button" class="secondary" id="testNotifications" data-i18n="notifications.test">Send test notification</button></div>
</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>
<div class="settings-grid">
@@ -281,17 +295,6 @@
<label class="check wide"><input type="checkbox" name="history_compaction_enabled"> <span data-i18n="settings.compaction">Compact old metrics</span></label>
</div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.greeCommands">GREE commands</h3><p data-i18n="settings.suppressBeepHint">Only changed properties are sent. Buzzer suppression is also requested when supported by the unit firmware.</p></div></div>
<div class="settings-grid"><label class="check wide"><input type="checkbox" name="suppress_device_beep"> <span data-i18n="settings.suppressBeep">Try to suppress command beeps</span></label></div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.greeTraffic">GREE traffic</h3><p data-i18n="settings.greeTrafficHint">Live counters of UDP frames received from configured air conditioners since controller startup.</p></div></div>
<div id="greeFrameStats" class="gree-frame-stats"></div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.influx">Long-term InfluxDB history</h3><p data-i18n="settings.influxHint">Optional archive for older history. InfluxDB 1.x and 2.x are supported.</p></div></div>
<div class="settings-grid">
@@ -311,7 +314,6 @@
</div>
</div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.debug">On-screen debug</h3><p data-i18n="settings.debugHint">Live diagnostics displayed on every application page.</p></div></div>
<div class="settings-grid">
@@ -319,24 +321,39 @@
<label class="check"><input type="checkbox" name="debug_gree_frames"> <span data-i18n="settings.debugGreeFrames">Include GREE protocol frames</span></label>
</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>
<div class="form-actions backup-actions"><button type="button" class="secondary" id="exportSettings" data-i18n="settings.export">Export settings</button><button type="button" class="secondary" id="importSettings" data-i18n="settings.import">Import settings</button><input type="file" id="importSettingsFile" accept="application/json,.json" hidden></div>
</section>
<div class="panel system-panel" id="systemInfo"></div>
</div>
<div class="settings-pane" data-settings-pane="gree" hidden>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.greeConnection">GREE connection</h3><p data-i18n="settings.greeConnectionHint">Controller identity, polling intervals and LAN discovery transport.</p></div></div>
<div class="settings-grid">
<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>
</div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.greeCommands">GREE commands</h3><p data-i18n="settings.suppressBeepHint">Only changed properties are sent. Buzzer suppression is also requested when supported by the unit firmware.</p></div></div>
<div class="settings-grid"><label class="check wide"><input type="checkbox" name="suppress_device_beep"> <span data-i18n="settings.suppressBeep">Try to suppress command beeps</span></label></div>
</section>
<section class="panel settings-block">
<div class="settings-block-head"><div><h3 data-i18n="settings.greeTraffic">GREE traffic</h3><p data-i18n="settings.greeTrafficHint">Live counters of UDP frames received from configured air conditioners since controller startup.</p></div></div>
<div id="greeFrameStats" class="gree-frame-stats"></div>
</section>
</div>
<div class="settings-save-bar"><span data-i18n="settings.saveHint">Save changes made in the blocks above.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></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 logs-toolbar"><div><strong data-i18n="logs.retention">Log retention</strong><small data-i18n="logs.retentionHint">Old event rows are removed automatically during maintenance.</small></div><label><span data-i18n="logs.level">Level</span><select id="logLevelFilter"><option value="all" data-i18n="logs.all">All</option><option value="error" data-i18n="logs.errors">Errors</option><option value="warn" data-i18n="logs.warnings">Warnings</option><option value="info">Info</option></select></label><label><span data-i18n="logs.category">Category</span><select id="logCategoryFilter"><option value="all" data-i18n="logs.all">All</option><option value="device" data-i18n="nav.devices">Devices</option><option value="zone" data-i18n="nav.zones">Thermostats</option><option value="automation" data-i18n="nav.automations">Automations</option><option value="settings" data-i18n="nav.settings">Settings</option><option value="integration" data-i18n="logs.integrations">Integrations</option><option value="system" data-i18n="logs.systemOther">System/other</option></select></label><label><span data-i18n="logs.keepFor">Keep for</span><select id="logRetentionDays"><option value="7">7 days</option><option value="14">14 days</option><option value="30">30 days</option><option value="90">90 days</option><option value="180">180 days</option><option value="365">365 days</option></select></label><button class="secondary" id="saveLogRetention" data-i18n="actions.save">Save</button></div>
<div class="panel logs-toolbar"><div><strong data-i18n="logs.retention">Log retention</strong><small data-i18n="logs.retentionHint">Old event rows are removed automatically during maintenance.</small></div><label><span data-i18n="logs.level">Level</span><select id="logLevelFilter"><option value="all" data-i18n="logs.all">All</option><option value="error" data-i18n="logs.errors">Errors</option><option value="warn" data-i18n="logs.warnings">Warnings</option><option value="info" data-i18n="logs.info">Info</option></select></label><label><span data-i18n="logs.category">Category</span><select id="logCategoryFilter"><option value="all" data-i18n="logs.all">All</option><option value="device" data-i18n="nav.devices">Devices</option><option value="zone" data-i18n="nav.zones">Thermostats</option><option value="automation" data-i18n="nav.automations">Automations</option><option value="settings" data-i18n="nav.settings">Settings</option><option value="integration" data-i18n="logs.integrations">Integrations</option><option value="system" data-i18n="logs.systemOther">System/other</option></select></label><label><span data-i18n="logs.keepFor">Keep for</span><select id="logRetentionDays"><option value="7" data-i18n="logs.days7">7 days</option><option value="14" data-i18n="logs.days14">14 days</option><option value="30" data-i18n="logs.days30">30 days</option><option value="90" data-i18n="logs.days90">90 days</option><option value="180" data-i18n="logs.days180">180 days</option><option value="365" data-i18n="logs.days365">365 days</option></select></label><button class="secondary" id="saveLogRetention" data-i18n="actions.save">Save</button></div>
<div class="panel log-list" id="logList"></div>
</section>
</main>
@@ -557,7 +574,17 @@
</dialog>
<aside id="debugOverlay" class="debug-overlay" hidden>
<div class="debug-overlay-head"><div><strong data-i18n="debug.title">Live debug</strong><small id="debugOverlayStatus"></small></div><button type="button" data-action="debug-clear" data-i18n="debug.clear">Clear</button></div>
<div class="debug-overlay-head">
<div class="debug-overlay-title"><strong data-i18n="debug.title">Live debug</strong><small id="debugOverlayStatus"></small></div>
<div class="debug-overlay-actions">
<div class="debug-source-tabs" role="tablist" data-i18n-aria="debug.filterAria" aria-label="Debug source">
<button type="button" class="active" role="tab" aria-selected="true" data-debug-filter="all" data-i18n="debug.all">All</button>
<button type="button" role="tab" aria-selected="false" data-debug-filter="requests" data-i18n="debug.requests">Requests</button>
<button type="button" role="tab" aria-selected="false" data-debug-filter="gree" data-i18n="debug.gree">GREE</button>
</div>
<button type="button" class="debug-clear" data-action="debug-clear" data-i18n="debug.clear">Clear</button>
</div>
</div>
<div id="debugOverlayLines" class="debug-overlay-lines"></div>
</aside>
<div id="toastStack" class="toast-stack" role="status" aria-live="polite" aria-atomic="false"></div>