This commit is contained in:
Mateusz Gruszczyński
2026-09-14 23:10:17 +02:00
parent 4bb9c8621a
commit 0a2fb8c6c7
47 changed files with 1337 additions and 372 deletions
+47 -4
View File
@@ -24,6 +24,35 @@ function deviceTransportLabel(device) {
return device.connection_type === 'gree_cloud' ? 'GREE Cloud' : 'Local';
}
function deviceInstallationForDevice(deviceId) {
return (app.deviceGroups || []).find(group => (group.device_ids || []).includes(deviceId)) || null;
}
function deviceInstallationKindLabel(group) {
return tr(group?.kind === 'multisplit' ? 'devices.multisplit' : 'devices.split');
}
function installationEnergySourceLabel(group) {
if (!group) return '—';
if (group.energy_source === 'home_assistant' || (group.energy_source === 'auto' && group.ha_energy_entity_id && !group.energy_device_id)) {
return group.ha_energy_entity_id ? `Home Assistant · ${group.ha_energy_entity_id}` : 'Home Assistant';
}
if (group.energy_source === 'gree_cloud' || group.energy_device_id) {
const source = app.devices.find(device => device.id === group.energy_device_id);
return source ? `GREE Cloud · ${source.name}` : 'GREE Cloud';
}
return tr('energy.auto');
}
function effectiveDeviceOutdoorTemperature(device) {
const group = deviceInstallationForDevice(device?.id);
if (group?.outdoor_temperature_device_id) {
const source = app.devices.find(item => item.id === group.outdoor_temperature_device_id);
if (source?.outdoor_temperature != null) return source.outdoor_temperature;
}
return device?.outdoor_temperature;
}
function deviceConnectionStatusLabel(device) {
const key = {
online: 'status.online',
@@ -85,7 +114,7 @@ function manualLocalDeviceCard(device) {
<div class="target-temp editable-target" data-temperature-kind="device" data-id="${esc(device.id)}" data-value="${Number(device.target_temperature)}" data-editable="true" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number(device.target_temperature).toFixed(1)}<small>°C</small></div>
<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="current-line">${esc(tr('devices.currentTemperature'))}: <strong>${fmtTemp(device.current_temperature)}</strong>${effectiveDeviceOutdoorTemperature(device) == null ? '' : ` · ${esc(tr('devices.outdoor'))} ${fmtTemp(effectiveDeviceOutdoorTemperature(device))}`}</div>
${compressorQueuePanel(managedZone)}
<div class="mode-row quick-control-row quick-control-row-5">${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>
<div class="fan-row quick-control-row quick-control-row-4">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>
@@ -123,7 +152,7 @@ function manualCloudDeviceCard(device) {
<div class="target-temp editable-target" data-temperature-kind="device" data-id="${esc(device.id)}" data-value="${Number(device.target_temperature)}" data-temp-step="${tempStep}" data-temp-min="${minTemp}" data-temp-max="${maxTemp}" data-editable="true" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number(device.target_temperature).toFixed(1)}<small>°C</small></div>
<button data-action="temperature" data-delta="${tempStep}" 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="current-line">${esc(tr('devices.currentTemperature'))}: <strong>${fmtTemp(device.current_temperature)}</strong>${effectiveDeviceOutdoorTemperature(device) == null ? '' : ` · ${esc(tr('devices.outdoor'))} ${fmtTemp(effectiveDeviceOutdoorTemperature(device))}`}</div>
${compressorQueuePanel(managedZone)}
${modes.length ? `<div class="mode-row quick-control-row quick-control-row-5">${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>` : ''}
${fans.length ? `<div class="fan-row quick-control-row quick-control-row-${Math.min(6, fans.length)}">${fans.map(fan => `<button class="${Number(device.fan_speed) === fan ? 'active' : ''}" data-action="fan" data-value="${fan}" data-device="${esc(device.id)}">${esc(fanLabel(fan))}</button>`).join('')}</div>` : ''}
@@ -148,7 +177,7 @@ function technicalDeviceCard(device) {
const model = device.model || tr('common.unavailable');
const firmware = device.firmware || tr('common.unavailable');
const cid = device.cid || tr('common.unavailable');
const error = device.last_error ? `<div class="technical-device-error"><span>${esc(tr('devices.lastError'))}</span><strong title="${esc(device.last_error)}">${esc(device.last_error)}</strong></div>` : '';
const error = device.last_error ? `<div class="inline-alert error"><span>${esc(tr('devices.lastError'))}</span><strong title="${esc(device.last_error)}">${esc(device.last_error)}</strong></div>` : '';
return `<article class="device-card technical-device-card" data-device-card="${esc(device.id)}">
<div class="technical-device-head">
<div class="device-title"><span class="eyebrow">${esc(tr('devices.technicalUnit'))} · ${esc(deviceTransportLabel(device))}</span><h3>${esc(device.name)}</h3><p><span class="status ${device.online ? 'online' : ''}">${esc(device.connection_type === 'gree_cloud' ? (device.connection_status || 'unknown').replaceAll('_', ' ') : tr(device.online ? 'status.online' : 'status.offline'))}</span> · ${esc(model)}</p></div>
@@ -162,6 +191,7 @@ function technicalDeviceCard(device) {
<div><span>${esc(tr('devices.modelFirmware'))}</span><strong>${esc(model)}</strong><small>${esc(firmware)}</small></div>
<div><span>${esc(tr('devices.lastSeen'))}</span><strong>${esc(lastSeen)}</strong></div>
<div><span>${esc(tr('devices.communicationFailures'))}</span><strong>${esc(device.communication_failures ?? 0)}</strong></div>
${deviceInstallationForDevice(device.id) ? `<div><span>${esc(tr('devices.installation'))}</span><strong>${esc(deviceInstallationForDevice(device.id).name)}</strong><small>${esc(deviceInstallationKindLabel(deviceInstallationForDevice(device.id)))}</small></div>` : ''}
</div>
${error}
<div class="technical-device-actions">
@@ -182,7 +212,7 @@ function cloudTechnicalDeviceCard(device) {
const responseTime = deviceResponseTimeLabel(device);
const modelCell = device.model ? `<div><span>${esc(tr('devices.model'))}</span><strong>${esc(device.model)}</strong></div>` : '';
const firmwareCell = device.firmware ? `<div><span>${esc(tr('devices.firmware'))}</span><strong>${esc(device.firmware)}</strong></div>` : '';
const error = device.last_error ? `<div class="technical-device-error"><span>${esc(tr('devices.lastError'))}</span><strong title="${esc(device.last_error)}">${esc(device.last_error)}</strong></div>` : '';
const error = device.last_error ? `<div class="inline-alert error"><span>${esc(tr('devices.lastError'))}</span><strong title="${esc(device.last_error)}">${esc(device.last_error)}</strong></div>` : '';
return `<article class="device-card technical-device-card" data-device-card="${esc(device.id)}">
<div class="technical-device-head">
<div class="device-title"><span class="eyebrow">${esc(tr('devices.cloudUnit'))}</span><h3>${esc(device.name)}</h3><p><span class="status ${device.online ? 'online' : ''}">${esc(status)}</span> · GREE Cloud</p></div>
@@ -198,6 +228,7 @@ function cloudTechnicalDeviceCard(device) {
<div><span>${esc(tr('devices.lastSync'))}</span><strong>${esc(lastSync)}</strong></div>
<div><span>${esc(tr('devices.lastResponse'))}</span><strong>${esc(lastSeen)}</strong><small>${esc(responseTime)}</small></div>
<div><span>${esc(tr('devices.communicationFailures'))}</span><strong>${esc(device.communication_failures ?? 0)}</strong></div>
${deviceInstallationForDevice(device.id) ? `<div><span>${esc(tr('devices.installation'))}</span><strong>${esc(deviceInstallationForDevice(device.id).name)}</strong><small>${esc(deviceInstallationKindLabel(deviceInstallationForDevice(device.id)))}</small></div>` : ''}
</div>
${error}
<div class="technical-device-actions">
@@ -209,7 +240,19 @@ function cloudTechnicalDeviceCard(device) {
</article>`;
}
function renderDeviceInstallationsSummary() {
const host = $('#deviceGroupsSummary');
if (!host) return;
const groups = app.deviceGroups || [];
host.innerHTML = groups.length ? groups.map(group => {
const members = (group.device_ids || []).map(id => app.devices.find(device => device.id === id)?.name).filter(Boolean);
const outdoor = app.devices.find(device => device.id === group.outdoor_temperature_device_id)?.name;
return `<article class="installation-summary-card"><div><span class="eyebrow">${esc(deviceInstallationKindLabel(group))}</span><strong>${esc(group.name)}</strong><small>${esc(members.join(' · ') || '—')}</small></div><div><span>${esc(tr('energy.source'))}</span><b>${esc(installationEnergySourceLabel(group))}</b>${outdoor ? `<small>${esc(tr('devices.outdoorSourceDevice'))}: ${esc(outdoor)}</small>` : ''}</div><button type="button" class="secondary" data-action="edit-device-group" data-id="${esc(group.id)}">${esc(tr('actions.edit'))}</button></article>`;
}).join('') : '';
}
function renderDevices() {
renderDeviceInstallationsSummary();
const empty = `<div class="empty"><strong>${esc(tr('devices.emptyTitle'))}</strong>${esc(tr('devices.emptyText'))}</div>`;
$('#dashboardDevices').innerHTML = app.devices.length ? app.devices.map(manualDeviceCard).join('') : empty;
$('#deviceList').innerHTML = app.devices.length ? app.devices.map(technicalDeviceCard).join('') : empty;