v0.12.0
This commit is contained in:
+175
-115
@@ -1,103 +1,127 @@
|
||||
const SETTINGS_ENDPOINTS = {
|
||||
application: '/api/settings/application',
|
||||
gree: '/api/settings/gree',
|
||||
history: '/api/settings/history',
|
||||
influxdb: '/api/settings/influxdb',
|
||||
notifications: '/api/settings/notifications',
|
||||
night: '/api/settings/night',
|
||||
homeAssistant: '/api/settings/home-assistant',
|
||||
debug: '/api/settings/debug',
|
||||
};
|
||||
|
||||
function currentSettingsBody() {
|
||||
const settings = app.settings || {};
|
||||
const influx = settings.influxdb || {};
|
||||
const ha = settings.home_assistant || {};
|
||||
async function fetchSettingsSections() {
|
||||
const [application, gree, history, influxdb, notifications, night, homeAssistant, debug] = await Promise.all([
|
||||
api(SETTINGS_ENDPOINTS.application),
|
||||
api(SETTINGS_ENDPOINTS.gree),
|
||||
api(SETTINGS_ENDPOINTS.history),
|
||||
api(SETTINGS_ENDPOINTS.influxdb),
|
||||
api(SETTINGS_ENDPOINTS.notifications),
|
||||
api(SETTINGS_ENDPOINTS.night),
|
||||
api(SETTINGS_ENDPOINTS.homeAssistant),
|
||||
api(SETTINGS_ENDPOINTS.debug),
|
||||
]);
|
||||
return { application, gree, history, influxdb, notifications, night, homeAssistant, debug };
|
||||
}
|
||||
|
||||
function aggregateSettingsSections(sections, houseMode = app.settings?.house_mode || 'cool') {
|
||||
return {
|
||||
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',
|
||||
house_power_enabled: settings.house_power_enabled !== false,
|
||||
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,
|
||||
compressor_protection_enabled: settings.compressor_protection_enabled !== false,
|
||||
compressor_protection_seconds: Number(settings.compressor_protection_seconds || 180),
|
||||
notifications: {
|
||||
enabled: !!settings.notifications?.enabled, mode: settings.notifications?.mode || 'problems', provider: settings.notifications?.provider || 'pushover',
|
||||
pushover_app_token: '', pushover_user_key: '', slack_webhook_url: '', discord_webhook_url: '',
|
||||
cooldown_seconds: Number(settings.notifications?.cooldown_seconds || 300), communication_failure_threshold: Number(settings.notifications?.communication_failure_threshold || 3), target_timeout_minutes: Number(settings.notifications?.target_timeout_minutes || 60),
|
||||
alert_types: {
|
||||
stale_sensor: settings.notifications?.alert_types?.stale_sensor !== false,
|
||||
sensor_errors: settings.notifications?.alert_types?.sensor_errors !== false,
|
||||
communication: settings.notifications?.alert_types?.communication !== false,
|
||||
target_timeout: settings.notifications?.alert_types?.target_timeout !== false,
|
||||
automation: settings.notifications?.alert_types?.automation !== false,
|
||||
control_errors: settings.notifications?.alert_types?.control_errors !== false,
|
||||
important_events: settings.notifications?.alert_types?.important_events !== false,
|
||||
other: settings.notifications?.alert_types?.other !== false,
|
||||
},
|
||||
},
|
||||
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: !!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 || '',
|
||||
sensor_stale_after_seconds: Number(ha.sensor_stale_after_seconds || 300),
|
||||
allow_invalid_tls: !!ha.allow_invalid_tls,
|
||||
sensor_aliases: { ...(ha.sensor_aliases || {}) },
|
||||
flow_inputs: JSON.parse(JSON.stringify(ha.flow_inputs || [])),
|
||||
},
|
||||
simulator_enabled: !!sections.application.simulator_enabled,
|
||||
controller_id: sections.gree.controller_id,
|
||||
poll_interval_seconds: Number(sections.gree.poll_interval_seconds),
|
||||
zone_interval_seconds: Number(sections.gree.zone_interval_seconds),
|
||||
discovery_timeout_ms: Number(sections.gree.discovery_timeout_ms),
|
||||
discovery_broadcast: sections.gree.discovery_broadcast,
|
||||
suppress_device_beep: !!sections.gree.suppress_device_beep,
|
||||
compressor_protection_enabled: sections.gree.compressor_protection_enabled !== false,
|
||||
compressor_protection_seconds: Number(sections.gree.compressor_protection_seconds),
|
||||
history_retention_days: Number(sections.history.retention_days),
|
||||
history_compaction_enabled: sections.history.compaction_enabled !== false,
|
||||
event_log_retention_days: Number(sections.history.event_retention_days),
|
||||
influxdb: sections.influxdb,
|
||||
notifications: sections.notifications,
|
||||
night_mode: sections.night,
|
||||
home_assistant: sections.homeAssistant,
|
||||
outdoor_assist_enabled: !!sections.homeAssistant.outdoor_assist_enabled,
|
||||
debug: sections.debug,
|
||||
house_mode: houseMode || 'cool',
|
||||
};
|
||||
}
|
||||
|
||||
function settingsBodyFromForm(form) {
|
||||
async function loadSettingsSections(houseMode = app.settings?.house_mode || 'cool') {
|
||||
const sections = await fetchSettingsSections();
|
||||
app.settings = aggregateSettingsSections(sections, houseMode);
|
||||
app.sensorAliases = { ...(app.settings.home_assistant?.sensor_aliases || {}) };
|
||||
app.flowSharedInputs = JSON.parse(JSON.stringify(app.settings.home_assistant?.flow_inputs || []));
|
||||
return app.settings;
|
||||
}
|
||||
|
||||
function applySettingsSection(section, data) {
|
||||
app.settings = app.settings || {};
|
||||
if (section === 'application') app.settings.simulator_enabled = !!data.simulator_enabled;
|
||||
else if (section === 'gree') Object.assign(app.settings, data);
|
||||
else if (section === 'history') {
|
||||
app.settings.history_retention_days = Number(data.retention_days);
|
||||
app.settings.history_compaction_enabled = data.compaction_enabled !== false;
|
||||
app.settings.event_log_retention_days = Number(data.event_retention_days);
|
||||
} else if (section === 'influxdb') app.settings.influxdb = data;
|
||||
else if (section === 'notifications') app.settings.notifications = data;
|
||||
else if (section === 'night') app.settings.night_mode = data;
|
||||
else if (section === 'homeAssistant') {
|
||||
app.settings.home_assistant = data;
|
||||
app.settings.outdoor_assist_enabled = !!data.outdoor_assist_enabled;
|
||||
} else if (section === 'debug') app.settings.debug = data;
|
||||
}
|
||||
|
||||
function applicationSettingsBodyFromForm(form) {
|
||||
return { simulator_enabled: form.simulator_enabled.checked };
|
||||
}
|
||||
|
||||
function greeSettingsBodyFromForm(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.compressor_protection_enabled = form.compressor_protection_enabled.checked;
|
||||
body.compressor_protection_seconds = Math.max(30, Math.min(1800, Math.round(Number(raw.compressor_protection_minutes || 3) * 60)));
|
||||
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,
|
||||
return {
|
||||
controller_id: raw.controller_id,
|
||||
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,
|
||||
suppress_device_beep: form.suppress_device_beep.checked,
|
||||
compressor_protection_enabled: form.compressor_protection_enabled.checked,
|
||||
compressor_protection_seconds: Math.max(30, Math.min(1800, Math.round(Number(raw.compressor_protection_minutes || 3) * 60))),
|
||||
};
|
||||
}
|
||||
|
||||
function historySettingsBodyFromForm(form, eventRetentionDays = null) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
return {
|
||||
retention_days: Number(raw.history_retention_days),
|
||||
compaction_enabled: form.history_compaction_enabled.checked,
|
||||
event_retention_days: eventRetentionDays == null ? Number(raw.event_log_retention_days) : Number(eventRetentionDays),
|
||||
};
|
||||
}
|
||||
|
||||
function influxDbSettingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = {
|
||||
enabled: form.influx_enabled.checked,
|
||||
version: raw.influx_version,
|
||||
url: raw.influx_url,
|
||||
database: raw.influx_database,
|
||||
username: raw.influx_username,
|
||||
org: raw.influx_org,
|
||||
bucket: raw.influx_bucket,
|
||||
history_threshold_days: Number(raw.influx_threshold_days),
|
||||
};
|
||||
body.debug = { overlay_enabled: form.debug_overlay_enabled.checked, gree_frames: form.debug_gree_frames.checked };
|
||||
body.notifications = {
|
||||
enabled: form.notifications_enabled.checked, mode: raw.notifications_mode, provider: raw.notifications_provider,
|
||||
pushover_app_token: raw.pushover_app_token || '', pushover_user_key: raw.pushover_user_key || '',
|
||||
slack_webhook_url: raw.slack_webhook_url || '', discord_webhook_url: raw.discord_webhook_url || '',
|
||||
if (raw.influx_password) body.password = raw.influx_password;
|
||||
if (raw.influx_token) body.token = raw.influx_token;
|
||||
return body;
|
||||
}
|
||||
|
||||
function notificationSettingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = {
|
||||
enabled: form.notifications_enabled.checked,
|
||||
mode: raw.notifications_mode,
|
||||
provider: raw.notifications_provider,
|
||||
cooldown_seconds: Number(raw.notification_cooldown_seconds || 300),
|
||||
communication_failure_threshold: Number(raw.notification_failure_threshold || 3),
|
||||
target_timeout_minutes: Number(raw.notification_target_timeout || 60),
|
||||
@@ -112,13 +136,20 @@ function settingsBodyFromForm(form) {
|
||||
other: form.notification_alert_other.checked,
|
||||
},
|
||||
};
|
||||
if (raw.pushover_app_token) body.pushover_app_token = raw.pushover_app_token;
|
||||
if (raw.pushover_user_key) body.pushover_user_key = raw.pushover_user_key;
|
||||
if (raw.slack_webhook_url) body.slack_webhook_url = raw.slack_webhook_url;
|
||||
if (raw.discord_webhook_url) body.discord_webhook_url = raw.discord_webhook_url;
|
||||
return body;
|
||||
}
|
||||
|
||||
function debugSettingsBodyFromForm(form) {
|
||||
return { overlay_enabled: form.debug_overlay_enabled.checked, gree_frames: form.debug_gree_frames.checked };
|
||||
}
|
||||
|
||||
function nightSettingsBodyFromForm(form) {
|
||||
const raw = Object.fromEntries(new FormData(form));
|
||||
const body = currentSettingsBody();
|
||||
body.night_mode = {
|
||||
return {
|
||||
enabled: form.night_mode_enabled.checked,
|
||||
start_time: raw.night_mode_start,
|
||||
end_time: raw.night_mode_end,
|
||||
@@ -126,30 +157,25 @@ function nightSettingsBodyFromForm(form) {
|
||||
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 = {
|
||||
const body = {
|
||||
url: raw.ha_url,
|
||||
token: raw.ha_token,
|
||||
default_entity_id: raw.ha_entity_id,
|
||||
outdoor_entity_id: raw.ha_outdoor_entity_id,
|
||||
sensor_stale_after_seconds: Math.max(60, Math.min(86400, Math.round(Number(raw.ha_sensor_stale_after_minutes || 5) * 60))),
|
||||
allow_invalid_tls: form.ha_allow_invalid_tls.checked,
|
||||
sensor_aliases: { ...(app.sensorAliases || {}) },
|
||||
flow_inputs: JSON.parse(JSON.stringify(app.flowSharedInputs || [])),
|
||||
outdoor_assist_enabled: form.outdoor_assist_enabled.checked,
|
||||
};
|
||||
if (raw.ha_token) body.token = raw.ha_token;
|
||||
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 || {}) };
|
||||
app.flowSharedInputs = JSON.parse(JSON.stringify(app.settings?.home_assistant?.flow_inputs || []));
|
||||
function refreshSettingsUi() {
|
||||
renderSettings();
|
||||
renderNightSettings();
|
||||
renderHomeAssistantSettings();
|
||||
@@ -160,23 +186,56 @@ async function saveRuntimeSettings(body, notify = true) {
|
||||
if (app.flowDraft) renderFlowEditor();
|
||||
scheduleControlPlanLoad();
|
||||
if (app.settings?.debug?.overlay_enabled) loadDebugBacklog();
|
||||
}
|
||||
|
||||
async function saveMainSettings(form, notify = true) {
|
||||
const [application, gree, history, influxdb, notifications, debug] = await Promise.all([
|
||||
api(SETTINGS_ENDPOINTS.application, { method: 'PUT', body: applicationSettingsBodyFromForm(form) }),
|
||||
api(SETTINGS_ENDPOINTS.gree, { method: 'PUT', body: greeSettingsBodyFromForm(form) }),
|
||||
api(SETTINGS_ENDPOINTS.history, { method: 'PUT', body: historySettingsBodyFromForm(form) }),
|
||||
api(SETTINGS_ENDPOINTS.influxdb, { method: 'PUT', body: influxDbSettingsBodyFromForm(form) }),
|
||||
api(SETTINGS_ENDPOINTS.notifications, { method: 'PUT', body: notificationSettingsBodyFromForm(form) }),
|
||||
api(SETTINGS_ENDPOINTS.debug, { method: 'PUT', body: debugSettingsBodyFromForm(form) }),
|
||||
]);
|
||||
applySettingsSection('application', application);
|
||||
applySettingsSection('gree', gree);
|
||||
applySettingsSection('history', history);
|
||||
applySettingsSection('influxdb', influxdb);
|
||||
applySettingsSection('notifications', notifications);
|
||||
applySettingsSection('debug', debug);
|
||||
refreshSettingsUi();
|
||||
if (notify) toast(tr('common.saved'));
|
||||
}
|
||||
|
||||
async function saveNightSettings(form, notify = true) {
|
||||
const data = await api(SETTINGS_ENDPOINTS.night, { method: 'PUT', body: nightSettingsBodyFromForm(form) });
|
||||
applySettingsSection('night', data);
|
||||
refreshSettingsUi();
|
||||
if (notify) toast(tr('common.saved'));
|
||||
}
|
||||
|
||||
async function saveHomeAssistantSettings(form, notify = true) {
|
||||
const data = await api(SETTINGS_ENDPOINTS.homeAssistant, { method: 'PUT', body: homeAssistantSettingsBodyFromForm(form) });
|
||||
applySettingsSection('homeAssistant', data);
|
||||
app.sensorAliases = { ...(data.sensor_aliases || {}) };
|
||||
app.flowSharedInputs = JSON.parse(JSON.stringify(data.flow_inputs || []));
|
||||
refreshSettingsUi();
|
||||
if (notify) toast(tr('common.saved'));
|
||||
return app.settings;
|
||||
}
|
||||
|
||||
$('#settingsForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form = event.currentTarget;
|
||||
await runFormTask(form, () => saveRuntimeSettings(settingsBodyFromForm(form), true));
|
||||
await runFormTask(form, () => saveMainSettings(form, true));
|
||||
});
|
||||
|
||||
$('#nightModeForm')?.addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form = event.currentTarget;
|
||||
await runFormTask(form, () => saveRuntimeSettings(nightSettingsBodyFromForm(form), true));
|
||||
await runFormTask(form, () => saveNightSettings(form, true));
|
||||
});
|
||||
|
||||
$('#homeAssistantForm')?.addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form = event.currentTarget;
|
||||
await runFormTask(form, () => saveRuntimeSettings(homeAssistantSettingsBodyFromForm(form), true));
|
||||
await runFormTask(form, () => saveHomeAssistantSettings(form, true));
|
||||
});
|
||||
|
||||
$('#addSensorAlias')?.addEventListener('click', () => {
|
||||
@@ -206,10 +265,11 @@ document.addEventListener('click', event => {
|
||||
$('#saveLogRetention')?.addEventListener('click', async () => {
|
||||
const days = Number($('#logRetentionDays')?.value || 30);
|
||||
try {
|
||||
const result = await api('/api/events/retention', { method: 'PUT', body: { days } });
|
||||
app.settings.event_log_retention_days = result.days;
|
||||
$('#settingsForm').event_log_retention_days.value = result.days;
|
||||
renderLogRetention(); await loadLogs(); toast(tr('logs.retentionSaved', { days: result.days }));
|
||||
const form = $('#settingsForm');
|
||||
const result = await api(SETTINGS_ENDPOINTS.history, { method: 'PUT', body: historySettingsBodyFromForm(form, days) });
|
||||
applySettingsSection('history', result);
|
||||
form.event_log_retention_days.value = result.event_retention_days;
|
||||
renderLogRetention(); await loadLogs(); toast(tr('logs.retentionSaved', { days: result.event_retention_days }));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
});
|
||||
|
||||
@@ -246,7 +306,7 @@ $('#haTest').addEventListener('click', async event => {
|
||||
const idle = button.textContent;
|
||||
button.textContent = tr('settings.testingHa');
|
||||
try {
|
||||
await saveRuntimeSettings(homeAssistantSettingsBodyFromForm(form), false);
|
||||
await saveHomeAssistantSettings(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 } });
|
||||
markFormClean(form);
|
||||
@@ -257,11 +317,11 @@ $('#haTest').addEventListener('click', async event => {
|
||||
|
||||
$('#exportSettings').addEventListener('click', async () => {
|
||||
try {
|
||||
const data = await api('/api/settings/export');
|
||||
const data = await api('/api/configuration/export');
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `gree-controller-settings-${new Date().toISOString().slice(0, 10)}.json`;
|
||||
link.download = `gree-controller-configuration-${new Date().toISOString().slice(0, 10)}.json`;
|
||||
document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(link.href);
|
||||
toast(tr('toast.exported'));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
@@ -273,7 +333,7 @@ $('#importSettingsFile').addEventListener('change', async event => {
|
||||
try {
|
||||
if (!confirm(tr('settings.importConfirm'))) return;
|
||||
const body = JSON.parse(await file.text());
|
||||
await api('/api/settings/import', { method: 'POST', body });
|
||||
await api('/api/configuration/import', { method: 'POST', body });
|
||||
app.debugBacklogLoaded = false;
|
||||
await loadBootstrap();
|
||||
toast(tr('toast.imported'));
|
||||
|
||||
Reference in New Issue
Block a user