iobroker.eos-admin 7.9.35 → 7.9.37

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.
@@ -3319,3 +3319,153 @@ html.eos-app #eos-assist-root {
3319
3319
  html.eos-app .eos-assist-root,
3320
3320
  html.eos-app #eos-assist-root { right: 96px !important; }
3321
3321
  }
3322
+
3323
+
3324
+ /* === NexoWatt EOS v37: native adapter configuration safe mode =============
3325
+ Custom adapter configuration pages (React/HTML/jsonConfig) must be fully
3326
+ controlled by the adapter itself. EOS shell decoration is disabled inside the
3327
+ content area so buttons such as "Gerät hinzufügen" and "Gerät bearbeiten" stay
3328
+ clickable and dialogs/popovers are not hidden behind overlays. */
3329
+ html.eos-app.eos-adapter-config-surface #app-paper,
3330
+ html.eos-app.eos-adapter-config-surface #app-paper * {
3331
+ pointer-events: auto !important;
3332
+ }
3333
+ html.eos-app.eos-adapter-config-surface #app-paper button,
3334
+ html.eos-app.eos-adapter-config-surface #app-paper [role="button"],
3335
+ html.eos-app.eos-adapter-config-surface #app-paper a,
3336
+ html.eos-app.eos-adapter-config-surface #app-paper input,
3337
+ html.eos-app.eos-adapter-config-surface #app-paper select,
3338
+ html.eos-app.eos-adapter-config-surface #app-paper textarea,
3339
+ html.eos-app.eos-adapter-config-surface #app-paper .MuiButton-root,
3340
+ html.eos-app.eos-adapter-config-surface #app-paper .MuiIconButton-root,
3341
+ html.eos-app.eos-adapter-config-surface #app-paper .MuiMenuItem-root {
3342
+ pointer-events: auto !important;
3343
+ user-select: auto !important;
3344
+ }
3345
+ html.eos-app.eos-adapter-config-surface .eos-assist-root,
3346
+ html.eos-app.eos-adapter-config-surface #eos-assist-root,
3347
+ html.eos-app.eos-adapter-config-surface .eos-assist-config-hidden {
3348
+ display: none !important;
3349
+ visibility: hidden !important;
3350
+ pointer-events: none !important;
3351
+ }
3352
+ html.eos-app.eos-adapter-config-surface .MuiDialog-root,
3353
+ html.eos-app.eos-adapter-config-surface .MuiModal-root,
3354
+ html.eos-app.eos-adapter-config-surface .MuiPopover-root,
3355
+ html.eos-app.eos-adapter-config-surface .MuiMenu-root,
3356
+ html.eos-app.eos-adapter-config-surface .MuiDialog-container {
3357
+ pointer-events: auto !important;
3358
+ z-index: 4200 !important;
3359
+ }
3360
+ html.eos-app.eos-adapter-config-surface .MuiDialog-paper,
3361
+ html.eos-app.eos-adapter-config-surface .MuiPopover-paper,
3362
+ html.eos-app.eos-adapter-config-surface .MuiMenu-paper {
3363
+ pointer-events: auto !important;
3364
+ }
3365
+ html.eos-app.eos-adapter-config-surface #app-paper .eos-protected-delete-control,
3366
+ html.eos-app.eos-adapter-config-surface #app-paper .eos-security-hidden-delete,
3367
+ html.eos-app.eos-adapter-config-surface #app-paper .eos-protected-adapter-row {
3368
+ display: revert !important;
3369
+ visibility: visible !important;
3370
+ opacity: 1 !important;
3371
+ filter: none !important;
3372
+ pointer-events: auto !important;
3373
+ }
3374
+
3375
+ /* === NexoWatt EOS v37: notification close compatibility ==================
3376
+ Toasts/snackbars/alerts must stay above EOS decoration and their close
3377
+ actions must remain clickable on all routes. */
3378
+ html.eos-app .MuiSnackbar-root,
3379
+ html.eos-app .SnackbarItem-root,
3380
+ html.eos-app .SnackbarItem-wrappedRoot,
3381
+ html.eos-app .notistack-Snackbar,
3382
+ html.eos-app .Toastify__toast-container,
3383
+ html.eos-app .Toastify__toast,
3384
+ html.eos-app [role="alert"],
3385
+ html.eos-app .MuiAlert-root {
3386
+ pointer-events: auto !important;
3387
+ z-index: 5200 !important;
3388
+ }
3389
+ html.eos-app .MuiSnackbar-root button,
3390
+ html.eos-app .SnackbarItem-root button,
3391
+ html.eos-app .notistack-Snackbar button,
3392
+ html.eos-app .Toastify__toast button,
3393
+ html.eos-app .MuiAlert-root button,
3394
+ html.eos-app [role="alert"] button,
3395
+ html.eos-app button[aria-label="close"],
3396
+ html.eos-app button[aria-label="Close"],
3397
+ html.eos-app button[title="Schließen"],
3398
+ html.eos-app button[title="Close"] {
3399
+ pointer-events: auto !important;
3400
+ opacity: 1 !important;
3401
+ visibility: visible !important;
3402
+ z-index: 5201 !important;
3403
+ }
3404
+ html.eos-app .MuiSnackbar-root svg,
3405
+ html.eos-app .SnackbarItem-root svg,
3406
+ html.eos-app .MuiAlert-root svg,
3407
+ html.eos-app [role="alert"] svg {
3408
+ pointer-events: none !important;
3409
+ }
3410
+
3411
+ /* v37: BackItUp-safe mode. EOS keeps delete/stop controls protected in the UI,
3412
+ but does not force adapter-object ACLs for runtime adapters by default. */
3413
+ html.eos-app .eos-backitup-safe-note { color: rgba(226,245,255,.78); }
3414
+
3415
+ /* v37: native Admin notifications must always be closable. */
3416
+ html.eos-app .MuiSnackbar-root,
3417
+ html.eos-app .MuiAlert-root,
3418
+ html.eos-app .MuiSnackbarContent-root,
3419
+ html.eos-app [role="alert"],
3420
+ html.eos-app .Toastify__toast,
3421
+ html.eos-app .notistack-Snackbar,
3422
+ html.eos-app .eos-notification-safe {
3423
+ pointer-events: auto !important;
3424
+ z-index: 5200 !important;
3425
+ }
3426
+ html.eos-app .MuiSnackbar-root button,
3427
+ html.eos-app .MuiAlert-root button,
3428
+ html.eos-app .MuiSnackbarContent-root button,
3429
+ html.eos-app [role="alert"] button,
3430
+ html.eos-app .Toastify__toast button,
3431
+ html.eos-app .notistack-Snackbar button,
3432
+ html.eos-app .eos-notification-safe button,
3433
+ html.eos-app .eos-notification-safe [role="button"],
3434
+ html.eos-app .eos-notification-safe a,
3435
+ html.eos-app .eos-notification-safe .MuiIconButton-root {
3436
+ pointer-events: auto !important;
3437
+ visibility: visible !important;
3438
+ opacity: 1 !important;
3439
+ }
3440
+ html.eos-app .eos-notification-safe .eos-protected-delete-control,
3441
+ html.eos-app .eos-notification-safe .eos-security-hidden-delete {
3442
+ display: inline-flex !important;
3443
+ pointer-events: auto !important;
3444
+ }
3445
+
3446
+
3447
+ /* v37 notification dialog safety: notification dialogs must stay fully native/clickable. */
3448
+ html.eos-app .eos-notification-dialog-root,
3449
+ html.eos-app .MuiModal-root:has(#notifications-dialog-close),
3450
+ html.eos-app .MuiDialog-root:has(#notifications-dialog-close) {
3451
+ pointer-events: auto !important;
3452
+ z-index: 4300 !important;
3453
+ }
3454
+ html.eos-app .eos-notification-dialog,
3455
+ html.eos-app .MuiDialog-paper:has(#notifications-dialog-close) {
3456
+ pointer-events: auto !important;
3457
+ z-index: 4301 !important;
3458
+ }
3459
+ html.eos-app .eos-notification-dialog button,
3460
+ html.eos-app .eos-notification-dialog [role="button"],
3461
+ html.eos-app .MuiDialog-paper:has(#notifications-dialog-close) button,
3462
+ html.eos-app .MuiDialog-paper:has(#notifications-dialog-close) [role="button"] {
3463
+ pointer-events: auto !important;
3464
+ visibility: visible !important;
3465
+ }
3466
+
3467
+ /* v37: security text must never show mojibake-like fragments after runtime repair. */
3468
+ html.eos-app .eos-security-admin-only-field,
3469
+ html.eos-app .eos-settings-dialog {
3470
+ unicode-bidi: plaintext;
3471
+ }
@@ -31,7 +31,7 @@
31
31
  rel="stylesheet"
32
32
  href="css/leaflet.css"
33
33
  />
34
- <link rel="stylesheet" href="./css/eos-branding.css?v=35" />
34
+ <link rel="stylesheet" href="./css/eos-branding.css?v=37" />
35
35
  <link
36
36
  rel="manifest"
37
37
  href="manifest.json"
@@ -154,10 +154,9 @@
154
154
  <script type="module" crossorigin src="./assets/index-CQZugZ1z.js"></script>
155
155
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
156
156
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
157
- <script defer src="./js/eos-branding.js?v=35"></script>
158
- <script defer src="./js/eos-security-ui.js?v=35"></script>
159
- <script defer src="./js/eos-hard-logout.js?v=35"></script>
160
- <script defer src="./js/eos-assistant.js?v=35"></script>
157
+ <script defer src="./js/eos-branding.js?v=37"></script>
158
+ <script defer src="./js/eos-security-ui.js?v=37"></script>
159
+ <script defer src="./js/eos-assistant.js?v=37"></script>
161
160
  </head>
162
161
  <body>
163
162
  <noscript>You need to enable JavaScript to run this app.</noscript>
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_UI_VERSION = 'v35-login-config-fix';
4
+ window.NEXOWATT_EOS_UI_VERSION = 'v37-notification-backitup-security-text-fix';
5
5
 
6
6
  const BRAND = 'NexoWatt EOS';
7
7
  const EOS_MEANING = 'Energy Operation System';
@@ -48,6 +48,46 @@
48
48
  'ß': 'ß', 'Ä': 'Ä', 'Ö': 'Ö', 'Ü': 'Ü', 'ä': 'ä', 'ö': 'ö', 'ü': 'ü'
49
49
  }));
50
50
 
51
+
52
+
53
+ const decodeMojibakeChunk = chunk => {
54
+ try {
55
+ const bytes = Uint8Array.from(Array.from(chunk, char => char.charCodeAt(0) & 0xff));
56
+ const decoded = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
57
+ return decoded && decoded !== chunk ? decoded : chunk;
58
+ } catch (_) {
59
+ return chunk;
60
+ }
61
+ };
62
+
63
+ const repairMojibake = value => {
64
+ let text = String(value || '');
65
+ // Repair common UTF-8-as-Latin1 fragments, including double-encoded strings
66
+ // such as dürfen, Löschen, geschützt.
67
+ for (let i = 0; i < 3 && /(?:Ã.|Â.|â.|�)/.test(text); i += 1) {
68
+ const repaired = text.replace(/[\u00C2-\u00F4][\u0080-\u00BF\u00A0-\u00BF]+/g, decodeMojibakeChunk);
69
+ if (repaired === text) break;
70
+ text = repaired;
71
+ }
72
+ const hardMap = new Map(Object.entries({
73
+ 'dürfen': 'dürfen', 'dürfen': 'dürfen', 'Dürfen': 'Dürfen', 'Dürfen': 'Dürfen',
74
+ 'für': 'für', 'für': 'für', 'Für': 'Für', 'Für': 'Für',
75
+ 'können': 'können', 'können': 'können', 'Können': 'Können', 'Können': 'Können',
76
+ 'möglich': 'möglich', 'möglich': 'möglich', 'Möglich': 'Möglich', 'Möglich': 'Möglich',
77
+ 'Löschen': 'Löschen', 'Löschen': 'Löschen', 'löschen': 'löschen', 'löschen': 'löschen',
78
+ 'schützen': 'schützen', 'schützen': 'schützen', 'Schützen': 'Schützen', 'Schützen': 'Schützen',
79
+ 'Geschützte': 'Geschützte', 'Geschützte': 'Geschützte', 'geschützte': 'geschützte', 'geschützte': 'geschützte',
80
+ 'ausgewählte': 'ausgewählte', 'ausgewählte': 'ausgewählte', 'Ausgewählte': 'Ausgewählte', 'Ausgewählte': 'Ausgewählte',
81
+ 'ändern': 'ändern', 'ändern': 'ändern', 'Über': 'Über', 'Über': 'Über', 'über': 'über', 'über': 'über',
82
+ 'Gerät': 'Gerät', 'Gerät': 'Gerät', 'Geräte': 'Geräte', 'Geräte': 'Geräte',
83
+ 'schließen': 'schließen', 'schließen': 'schließen', 'ß': 'ß', 'ß': 'ß',
84
+ 'ä': 'ä', 'ä': 'ä', 'ö': 'ö', 'ö': 'ö', 'ü': 'ü', 'ü': 'ü',
85
+ 'Ä': 'Ä', 'Ä': 'Ä', 'Ö': 'Ö', 'Ö': 'Ö', 'Ü': 'Ü', 'Ü': 'Ü', ' ': ' ', 'Â': ''
86
+ }));
87
+ for (const [from, to] of hardMap) if (text.includes(from)) text = text.split(from).join(to);
88
+ return text;
89
+ };
90
+
51
91
  const EXACT_LABELS = new Map(Object.entries({
52
92
  'Admin': BRAND,
53
93
  'NEXOWATT': 'NEXOWATT EOS',
@@ -104,11 +144,13 @@
104
144
 
105
145
  const replaceBrand = value => {
106
146
  if (!value || typeof value !== 'string') return value;
107
- let next = value;
147
+ let next = repairMojibake(value);
108
148
  for (const [from, to] of MOJIBAKE_REPLACEMENTS) {
109
149
  if (next.includes(from)) next = next.split(from).join(to);
110
150
  }
151
+ next = repairMojibake(next);
111
152
  for (const [pattern, replacement] of TEXT_REPLACEMENTS) next = next.replace(pattern, replacement);
153
+ next = repairMojibake(next);
112
154
  const compact = next.trim();
113
155
  if (EXACT_LABELS.has(compact)) next = next.replace(compact, EXACT_LABELS.get(compact));
114
156
  return next;
@@ -145,6 +187,32 @@
145
187
  while ((node = walker.nextNode())) patchTextNode(node);
146
188
  });
147
189
 
190
+
191
+ const patchMojibakeTextNode = node => {
192
+ if (!node || node.nodeType !== Node.TEXT_NODE || !node.nodeValue) return;
193
+ if (skipElement(node.parentElement)) return;
194
+ const before = node.nodeValue;
195
+ const after = repairMojibake(before);
196
+ if (after !== before) node.nodeValue = after;
197
+ };
198
+
199
+ const patchMojibakeTextNodes = root => safe(() => {
200
+ if (!root) return;
201
+ if (root.nodeType === Node.TEXT_NODE) {
202
+ patchMojibakeTextNode(root);
203
+ return;
204
+ }
205
+ if (root.nodeType !== Node.ELEMENT_NODE && root.nodeType !== Node.DOCUMENT_NODE) return;
206
+ if (skipElement(root)) return;
207
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
208
+ acceptNode(node) {
209
+ return skipElement(node.parentElement) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
210
+ }
211
+ });
212
+ let node;
213
+ while ((node = walker.nextNode())) patchMojibakeTextNode(node);
214
+ });
215
+
148
216
  const patchImage = img => {
149
217
  const src = img.getAttribute('src') || '';
150
218
  const alt = img.getAttribute('alt') || '';
@@ -335,6 +403,26 @@
335
403
  });
336
404
  };
337
405
 
406
+
407
+ const releaseNotificationControls = () => safe(() => {
408
+ // v37: notification/snackbar close buttons belong to the native Admin UI.
409
+ // They must never be disabled or covered by EOS security/layout layers.
410
+ document.querySelectorAll('.MuiSnackbar-root, .MuiAlert-root, .MuiSnackbarContent-root, [role="alert"], .Toastify__toast, .notistack-Snackbar').forEach(box => {
411
+ box.classList.add('eos-notification-safe');
412
+ box.style.pointerEvents = 'auto';
413
+ box.querySelectorAll('button, [role="button"], a, .MuiIconButton-root, svg').forEach(control => {
414
+ control.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete');
415
+ control.removeAttribute('disabled');
416
+ control.removeAttribute('aria-disabled');
417
+ if ('disabled' in control) control.disabled = false;
418
+ control.style.pointerEvents = 'auto';
419
+ control.style.display = '';
420
+ control.style.visibility = '';
421
+ control.style.opacity = '';
422
+ });
423
+ });
424
+ });
425
+
338
426
  const protectDeleteDialogs = () => {
339
427
  if (isAdminUser() || state.securityPolicy.restrictProtectedAdapterControls === false) return;
340
428
  const protectedAdapters = state.securityPolicy.protectedAdapters || [];
@@ -398,6 +486,10 @@
398
486
  const applySecurityUiGuard = () => safe(() => {
399
487
  const policy = state.securityPolicy;
400
488
  applySecurityClasses();
489
+ releaseNotificationControls();
490
+ // Do not apply EOS security decoration inside native adapter configuration pages.
491
+ // Adapter UIs must remain 100% functional; backend/role checks still protect EOS actions.
492
+ if (isAdapterConfigSurface()) return;
401
493
  if (!policy.loaded) return;
402
494
  if (isAdminUser()) {
403
495
  document.querySelectorAll('.eos-hidden-legacy-admin, .eos-protected-adapter-row').forEach(el => {
@@ -470,16 +562,33 @@
470
562
  };
471
563
 
472
564
 
565
+ const isAdapterConfigSurface = () => document.documentElement.classList.contains('eos-adapter-config-surface');
566
+
473
567
  const markAdapterConfigSurface = () => safe(() => {
474
- const text = textOfElement(document.querySelector('#app-paper') || document.body).slice(0, 1500);
475
- const isConfig = /Instanzeinstellungen:|Instance settings:|Gerät hinzufügen|Gerät bearbeiten|Adapterkonfiguration|json exportieren/i.test(text)
476
- || /tab-instances/i.test(window.location.hash || '');
568
+ const app = document.querySelector('#app-paper') || document.body;
569
+ const text = textOfElement(app).slice(0, 3500);
570
+ const isConfig = /Instanzeinstellungen:|Instance settings:|Gerät hinzufügen|Gerät bearbeiten|Geräteliste|Adapterkonfiguration|json exportieren|json importieren|Speichern und schließen|Save and close/i.test(text);
477
571
  document.documentElement.classList.toggle('eos-adapter-config-surface', !!isConfig);
478
572
  if (!isConfig) return;
479
- // Do not let EOS decorative layers influence adapter specific buttons/dialogs.
480
- document.querySelectorAll('#app-paper button, #app-paper [role="button"], #app-paper a, #app-paper input, #app-paper select, #app-paper textarea').forEach(control => {
573
+
574
+ // Native adapter configuration UIs are owned by the adapter. EOS must never
575
+ // block, rewrite or intercept their controls. This is critical for custom
576
+ // React/HTML configuration pages such as nexowatt-devices.
577
+ document.querySelectorAll('#app-paper button, #app-paper [role="button"], #app-paper a, #app-paper input, #app-paper select, #app-paper textarea, #app-paper [tabindex]').forEach(control => {
481
578
  if (control.closest('.eos-assist-root, #eos-assist-root, .eos-standalone-nav-toggle')) return;
482
579
  control.style.pointerEvents = 'auto';
580
+ control.removeAttribute('aria-disabled');
581
+ });
582
+ document.querySelectorAll('#app-paper .eos-protected-delete-control, #app-paper .eos-security-hidden-delete, #app-paper .eos-protected-adapter-row').forEach(el => {
583
+ el.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete', 'eos-protected-adapter-row');
584
+ el.removeAttribute('aria-disabled');
585
+ if (el.style) {
586
+ el.style.pointerEvents = '';
587
+ el.style.display = '';
588
+ el.style.visibility = '';
589
+ el.style.opacity = '';
590
+ }
591
+ if ('disabled' in el && !el.dataset.eosOriginalDisabled) el.disabled = false;
483
592
  });
484
593
  });
485
594
 
@@ -592,13 +701,10 @@
592
701
  }
593
702
 
594
703
  const installHardLogoutWatchdog = () => {
595
- if (hardLogoutInstalled) return;
704
+ // v36: disabled. Upstream ioBroker Admin session handling is used again so
705
+ // the configured admin TTL is respected and native adapter config pages are
706
+ // not interrupted by duplicate EOS timers.
596
707
  hardLogoutInstalled = true;
597
- window.addEventListener('focus', () => checkHardLogoutSession(), { passive: true });
598
- document.addEventListener('visibilitychange', () => {
599
- if (!document.hidden) checkHardLogoutSession();
600
- }, { passive: true });
601
- scheduleHardLogoutCheck(2500);
602
708
  };
603
709
 
604
710
  const ensureBrandBadge = toolbar => {
@@ -730,6 +836,7 @@
730
836
  }
731
837
  patchDrawerHeader(document.querySelector('.MuiDrawer-paper'));
732
838
  hideNativeLogoutNav();
839
+ patchNotifications();
733
840
  removeLogoutButton();
734
841
  });
735
842
 
@@ -850,6 +957,26 @@
850
957
  });
851
958
 
852
959
 
960
+
961
+
962
+ const ensureNotificationDialogClasses = () => safe(() => {
963
+ document.querySelectorAll('.MuiDialog-root, .MuiModal-root, [role="presentation"]').forEach(root => {
964
+ const paper = root.querySelector?.('.MuiDialog-paper, [role="dialog"]');
965
+ if (!paper) return;
966
+ const txt = normalize(paper.textContent || '');
967
+ if (!/(benachrichtigungen|notifications|acknowledge|bestätigen|schließen|close)/i.test(txt) && !paper.querySelector('#notifications-dialog-close')) return;
968
+ root.classList.add('eos-notification-dialog-root');
969
+ paper.classList.add('eos-notification-dialog');
970
+ paper.querySelectorAll('button, [role="button"], a, .MuiButtonBase-root, .MuiIconButton-root').forEach(control => {
971
+ control.style.pointerEvents = 'auto';
972
+ control.style.userSelect = 'auto';
973
+ if (control.getAttribute('aria-disabled') === 'true' && /schließen|close/i.test(control.textContent || control.getAttribute('aria-label') || control.getAttribute('title') || '')) {
974
+ control.removeAttribute('aria-disabled');
975
+ }
976
+ });
977
+ });
978
+ });
979
+
853
980
  const ensureSettingsDialogClasses = () => safe(() => {
854
981
  const dialogs = Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]'));
855
982
  dialogs.forEach(dialog => {
@@ -903,6 +1030,28 @@
903
1030
  });
904
1031
 
905
1032
 
1033
+
1034
+
1035
+ const patchNotifications = () => safe(() => {
1036
+ const selectors = [
1037
+ '.MuiSnackbar-root', '.SnackbarItem-root', '.SnackbarItem-wrappedRoot', '.notistack-Snackbar',
1038
+ '.Toastify__toast-container', '.Toastify__toast', '.MuiAlert-root', '[role="alert"]'
1039
+ ];
1040
+ document.querySelectorAll(selectors.join(',')).forEach(node => {
1041
+ node.classList.add('eos-notification-surface');
1042
+ if (node.style) {
1043
+ node.style.pointerEvents = 'auto';
1044
+ if (!node.closest('.MuiDialog-root')) node.style.zIndex = '5200';
1045
+ }
1046
+ node.querySelectorAll('button,[role="button"],a').forEach(control => {
1047
+ control.classList.add('eos-notification-action');
1048
+ control.style.pointerEvents = 'auto';
1049
+ control.style.visibility = 'visible';
1050
+ control.style.opacity = '1';
1051
+ });
1052
+ });
1053
+ });
1054
+
906
1055
  const applyNavCompactPreference = () => safe(() => {
907
1056
  const compact = localStorage.getItem('nexowatt:eosNavCompact') === '1';
908
1057
  document.documentElement.classList.toggle('eos-nav-compact', compact);
@@ -1009,6 +1158,11 @@
1009
1158
  document.querySelectorAll('.eos-assist-launcher,.eos-assist-panel:not(#eos-assist-root .eos-assist-panel)').forEach(el => el.remove());
1010
1159
  return;
1011
1160
  }
1161
+ if (isAdapterConfigSurface()) {
1162
+ const existing = document.getElementById('eos-assist-root');
1163
+ if (existing) existing.classList.add('eos-assist-config-hidden');
1164
+ return;
1165
+ }
1012
1166
 
1013
1167
  let root = document.getElementById('eos-assist-root');
1014
1168
  if (!root) {
@@ -1042,6 +1196,7 @@
1042
1196
  document.body.appendChild(root);
1043
1197
  }
1044
1198
 
1199
+ root.classList.remove('eos-assist-config-hidden');
1045
1200
  const ctx = assistContext();
1046
1201
  const button = root.querySelector('.eos-assist-button');
1047
1202
  const input = root.querySelector('.eos-assist-input');
@@ -1200,11 +1355,27 @@
1200
1355
  ensureRightsHelper();
1201
1356
  ensurePermissionPresets();
1202
1357
  ensureSettingsDialogClasses();
1358
+ ensureNotificationDialogClasses();
1203
1359
  hideNativeLogoutNav();
1204
1360
  hideOfficialNexoWattRepoWarning();
1361
+ patchNotifications();
1205
1362
  applySecurityUiGuard();
1206
- patchTextNodes(document.body || document.documentElement);
1207
- patchAttributes(document.body || document.documentElement);
1363
+ if (isAdapterConfigSurface()) {
1364
+ // Adapter-owned configuration pages must not be rebranded or structurally patched.
1365
+ // We still repair broken UTF-8/mojibake text because jsonConfig labels can be
1366
+ // rendered through different legacy paths. This is text-only and does not touch
1367
+ // adapter controls, React state, events or attributes.
1368
+ patchMojibakeTextNodes(document.getElementById('app-paper'));
1369
+ ['.MuiAppBar-root', '.MuiDrawer-paper', 'nav', '.eos-brand-badge', '.eos-top-toolbar'].forEach(selector => {
1370
+ document.querySelectorAll(selector).forEach(scope => {
1371
+ patchTextNodes(scope);
1372
+ patchAttributes(scope);
1373
+ });
1374
+ });
1375
+ } else {
1376
+ patchTextNodes(document.body || document.documentElement);
1377
+ patchAttributes(document.body || document.documentElement);
1378
+ }
1208
1379
  };
1209
1380
 
1210
1381
  const scopePatch = () => {
@@ -1223,11 +1394,17 @@
1223
1394
  ensureRightsHelper();
1224
1395
  ensurePermissionPresets();
1225
1396
  ensureSettingsDialogClasses();
1397
+ ensureNotificationDialogClasses();
1226
1398
  hideNativeLogoutNav();
1227
1399
  hideOfficialNexoWattRepoWarning();
1400
+ patchNotifications();
1228
1401
  applySecurityUiGuard();
1229
1402
  for (const scope of scopes.slice(0, 80)) {
1230
1403
  if (!scope || !scope.isConnected) continue;
1404
+ if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper'))) {
1405
+ patchMojibakeTextNodes(scope);
1406
+ continue;
1407
+ }
1231
1408
  patchTextNodes(scope);
1232
1409
  patchAttributes(scope);
1233
1410
  }
@@ -1258,14 +1435,17 @@
1258
1435
  const observer = new MutationObserver(mutations => {
1259
1436
  for (const mutation of mutations) {
1260
1437
  if (mutation.type === 'characterData') {
1261
- patchTextNode(mutation.target);
1438
+ if (isAdapterConfigSurface() && mutation.target?.parentElement?.closest?.('#app-paper')) patchMojibakeTextNode(mutation.target);
1439
+ else patchTextNode(mutation.target);
1262
1440
  continue;
1263
1441
  }
1264
1442
  if (mutation.type !== 'childList') continue;
1265
1443
  mutation.addedNodes.forEach(node => {
1266
1444
  if (!node) return;
1267
- if (node.nodeType === Node.TEXT_NODE) patchTextNode(node);
1268
- else if (node.nodeType === Node.ELEMENT_NODE) state.pendingScopes.add(node);
1445
+ if (node.nodeType === Node.TEXT_NODE) {
1446
+ if (isAdapterConfigSurface() && node.parentElement?.closest?.('#app-paper')) patchMojibakeTextNode(node);
1447
+ else patchTextNode(node);
1448
+ } else if (node.nodeType === Node.ELEMENT_NODE) state.pendingScopes.add(node);
1269
1449
  });
1270
1450
  }
1271
1451
  if (state.pendingScopes.size) scheduleScopePatch();
@@ -1294,3 +1474,21 @@
1294
1474
  window.addEventListener('load', () => scheduleFullPatch(0), { once: true });
1295
1475
  window.addEventListener('hashchange', () => scheduleFullPatch(0));
1296
1476
  })();
1477
+
1478
+
1479
+ // v37 eos notification close compatibility: never let EOS overlays block native notification dialogs.
1480
+ (() => {
1481
+ const normalize = value => String(value || '').replace(/\s+/g, ' ').trim();
1482
+ document.addEventListener('click', event => {
1483
+ const target = event.target?.closest?.('button, [role="button"], a, .MuiButtonBase-root, .MuiIconButton-root');
1484
+ if (!target) return;
1485
+ const dialog = target.closest?.('.eos-notification-dialog, .MuiDialog-paper, [role="dialog"]');
1486
+ if (!dialog || !/benachrichtigungen|notifications|acknowledge|bestätigen|schließen|close/i.test(dialog.textContent || '')) return;
1487
+ const label = normalize(`${target.textContent || ''} ${target.getAttribute?.('aria-label') || ''} ${target.getAttribute?.('title') || ''}`);
1488
+ if (/schließen|close|bestätigen|acknowledge/i.test(label)) {
1489
+ target.style.pointerEvents = 'auto';
1490
+ // Do not prevent React handlers; only stop EOS-specific bubbling side effects.
1491
+ event.stopPropagation();
1492
+ }
1493
+ }, true);
1494
+ })();