v0.6.0
This commit is contained in:
+67
-23
@@ -1,11 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const APP_BASE = (() => {
|
||||
const src = document.currentScript?.src || '';
|
||||
try { const path = new URL(src, location.href).pathname; return path.endsWith('/app.js') ? path.slice(0, -7).replace(/\/$/, '') : ''; } catch (_) { return ''; }
|
||||
})();
|
||||
const withBase = path => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
const parseDecimal = value => { const normalized = String(value ?? '').trim().replace(',', '.'); const parsed = Number(normalized); return Number.isFinite(parsed) ? parsed : NaN; };
|
||||
|
||||
const getCookie = name => {
|
||||
const row = document.cookie.split('; ').find(item => item.startsWith(`${name}=`));
|
||||
return row ? decodeURIComponent(row.split('=').slice(1).join('=')) : '';
|
||||
};
|
||||
const setCookie = (name, value) => {
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=31536000; Path=/; SameSite=Lax`;
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=31536000; Path=${APP_BASE || '/'}; SameSite=Lax`;
|
||||
};
|
||||
|
||||
const DEFAULT_LANGUAGE = 'en';
|
||||
@@ -71,7 +78,7 @@ function applyTranslations() {
|
||||
$$('[data-i18n-content]').forEach(node => { node.setAttribute('content', tr(node.dataset.i18nContent)); });
|
||||
$$('[data-day]').forEach(node => { node.textContent = tr(`day.${node.dataset.day}`); });
|
||||
$('#languageSelect').value = app.language;
|
||||
$('#themeSelect').value = app.theme;
|
||||
const themeSelect = $('#themeSelect'); if (themeSelect) { const icons={system:'◐',light:'☀',dark:'☾'}; [...themeSelect.options].forEach(o => { const key=`theme.${o.value}`; o.textContent=`${icons[o.value]||'◐'} ${tr(key)}`; }); themeSelect.value=app.theme; }
|
||||
updateConnectionIndicator(app.connectionStatus);
|
||||
renderAll();
|
||||
if (app.currentView === 'logs') loadLogs();
|
||||
@@ -90,21 +97,21 @@ function renderLanguageOptions() {
|
||||
if (!select) return;
|
||||
select.innerHTML = app.languages.map(item => {
|
||||
const label = item.native_name || item.name || item.code.toUpperCase();
|
||||
return `<option value="${esc(item.code)}">${esc(label)}</option>`;
|
||||
const flag = ({pl:'🇵🇱',en:'🇬🇧'})[item.code] || '🌐'; return `<option value="${esc(item.code)}">${flag} ${esc(label)}</option>`;
|
||||
}).join('');
|
||||
select.value = app.language;
|
||||
}
|
||||
|
||||
async function loadLanguages() {
|
||||
try {
|
||||
const response = await fetch('/lang/index.json', {cache: 'no-cache'});
|
||||
const response = await fetch(withBase('/lang/index.json'), {cache: 'no-cache'});
|
||||
if (!response.ok) throw new Error(`Language index HTTP ${response.status}`);
|
||||
const manifest = await response.json();
|
||||
const languages = Array.isArray(manifest.languages) ? manifest.languages : [];
|
||||
if (!languages.some(item => item.code === DEFAULT_LANGUAGE)) throw new Error('Default English language pack is missing');
|
||||
|
||||
const loaded = await Promise.all(languages.map(async item => {
|
||||
const packResponse = await fetch(item.path || `/lang/${encodeURIComponent(item.code)}.json`, {cache: 'no-cache'});
|
||||
const packResponse = await fetch(withBase(item.path || `/lang/${encodeURIComponent(item.code)}.json`), {cache: 'no-cache'});
|
||||
if (!packResponse.ok) throw new Error(`Language ${item.code} HTTP ${packResponse.status}`);
|
||||
const pack = await packResponse.json();
|
||||
return {item, pack};
|
||||
@@ -143,7 +150,7 @@ async function api(path, options = {}) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
const response = await fetch(path, {...options, headers, body});
|
||||
const response = await fetch(withBase(path), {...options, headers, body});
|
||||
if (response.status === 401) {
|
||||
showTokenDialog();
|
||||
throw new Error(tr('auth.invalid'));
|
||||
@@ -625,6 +632,7 @@ function fillSelects() {
|
||||
if (zoneSelect) { const currentZone = zoneSelect.value; zoneSelect.innerHTML = zoneOptions; if ([...zoneSelect.options].some(o => o.value === currentZone)) zoneSelect.value = currentZone; }
|
||||
const presetZone = $('#schedulePresetZone');
|
||||
if (presetZone) { const currentZone = presetZone.value; presetZone.innerHTML = zoneOptions; if ([...presetZone.options].some(o => o.value === currentZone)) presetZone.value = currentZone; }
|
||||
const copyZone=$('#copyZoneSource'); if(copyZone){ const current=copyZone.value; copyZone.innerHTML='<option value="">Copy thermostat settings from…</option>'+app.zones.map(z=>`<option value="${esc(z.id)}">${esc(z.name)}</option>`).join(''); if([...copyZone.options].some(o=>o.value===current))copyZone.value=current; }
|
||||
}
|
||||
|
||||
function renderAccessTokens() {
|
||||
@@ -691,6 +699,15 @@ function renderSettings() {
|
||||
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;
|
||||
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?'Saved - leave empty to keep':'Application token';
|
||||
form.pushover_user_key.placeholder=n.pushover_configured?'Saved - leave empty to keep':'User/group key';
|
||||
form.slack_webhook_url.placeholder=n.slack_configured?'Saved - leave empty to keep':'https://hooks.slack.com/services/…';
|
||||
form.discord_webhook_url.placeholder=n.discord_configured?'Saved - leave empty to keep':'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;
|
||||
updateNotificationFields();
|
||||
updateInfluxFields();
|
||||
renderLogRetention();
|
||||
$('#systemInfo').innerHTML = `<h3>${esc(tr('settings.systemState'))}</h3><div>${esc(tr('settings.version'))}: <strong>${esc(app.system.version || '—')}</strong></div><div>${esc(tr('settings.uptime'))}: <strong>${esc(formatDuration(app.system.uptime_seconds || 0))}</strong></div><div>${esc(tr('settings.apiAuth'))}: <strong>${esc(app.system.auth_required ? tr('settings.enabled') : tr('settings.disabled'))}</strong></div>`;
|
||||
@@ -728,6 +745,9 @@ function updateInfluxFields() {
|
||||
$$('[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);
|
||||
@@ -781,9 +801,10 @@ function currentHistoryPath() {
|
||||
}
|
||||
|
||||
function updateBrowserUrl(path, replace=false) {
|
||||
const target = `${APP_BASE}${path}` || path;
|
||||
const current = `${location.pathname}${location.search}`;
|
||||
if (current === path) return;
|
||||
history[replace ? 'replaceState' : 'pushState']({}, '', path);
|
||||
if (current === target) return;
|
||||
history[replace ? 'replaceState' : 'pushState']({}, '', target);
|
||||
}
|
||||
|
||||
function showView(name, {push=true, scroll=true}={}) {
|
||||
@@ -804,7 +825,8 @@ function showHistoryTab(tab, {push=true, load=true}={}) {
|
||||
}
|
||||
|
||||
function applyRouteFromLocation() {
|
||||
const parts = location.pathname.split('/').filter(Boolean);
|
||||
const routePath = APP_BASE && location.pathname.startsWith(APP_BASE) ? location.pathname.slice(APP_BASE.length) : location.pathname;
|
||||
const parts = routePath.split('/').filter(Boolean);
|
||||
const first = parts[0] || 'dashboard';
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (first === 'history') {
|
||||
@@ -1282,7 +1304,7 @@ async function handleHistoryAction(button) {
|
||||
}
|
||||
if(action==='copy-chart-link'){
|
||||
if(!app.customChartSeries.length) return toast(tr('history.noCustomSeries'), true);
|
||||
const path=currentHistoryPath(); updateBrowserUrl(path, true); const link=`${location.origin}${path}`;
|
||||
const path=currentHistoryPath(); updateBrowserUrl(path, true); const link=`${location.origin}${APP_BASE}${path}`;
|
||||
try { await navigator.clipboard.writeText(link); } catch (_) { const area=document.createElement('textarea'); area.value=link; document.body.appendChild(area); area.select(); document.execCommand('copy'); area.remove(); }
|
||||
toast(tr('history.linkCopied')); return;
|
||||
}
|
||||
@@ -1292,9 +1314,10 @@ async function loadLogs() {
|
||||
renderLogRetention();
|
||||
try {
|
||||
const data = await api('/api/events?limit=150');
|
||||
const logs = data.events || [];
|
||||
const level=$('#logLevelFilter')?.value||'all', category=$('#logCategoryFilter')?.value||'all';
|
||||
const logs = (data.events || []).filter(item => (level==='all'||item.level===level) && (category==='all'||logCategory(item.kind)===category));
|
||||
$('#logList').innerHTML = logs.length
|
||||
? logs.map(item => `<div class="log-row ${esc(item.level)}"><time>${esc(new Date(item.timestamp).toLocaleTimeString(locale()))}</time><span class="kind">${esc(item.kind)}</span><span class="message">${esc(item.message)}</span></div>`).join('')
|
||||
? logs.map(item => `<div class="log-row ${esc(item.level)} category-${esc(logCategory(item.kind))}"><time>${esc(new Date(item.timestamp).toLocaleTimeString(locale()))}</time><span class="log-category">${esc(logCategory(item.kind))}</span><span class="kind">${esc(item.kind)}</span><span class="message">${esc(item.message)}</span></div>`).join('')
|
||||
: `<div class="empty"><strong>${esc(tr('logs.emptyTitle'))}</strong>${esc(tr('logs.emptyText'))}</div>`;
|
||||
} catch (error) { toast(error.message, true); }
|
||||
}
|
||||
@@ -1304,7 +1327,7 @@ function connectWebSocket() {
|
||||
clearTimeout(app.wsTimer);
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const query = app.token ? `?token=${encodeURIComponent(app.token)}` : '';
|
||||
const ws = new WebSocket(`${protocol}//${location.host}/ws${query}`); app.ws = ws;
|
||||
const ws = new WebSocket(`${protocol}//${location.host}${withBase('/ws')}${query}`); app.ws = ws;
|
||||
ws.onopen = () => updateConnectionIndicator('connected');
|
||||
ws.onclose = () => { updateConnectionIndicator('disconnected'); app.wsTimer = setTimeout(connectWebSocket, 3000); };
|
||||
ws.onerror = () => updateConnectionIndicator('connectionError');
|
||||
@@ -1404,9 +1427,17 @@ $('#historyHours').addEventListener('change', () => { updateBrowserUrl(currentHi
|
||||
$('#logsRefresh').addEventListener('click', loadLogs);
|
||||
$('#languageSelect').addEventListener('change', event => setLanguage(event.target.value));
|
||||
$('#themeSelect').addEventListener('change', event => setTheme(event.target.value));
|
||||
$('#logLevelFilter')?.addEventListener('change', loadLogs); $('#logCategoryFilter')?.addEventListener('change', loadLogs);
|
||||
$('#settingsForm [name=notifications_provider]')?.addEventListener('change', updateNotificationFields);
|
||||
$('#zoneForm [name=sensor_source]').addEventListener('change', updateZoneSensorFields);
|
||||
$('#scheduleForm [name=preset]').addEventListener('change', updateSchedulePresetField);
|
||||
|
||||
$('#testNotifications')?.addEventListener('click', async () => { const f=$('#settingsForm'); const saved=app.settings?.notifications||{}; const body={enabled:true,mode:f.notifications_mode.value,provider:f.notifications_provider.value,pushover_app_token:f.pushover_app_token.value.trim(),pushover_user_key:f.pushover_user_key.value.trim(),slack_webhook_url:f.slack_webhook_url.value.trim(),discord_webhook_url:f.discord_webhook_url.value.trim(),cooldown_seconds:Number(f.notification_cooldown_seconds.value||300),communication_failure_threshold:Number(f.notification_failure_threshold.value||3),target_timeout_minutes:Number(f.notification_target_timeout.value||60)}; try{await api('/api/integrations/notifications/test',{method:'POST',body});toast('Test notification sent');}catch(e){toast(e.message,true);} });
|
||||
|
||||
$('#copyZoneSettings')?.addEventListener('click',()=>{ const source=app.zones.find(z=>z.id===$('#copyZoneSource')?.value); if(!source)return; const f=$('#zoneForm'); ['setpoint','cool_comfort_setpoint','cool_sleep_setpoint','cool_away_setpoint','heat_comfort_setpoint','heat_sleep_setpoint','heat_away_setpoint','hysteresis','min_on_seconds','min_off_seconds','min_adjust_seconds','standby_offset_c','external_sensor_weight_percent','max_sensor_difference'].forEach(name=>{ if(!f[name])return; const value=name==='external_sensor_weight_percent'?(source.external_sensor_weight*100):source[name]; if(value!==undefined&&value!==null)f[name].value=String(value); }); f.smart_fan.checked=source.smart_fan!==false; if(f.mode_policy) f.mode_policy.value=source.inherit_house_mode===false?(source.mode||'cool'):'house'; toast('Thermostat settings copied'); });
|
||||
|
||||
$('#applyAutomationPreset')?.addEventListener('click',()=>{ const f=$('#automationForm'), p=$('#automationPreset')?.value; if(!p)return; const first=app.devices[0]?.id||''; const presets={hot:{name:'High temperature cooling',trigger_kind:'temperature_above',threshold:'28',action_power:'true',action_mode:'cool',action_target_temperature:'23',cooldown_seconds:'900'},cold:{name:'Low temperature heating',trigger_kind:'temperature_below',threshold:'17',action_power:'true',action_mode:'heat',action_target_temperature:'21',cooldown_seconds:'900'},morning:{name:'Morning comfort',trigger_kind:'time',at_time:'07:00',action_power:'true',action_mode:'auto',action_target_temperature:'22',cooldown_seconds:'3600'},nightoff:{name:'Night power off',trigger_kind:'time',at_time:'23:30',action_power:'false',action_mode:'',action_target_temperature:'',cooldown_seconds:'3600'}}; const x=presets[p]; Object.entries(x).forEach(([k,v])=>{if(f[k])f[k].value=v}); if(!f.trigger_device_id.value)f.trigger_device_id.value=first; if(!f.action_device_id.value)f.action_device_id.value=first; });
|
||||
|
||||
$('#tokenForm').addEventListener('submit', event => {
|
||||
event.preventDefault(); app.token = new FormData(event.currentTarget).get('token').trim();
|
||||
localStorage.setItem('gree_controller_token', app.token); if (app.ws) app.ws.close(); loadBootstrap();
|
||||
@@ -1453,12 +1484,12 @@ $('#zoneForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, raw=Object.fromEntries(new FormData(form));
|
||||
const id=raw.id; const body={
|
||||
name:raw.name,device_id:raw.device_id,enabled:form.enabled.checked,
|
||||
mode:raw.mode_policy==='house'?'cool':raw.mode_policy,inherit_house_mode:raw.mode_policy==='house',setpoint:Number(raw.setpoint),
|
||||
cool_comfort_setpoint:Number(raw.cool_comfort_setpoint),cool_sleep_setpoint:Number(raw.cool_sleep_setpoint),cool_away_setpoint:Number(raw.cool_away_setpoint),
|
||||
heat_comfort_setpoint:Number(raw.heat_comfort_setpoint),heat_sleep_setpoint:Number(raw.heat_sleep_setpoint),heat_away_setpoint:Number(raw.heat_away_setpoint),
|
||||
hysteresis:Number(raw.hysteresis),min_on_seconds:Number(raw.min_on_seconds),min_off_seconds:Number(raw.min_off_seconds),
|
||||
min_adjust_seconds:Number(raw.min_adjust_seconds),standby_offset_c:Number(raw.standby_offset_c),smart_fan:form.smart_fan.checked,
|
||||
sensor_source:raw.sensor_source,ha_entity_id:raw.ha_entity_id||null,external_sensor_weight:Number(raw.external_sensor_weight_percent)/100,max_sensor_difference:Number(raw.max_sensor_difference)
|
||||
mode:raw.mode_policy==='house'?'cool':raw.mode_policy,inherit_house_mode:raw.mode_policy==='house',setpoint:parseDecimal(raw.setpoint),
|
||||
cool_comfort_setpoint:parseDecimal(raw.cool_comfort_setpoint),cool_sleep_setpoint:parseDecimal(raw.cool_sleep_setpoint),cool_away_setpoint:parseDecimal(raw.cool_away_setpoint),
|
||||
heat_comfort_setpoint:parseDecimal(raw.heat_comfort_setpoint),heat_sleep_setpoint:parseDecimal(raw.heat_sleep_setpoint),heat_away_setpoint:parseDecimal(raw.heat_away_setpoint),
|
||||
hysteresis:parseDecimal(raw.hysteresis),min_on_seconds:Number(raw.min_on_seconds),min_off_seconds:Number(raw.min_off_seconds),
|
||||
min_adjust_seconds:Number(raw.min_adjust_seconds),standby_offset_c:parseDecimal(raw.standby_offset_c),smart_fan:form.smart_fan.checked,
|
||||
sensor_source:raw.sensor_source,ha_entity_id:raw.ha_entity_id||null,external_sensor_weight:Number(raw.external_sensor_weight_percent)/100,max_sensor_difference:parseDecimal(raw.max_sensor_difference)
|
||||
};
|
||||
try { await api(id?`/api/zones/${encodeURIComponent(id)}`:'/api/zones',{method:id?'PUT':'POST',body}); form.closest('dialog').close(); form.reset(); await loadBootstrap(); toast(tr('common.saved')); }
|
||||
catch(error){toast(error.message,true);}
|
||||
@@ -1467,15 +1498,15 @@ $('#zoneForm').addEventListener('submit', async event => {
|
||||
$('#scheduleForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, raw=Object.fromEntries(new FormData(form));
|
||||
const id=raw.id, weekdays=$$('[name=weekday]:checked',form).map(v=>Number(v.value));
|
||||
const body={name:raw.name,zone_id:raw.zone_id,enabled:form.enabled.checked,weekdays,start_time:raw.start_time,end_time:raw.end_time,preset:raw.preset,setpoint:Number(raw.setpoint||23)};
|
||||
const body={name:raw.name,zone_id:raw.zone_id,enabled:form.enabled.checked,weekdays,start_time:raw.start_time,end_time:raw.end_time,preset:raw.preset,setpoint:parseDecimal(raw.setpoint||23)};
|
||||
try { await api(id?`/api/schedules/${encodeURIComponent(id)}`:'/api/schedules',{method:id?'PUT':'POST',body}); form.closest('dialog').close(); form.reset(); await loadBootstrap(); toast(tr('common.saved')); }
|
||||
catch(error){toast(error.message,true);}
|
||||
});
|
||||
|
||||
$('#automationForm').addEventListener('submit', async event => {
|
||||
event.preventDefault(); const form=event.currentTarget, raw=Object.fromEntries(new FormData(form)); const id=raw.id;
|
||||
const action={}; if(raw.action_power!=='') action.power=raw.action_power==='true'; if(raw.action_mode) action.mode=raw.action_mode; if(raw.action_target_temperature!=='') action.target_temperature=Number(raw.action_target_temperature);
|
||||
const body={name:raw.name,enabled:form.enabled.checked,trigger_kind:raw.trigger_kind,trigger_device_id:raw.trigger_device_id||null,threshold:raw.threshold===''?null:Number(raw.threshold),at_time:raw.at_time||null,action_device_id:raw.action_device_id,action,cooldown_seconds:Number(raw.cooldown_seconds)};
|
||||
const action={}; if(raw.action_power!=='') action.power=raw.action_power==='true'; if(raw.action_mode) action.mode=raw.action_mode; if(raw.action_target_temperature!=='') action.target_temperature=parseDecimal(raw.action_target_temperature);
|
||||
const body={name:raw.name,enabled:form.enabled.checked,trigger_kind:raw.trigger_kind,trigger_device_id:raw.trigger_device_id||null,threshold:raw.threshold===''?null:parseDecimal(raw.threshold),at_time:raw.at_time||null,action_device_id:raw.action_device_id,action,cooldown_seconds:Number(raw.cooldown_seconds)};
|
||||
try { await api(id?`/api/automations/${encodeURIComponent(id)}`:'/api/automations',{method:id?'PUT':'POST',body}); form.closest('dialog').close(); form.reset(); await loadBootstrap(); toast(tr('common.saved')); }
|
||||
catch(error){toast(error.message,true);}
|
||||
});
|
||||
@@ -1498,6 +1529,11 @@ function currentSettingsBody() {
|
||||
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,
|
||||
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),
|
||||
},
|
||||
night_mode: {
|
||||
enabled: !!settings.night_mode?.enabled,
|
||||
start_time: settings.night_mode?.start_time || '22:00',
|
||||
@@ -1553,6 +1589,14 @@ function settingsBodyFromForm(form) {
|
||||
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 || '',
|
||||
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),
|
||||
};
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -1738,7 +1782,7 @@ async function startApplication() {
|
||||
updateSchedulePresetField();
|
||||
await loadBootstrap();
|
||||
applyRouteFromLocation();
|
||||
if (location.pathname === '/') updateBrowserUrl('/dashboard', true);
|
||||
if (location.pathname === '/' || location.pathname === `${APP_BASE}/`) updateBrowserUrl('/dashboard', true);
|
||||
}
|
||||
|
||||
startApplication().catch(error => console.error('Application startup failed:', error));
|
||||
|
||||
Reference in New Issue
Block a user