function renderAccessTokens() { const list = $('#accessTokenList'); if (!list) return; list.innerHTML = app.accessTokens.length ? app.accessTokens.map(item => `
${esc(item.name)}${esc(item.token_prefix)}${esc(tr('settings.created'))}: ${esc(dateTime(item.created_at))}
`).join('') : `
${esc(tr('settings.noTokens'))}${esc(tr('settings.noTokensHint'))}
`; } function metricHaEntities() { return new Set([ app.settings?.home_assistant?.outdoor_entity_id, ...app.zones.filter(zone => ['home_assistant', 'combined'].includes(zone.sensor_source)).map(zone => zone.ha_entity_id), ...app.historyData.sensors.map(row => row.entity_id), ].filter(Boolean)); } function flowHaEntities() { return new Set((app.flowSharedInputs || []).map(item => item?.config?.entity_id).filter(Boolean)); } function knownHaEntities() { return [...new Set([ ...Object.keys(app.sensorAliases || {}), ...app.zones.map(zone => zone.ha_entity_id).filter(Boolean), app.settings?.home_assistant?.default_entity_id, app.settings?.home_assistant?.outdoor_entity_id, ...(app.flowSharedInputs || []).map(item => item?.config?.entity_id).filter(Boolean), ...app.historyData.sensors.map(row => row.entity_id), ].filter(Boolean))].sort(); } function renderSensorAliases() { const host = $('#sensorAliasList'); if (!host) return; const entities = knownHaEntities(); const metricEntities = metricHaEntities(), flowEntities = flowHaEntities(); host.innerHTML = entities.length ? entities.map(entity => { const badges = `${metricEntities.has(entity) ? `${esc(tr('settings.sensorMetricBadge'))}` : ''}${flowEntities.has(entity) ? `${esc(tr('settings.sensorFlowBadge'))}` : ''}`; return `
${esc(entity)}${badges ? `${badges}` : ''}
`; }).join('') : `
${esc(tr('settings.noSensorAliases'))}
`; } function flowSharedInputKinds() { return [ ['constant', 'flow.node.constant'], ['ha_state', 'flow.node.haState'], ['ha_numeric', 'flow.node.haNumeric'], ['ha_attribute', 'flow.node.haAttribute'], ['ha_available', 'flow.node.haAvailable'], ['outdoor_temperature', 'flow.node.outdoorTemperature'], ['device_temperature', 'flow.node.deviceTemperature'], ['zone_temperature', 'flow.node.zoneTemperature'], ['house_mode', 'flow.node.houseMode'], ['device_state', 'flow.node.deviceState'], ['zone_state', 'flow.node.zoneState'], ['group_state', 'flow.node.groupState'], ['night_mode', 'flow.node.nightMode'], ]; } function flowSharedInputDefaultConfig(kind) { if (kind === 'device_temperature') return { device_id: app.devices[0]?.id || '' }; if (kind === 'zone_temperature') return { zone_id: app.zones[0]?.id || '' }; if (kind === 'ha_state' || kind === 'ha_numeric' || kind === 'ha_available') return { entity_id: '' }; if (kind === 'ha_attribute') return { entity_id: '', attribute: '' }; if (kind === 'device_state') return { device_id: app.devices[0]?.id || '', field: 'online' }; if (kind === 'zone_state') return { zone_id: app.zones[0]?.id || '', field: 'demand' }; if (kind === 'group_state') return { group_id: app.groups[0]?.id || '', field: 'power_enabled' }; if (kind === 'constant') return { value: true }; return {}; } function sharedFlowInputSourceSummary(item) { if (!item) return '—'; const c = item.config || {}; if (item.kind === 'outdoor_temperature') return tr('flow.node.outdoorTemperature'); if (item.kind === 'device_temperature') return app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice'); if (item.kind === 'zone_temperature') return app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone'); if (item.kind === 'ha_state' || item.kind === 'ha_numeric' || item.kind === 'ha_available') return c.entity_id || 'entity_id'; if (item.kind === 'ha_attribute') return `${c.entity_id || 'entity_id'}.${c.attribute || 'attribute'}`; if (item.kind === 'house_mode') return tr('flow.houseMode'); if (item.kind === 'device_state') return `${app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice')} · ${c.field || 'state'}`; if (item.kind === 'zone_state') return `${app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'}`; if (item.kind === 'group_state') return `${app.groups.find(value => value.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'}`; return flowNodeSummary({ kind: item.kind, config: c }); } function isHaSharedInputKind(kind) { return ['ha_state', 'ha_numeric', 'ha_attribute', 'ha_available'].includes(kind); } function flowSharedInputUsages(id) { return (app.flows || []).filter(flow => (flow.nodes || []).some(node => node.kind === 'shared_input' && node.config?.input_id === id)); } function renderFlowSharedInputs() { const host = $('#flowSharedInputList'); if (!host) return; const items = app.flowSharedInputs || []; host.innerHTML = items.length ? items.map(item => { const usages = flowSharedInputUsages(item.id); const usageMarkup = usages.length ? `
${esc(tr('flow.sharedInputUsedBy', { count: usages.length }))}
${usages.slice(0, 4).map(flow => ``).join('')}${usages.length > 4 ? `+${usages.length - 4}` : ''}
` : `${esc(tr('flow.sharedInputUnused'))}`; return `
${esc(item.name)}${esc(flowNodeTitle(FLOW_NODE_META[item.kind] || { title: item.kind }))} · ${esc(sharedFlowInputSourceSummary(item))}${esc(item.id)}${usageMarkup}
`; }).join('') : `
${esc(tr('flow.sharedInputsEmpty'))}${esc(tr('flow.sharedInputsEmptyHint'))}
`; const revision = $('#flowSharedInputsRevision'); if (revision) revision.value = JSON.stringify(items); } function sharedFlowOptions(items, selected, label = item => item.name) { return items.map(item => ``).join(''); } function renderFlowSharedInputFields(kind, config = {}) { const host = $('#flowSharedInputFields'); if (!host) return; const c = config || {}; let fields = ''; if (kind === 'constant') fields = ``; else if (kind === 'ha_state') fields = ``; else if (kind === 'ha_numeric') fields = ``; else if (kind === 'ha_attribute') fields = ``; else if (kind === 'ha_available') fields = `

${esc(tr('flow.haAvailableHint'))}

`; else if (kind === 'outdoor_temperature') fields = `

${esc(tr('flow.sharedInputSourceOnlyHint'))}

`; else if (kind === 'device_temperature') fields = ``; else if (kind === 'zone_temperature') fields = ``; else if (kind === 'house_mode') fields = `

${esc(tr('flow.sharedInputSourceOnlyHint'))}

`; else if (kind === 'device_state') fields = ``; else if (kind === 'zone_state') fields = ``; else if (kind === 'group_state') fields = ``; else if (kind === 'night_mode') fields = `

${esc(tr('flow.nightModeHint'))}

`; host.innerHTML = fields; const testPanel = $('#flowSharedInputTestPanel'), testResult = $('#flowSharedInputTestResult'); if (testPanel) testPanel.hidden = !isHaSharedInputKind(kind); if (testResult) { testResult.hidden = true; testResult.innerHTML = ''; } } function openFlowSharedInputEditor(id = '') { const form = $('#flowSharedInputForm'), dialog = $('#flowSharedInputDialog'); if (!form || !dialog) return; const item = id ? (app.flowSharedInputs || []).find(value => value.id === id) : null; const kindSelect = form.kind; kindSelect.innerHTML = flowSharedInputKinds().map(([kind, key]) => ``).join(''); form.id.value = item?.id || ''; form.name.value = item?.name || ''; form.kind.value = item?.kind || 'constant'; form.dataset.editingId = item?.id || ''; renderFlowSharedInputFields(form.kind.value, item?.config || flowSharedInputDefaultConfig(form.kind.value)); dialog.showModal(); setTimeout(() => form.name.focus(), 0); } function collectFlowSharedInputConfig(kind) { const config = {}; $$('[data-shared-config]', $('#flowSharedInputFields')).forEach(field => { const key = field.dataset.sharedConfig; let value = field.value; if (kind === 'constant' && key === 'value') value = value === 'true'; config[key] = value; }); return config; } function renderLogRetention() { const select = $('#logRetentionDays'); if (!select || !app.settings) return; [...select.options].forEach(option => { option.textContent = `${option.value} ${tr('common.days')}`; }); const days = String(app.settings.event_log_retention_days || 30); if (![...select.options].some(option => option.value === days)) { const option = document.createElement('option'); option.value = days; option.textContent = `${days} ${tr('common.days')}`; select.appendChild(option); } select.value = days; } 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; form.discovery_broadcast.value = app.settings.discovery_broadcast || '255.255.255.255:7000'; form.discovery_timeout_ms.value = app.settings.discovery_timeout_ms || 3000; form.simulator_enabled.checked = !!app.settings.simulator_enabled; form.history_retention_days.value = app.settings.history_retention_days || 30; 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; const cloud = app.settings.gree_cloud || {}; form.gree_cloud_enabled.checked = !!cloud.enabled; form.gree_cloud_region.value = cloud.region || 'Europe'; form.gree_cloud_username.value = cloud.username || ''; form.gree_cloud_password.value = ''; form.gree_cloud_password.placeholder = cloud.password_configured ? tr('settings.secretSaved') : tr('settings.secretKeep'); form.gree_cloud_polling_interval_seconds.value = Number(cloud.polling_interval_seconds || 60); const cloudInstance = $('#greeCloudInstanceId'); if (cloudInstance) cloudInstance.textContent = cloud.installation_id || '—'; const cloudLastContact = $('#greeCloudLastContact'); if (cloudLastContact) cloudLastContact.textContent = cloud.last_successful_contact ? dateTime(cloud.last_successful_contact) : tr('common.unavailable'); form.compressor_protection_enabled.checked = app.settings.compressor_protection_enabled !== false; form.compressor_protection_minutes.value = (Number(app.settings.compressor_protection_seconds || 180) / 60).toFixed(1).replace(/\.0$/, ''); updateCompressorProtectionFields(); 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; form.influx_url.value = app.settings.influxdb?.url || ''; form.influx_database.value = app.settings.influxdb?.database || 'gree_controller'; form.influx_username.value = app.settings.influxdb?.username || ''; form.influx_password.value = ''; form.influx_password.placeholder = app.settings.influxdb?.password_configured ? tr('settings.secretSaved') : tr('settings.secretKeep'); form.influx_org.value = app.settings.influxdb?.org || ''; form.influx_bucket.value = app.settings.influxdb?.bucket || 'gree_controller'; form.influx_token.value = ''; 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; form.debug_cloud_requests.checked = !!app.settings.debug?.cloud_requests; form.debug_cloud_mqtt.checked = !!app.settings.debug?.cloud_mqtt; const n = app.settings.notifications || {}; form.notifications_enabled.checked = !!n.enabled; form.notifications_mode.value = n.mode || 'problems'; form.notifications_provider.value = n.provider || 'pushover'; form.pushover_app_token.value = ''; form.pushover_user_key.value = ''; form.slack_webhook_url.value = ''; form.discord_webhook_url.value = ''; form.pushover_app_token.placeholder = n.pushover_configured ? tr('settings.secretSaved') : tr('notifications.applicationTokenPlaceholder'); form.pushover_user_key.placeholder = n.pushover_configured ? tr('settings.secretSaved') : tr('notifications.userKeyPlaceholder'); form.slack_webhook_url.placeholder = n.slack_configured ? tr('settings.secretSaved') : 'https://hooks.slack.com/services/…'; form.discord_webhook_url.placeholder = n.discord_configured ? tr('settings.secretSaved') : 'https://discord.com/api/webhooks/…'; form.notification_cooldown_seconds.value = n.cooldown_seconds || 300; form.notification_failure_threshold.value = n.communication_failure_threshold || 3; form.notification_target_timeout.value = n.target_timeout_minutes || 60; const alerts = n.alert_types || {}; form.notification_alert_stale_sensor.checked = alerts.stale_sensor !== false; form.notification_alert_sensor_errors.checked = alerts.sensor_errors !== false; form.notification_alert_communication.checked = alerts.communication !== false; form.notification_alert_target_timeout.checked = alerts.target_timeout !== false; form.notification_alert_automation.checked = alerts.automation !== false; form.notification_alert_sensor_discrepancy.checked = alerts.sensor_discrepancy !== false; form.notification_alert_control_errors.checked = alerts.control_errors !== false; form.notification_alert_important_events.checked = alerts.important_events !== false; form.notification_alert_other.checked = alerts.other !== false; updateNotificationFields(); updateInfluxFields(); renderLogRetention(); renderGreeFrameStats(); renderSystemInfo(); renderSimulationModeBanner(); setSettingsTab(app.settingsTab); markFormClean(form); void refreshGreeCloudRuntimeStatus(); } function updateCompressorProtectionFields() { const form = $('#settingsForm'); if (!form?.compressor_protection_enabled || !form?.compressor_protection_minutes) return; form.compressor_protection_minutes.disabled = !form.compressor_protection_enabled.checked; } function renderSimulationModeBanner() { const banner = $('#simulationModeBanner'); if (!banner) return; const enabled = !!app.settings?.simulator_enabled; banner.hidden = !enabled; document.body.classList.toggle('simulation-mode-enabled', enabled); } function setSettingsTab(tab) { app.settingsTab = ['gree', 'cloud'].includes(tab) ? tab : 'app'; $$('[data-settings-pane]').forEach(pane => { pane.hidden = pane.dataset.settingsPane !== app.settingsTab; }); $$('[data-settings-tab]').forEach(button => { const active = button.dataset.settingsTab === app.settingsTab; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); }); } function renderSystemInfo() { const host = $('#systemInfo'); if (!host) return; const elapsed = Math.max(0, Math.floor((Date.now() - Number(app.systemSnapshotAt || Date.now())) / 1000)); const uptime = Number(app.system?.uptime_seconds || 0) + elapsed; const dbPath = String(app.system?.database || ''); const database = dbPath ? dbPath.split(/[\\/]/).filter(Boolean).pop() : '—'; const ready = !!app.system?.control_ready; const connected = app.connectionStatus === 'connected'; const items = [ [tr('settings.version'), app.system?.version || '—', 'version'], [tr('settings.uptime'), formatDuration(uptime), 'uptime'], [tr('settings.controlEngine'), ready ? tr('settings.ready') : tr('settings.syncing'), ready ? 'ok' : 'warn'], [tr('settings.websocket'), tr(`status.${app.connectionStatus}`), connected ? 'ok' : 'warn'], [tr('settings.apiAuth'), app.system?.auth_required ? tr('settings.enabled') : tr('settings.disabled'), 'neutral'], [tr('settings.httpBind'), app.system?.bind || '—', 'mono'], [tr('settings.database'), database, 'mono'], [tr('settings.basePath'), app.system?.base_path || APP_BASE || '/', 'mono'], ]; host.innerHTML = `
${esc(tr('settings.systemState'))}

${esc(tr('settings.systemStatusTitle'))}

${esc(tr('settings.systemStateHint'))}

${esc(ready && connected ? tr('settings.healthy') : tr('settings.attention'))}
${items.map(([label, value, tone]) => `
${esc(label)}${esc(value)}
`).join('')}
`; } function renderGreeFrameStats() { const host = $('#greeFrameStats'); if (!host) return; const total = Number(app.system?.gree_received_frames || 0); const byDevice = app.system?.gree_received_frames_by_device || {}; const deviceRows = app.devices.filter(device => !device.simulated).map(device => { const count = Number(byDevice[device.id] || 0); return `
${esc(device.name || device.id)}${count.toLocaleString(locale())}
`; }).join(''); host.innerHTML = `
${esc(tr('settings.receivedFramesTotal'))}${total.toLocaleString(locale())}
${deviceRows || `
${esc(tr('settings.receivedFramesDevices'))}0
`}`; } 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; markFormClean(form); } 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'); form.ha_entity_id.value = app.settings.home_assistant?.default_entity_id || ''; form.ha_outdoor_entity_id.value = app.settings.home_assistant?.outdoor_entity_id || ''; form.ha_sensor_stale_after_minutes.value = String(Math.max(1, Math.round(Number(app.settings.home_assistant?.sensor_stale_after_seconds || 300) / 60))); form.ha_allow_invalid_tls.checked = !!app.settings.home_assistant?.allow_invalid_tls; form.outdoor_assist_enabled.checked = !!app.settings.outdoor_assist_enabled; renderSensorAliases(); renderFlowSharedInputs(); renderAccessTokens(); markFormClean(form); } function updateInfluxFields() { const version = $('#settingsForm [name=influx_version]')?.value || '2'; $$('[data-influx-fields]').forEach(node => { node.hidden = node.dataset.influxFields !== version; }); } function updateNotificationFields() { const provider = $('#settingsForm [name=notifications_provider]')?.value || 'pushover'; $$('[data-notification-provider]').forEach(n => n.hidden = n.dataset.notificationProvider !== provider); } function logCategory(kind = '') { const prefix = String(kind).split('.')[0]; return ['device', 'zone', 'automation', 'settings'].includes(prefix) ? prefix : (['home_assistant', 'influx', 'notification'].includes(prefix) ? 'integration' : 'system'); } function debugLine(source, kind, message, timestamp = new Date().toISOString(), data = null) { app.debugLines.push({ source, kind, message, timestamp, data }); if (app.debugLines.length > 160) app.debugLines.splice(0, app.debugLines.length - 160); renderDebugOverlay(); } function renderDebugOverlay() { const overlay = $('#debugOverlay'); if (!overlay) return; const enabled = !!app.settings?.debug?.overlay_enabled; overlay.hidden = !enabled; if (!enabled) return; if (!['all', 'requests', 'gree', 'cloud', 'mqtt'].includes(app.debugFilter)) app.debugFilter = 'all'; const status = $('#debugOverlayStatus'); if (status) { const enabledSources = [ app.settings?.debug?.gree_frames ? tr('debug.gree') : '', app.settings?.debug?.cloud_requests ? tr('debug.cloud') : '', app.settings?.debug?.cloud_mqtt ? tr('debug.mqtt') : '', ].filter(Boolean); status.textContent = enabledSources.length ? `${tr('debug.liveSources')}: ${enabledSources.join(' · ')}` : tr('debug.apiOnly'); } $$('[data-debug-filter]', overlay).forEach(button => { const active = button.dataset.debugFilter === app.debugFilter; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); }); const host = $('#debugOverlayLines'); if (!host) return; const visible = app.debugLines.filter(line => { if (app.debugFilter === 'gree') return line.source === 'GREE'; if (app.debugFilter === 'requests') return line.source === 'HTTP'; if (app.debugFilter === 'cloud') return line.source === 'CLOUD'; if (app.debugFilter === 'mqtt') return line.source === 'MQTT'; return true; }).slice(-120); host.innerHTML = visible.length ? visible.map(line => { const details = line.data == null ? '' : ` ${typeof line.data === 'string' ? line.data : JSON.stringify(line.data)}`; const sourceClass = line.source === 'GREE' ? 'gree' : line.source === 'HTTP' ? 'request' : line.source === 'CLOUD' ? 'cloud' : line.source === 'MQTT' ? 'mqtt' : 'api'; return `
${esc(line.source)}${esc(line.kind)}: ${esc(line.message || '')}${esc(details)}
`; }).join('') : `
${esc(tr(app.debugFilter === 'gree' ? 'debug.emptyGree' : app.debugFilter === 'requests' ? 'debug.emptyRequests' : app.debugFilter === 'cloud' ? 'debug.emptyCloud' : app.debugFilter === 'mqtt' ? 'debug.emptyMqtt' : 'debug.empty'))}
`; host.scrollTop = host.scrollHeight; } async function loadDebugBacklog() { if (app.debugBacklogLoaded || !app.settings?.debug?.overlay_enabled) return; try { const data = await api('/api/events?limit=60'); app.debugLines = (data.events || []).reverse().map(item => ({ source: 'API', kind: item.kind, message: item.message, timestamp: item.timestamp, data: item.metadata })).slice(-120); app.debugBacklogLoaded = true; renderDebugOverlay(); } catch (_) { } } function formatDuration(seconds) { const days = Math.floor(seconds / 86400), hours = Math.floor((seconds % 86400) / 3600), minutes = Math.floor((seconds % 3600) / 60); return `${days ? `${days}d ` : ''}${hours}h ${minutes}m`; } setInterval(() => { if (app.currentView === 'settings') renderSystemInfo(); }, 30000); async function refreshGreeCloudRuntimeStatus() { const account = $('#greeCloudAccountStatus'); const mqtt = $('#greeCloudMqttStatus'); if (!account || !mqtt) return; const setText = (selector, value) => { const node = $(selector); if (node) node.textContent = value; }; try { const status = await api('/api/integrations/gree-cloud/status'); const runtime = status.runtime || {}; account.textContent = String(status.account_status || 'unknown').replaceAll('_', ' '); mqtt.textContent = String(status.mqtt_status || 'disconnected').replaceAll('_', ' '); setText('#greeCloudDevicesOnline', `${Number(status.online_device_count || 0)} / ${Number(status.device_count || 0)}`); setText('#greeCloudRestResponseTime', status.last_rest_response_time_ms != null && Number.isFinite(Number(status.last_rest_response_time_ms)) ? `${Number(status.last_rest_response_time_ms)} ms` : tr('common.unavailable')); setText('#greeCloudResponseTime', runtime.last_response_time_ms != null && Number.isFinite(Number(runtime.last_response_time_ms)) ? `${Number(runtime.last_response_time_ms)} ms` : tr('common.unavailable')); setText('#greeCloudLastDeviceResponse', runtime.last_device_response ? dateTime(runtime.last_device_response) : tr('common.unavailable')); setText('#greeCloudLastMqttMessage', runtime.last_mqtt_message ? dateTime(runtime.last_mqtt_message) : tr('common.unavailable')); setText('#greeCloudConnectedSince', runtime.mqtt_connected_since ? dateTime(runtime.mqtt_connected_since) : tr('common.unavailable')); setText('#greeCloudBroker', runtime.broker_host || tr('common.unavailable')); setText('#greeCloudTraffic', `${Number(runtime.requests_sent || 0)} / ${Number(runtime.responses_received || 0)} / ${Number(runtime.request_timeouts || 0)}`); if (status.last_successful_contact) setText('#greeCloudLastContact', dateTime(status.last_successful_contact)); } catch (_) { account.textContent = 'unknown'; mqtt.textContent = 'disconnected'; ['#greeCloudDevicesOnline', '#greeCloudRestResponseTime', '#greeCloudResponseTime', '#greeCloudLastDeviceResponse', '#greeCloudLastMqttMessage', '#greeCloudConnectedSince', '#greeCloudBroker', '#greeCloudTraffic'].forEach(selector => setText(selector, tr('common.unavailable'))); } }