iobroker.eos-admin 7.9.97 → 7.9.98

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.
Files changed (42) hide show
  1. package/INSTALL_TEST_V7.9.98.md +39 -0
  2. package/MERGE_UPDATE_README_V7.9.98.md +29 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +20 -20
  4. package/PUBLISH_STABLE_V7.9.98.md +33 -0
  5. package/README.md +16 -6
  6. package/README_STABILITY_V7.9.98.md +22 -0
  7. package/RELEASE_ACCEPTANCE_V7.9.98.md +48 -0
  8. package/RELEASE_NOTES_V7.9.98.md +32 -0
  9. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  10. package/adminWww/css/eos-ems-overview.css +1 -1
  11. package/adminWww/css/nexowatt-native-shell.css +117 -0
  12. package/adminWww/index.html +15 -15
  13. package/adminWww/js/eos-account-management.js +26 -13
  14. package/adminWww/js/eos-ems-overview.js +1 -1
  15. package/adminWww/js/eos-role-bootstrap.js +7 -7
  16. package/adminWww/js/eos-role-ui.js +1 -1
  17. package/adminWww/js/nexowatt-native-shell.js +1 -1
  18. package/adminWww/static/js/nexowatt-stable-login.js +3 -3
  19. package/adminWww/static/js/{nexowatt-stable-v97.js → nexowatt-stable-v98.js} +2 -2
  20. package/build/lib/web.js +112 -79
  21. package/build/main.js +1 -1
  22. package/io-package.json +10 -5
  23. package/package.json +14 -12
  24. package/tools/nexowatt-account-management-selftest.cjs +16 -57
  25. package/tools/nexowatt-account-password-flow-selftest.cjs +186 -0
  26. package/tools/nexowatt-login-layout-selftest.cjs +8 -8
  27. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  28. package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
  29. package/tools/nexowatt-password-write-selftest.cjs +48 -0
  30. package/tools/nexowatt-runtime-cleanup-selftest.cjs +5 -5
  31. package/tools/nexowatt-stable-v98-selftest.cjs +14 -0
  32. package/tools/nexowatt-sync-release-version.cjs +42 -1
  33. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +1 -1
  34. package/tools/nexowatt-validate-package.cjs +20 -11
  35. package/tools/nexowatt-version-sync-selftest.cjs +18 -1
  36. package/INSTALL_TEST_V7.9.97.md +0 -11
  37. package/MERGE_UPDATE_README_V7.9.97.md +0 -5
  38. package/PUBLISH_STABLE_V7.9.97.md +0 -15
  39. package/README_STABILITY_V7.9.97.md +0 -18
  40. package/RELEASE_ACCEPTANCE_V7.9.97.md +0 -11
  41. package/RELEASE_NOTES_V7.9.97.md +0 -19
  42. package/tools/nexowatt-stable-v97-selftest.cjs +0 -16
@@ -61,7 +61,7 @@
61
61
  .eos-ems-overview-footer { display:grid; }
62
62
  }
63
63
 
64
- /* v97: startup and compatibility messages stay compact. */
64
+ /* v98: startup and compatibility messages stay compact. */
65
65
  .eos-ems-overview-card.is-loading,
66
66
  .eos-ems-overview-card.is-unavailable {
67
67
  min-height: 0;
@@ -1406,3 +1406,120 @@ html.eos-login form { max-width: 100% !important; }
1406
1406
  .eos-role-safe-overview { display: none !important; }
1407
1407
  html.eos-safe-overview-active #app-paper > :not(.MuiSnackbar-root):not([role="presentation"]) { pointer-events: auto !important; }
1408
1408
  .eos-intro-admin-edit-hidden { display: none !important; }
1409
+
1410
+ /* NexoWatt EOS 7.9.98 – stable scrolling, clean login fields and expandable errors. */
1411
+ html.eos-app,
1412
+ html.eos-app body,
1413
+ html.eos-app #root {
1414
+ height: 100% !important;
1415
+ max-height: 100% !important;
1416
+ overflow: hidden !important;
1417
+ }
1418
+ html.eos-app #app-paper {
1419
+ height: calc(100vh - var(--nx-content-top) - 12px) !important;
1420
+ height: calc(100dvh - var(--nx-content-top) - 12px) !important;
1421
+ min-height: 0 !important;
1422
+ max-height: calc(100vh - var(--nx-content-top) - 12px) !important;
1423
+ max-height: calc(100dvh - var(--nx-content-top) - 12px) !important;
1424
+ overflow-x: hidden !important;
1425
+ overflow-y: auto !important;
1426
+ overscroll-behavior: contain;
1427
+ scrollbar-gutter: stable;
1428
+ scrollbar-width: thin;
1429
+ scrollbar-color: rgba(0, 229, 138, .88) rgba(3, 18, 29, .72);
1430
+ }
1431
+ html.eos-app #app-paper::-webkit-scrollbar { width: 11px; }
1432
+ html.eos-app #app-paper::-webkit-scrollbar-track {
1433
+ border-radius: 12px;
1434
+ background: rgba(3, 18, 29, .72);
1435
+ }
1436
+ html.eos-app #app-paper::-webkit-scrollbar-thumb {
1437
+ min-height: 46px;
1438
+ border: 2px solid rgba(3, 18, 29, .72);
1439
+ border-radius: 12px;
1440
+ background: linear-gradient(180deg, #00e58a, #39c9ef);
1441
+ }
1442
+ html.eos-app #app-paper::-webkit-scrollbar-thumb:hover {
1443
+ background: linear-gradient(180deg, #21f2a1, #60dcff);
1444
+ }
1445
+
1446
+ html.eos-login main.MuiPaper-root::before {
1447
+ width: min(456px, calc(100vw - 20px)) !important;
1448
+ height: min(536px, calc(100dvh - 20px)) !important;
1449
+ border-radius: 24px !important;
1450
+ }
1451
+ html.eos-login main.MuiPaper-root > .eos-login-card-modern,
1452
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username),
1453
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) {
1454
+ width: min(400px, calc(100vw - 48px)) !important;
1455
+ max-width: 400px !important;
1456
+ min-height: 0 !important;
1457
+ height: auto !important;
1458
+ padding: 30px 32px 28px !important;
1459
+ gap: 0 !important;
1460
+ overflow: visible !important;
1461
+ border-radius: 22px !important;
1462
+ }
1463
+ html.eos-login .eos-login-card-modern > .MuiGrid2-root,
1464
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username) > .MuiGrid2-root,
1465
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) > .MuiGrid2-root {
1466
+ width: 100% !important;
1467
+ min-width: 0 !important;
1468
+ }
1469
+ html.eos-login .MuiFormControl-root {
1470
+ width: 100% !important;
1471
+ margin: 13px 0 3px !important;
1472
+ }
1473
+ html.eos-login .MuiInputLabel-root { display: none !important; }
1474
+ html.eos-login .MuiOutlinedInput-root {
1475
+ min-height: 46px !important;
1476
+ border-radius: 6px !important;
1477
+ background: rgba(4, 19, 31, .76) !important;
1478
+ }
1479
+ html.eos-login .MuiOutlinedInput-input {
1480
+ min-height: 0 !important;
1481
+ padding: 11px 14px !important;
1482
+ line-height: 22px !important;
1483
+ }
1484
+ html.eos-login .MuiOutlinedInput-input::placeholder {
1485
+ color: rgba(245, 251, 255, .92) !important;
1486
+ opacity: 1 !important;
1487
+ }
1488
+ html.eos-login .MuiOutlinedInput-notchedOutline legend {
1489
+ width: 0 !important;
1490
+ max-width: 0 !important;
1491
+ padding: 0 !important;
1492
+ }
1493
+ html.eos-login .MuiFormControlLabel-root {
1494
+ margin: 7px 0 5px !important;
1495
+ }
1496
+ html.eos-login .MuiButton-root[type="submit"] {
1497
+ min-height: 44px !important;
1498
+ margin: 8px 0 0 !important;
1499
+ }
1500
+ html.eos-login .eos-login-card-modern > .MuiBox-root:last-child,
1501
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username) > .MuiBox-root:last-child,
1502
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) > .MuiBox-root:last-child {
1503
+ margin-top: 28px !important;
1504
+ }
1505
+ html.eos-login .MuiAlert-root,
1506
+ html.eos-login [role="alert"] {
1507
+ margin: 14px 0 1px !important;
1508
+ padding: 9px 11px !important;
1509
+ line-height: 1.35 !important;
1510
+ }
1511
+ @media (max-width: 500px) {
1512
+ html.eos-login main.MuiPaper-root::before {
1513
+ width: calc(100vw - 12px) !important;
1514
+ height: auto !important;
1515
+ min-height: min(520px, calc(100dvh - 12px)) !important;
1516
+ }
1517
+ html.eos-login main.MuiPaper-root > .eos-login-card-modern,
1518
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username),
1519
+ html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password) {
1520
+ width: calc(100vw - 38px) !important;
1521
+ max-width: none !important;
1522
+ padding: 25px 22px 23px !important;
1523
+ }
1524
+ }
1525
+
@@ -8,7 +8,7 @@
8
8
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
9
9
  <meta http-equiv="Pragma" content="no-cache" />
10
10
  <meta http-equiv="Expires" content="0" />
11
- <script src="./js/eos-manual-write-policy.js?v=97"></script>
11
+ <script src="./js/eos-manual-write-policy.js?v=98"></script>
12
12
  <script type="module" src="./assets/hostInit-v84.js?v=84"></script>
13
13
 
14
14
  <meta charset="utf-8" />
@@ -36,8 +36,8 @@
36
36
  rel="stylesheet"
37
37
  href="css/leaflet.css"
38
38
  />
39
- <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=97" />
40
- <link rel="stylesheet" href="./css/eos-ems-overview.css?v=97" />
39
+ <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=98" />
40
+ <link rel="stylesheet" href="./css/eos-ems-overview.css?v=98" />
41
41
  <link
42
42
  rel="manifest"
43
43
  href="manifest.json"
@@ -159,21 +159,21 @@
159
159
  </script>
160
160
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
161
161
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
162
- <script defer src="./js/eos-role-bootstrap.js?v=97" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
163
- <script defer src="./js/eos-policy-client.js?v=97"></script>
164
- <script defer src="./js/eos-branding-sanitizer.js?v=97"></script>
165
- <script defer src="./js/nexowatt-native-shell.js?v=97"></script>
166
- <script defer src="./js/eos-native-security.js?v=97"></script>
167
- <script defer src="./js/eos-basic-settings.js?v=97"></script>
168
- <script defer src="./js/eos-role-ui.js?v=97"></script>
169
- <script defer src="./js/eos-ems-overview.js?v=97"></script>
170
- <script defer src="./js/eos-account-management.js?v=97"></script>
171
- <script defer src="./js/eos-assistant.js?v=97"></script>
162
+ <script defer src="./js/eos-role-bootstrap.js?v=98" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
163
+ <script defer src="./js/eos-policy-client.js?v=98"></script>
164
+ <script defer src="./js/eos-branding-sanitizer.js?v=98"></script>
165
+ <script defer src="./js/nexowatt-native-shell.js?v=98"></script>
166
+ <script defer src="./js/eos-native-security.js?v=98"></script>
167
+ <script defer src="./js/eos-basic-settings.js?v=98"></script>
168
+ <script defer src="./js/eos-role-ui.js?v=98"></script>
169
+ <script defer src="./js/eos-ems-overview.js?v=98"></script>
170
+ <script defer src="./js/eos-account-management.js?v=98"></script>
171
+ <script defer src="./js/eos-assistant.js?v=98"></script>
172
172
  </head>
173
173
  <body>
174
174
  <noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
175
175
  <div id="root"></div>
176
- <script defer src="static/js/nexowatt-stable-login.js?v=97"></script>
177
- <script defer src="static/js/nexowatt-stable-v97.js?v=97"></script>
176
+ <script defer src="static/js/nexowatt-stable-login.js?v=98"></script>
177
+ <script defer src="static/js/nexowatt-stable-v98.js?v=98"></script>
178
178
  </body>
179
179
  </html>
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v91-account-management-under-access-rights';
4
+ const VERSION = 'v98-account-management-real-password-write';
5
5
  const script = document.currentScript || document.querySelector('script[src*="eos-account-management.js"]');
6
6
  const base = new URL('../', script?.src || window.location.href);
7
7
  const abort = new AbortController();
@@ -19,9 +19,9 @@
19
19
  introInstaller: 'Du kannst Endkundenzugänge zurücksetzen. Admin- und Installateurzugänge bleiben NexoWatt Service vorbehalten.',
20
20
  loading: 'Zugänge werden geladen …', empty: 'Keine passenden Konten gefunden.', close: 'Schließen', refresh: 'Aktualisieren',
21
21
  installer: 'Installateur', enduser: 'Endkunde / Gast', active: 'Passwort aktiv', first: 'Erstanmeldung offen', disabled: 'Deaktiviert',
22
- reset: 'Passwort zurücksetzen', resetting: 'Wird zurückgesetzt …', resetConfirm: name => `Soll der Zugang „${name}“ wirklich zurückgesetzt werden? Das bisherige Passwort wird ungültig und bei der nächsten Erstanmeldung muss ein neues Passwort vergeben werden.`,
23
- resetDone: name => `Der Zugang „${name}“ ist zurückgesetzt. Bei der nächsten Anmeldung wird im normalen Anmeldefeld einmalig ohne Passwort gestartet und anschließend ein neues Passwort vergeben.`,
24
- resetFailed: 'Der Zugang konnte nicht zurückgesetzt werden.', lastSet: 'Letzte Passwortvergabe', lastReset: 'Letzter Reset', by: 'durch',
22
+ reset: 'Passwort zurücksetzen', resetting: 'Wird zurückgesetzt …', resetConfirm: name => `Soll der Zugang „${name}“ wirklich zurückgesetzt werden? Das bisherige Passwort wird ungültig. Danach gilt einmalig das Startpasswort „nexowatt“ und beim nächsten Login muss sofort ein persönliches Passwort vergeben werden.`,
23
+ resetDone: name => `Der Zugang „${name}“ ist zurückgesetzt. Einmalig mit dem Startpasswort „nexowatt“ anmelden und anschließend direkt ein persönliches Passwort festlegen.`,
24
+ resetFailed: 'Der Zugang konnte nicht zurückgesetzt werden.', permissionError: 'Für dieses Konto ist das Zurücksetzen nicht erlaubt.', originError: 'Die Sicherheitsprüfung der Anfrage ist fehlgeschlagen. Seite mit Strg+F5 neu laden.', disabledError: 'Das Konto ist deaktiviert und kann nicht zurückgesetzt werden.', notFoundError: 'Das Konto wurde nicht gefunden.', writeError: 'Das neue Startpasswort konnte nicht in das Benutzerkonto geschrieben werden.', lastSet: 'Letzte Passwortvergabe', lastReset: 'Letzter Reset', by: 'durch',
25
25
  security: 'Admin/Service darf Installateur und Endkunde zurücksetzen. Installateure dürfen ausschließlich Endkunden zurücksetzen.',
26
26
  },
27
27
  en: {
@@ -30,9 +30,9 @@
30
30
  introInstaller: 'You may reset end-user accounts. Admin and installer accounts remain reserved for NexoWatt Service.',
31
31
  loading: 'Loading accounts …', empty: 'No matching accounts found.', close: 'Close', refresh: 'Refresh',
32
32
  installer: 'Installer', enduser: 'End user / Guest', active: 'Password active', first: 'First activation open', disabled: 'Disabled',
33
- reset: 'Reset password', resetting: 'Resetting …', resetConfirm: name => `Reset account “${name}”? The current password will become invalid and a new password must be created during the next first activation.`,
34
- resetDone: name => `Account “${name}” was reset. At the next sign-in the normal login form is used once with an empty password, followed by creation of a new password.`,
35
- resetFailed: 'The account could not be reset.', lastSet: 'Last password setup', lastReset: 'Last reset', by: 'by',
33
+ reset: 'Reset password', resetting: 'Resetting …', resetConfirm: name => `Reset account “${name}”? The current password becomes invalid. Sign in once with the initial password “nexowatt”, then create a personal password immediately.`,
34
+ resetDone: name => `Account “${name}” was reset. Sign in once with “nexowatt” and then create a personal password.`,
35
+ resetFailed: 'The account could not be reset.', permissionError: 'Reset is not permitted for this account.', originError: 'The request security check failed. Reload the page with Ctrl+F5.', disabledError: 'The account is disabled and cannot be reset.', notFoundError: 'The account was not found.', writeError: 'The initial password could not be written to the user account.', lastSet: 'Last password setup', lastReset: 'Last reset', by: 'by',
36
36
  security: 'Admin/Service may reset installer and end-user accounts. Installers may reset end-user accounts only.',
37
37
  },
38
38
  nl: {
@@ -41,13 +41,26 @@
41
41
  introInstaller: 'Je kunt eindgebruikersaccounts resetten. Admin- en installateurstoegang blijft voor NexoWatt Service.',
42
42
  loading: 'Toegangen worden geladen …', empty: 'Geen passende accounts gevonden.', close: 'Sluiten', refresh: 'Vernieuwen',
43
43
  installer: 'Installateur', enduser: 'Eindgebruiker / Guest', active: 'Wachtwoord actief', first: 'Eerste activering open', disabled: 'Uitgeschakeld',
44
- reset: 'Wachtwoord resetten', resetting: 'Wordt gereset …', resetConfirm: name => `Account „${name}“ resetten? Het huidige wachtwoord wordt ongeldig en bij de volgende eerste activering moet een nieuw wachtwoord worden ingesteld.`,
45
- resetDone: name => `Account „${name}“ is gereset. Bij de volgende aanmelding wordt het normale formulier één keer met een leeg wachtwoord gebruikt, waarna een nieuw wachtwoord wordt ingesteld.`,
46
- resetFailed: 'Het account kon niet worden gereset.', lastSet: 'Laatste wachtwoordinstelling', lastReset: 'Laatste reset', by: 'door',
44
+ reset: 'Wachtwoord resetten', resetting: 'Wordt gereset …', resetConfirm: name => `Account „${name}“ resetten? Het huidige wachtwoord wordt ongeldig. Meld één keer aan met het startwachtwoord „nexowatt“ en stel daarna direct een persoonlijk wachtwoord in.`,
45
+ resetDone: name => `Account „${name}“ is gereset. Meld één keer aan met „nexowatt“ en stel daarna een persoonlijk wachtwoord in.`,
46
+ resetFailed: 'Het account kon niet worden gereset.', permissionError: 'Resetten is voor dit account niet toegestaan.', originError: 'De veiligheidscontrole van het verzoek is mislukt. Herlaad de pagina met Ctrl+F5.', disabledError: 'Het account is uitgeschakeld en kan niet worden gereset.', notFoundError: 'Het account werd niet gevonden.', writeError: 'Het startwachtwoord kon niet naar het gebruikersaccount worden geschreven.', lastSet: 'Laatste wachtwoordinstelling', lastReset: 'Laatste reset', by: 'door',
47
47
  security: 'Admin/service mag installateur- en eindgebruikersaccounts resetten. Installateurs alleen eindgebruikers.',
48
48
  },
49
49
  };
50
50
  const t = () => i18n[language()];
51
+ const accountErrorText = code => {
52
+ const text = t();
53
+ return ({
54
+ permissionError: text.permissionError,
55
+ invalidRequestOrigin: text.originError,
56
+ accountDisabled: text.disabledError,
57
+ accountNotFound: text.notFoundError,
58
+ passwordApiUnavailable: text.writeError,
59
+ passwordWriteFailed: text.writeError,
60
+ passwordVerificationFailed: text.writeError,
61
+ userObjectUnavailableAfterPasswordChange: text.writeError,
62
+ })[String(code || '')] || text.resetFailed;
63
+ };
51
64
  const roleFromPolicy = policy => {
52
65
  const raw = normalize(policy?.role || '');
53
66
  if (policy?.isAdmin || policy?.isEosAdminGroup || /admin|service/.test(raw)) return 'admin';
@@ -144,12 +157,12 @@
144
157
  reset.disabled = true; reset.textContent = t().resetting; setStatus('', '');
145
158
  try {
146
159
  const response = await fetch(new URL('nexowatt/account/reset', base).href, {
147
- method: 'POST', credentials: 'same-origin', cache: 'no-store',
160
+ method: 'POST', credentials: 'include', cache: 'no-store',
148
161
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Account-Reset': '1' },
149
162
  body: JSON.stringify({ user: account.id }),
150
163
  });
151
164
  const payload = await response.json().catch(() => ({}));
152
- if (!response.ok || payload.error) throw new Error(payload.error || t().resetFailed);
165
+ if (!response.ok || payload.error) throw new Error(accountErrorText(payload.error));
153
166
  setStatus(t().resetDone(label), 'success');
154
167
  await loadAccounts();
155
168
  } catch (error) {
@@ -176,7 +189,7 @@
176
189
  if (!state.overlay) return;
177
190
  setStatus(t().loading, '');
178
191
  try {
179
- const response = await fetch(new URL('nexowatt/account/manage', base).href, { credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' } });
192
+ const response = await fetch(new URL('nexowatt/account/manage', base).href, { credentials: 'include', cache: 'no-store', headers: { Accept: 'application/json' } });
180
193
  const data = await response.json().catch(() => ({}));
181
194
  if (!response.ok || data.error) throw new Error(data.error || t().resetFailed);
182
195
  renderAccounts(data); setStatus('', '');
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- const VERSION = 'v97-nexowatt-ems-overview-runtime';
3
+ const VERSION = 'v98-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;
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v97-first-login-password-native-overview';
4
+ const VERSION = 'v98-first-login-password-native-overview';
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;
@@ -192,13 +192,13 @@
192
192
  submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
193
193
  try {
194
194
  const response = await fetch(endpoint, {
195
- method: 'POST', credentials: 'same-origin', cache: 'no-store',
195
+ method: 'POST', credentials: 'include', cache: 'no-store',
196
196
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-First-Login': '1' },
197
197
  body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
198
198
  });
199
199
  const data = await response.json().catch(() => ({}));
200
200
  if (!response.ok || data.error) {
201
- const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy };
201
+ const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy, passwordSetupNotAllowed: t.generic, invalidRequestOrigin: t.generic, passwordApiUnavailable: t.generic, passwordWriteFailed: t.generic, passwordVerificationFailed: t.generic, userObjectUnavailableAfterPasswordChange: t.generic };
202
202
  throw new Error(messages[data.error] || t.generic);
203
203
  }
204
204
  setStatus(t.success, 'success');
@@ -265,7 +265,7 @@
265
265
  submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
266
266
  try {
267
267
  const response = await fetch(endpoint, {
268
- method: 'POST', credentials: 'same-origin', cache: 'no-store',
268
+ method: 'POST', credentials: 'include', cache: 'no-store',
269
269
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Password': '1' },
270
270
  body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
271
271
  });
@@ -473,7 +473,7 @@
473
473
  setStatus(card, t.claimChecking, '');
474
474
  try {
475
475
  const response = await fetch(new URL('nexowatt/account/passwordless-claim', base).href, {
476
- method: 'POST', credentials: 'same-origin', cache: 'no-store',
476
+ method: 'POST', credentials: 'include', cache: 'no-store',
477
477
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Claim': '1' },
478
478
  body: JSON.stringify({ user: account }),
479
479
  });
@@ -500,7 +500,7 @@
500
500
  const { username, password, card, submit } = loginElements();
501
501
  if (!username || !password || !card) return;
502
502
  card.classList.add('eos-login-card-modern');
503
- // v97 compact normal-login first activation: do not add role buttons or a second panel.
503
+ // v98 compact normal-login first activation: do not add role buttons or a second panel.
504
504
  // The account is entered in the existing Loginname field; a blank password is accepted only
505
505
  // after the backend confirms that this exact managed account is eligible.
506
506
  card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
@@ -634,7 +634,7 @@
634
634
  const timeout = window.setTimeout(() => controller.abort(), 8000);
635
635
 
636
636
  window.NEXOWATT_EOS_BOOTSTRAP_READY = fetch(contextUrl, {
637
- credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
637
+ credentials: 'include', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
638
638
  })
639
639
  .then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); })
640
640
  .then(policy => {
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v97-native-overview-ems-live-reserve-filter';
4
+ const VERSION = 'v98-native-overview-ems-live-reserve-filter';
5
5
  window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
6
6
 
7
7
  const state = {
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v97-nexowatt-native-shell-native-overview';
4
+ const VERSION = 'v98-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 v97 */
1
+ /* NexoWatt EOS stable runtime v98 */
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-v97');
18
- document.body?.classList.add('nw-eos-login-v97');
17
+ document.documentElement.classList.add('nw-eos-login-v98');
18
+ document.body?.classList.add('nw-eos-login-v98');
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 = 'v97-compact-login-ems-connection';
3
+ const VERSION = 'v98-scroll-login-password-write';
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.NEXOWATT_EOS_STABLE_V97 = Object.freeze({ version: VERSION, refresh: apply });
32
+ window.NEXOWATT_EOS_STABLE_V98 = Object.freeze({ version: VERSION, refresh: apply });
33
33
  })();