iobroker.eos-admin 7.9.98 → 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.
Files changed (52) hide show
  1. package/INSTALL_TEST_V7.9.99.md +11 -0
  2. package/MERGE_UPDATE_README_V7.9.99.md +5 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +21 -20
  4. package/PUBLISH_STABLE_V7.9.99.md +15 -0
  5. package/README.md +6 -16
  6. package/README_STABILITY_V7.9.99.md +18 -0
  7. package/RELEASE_ACCEPTANCE_V7.9.99.md +11 -0
  8. package/RELEASE_NOTES_V7.9.99.md +9 -0
  9. package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
  10. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  11. package/adminWww/css/eos-ems-overview.css +3 -5
  12. package/adminWww/css/nexowatt-native-shell.css +26 -112
  13. package/adminWww/index.html +19 -18
  14. package/adminWww/js/eos-account-management.js +19 -28
  15. package/adminWww/js/eos-assistant.js +1 -1
  16. package/adminWww/js/eos-ems-overview.js +2 -3
  17. package/adminWww/js/eos-release-watch.js +77 -0
  18. package/adminWww/js/eos-role-bootstrap.js +11 -8
  19. package/adminWww/js/eos-role-ui.js +2 -2
  20. package/adminWww/js/nexowatt-native-shell.js +1 -1
  21. package/adminWww/static/js/nexowatt-stable-login.js +3 -3
  22. package/adminWww/static/js/{nexowatt-stable-v98.js → nexowatt-stable-v99.js} +2 -2
  23. package/build/lib/eosPassword.js +62 -0
  24. package/build/lib/eosRequestSecurity.js +31 -0
  25. package/build/lib/web.js +149 -154
  26. package/build/main.js +2 -1
  27. package/io-package.json +5 -10
  28. package/package.json +17 -13
  29. package/tools/nexowatt-account-management-selftest.cjs +19 -15
  30. package/tools/nexowatt-apply-ems-overview.cjs +2 -4
  31. package/tools/nexowatt-cache-update-selftest.cjs +11 -0
  32. package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
  33. package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
  34. package/tools/nexowatt-login-layout-selftest.cjs +11 -11
  35. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  36. package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
  37. package/tools/nexowatt-password-write-selftest.cjs +42 -44
  38. package/tools/nexowatt-request-security-selftest.cjs +11 -0
  39. package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -6
  40. package/tools/nexowatt-stable-v99-selftest.cjs +25 -0
  41. package/tools/nexowatt-sync-release-version.cjs +1 -42
  42. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +3 -1
  43. package/tools/nexowatt-validate-package.cjs +23 -23
  44. package/tools/nexowatt-version-sync-selftest.cjs +1 -18
  45. package/INSTALL_TEST_V7.9.98.md +0 -39
  46. package/MERGE_UPDATE_README_V7.9.98.md +0 -29
  47. package/PUBLISH_STABLE_V7.9.98.md +0 -33
  48. package/README_STABILITY_V7.9.98.md +0 -22
  49. package/RELEASE_ACCEPTANCE_V7.9.98.md +0 -48
  50. package/RELEASE_NOTES_V7.9.98.md +0 -32
  51. package/tools/nexowatt-account-password-flow-selftest.cjs +0 -186
  52. package/tools/nexowatt-stable-v98-selftest.cjs +0 -14
@@ -17,7 +17,7 @@
17
17
  }
18
18
  #eos-ems-overview-runtime { width: calc(100% - 32px); max-width: none; margin: 0 16px 20px; }
19
19
  .eos-ems-overview-card * { box-sizing: border-box; }
20
- .eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
20
+ .eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:20px; }
21
21
  .eos-ems-overview-header h2 { margin:5px 0 6px; font-size:clamp(1.2rem,2vw,1.65rem); color:#f5fbff; }
22
22
  .eos-ems-overview-header p { margin:0; max-width:760px; color:#9eb2c2; line-height:1.45; }
23
23
  .eos-ems-overview-eyebrow { color:#49e39e; font-size:.72rem; font-weight:900; letter-spacing:.13em; text-transform:uppercase; }
@@ -48,9 +48,7 @@
48
48
  .eos-ems-overview-event time { color:#6f8799; font-size:.68rem; font-variant-numeric:tabular-nums; }
49
49
  .eos-ems-overview-event div { display:grid; gap:2px; min-width:0; }
50
50
  .eos-ems-overview-empty { color:#718797; font-size:.76rem; }
51
- .eos-ems-overview-footer { display:flex; justify-content:space-between; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
52
- .eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
53
- .eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
51
+ .eos-ems-overview-footer { display:flex; justify-content:flex-start; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
54
52
  @media (max-width: 1100px) { .eos-ems-overview-card { flex-basis:100%; max-width:none; } }
55
53
  @media (max-width: 760px) {
56
54
  #eos-ems-overview-runtime,.eos-ems-overview-card { min-width:0; width:calc(100% - 16px); margin:8px; padding:16px; }
@@ -61,7 +59,7 @@
61
59
  .eos-ems-overview-footer { display:grid; }
62
60
  }
63
61
 
64
- /* v98: startup and compatibility messages stay compact. */
62
+ /* v99: startup and compatibility messages stay compact. */
65
63
  .eos-ems-overview-card.is-loading,
66
64
  .eos-ems-overview-card.is-unavailable {
67
65
  min-height: 0;
@@ -1292,7 +1292,7 @@ html.eos-route-intro #app-paper [class*="card"] {
1292
1292
  .eos-assist-header-root .eos-assist-panel { right: 8px !important; width: calc(100vw - 16px); }
1293
1293
  }
1294
1294
 
1295
- /* NexoWatt EOS 7.9.97 – orderly login, moderately enlarged. */
1295
+ /* NexoWatt EOS 7.9.99 – orderly login, moderately enlarged. */
1296
1296
  html.eos-login,
1297
1297
  html.eos-login body {
1298
1298
  min-height: 100%;
@@ -1407,119 +1407,33 @@ html.eos-login form { max-width: 100% !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
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
1410
 
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 {
1411
+ /* NexoWatt EOS 7.9.99 – edit controls belong to the lowest Intro card grid. */
1412
+ html.eos-route-intro .eos-native-intro-grid {
1413
+ position: relative !important;
1470
1414
  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;
1415
+ min-height: 80px !important;
1416
+ padding-bottom: 80px !important;
1417
+ overflow: visible !important;
1504
1418
  }
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;
1419
+ html.eos-route-intro .eos-overview-edit-controls {
1420
+ position: absolute !important;
1421
+ right: 16px !important;
1422
+ bottom: 16px !important;
1423
+ z-index: 3 !important;
1424
+ display: flex !important;
1425
+ align-items: center !important;
1426
+ gap: 8px !important;
1427
+ min-width: 52px !important;
1428
+ min-height: 52px !important;
1429
+ padding: 6px !important;
1430
+ border: 1px solid rgba(0, 229, 138, .24) !important;
1431
+ border-radius: 16px !important;
1432
+ background: rgba(3, 23, 34, .94) !important;
1433
+ box-shadow: 0 12px 30px rgba(0, 0, 0, .26) !important;
1510
1434
  }
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
- }
1435
+ html.eos-route-intro .eos-overview-edit-controls .MuiFab-root {
1436
+ position: static !important;
1437
+ inset: auto !important;
1438
+ margin: 0 !important;
1524
1439
  }
1525
-
@@ -8,8 +8,9 @@
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=98"></script>
12
- <script type="module" src="./assets/hostInit-v84.js?v=84"></script>
11
+ <script src="./js/eos-release-watch.js?v=99"></script>
12
+ <script src="./js/eos-manual-write-policy.js?v=99"></script>
13
+ <script type="module" src="./assets/hostInit-v84.js?v=99"></script>
13
14
 
14
15
  <meta charset="utf-8" />
15
16
  <link
@@ -36,8 +37,8 @@
36
37
  rel="stylesheet"
37
38
  href="css/leaflet.css"
38
39
  />
39
- <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=98" />
40
- <link rel="stylesheet" href="./css/eos-ems-overview.css?v=98" />
40
+ <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=99" />
41
+ <link rel="stylesheet" href="./css/eos-ems-overview.css?v=99" />
41
42
  <link
42
43
  rel="manifest"
43
44
  href="manifest.json"
@@ -157,23 +158,23 @@
157
158
  false,
158
159
  );
159
160
  </script>
160
- <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
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=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>
161
+ <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js?v=99">
162
+ <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js?v=99">
163
+ <script defer src="./js/eos-role-bootstrap.js?v=99" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=99"></script>
164
+ <script defer src="./js/eos-policy-client.js?v=99"></script>
165
+ <script defer src="./js/eos-branding-sanitizer.js?v=99"></script>
166
+ <script defer src="./js/nexowatt-native-shell.js?v=99"></script>
167
+ <script defer src="./js/eos-native-security.js?v=99"></script>
168
+ <script defer src="./js/eos-basic-settings.js?v=99"></script>
169
+ <script defer src="./js/eos-role-ui.js?v=99"></script>
170
+ <script defer src="./js/eos-ems-overview.js?v=99"></script>
171
+ <script defer src="./js/eos-account-management.js?v=99"></script>
172
+ <script defer src="./js/eos-assistant.js?v=99"></script>
172
173
  </head>
173
174
  <body>
174
175
  <noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
175
176
  <div id="root"></div>
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>
177
+ <script defer src="static/js/nexowatt-stable-login.js?v=99"></script>
178
+ <script defer src="static/js/nexowatt-stable-v99.js?v=99"></script>
178
179
  </body>
179
180
  </html>
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v98-account-management-real-password-write';
4
+ const VERSION = 'v99-account-management-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,10 +19,11 @@
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. 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',
22
+ reset: 'Passwort zurücksetzen', resetting: 'Wird zurückgesetzt …', resetConfirm: name => `Soll der Zugang „${name}“ wirklich zurückgesetzt werden? Danach gilt einmalig das Startpasswort „nexowatt“ und anschließend muss ein persönliches Passwort vergeben werden.`,
23
+ resetDone: name => `Der Zugang „${name}“ wurde im EOS-Benutzerkonto auf „nexowatt“ zurückgesetzt. Nach der nächsten Anmeldung muss sofort ein persönliches Passwort vergeben werden.`,
24
+ resetFailed: 'Der Zugang konnte nicht zurückgesetzt 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
+ errors: { permissionError: 'Die Admin-/Installateurberechtigung konnte nicht bestätigt werden. Bitte neu anmelden.', invalidRequest: 'Die Sicherheitsbestätigung fehlt. Bitte die Seite neu laden.', invalidRequestOrigin: 'Die Sitzung oder Adresse hat sich geändert. Bitte die Seite neu laden.', invalidTarget: 'Dieses Konto darf nicht über EOS zurückgesetzt werden.', accountDisabled: 'Das Konto ist deaktiviert.', accountNotFound: 'Das Benutzerkonto wurde nicht gefunden.', passwordApiUnavailable: 'Die EOS-Passwortverwaltung ist nicht verfügbar.', passwordNotPersisted: 'Das Startpasswort wurde nicht gespeichert.', passwordVerificationFailed: 'Das Startpasswort wurde geschrieben, aber nicht als gültige Anmeldung bestätigt.', passwordMetadataNotPersisted: 'Der Erstanmeldungsstatus wurde nicht gespeichert.', accountResetFailed: 'Das Konto konnte serverseitig nicht zurückgesetzt werden.' },
26
27
  },
27
28
  en: {
28
29
  launcher: 'Accounts', titleAdmin: 'Accounts & roles', titleInstaller: 'End-user accounts',
@@ -30,10 +31,11 @@
30
31
  introInstaller: 'You may reset end-user accounts. Admin and installer accounts remain reserved for NexoWatt Service.',
31
32
  loading: 'Loading accounts …', empty: 'No matching accounts found.', close: 'Close', refresh: 'Refresh',
32
33
  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 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',
34
+ reset: 'Reset password', resetting: 'Resetting …', resetConfirm: name => `Reset account “${name}”? The one-time initial password will be “nexowatt”; a personal password must then be created immediately.`,
35
+ resetDone: name => `Account “${name}” was reset in the EOS user account to “nexowatt”. The next sign-in requires an immediate personal password change.`,
36
+ resetFailed: 'The account could not be reset.', lastSet: 'Last password setup', lastReset: 'Last reset', by: 'by',
36
37
  security: 'Admin/Service may reset installer and end-user accounts. Installers may reset end-user accounts only.',
38
+ errors: { permissionError: 'Admin/Installer permission could not be confirmed. Sign in again.', invalidRequest: 'The security confirmation is missing. Reload the page.', invalidRequestOrigin: 'The session or address changed. Reload the page.', invalidTarget: 'This account cannot be reset through EOS.', accountDisabled: 'The account is disabled.', accountNotFound: 'The user account was not found.', passwordApiUnavailable: 'The EOS password service is unavailable.', passwordNotPersisted: 'The initial password was not saved.', passwordVerificationFailed: 'The initial password was written but could not be verified.', passwordMetadataNotPersisted: 'The first-login status was not saved.', accountResetFailed: 'The account could not be reset on the server.' },
37
39
  },
38
40
  nl: {
39
41
  launcher: 'Toegangen', titleAdmin: 'Toegangen & rollen', titleInstaller: 'Eindgebruikerstoegangen',
@@ -41,26 +43,15 @@
41
43
  introInstaller: 'Je kunt eindgebruikersaccounts resetten. Admin- en installateurstoegang blijft voor NexoWatt Service.',
42
44
  loading: 'Toegangen worden geladen …', empty: 'Geen passende accounts gevonden.', close: 'Sluiten', refresh: 'Vernieuwen',
43
45
  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. 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',
46
+ reset: 'Wachtwoord resetten', resetting: 'Wordt gereset …', resetConfirm: name => `Account „${name}“ resetten? Eenmalig geldt het startwachtwoord „nexowatt“, waarna direct een persoonlijk wachtwoord moet worden ingesteld.`,
47
+ resetDone: name => `Account „${name}“ is in het EOS-gebruikersaccount teruggezet op „nexowatt“. Bij de volgende aanmelding moet direct een persoonlijk wachtwoord worden ingesteld.`,
48
+ resetFailed: 'Het account kon niet worden gereset.', lastSet: 'Laatste wachtwoordinstelling', lastReset: 'Laatste reset', by: 'door',
47
49
  security: 'Admin/service mag installateur- en eindgebruikersaccounts resetten. Installateurs alleen eindgebruikers.',
50
+ errors: { permissionError: 'De Admin-/Installateurrechten konden niet worden bevestigd. Meld opnieuw aan.', invalidRequest: 'De beveiligingsbevestiging ontbreekt. Herlaad de pagina.', invalidRequestOrigin: 'De sessie of het adres is gewijzigd. Herlaad de pagina.', invalidTarget: 'Dit account kan niet via EOS worden gereset.', accountDisabled: 'Het account is uitgeschakeld.', accountNotFound: 'Het gebruikersaccount is niet gevonden.', passwordApiUnavailable: 'De EOS-wachtwoordservice is niet beschikbaar.', passwordNotPersisted: 'Het startwachtwoord is niet opgeslagen.', passwordVerificationFailed: 'Het startwachtwoord is geschreven maar kon niet worden geverifieerd.', passwordMetadataNotPersisted: 'De eerste-aanmeldstatus is niet opgeslagen.', accountResetFailed: 'Het account kon server-side niet worden gereset.' },
48
51
  },
49
52
  };
50
53
  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
- };
54
+ const errorText = code => t().errors?.[String(code || '')] || String(code || t().resetFailed);
64
55
  const roleFromPolicy = policy => {
65
56
  const raw = normalize(policy?.role || '');
66
57
  if (policy?.isAdmin || policy?.isEosAdminGroup || /admin|service/.test(raw)) return 'admin';
@@ -157,17 +148,17 @@
157
148
  reset.disabled = true; reset.textContent = t().resetting; setStatus('', '');
158
149
  try {
159
150
  const response = await fetch(new URL('nexowatt/account/reset', base).href, {
160
- method: 'POST', credentials: 'include', cache: 'no-store',
151
+ method: 'POST', credentials: 'same-origin', cache: 'no-store',
161
152
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Account-Reset': '1' },
162
153
  body: JSON.stringify({ user: account.id }),
163
154
  });
164
155
  const payload = await response.json().catch(() => ({}));
165
- if (!response.ok || payload.error) throw new Error(accountErrorText(payload.error));
156
+ if (!response.ok || payload.error) throw new Error(payload.error || t().resetFailed);
166
157
  setStatus(t().resetDone(label), 'success');
167
158
  await loadAccounts();
168
159
  } catch (error) {
169
160
  reset.disabled = false; reset.textContent = t().reset;
170
- setStatus(error?.message || t().resetFailed, 'error');
161
+ setStatus(errorText(error?.message), 'error');
171
162
  }
172
163
  });
173
164
  actions.appendChild(reset);
@@ -189,12 +180,12 @@
189
180
  if (!state.overlay) return;
190
181
  setStatus(t().loading, '');
191
182
  try {
192
- const response = await fetch(new URL('nexowatt/account/manage', base).href, { credentials: 'include', cache: 'no-store', headers: { Accept: 'application/json' } });
183
+ const response = await fetch(new URL('nexowatt/account/manage', base).href, { credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' } });
193
184
  const data = await response.json().catch(() => ({}));
194
185
  if (!response.ok || data.error) throw new Error(data.error || t().resetFailed);
195
186
  renderAccounts(data); setStatus('', '');
196
187
  } catch (error) {
197
- setStatus(error?.message || t().resetFailed, 'error');
188
+ setStatus(errorText(error?.message), 'error');
198
189
  }
199
190
  };
200
191
 
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- // NexoWatt EOS stable 7.9.97: EOS Assist remains disabled until the
3
+ // NexoWatt EOS stable 7.9.99: EOS Assist remains disabled until the
4
4
  // read-only system assistant is rebuilt and accepted separately.
5
5
  window.NEXOWATT_EOS_ASSIST_DISABLED = true;
6
6
  document.documentElement.classList.add('eos-assist-disabled');
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- const VERSION = 'v98-nexowatt-ems-overview-runtime';
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><a href="${esc(detailsUrl)}" target="_blank" rel="noreferrer">Vollständige EMS-Diagnose öffnen ↗</a></footer>`;
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 = 'v98-first-login-password-native-overview';
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',
@@ -192,13 +195,13 @@
192
195
  submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
193
196
  try {
194
197
  const response = await fetch(endpoint, {
195
- method: 'POST', credentials: 'include', cache: 'no-store',
198
+ method: 'POST', credentials: 'same-origin', cache: 'no-store',
196
199
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-First-Login': '1' },
197
200
  body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
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, passwordSetupNotAllowed: t.generic, invalidRequestOrigin: t.generic, passwordApiUnavailable: t.generic, passwordWriteFailed: t.generic, passwordVerificationFailed: t.generic, userObjectUnavailableAfterPasswordChange: t.generic };
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');
@@ -265,13 +268,13 @@
265
268
  submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
266
269
  try {
267
270
  const response = await fetch(endpoint, {
268
- method: 'POST', credentials: 'include', cache: 'no-store',
271
+ method: 'POST', credentials: 'same-origin', cache: 'no-store',
269
272
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Password': '1' },
270
273
  body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
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');
@@ -473,7 +476,7 @@
473
476
  setStatus(card, t.claimChecking, '');
474
477
  try {
475
478
  const response = await fetch(new URL('nexowatt/account/passwordless-claim', base).href, {
476
- method: 'POST', credentials: 'include', cache: 'no-store',
479
+ method: 'POST', credentials: 'same-origin', cache: 'no-store',
477
480
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Claim': '1' },
478
481
  body: JSON.stringify({ user: account }),
479
482
  });
@@ -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
- // v98 compact normal-login first activation: do not add role buttons or a second panel.
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());
@@ -634,7 +637,7 @@
634
637
  const timeout = window.setTimeout(() => controller.abort(), 8000);
635
638
 
636
639
  window.NEXOWATT_EOS_BOOTSTRAP_READY = fetch(contextUrl, {
637
- credentials: 'include', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
640
+ credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
638
641
  })
639
642
  .then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); })
640
643
  .then(policy => {
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v98-native-overview-ems-live-reserve-filter';
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.97: never cover the real Admin Intro with a second tile surface.
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 = 'v98-nexowatt-native-shell-native-overview';
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 v98 */
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-v98');
18
- document.body?.classList.add('nw-eos-login-v98');
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();