iobroker.eos-admin 7.9.97 → 7.9.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{INSTALL_TEST_V7.9.97.md → INSTALL_TEST_V7.9.99.md} +2 -2
- package/{MERGE_UPDATE_README_V7.9.97.md → MERGE_UPDATE_README_V7.9.99.md} +1 -1
- package/NEXOWATT_EOS_BUILD_INFO.json +21 -20
- package/{PUBLISH_STABLE_V7.9.97.md → PUBLISH_STABLE_V7.9.99.md} +2 -2
- package/{README_STABILITY_V7.9.97.md → README_STABILITY_V7.9.99.md} +1 -1
- package/{RELEASE_ACCEPTANCE_V7.9.97.md → RELEASE_ACCEPTANCE_V7.9.99.md} +1 -1
- package/RELEASE_NOTES_V7.9.99.md +9 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
- package/adminWww/css/eos-ems-overview.css +3 -5
- package/adminWww/css/nexowatt-native-shell.css +32 -1
- package/adminWww/index.html +19 -18
- package/adminWww/js/eos-account-management.js +13 -9
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +2 -3
- package/adminWww/js/eos-release-watch.js +77 -0
- package/adminWww/js/eos-role-bootstrap.js +7 -4
- package/adminWww/js/eos-role-ui.js +2 -2
- package/adminWww/js/nexowatt-native-shell.js +1 -1
- package/adminWww/static/js/nexowatt-stable-login.js +3 -3
- package/adminWww/static/js/{nexowatt-stable-v97.js → nexowatt-stable-v99.js} +2 -2
- package/build/lib/eosPassword.js +62 -0
- package/build/lib/eosRequestSecurity.js +31 -0
- package/build/lib/web.js +99 -71
- package/build/main.js +2 -1
- package/io-package.json +5 -5
- package/package.json +18 -12
- package/tools/nexowatt-account-management-selftest.cjs +20 -57
- package/tools/nexowatt-apply-ems-overview.cjs +2 -4
- package/tools/nexowatt-cache-update-selftest.cjs +11 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
- package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
- package/tools/nexowatt-login-layout-selftest.cjs +11 -11
- package/tools/nexowatt-merge-update-selftest.cjs +1 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +1 -1
- package/tools/nexowatt-password-write-selftest.cjs +46 -0
- package/tools/nexowatt-request-security-selftest.cjs +11 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -6
- package/tools/nexowatt-stable-v99-selftest.cjs +25 -0
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +3 -1
- package/tools/nexowatt-validate-package.cjs +23 -14
- package/RELEASE_NOTES_V7.9.97.md +0 -19
- package/tools/nexowatt-stable-v97-selftest.cjs +0 -16
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
const VERSION = '
|
|
3
|
+
const VERSION = 'v99-nexowatt-ems-overview-runtime';
|
|
4
4
|
const CONTRACT = 'nexowatt-ems-overview-v1';
|
|
5
5
|
const ROOT_ID = 'eos-ems-overview-runtime';
|
|
6
6
|
const POLL_MS = 5000;
|
|
@@ -161,11 +161,10 @@
|
|
|
161
161
|
if (tariff.available) tags.push({ label: `Tarif: ${text(tariff.state, 'aktiv')}${tariff.priceEurPerKwh != null ? ` · ${num(tariff.priceEurPerKwh).toFixed(3)} €/kWh` : ''}` });
|
|
162
162
|
if (forecast.available) tags.push({ label: `Prognose: ${text(forecast.source, 'aktiv')}${forecast.fresh === false ? ' · veraltet' : ''}`, warning: forecast.fresh === false });
|
|
163
163
|
if (ems.safetyActive || ems.safetyEmergencyStop || ems.safetyValid === false) tags.push({ label: 'EOS Safety aktiv', warning: true });
|
|
164
|
-
const detailsPort = Math.max(1, num(summary.details?.port, 8188)); const detailsPath = text(summary.details?.path, '/ems-apps.html?tab=status'); const detailsUrl = `${location.protocol}//${location.hostname}:${detailsPort}${detailsPath.startsWith('/') ? detailsPath : `/${detailsPath}`}`;
|
|
165
164
|
const metrics = `<div class="eos-ems-overview-metric"><span>EMS-Budget</span><strong>${totalBudget > 0 ? esc(formatPower(totalBudget)) : 'nicht begrenzt'}</strong><small>Rest ${esc(formatPower(remaining))}</small></div><div class="eos-ems-overview-metric"><span>Lademanagement</span><strong>${esc(formatPower(charging.actualW))} / ${esc(formatPower(charging.targetW))}</strong><small>${num(charging.activeCount)} lädt · ${num(charging.waitingCount)} wartet</small></div>${storage.available ? `<div class="eos-ems-overview-metric"><span>Speicher ${esc(text(storage.topology))}</span><strong>${esc(formatPower(storage.actualW))} / ${esc(formatPower(storage.targetW))}</strong><small>${storage.socPct != null ? `SoC ${Math.round(num(storage.socPct))} %` : esc(text(storage.reason, 'aktiv'))}</small></div>` : `<div class="eos-ems-overview-metric"><span>PV-Budget</span><strong>${esc(formatPower(budget.pvBudgetW))}</strong><small>PV-Rest ${esc(formatPower(budget.remainingPvW))}</small></div>`}<div class="eos-ems-overview-metric"><span>Letzter Regeltick</span><strong>${esc(formatAge(ems.lastTickTs || data.updatedAt))}</strong><small>${isTechnical ? `Zyklus ${num(ems.cycleMs)} ms` : esc(text(ems.decision, 'EMS aktiv'))}</small></div>`;
|
|
166
165
|
const decisionsHtml = decisions.length ? decisions.map(item => `<div class="eos-ems-overview-decision eos-ems-overview-decision--${severity(item.severity)}"><strong>${esc(text(item.title, 'EMS-Entscheidung'))}</strong><span>${esc(text(item.reason, 'Keine zusätzliche Begrenzung'))}</span>${isTechnical && item.details ? `<small>${esc(item.details)}</small>` : ''}</div>`).join('') : '<div class="eos-ems-overview-empty">Keine aktiven Regelentscheidungen. EOS arbeitet mit den aktuellen Messwerten.</div>';
|
|
167
166
|
const eventsHtml = isTechnical ? (events.length ? events.map(item => `<div class="eos-ems-overview-event eos-ems-overview-event--${severity(item.severity)}"><time>${esc(formatClock(item.ts))}</time><div><strong>${esc(text(item.title, 'EMS'))}</strong><span>${esc(text(item.message || item.reason, 'Zustand aktualisiert'))}</span></div></div>`).join('') : '<div class="eos-ems-overview-empty">Noch keine Zustandsänderung im begrenzten Ereignispuffer.</div>') : `<div class="eos-ems-overview-decision eos-ems-overview-decision--info"><strong>${esc(text(summary.headline, 'EMS aktiv'))}</strong><span>${esc(text(summary.reason, 'EOS optimiert die Anlage innerhalb aller Sicherheitsgrenzen.'))}</span></div>`;
|
|
168
|
-
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS · Live-Diagnose</span><h2>${esc(stale ? 'EMS-Diagnose ist nicht aktuell' : text(summary.headline, 'EMS arbeitet normal'))}</h2><p>${esc(stale ? 'Adapter oder Diagnosewerte sind älter als 20 Sekunden. Die Werte werden als veraltet markiert.' : text(summary.reason, 'EMS arbeitet innerhalb aller aktiven Grenzen.'))}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>${stale ? 'Offline / veraltet' : ({ok:'Normal',info:'Information',warning:'Begrenzt',error:'Störung'}[status])}</strong><small>${esc(data.instance)} · ${esc(formatAge(data.updatedAt))}</small></div></header><div class="eos-ems-overview-metrics">${metrics}</div>${totalBudget > 0 ? `<div class="eos-ems-overview-budget" title="${percent.toFixed(1)} % Restbudget"><span style="width:${percent}%"></span></div>` : ''}${tags.length ? `<div class="eos-ems-overview-tags">${tags.map(tag => `<span class="${tag.warning ? 'is-warning' : ''}">${esc(tag.label)}</span>`).join('')}</div>` : ''}<div class="eos-ems-overview-columns"><div class="eos-ems-overview-panel"><h3>Aktuelle Regelentscheidungen</h3>${decisionsHtml}</div><div class="eos-ems-overview-panel"><h3>${isTechnical ? 'Letzte EMS-Ereignisse' : 'Systemerklärung'}</h3>${eventsHtml}</div></div><footer class="eos-ems-overview-footer"><span>Diagnose ist rein lesend. Keine Kachel besitzt Schreibhoheit.</span
|
|
167
|
+
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS · Live-Diagnose</span><h2>${esc(stale ? 'EMS-Diagnose ist nicht aktuell' : text(summary.headline, 'EMS arbeitet normal'))}</h2><p>${esc(stale ? 'Adapter oder Diagnosewerte sind älter als 20 Sekunden. Die Werte werden als veraltet markiert.' : text(summary.reason, 'EMS arbeitet innerhalb aller aktiven Grenzen.'))}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>${stale ? 'Offline / veraltet' : ({ok:'Normal',info:'Information',warning:'Begrenzt',error:'Störung'}[status])}</strong><small>${esc(data.instance)} · ${esc(formatAge(data.updatedAt))}</small></div></header><div class="eos-ems-overview-metrics">${metrics}</div>${totalBudget > 0 ? `<div class="eos-ems-overview-budget" title="${percent.toFixed(1)} % Restbudget"><span style="width:${percent}%"></span></div>` : ''}${tags.length ? `<div class="eos-ems-overview-tags">${tags.map(tag => `<span class="${tag.warning ? 'is-warning' : ''}">${esc(tag.label)}</span>`).join('')}</div>` : ''}<div class="eos-ems-overview-columns"><div class="eos-ems-overview-panel"><h3>Aktuelle Regelentscheidungen</h3>${decisionsHtml}</div><div class="eos-ems-overview-panel"><h3>${isTechnical ? 'Letzte EMS-Ereignisse' : 'Systemerklärung'}</h3>${eventsHtml}</div></div><footer class="eos-ems-overview-footer"><span>Diagnose ist rein lesend. Keine Kachel besitzt Schreibhoheit.</span></footer>`;
|
|
169
168
|
};
|
|
170
169
|
|
|
171
170
|
const scheduleFastRetry = () => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const RELEASE = '7.9.99';
|
|
4
|
+
const RELEASE_KEY = 'nexowatt.eos.loadedRelease';
|
|
5
|
+
const RELOAD_KEY = 'nexowatt.eos.reloadTarget';
|
|
6
|
+
const CHECK_MS = 15000;
|
|
7
|
+
let refreshing = false;
|
|
8
|
+
|
|
9
|
+
const normalize = value => String(value || '').trim().replace(/^v/i, '');
|
|
10
|
+
const validRelease = value => /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(normalize(value));
|
|
11
|
+
const reloadUrl = version => {
|
|
12
|
+
const url = new URL(window.location.href);
|
|
13
|
+
url.searchParams.set('_eosRelease', normalize(version) || RELEASE);
|
|
14
|
+
url.searchParams.set('_eosReload', String(Date.now()));
|
|
15
|
+
return url.toString();
|
|
16
|
+
};
|
|
17
|
+
const purgeBrowserCaches = async () => {
|
|
18
|
+
const jobs = [];
|
|
19
|
+
try {
|
|
20
|
+
if ('caches' in window) {
|
|
21
|
+
jobs.push(caches.keys().then(keys => Promise.all(keys.map(key => caches.delete(key)))));
|
|
22
|
+
}
|
|
23
|
+
} catch (_) { /* Cache API is optional */ }
|
|
24
|
+
try {
|
|
25
|
+
if (navigator.serviceWorker?.getRegistrations) {
|
|
26
|
+
jobs.push(navigator.serviceWorker.getRegistrations().then(rows => Promise.all(rows.map(row => row.unregister()))));
|
|
27
|
+
}
|
|
28
|
+
} catch (_) { /* service workers may be unavailable */ }
|
|
29
|
+
await Promise.allSettled(jobs);
|
|
30
|
+
};
|
|
31
|
+
const reloadFor = async version => {
|
|
32
|
+
const target = normalize(version) || RELEASE;
|
|
33
|
+
if (refreshing || sessionStorage.getItem(RELOAD_KEY) === target) return;
|
|
34
|
+
refreshing = true;
|
|
35
|
+
sessionStorage.setItem(RELOAD_KEY, target);
|
|
36
|
+
document.documentElement.classList.add('eos-release-refreshing');
|
|
37
|
+
await purgeBrowserCaches();
|
|
38
|
+
window.location.replace(reloadUrl(target));
|
|
39
|
+
};
|
|
40
|
+
const checkServerVersion = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const endpoint = new URL('./version', window.location.href);
|
|
43
|
+
endpoint.searchParams.set('_eosTs', String(Date.now()));
|
|
44
|
+
const response = await fetch(endpoint, {
|
|
45
|
+
cache: 'no-store',
|
|
46
|
+
credentials: 'same-origin',
|
|
47
|
+
headers: { 'Cache-Control': 'no-cache' },
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) return;
|
|
50
|
+
const serverVersion = normalize(await response.text());
|
|
51
|
+
if (validRelease(serverVersion) && serverVersion !== RELEASE) {
|
|
52
|
+
await reloadFor(serverVersion);
|
|
53
|
+
}
|
|
54
|
+
} catch (_) { /* adapter restart or temporary network interruption */ }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
window.NEXOWATT_EOS_RELEASE = RELEASE;
|
|
58
|
+
const previous = normalize(localStorage.getItem(RELEASE_KEY));
|
|
59
|
+
localStorage.setItem(RELEASE_KEY, RELEASE);
|
|
60
|
+
const loadedFor = normalize(new URL(window.location.href).searchParams.get('_eosRelease'));
|
|
61
|
+
if (previous && previous !== RELEASE && loadedFor !== RELEASE) {
|
|
62
|
+
void reloadFor(RELEASE);
|
|
63
|
+
} else {
|
|
64
|
+
sessionStorage.removeItem(RELOAD_KEY);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
window.addEventListener('focus', () => void checkServerVersion());
|
|
68
|
+
document.addEventListener('visibilitychange', () => {
|
|
69
|
+
if (document.visibilityState === 'visible') void checkServerVersion();
|
|
70
|
+
});
|
|
71
|
+
window.setInterval(() => void checkServerVersion(), CHECK_MS);
|
|
72
|
+
window.NEXOWATT_EOS_RELEASE_WATCH = Object.freeze({
|
|
73
|
+
release: RELEASE,
|
|
74
|
+
check: checkServerVersion,
|
|
75
|
+
purge: purgeBrowserCaches,
|
|
76
|
+
});
|
|
77
|
+
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v99-first-login-password-persisted';
|
|
5
5
|
const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
|
|
6
6
|
const entry = script?.dataset?.eosEntry || '';
|
|
7
7
|
let launched = false;
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
required: 'Bitte beide Passwortfelder ausfüllen.', mismatch: 'Die Passwörter stimmen nicht überein.',
|
|
81
81
|
length: min => `Das Passwort muss mindestens ${min} Zeichen lang sein.`, complexity: 'Groß-/Kleinbuchstabe, Zahl und Sonderzeichen sind erforderlich.',
|
|
82
82
|
easy: 'Das Passwort ist zu leicht oder enthält den Kontonamen.', generic: 'Das Passwort konnte nicht gespeichert werden.', success: 'Passwort gespeichert. Die Anmeldung wird neu gestartet …',
|
|
83
|
+
backendErrors: { invalidRequest: 'Die Sicherheitsbestätigung fehlt. Bitte erneut anmelden.', invalidRequestOrigin: 'Die Sitzung oder aufgerufene Adresse hat sich geändert. Bitte erneut anmelden.', passwordApiUnavailable: 'Die ioBroker-Passwortverwaltung ist nicht verfügbar.', passwordNotPersisted: 'Das Passwort wurde nicht im ioBroker-Benutzerkonto gespeichert.', passwordVerificationFailed: 'Das neue Passwort wurde geschrieben, aber nicht als gültige Anmeldung bestätigt.', passwordMetadataNotPersisted: 'Das Passwort wurde gespeichert, aber der Erstanmeldungsstatus konnte nicht abgeschlossen werden.', passwordSetupFailed: 'Das Passwort konnte serverseitig nicht gespeichert werden. Bitte das EOS-Systemlog prüfen.' },
|
|
83
84
|
securityTitle: 'Zugriff wird geprüft', securityIntro: 'Die sichere Anmeldung wird gerade geprüft. Die Oberfläche bleibt gesperrt, bis die Berechtigungen eindeutig geladen wurden.',
|
|
84
85
|
securityRetry: 'Jetzt erneut prüfen', securityLogout: 'Zur Anmeldung', securityAuto: seconds => `Automatischer neuer Versuch in ${seconds} Sekunden …`,
|
|
85
86
|
claimLauncher: 'Erstanmeldung', claimTitle: 'Konto erstmals aktivieren',
|
|
@@ -99,6 +100,7 @@
|
|
|
99
100
|
required: 'Please complete both password fields.', mismatch: 'The passwords do not match.',
|
|
100
101
|
length: min => `The password must contain at least ${min} characters.`, complexity: 'Upper/lower case, a number and a special character are required.',
|
|
101
102
|
easy: 'The password is too easy or contains the account name.', generic: 'The password could not be saved.', success: 'Password saved. Restarting sign-in …',
|
|
103
|
+
backendErrors: { invalidRequest: 'The security confirmation is missing. Sign in again.', invalidRequestOrigin: 'The session or address changed. Sign in again.', passwordApiUnavailable: 'The ioBroker password service is unavailable.', passwordNotPersisted: 'The password was not persisted in the ioBroker user account.', passwordVerificationFailed: 'The new password was written but could not be verified as a valid login.', passwordMetadataNotPersisted: 'The password was saved, but the first-login status could not be completed.', passwordSetupFailed: 'The password could not be saved on the server. Check the EOS system log.' },
|
|
102
104
|
securityTitle: 'Checking access', securityIntro: 'The secure sign-in is being verified. The interface stays locked until the permissions are loaded unambiguously.',
|
|
103
105
|
securityRetry: 'Check again now', securityLogout: 'Return to sign-in', securityAuto: seconds => `Retrying automatically in ${seconds} seconds …`,
|
|
104
106
|
claimLauncher: 'First sign-in', claimTitle: 'Activate account for the first time',
|
|
@@ -118,6 +120,7 @@
|
|
|
118
120
|
required: 'Vul beide wachtwoordvelden in.', mismatch: 'De wachtwoorden zijn niet gelijk.',
|
|
119
121
|
length: min => `Het wachtwoord moet minimaal ${min} tekens bevatten.`, complexity: 'Hoofdletter, kleine letter, cijfer en speciaal teken zijn verplicht.',
|
|
120
122
|
easy: 'Het wachtwoord is te eenvoudig of bevat de accountnaam.', generic: 'Het wachtwoord kon niet worden opgeslagen.', success: 'Wachtwoord opgeslagen. De aanmelding wordt opnieuw gestart …',
|
|
123
|
+
backendErrors: { invalidRequest: 'De beveiligingsbevestiging ontbreekt. Meld opnieuw aan.', invalidRequestOrigin: 'De sessie of het adres is gewijzigd. Meld opnieuw aan.', passwordApiUnavailable: 'De ioBroker-wachtwoordservice is niet beschikbaar.', passwordNotPersisted: 'Het wachtwoord is niet in het ioBroker-gebruikersaccount opgeslagen.', passwordVerificationFailed: 'Het nieuwe wachtwoord is geschreven maar kon niet als geldige aanmelding worden bevestigd.', passwordMetadataNotPersisted: 'Het wachtwoord is opgeslagen, maar de eerste-aanmeldstatus kon niet worden afgerond.', passwordSetupFailed: 'Het wachtwoord kon server-side niet worden opgeslagen. Controleer het EOS-systeemlogboek.' },
|
|
121
124
|
securityTitle: 'Toegang wordt gecontroleerd', securityIntro: 'De veilige aanmelding wordt gecontroleerd. De interface blijft vergrendeld totdat de rechten eenduidig zijn geladen.',
|
|
122
125
|
securityRetry: 'Nu opnieuw controleren', securityLogout: 'Terug naar aanmelden', securityAuto: seconds => `Automatisch opnieuw proberen over ${seconds} seconden …`,
|
|
123
126
|
claimLauncher: 'Eerste aanmelding', claimTitle: 'Account voor het eerst activeren',
|
|
@@ -198,7 +201,7 @@
|
|
|
198
201
|
});
|
|
199
202
|
const data = await response.json().catch(() => ({}));
|
|
200
203
|
if (!response.ok || data.error) {
|
|
201
|
-
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy };
|
|
204
|
+
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy, ...(t.backendErrors || {}) };
|
|
202
205
|
throw new Error(messages[data.error] || t.generic);
|
|
203
206
|
}
|
|
204
207
|
setStatus(t.success, 'success');
|
|
@@ -271,7 +274,7 @@
|
|
|
271
274
|
});
|
|
272
275
|
const data = await response.json().catch(() => ({}));
|
|
273
276
|
if (!response.ok || data.error) {
|
|
274
|
-
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy, claimExpired: t.claimExpired };
|
|
277
|
+
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy, claimExpired: t.claimExpired, ...(t.backendErrors || {}) };
|
|
275
278
|
throw new Error(messages[data.error] || t.generic);
|
|
276
279
|
}
|
|
277
280
|
overlay.querySelector('.eos-activation-steps span:nth-of-type(2)')?.classList.add('done');
|
|
@@ -500,7 +503,7 @@
|
|
|
500
503
|
const { username, password, card, submit } = loginElements();
|
|
501
504
|
if (!username || !password || !card) return;
|
|
502
505
|
card.classList.add('eos-login-card-modern');
|
|
503
|
-
//
|
|
506
|
+
// v99 normal-login first activation: do not add role buttons or a second panel.
|
|
504
507
|
// The account is entered in the existing Loginname field; a blank password is accepted only
|
|
505
508
|
// after the backend confirms that this exact managed account is eligible.
|
|
506
509
|
card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v99-native-overview-ems-live-reserve-filter';
|
|
5
5
|
window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
|
|
6
6
|
|
|
7
7
|
const state = {
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
});
|
|
279
279
|
};
|
|
280
280
|
const ensureSafeOverview = () => safe(() => {
|
|
281
|
-
// 7.9.
|
|
281
|
+
// 7.9.99: never cover the real Admin Intro with a second tile surface.
|
|
282
282
|
// Installer and end-user use the native card component; security remains
|
|
283
283
|
// enforced by route/instance filters and backend permissions.
|
|
284
284
|
removeSafeOverview();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v99-nexowatt-native-shell-native-overview';
|
|
5
5
|
const previous = window.NEXOWATT_NATIVE_SHELL;
|
|
6
6
|
if (previous?.version === VERSION) return;
|
|
7
7
|
previous?.destroy?.();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* NexoWatt EOS stable runtime
|
|
1
|
+
/* NexoWatt EOS stable runtime v99 */
|
|
2
2
|
(() => {
|
|
3
3
|
'use strict';
|
|
4
4
|
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
const findSubmit = () => [...document.querySelectorAll('button,input[type="submit"]')].find(element => visible(element) && (/anmelden|login|sign in/i.test((element.textContent || element.value || '').trim()) || element.type === 'submit'));
|
|
15
15
|
const sync = () => {
|
|
16
16
|
if (!isLogin()) return;
|
|
17
|
-
document.documentElement.classList.add('nw-eos-login-
|
|
18
|
-
document.body?.classList.add('nw-eos-login-
|
|
17
|
+
document.documentElement.classList.add('nw-eos-login-v99');
|
|
18
|
+
document.body?.classList.add('nw-eos-login-v99');
|
|
19
19
|
const username = findInput('user');
|
|
20
20
|
const password = findInput('password');
|
|
21
21
|
const submit = findSubmit();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
const VERSION = '
|
|
3
|
+
const VERSION = 'v99-compact-login-ems-connection';
|
|
4
4
|
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
5
5
|
const normalizeUser = value => String(value || '').trim().toLowerCase().replace(/^system\.user\./, '');
|
|
6
6
|
const apply = () => {
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
document.addEventListener('change', schedule, true);
|
|
30
30
|
};
|
|
31
31
|
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, { once: true }) : start();
|
|
32
|
-
window.
|
|
32
|
+
window.NEXOWATT_EOS_STABLE_V99 = Object.freeze({ version: VERSION, refresh: apply });
|
|
33
33
|
})();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeEosPasswordTarget = normalizeEosPasswordTarget;
|
|
4
|
+
exports.setEosUserPasswordWithVerification = setEosUserPasswordWithVerification;
|
|
5
|
+
function normalizeEosPasswordTarget(value) {
|
|
6
|
+
const raw = String(value || '').trim();
|
|
7
|
+
const userId = raw.startsWith('system.user.') ? raw : `system.user.${raw}`;
|
|
8
|
+
const userName = userId.replace(/^system\.user\./, '').trim();
|
|
9
|
+
if (!userName || userName.includes('.') || !/^system\.user\.[^.]+$/.test(userId)) {
|
|
10
|
+
throw new Error('invalidPasswordTarget');
|
|
11
|
+
}
|
|
12
|
+
return { userId, userName };
|
|
13
|
+
}
|
|
14
|
+
async function callPasswordApi(adapter, userName, password, serviceUser) {
|
|
15
|
+
const options = { user: serviceUser };
|
|
16
|
+
if (typeof adapter.setPasswordAsync === 'function') {
|
|
17
|
+
await adapter.setPasswordAsync(userName, password, options);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (typeof adapter.setPassword === 'function') {
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
adapter.setPassword?.(userName, password, options, error => (error ? reject(error) : resolve()));
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('passwordApiUnavailable');
|
|
27
|
+
}
|
|
28
|
+
async function waitForPasswordHash(adapter, userId) {
|
|
29
|
+
for (let attempt = 0; attempt < 6; attempt++) {
|
|
30
|
+
const user = await adapter.getForeignObjectAsync(userId);
|
|
31
|
+
if (typeof user?.common?.password === 'string' && user.common.password.length > 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (attempt < 5) {
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 40 * (attempt + 1)));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error('passwordNotPersisted');
|
|
39
|
+
}
|
|
40
|
+
async function verifyCredential(adapter, userName, password, serviceUser) {
|
|
41
|
+
if (typeof adapter.checkPasswordAsync !== 'function') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
45
|
+
const result = await adapter.checkPasswordAsync(userName, password, { user: serviceUser });
|
|
46
|
+
const accepted = result === true || (Array.isArray(result) && result[0] === true);
|
|
47
|
+
if (accepted) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (attempt < 4) {
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 50 * (attempt + 1)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error('passwordVerificationFailed');
|
|
55
|
+
}
|
|
56
|
+
async function setEosUserPasswordWithVerification(adapter, target, password, serviceUser = 'system.user.admin') {
|
|
57
|
+
const normalized = normalizeEosPasswordTarget(target);
|
|
58
|
+
await callPasswordApi(adapter, normalized.userName, password, serviceUser);
|
|
59
|
+
await waitForPasswordHash(adapter, normalized.userId);
|
|
60
|
+
await verifyCredential(adapter, normalized.userName, password, serviceUser);
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isEosSameOriginRequest = isEosSameOriginRequest;
|
|
4
|
+
function isEosSameOriginRequest(headers) {
|
|
5
|
+
const origin = String(headers.origin || '').trim();
|
|
6
|
+
if (!origin) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(origin);
|
|
11
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const allowedHosts = new Set();
|
|
15
|
+
const addHost = value => {
|
|
16
|
+
const host = String(value || '').split(',')[0].trim().toLowerCase();
|
|
17
|
+
if (host)
|
|
18
|
+
allowedHosts.add(host);
|
|
19
|
+
};
|
|
20
|
+
addHost(headers.host);
|
|
21
|
+
addHost(headers['x-forwarded-host']);
|
|
22
|
+
if (!allowedHosts.size || !allowedHosts.has(parsed.host.toLowerCase())) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const fetchSite = String(headers['sec-fetch-site'] || '').trim().toLowerCase();
|
|
26
|
+
return !fetchSite || fetchSite === 'same-origin' || fetchSite === 'same-site' || fetchSite === 'none';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|