iobroker.eos-admin 7.9.36 → 7.9.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/i18n/de.json +7 -3
- package/admin/i18n/en.json +5 -3
- package/admin/i18n/es.json +5 -3
- package/admin/i18n/fr.json +5 -3
- package/admin/i18n/it.json +5 -3
- package/admin/i18n/nl.json +5 -3
- package/admin/i18n/pl.json +5 -3
- package/admin/i18n/pt.json +5 -3
- package/admin/i18n/ru.json +5 -3
- package/admin/i18n/uk.json +5 -3
- package/admin/i18n/zh-cn.json +5 -3
- package/admin/jsonConfig.json5 +8 -7
- package/adminWww/assets/bootstrap-COulQZax.js +1 -1
- package/adminWww/css/eos-branding.css +82 -1
- package/adminWww/index.html +4 -4
- package/adminWww/js/eos-branding.js +169 -6
- package/adminWww/js/eos-runtime-fixes.js +144 -0
- package/adminWww/js/eos-security-ui.js +40 -7
- package/build/i18n/de.json +2 -2
- package/build/i18n/en.json +2 -2
- package/build/main.js +13 -3
- package/docs/NEXOWATT_EOS_UI_V37_STABILITY_DE.md +13 -0
- package/io-package.json +14 -9
- package/package.json +2 -2
- package/tools/nexowatt-validate-package.cjs +3 -0
|
@@ -3321,7 +3321,7 @@ html.eos-app #eos-assist-root {
|
|
|
3321
3321
|
}
|
|
3322
3322
|
|
|
3323
3323
|
|
|
3324
|
-
/* === NexoWatt EOS
|
|
3324
|
+
/* === NexoWatt EOS v38: native adapter configuration safe mode =============
|
|
3325
3325
|
Custom adapter configuration pages (React/HTML/jsonConfig) must be fully
|
|
3326
3326
|
controlled by the adapter itself. EOS shell decoration is disabled inside the
|
|
3327
3327
|
content area so buttons such as "Gerät hinzufügen" and "Gerät bearbeiten" stay
|
|
@@ -3371,3 +3371,84 @@ html.eos-app.eos-adapter-config-surface #app-paper .eos-protected-adapter-row {
|
|
|
3371
3371
|
filter: none !important;
|
|
3372
3372
|
pointer-events: auto !important;
|
|
3373
3373
|
}
|
|
3374
|
+
|
|
3375
|
+
|
|
3376
|
+
/* === NexoWatt EOS v38: native popup/dialog compatibility ==================
|
|
3377
|
+
EOS decoration must never block native Admin dialogs, adapter install search,
|
|
3378
|
+
autocomplete poppers, menus or adapter-owned configuration dialogs. */
|
|
3379
|
+
html.eos-app .MuiDialog-root,
|
|
3380
|
+
html.eos-app .MuiModal-root,
|
|
3381
|
+
html.eos-app .MuiPopover-root,
|
|
3382
|
+
html.eos-app .MuiPopper-root,
|
|
3383
|
+
html.eos-app .MuiMenu-root,
|
|
3384
|
+
html.eos-app .MuiAutocomplete-popper,
|
|
3385
|
+
html.eos-app [role="dialog"],
|
|
3386
|
+
html.eos-app [role="listbox"],
|
|
3387
|
+
html.eos-app [role="menu"] {
|
|
3388
|
+
pointer-events: auto !important;
|
|
3389
|
+
}
|
|
3390
|
+
html.eos-app .MuiDialog-paper,
|
|
3391
|
+
html.eos-app .MuiPaper-root[role="dialog"],
|
|
3392
|
+
html.eos-app .MuiPopover-paper,
|
|
3393
|
+
html.eos-app .MuiMenu-paper,
|
|
3394
|
+
html.eos-app .MuiAutocomplete-paper,
|
|
3395
|
+
html.eos-app .MuiAutocomplete-listbox,
|
|
3396
|
+
html.eos-app .MuiList-root[role="listbox"] {
|
|
3397
|
+
pointer-events: auto !important;
|
|
3398
|
+
user-select: auto !important;
|
|
3399
|
+
}
|
|
3400
|
+
html.eos-app .MuiAutocomplete-popper,
|
|
3401
|
+
html.eos-app .MuiPopper-root,
|
|
3402
|
+
html.eos-app .MuiPopover-root,
|
|
3403
|
+
html.eos-app .MuiMenu-root {
|
|
3404
|
+
z-index: 6500 !important;
|
|
3405
|
+
}
|
|
3406
|
+
html.eos-app .MuiDialog-root button,
|
|
3407
|
+
html.eos-app .MuiDialog-root [role="button"],
|
|
3408
|
+
html.eos-app .MuiDialog-root a,
|
|
3409
|
+
html.eos-app .MuiModal-root button,
|
|
3410
|
+
html.eos-app .MuiModal-root [role="button"],
|
|
3411
|
+
html.eos-app .MuiModal-root a,
|
|
3412
|
+
html.eos-app .MuiPopover-root button,
|
|
3413
|
+
html.eos-app .MuiPopover-root [role="button"],
|
|
3414
|
+
html.eos-app .MuiPopover-root a,
|
|
3415
|
+
html.eos-app .MuiPopper-root button,
|
|
3416
|
+
html.eos-app .MuiPopper-root [role="button"],
|
|
3417
|
+
html.eos-app .MuiPopper-root a,
|
|
3418
|
+
html.eos-app .MuiMenu-root button,
|
|
3419
|
+
html.eos-app .MuiMenu-root [role="button"],
|
|
3420
|
+
html.eos-app .MuiMenu-root a,
|
|
3421
|
+
html.eos-app [role="listbox"] [role="option"],
|
|
3422
|
+
html.eos-app .MuiAutocomplete-option {
|
|
3423
|
+
pointer-events: auto !important;
|
|
3424
|
+
visibility: visible !important;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
/* v38: snackbars/toasts are clickable, but generic dialogs are not modified. */
|
|
3428
|
+
html.eos-app .MuiSnackbar-root,
|
|
3429
|
+
html.eos-app .SnackbarItem-root,
|
|
3430
|
+
html.eos-app .SnackbarItem-wrappedRoot,
|
|
3431
|
+
html.eos-app .notistack-Snackbar,
|
|
3432
|
+
html.eos-app .Toastify__toast-container,
|
|
3433
|
+
html.eos-app .Toastify__toast {
|
|
3434
|
+
pointer-events: auto !important;
|
|
3435
|
+
z-index: 5200 !important;
|
|
3436
|
+
}
|
|
3437
|
+
html.eos-app .MuiSnackbar-root button,
|
|
3438
|
+
html.eos-app .SnackbarItem-root button,
|
|
3439
|
+
html.eos-app .notistack-Snackbar button,
|
|
3440
|
+
html.eos-app .Toastify__toast button {
|
|
3441
|
+
pointer-events: auto !important;
|
|
3442
|
+
visibility: visible !important;
|
|
3443
|
+
opacity: 1 !important;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
/* v38: never allow decorative EOS layers to sit above native modals/popups. */
|
|
3447
|
+
html.eos-app .eos-top-toolbar::before,
|
|
3448
|
+
html.eos-app .eos-top-toolbar::after,
|
|
3449
|
+
html.eos-app .eos-brand-badge::before,
|
|
3450
|
+
html.eos-app .eos-brand-badge::after,
|
|
3451
|
+
html.eos-app .eos-panel::before,
|
|
3452
|
+
html.eos-app .eos-panel::after {
|
|
3453
|
+
pointer-events: none !important;
|
|
3454
|
+
}
|
package/adminWww/index.html
CHANGED
|
@@ -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=
|
|
34
|
+
<link rel="stylesheet" href="./css/eos-branding.css?v=38" />
|
|
35
35
|
<link
|
|
36
36
|
rel="manifest"
|
|
37
37
|
href="manifest.json"
|
|
@@ -154,9 +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=
|
|
158
|
-
<script defer src="./js/eos-security-ui.js?v=
|
|
159
|
-
<script defer src="./js/eos-assistant.js?v=
|
|
157
|
+
<script defer src="./js/eos-branding.js?v=38"></script>
|
|
158
|
+
<script defer src="./js/eos-security-ui.js?v=38"></script>
|
|
159
|
+
<script defer src="./js/eos-assistant.js?v=38"></script>
|
|
160
160
|
</head>
|
|
161
161
|
<body>
|
|
162
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 = '
|
|
4
|
+
window.NEXOWATT_EOS_UI_VERSION = 'v38-popup-config-stability-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,52 @@
|
|
|
335
403
|
});
|
|
336
404
|
};
|
|
337
405
|
|
|
406
|
+
|
|
407
|
+
const releaseNotificationControls = () => safe(() => {
|
|
408
|
+
// v38: Keep native snackbar/toast close buttons clickable without touching dialogs,
|
|
409
|
+
// poppers, menus or adapter-owned configuration surfaces. Earlier broad selectors
|
|
410
|
+
// changed generic dialogs and broke React click handlers.
|
|
411
|
+
const roots = [
|
|
412
|
+
'.MuiSnackbar-root',
|
|
413
|
+
'.SnackbarItem-root',
|
|
414
|
+
'.SnackbarItem-wrappedRoot',
|
|
415
|
+
'.notistack-Snackbar',
|
|
416
|
+
'.Toastify__toast-container',
|
|
417
|
+
'.Toastify__toast'
|
|
418
|
+
].join(',');
|
|
419
|
+
document.querySelectorAll(roots).forEach(box => {
|
|
420
|
+
if (box.closest('.MuiDialog-root,.MuiModal-root,.MuiPopover-root,.MuiPopper-root,.MuiMenu-root,[role="dialog"]')) return;
|
|
421
|
+
box.classList.add('eos-notification-safe');
|
|
422
|
+
box.style.pointerEvents = 'auto';
|
|
423
|
+
box.querySelectorAll('button, [role="button"], a, .MuiIconButton-root').forEach(control => {
|
|
424
|
+
control.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete');
|
|
425
|
+
// Do not remove disabled states globally. Only restore pointer handling.
|
|
426
|
+
control.style.pointerEvents = 'auto';
|
|
427
|
+
control.style.visibility = '';
|
|
428
|
+
control.style.opacity = '';
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const ensurePopupCompatibility = () => safe(() => {
|
|
434
|
+
// v38: All native Admin dialogs, adapter install/autocomplete poppers, menus and
|
|
435
|
+
// adapter-owned modals must stay above EOS decorative layers and keep their React
|
|
436
|
+
// event handlers untouched.
|
|
437
|
+
const selectors = [
|
|
438
|
+
'.MuiDialog-root', '.MuiModal-root', '.MuiPopover-root', '.MuiPopper-root',
|
|
439
|
+
'.MuiMenu-root', '.MuiAutocomplete-popper', '.MuiAutocomplete-listbox',
|
|
440
|
+
'[role="dialog"]', '[role="listbox"]', '[role="menu"]'
|
|
441
|
+
].join(',');
|
|
442
|
+
document.querySelectorAll(selectors).forEach(el => {
|
|
443
|
+
el.classList.add('eos-native-popup-safe');
|
|
444
|
+
if (el.style) {
|
|
445
|
+
el.style.pointerEvents = 'auto';
|
|
446
|
+
const isFloating = el.matches('.MuiPopover-root,.MuiPopper-root,.MuiMenu-root,.MuiAutocomplete-popper,[role="listbox"],[role="menu"]');
|
|
447
|
+
if (isFloating && !el.closest('.MuiDialog-paper')) el.style.zIndex = '6500';
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
338
452
|
const protectDeleteDialogs = () => {
|
|
339
453
|
if (isAdminUser() || state.securityPolicy.restrictProtectedAdapterControls === false) return;
|
|
340
454
|
const protectedAdapters = state.securityPolicy.protectedAdapters || [];
|
|
@@ -398,6 +512,7 @@
|
|
|
398
512
|
const applySecurityUiGuard = () => safe(() => {
|
|
399
513
|
const policy = state.securityPolicy;
|
|
400
514
|
applySecurityClasses();
|
|
515
|
+
releaseNotificationControls();
|
|
401
516
|
// Do not apply EOS security decoration inside native adapter configuration pages.
|
|
402
517
|
// Adapter UIs must remain 100% functional; backend/role checks still protect EOS actions.
|
|
403
518
|
if (isAdapterConfigSurface()) return;
|
|
@@ -747,6 +862,8 @@
|
|
|
747
862
|
}
|
|
748
863
|
patchDrawerHeader(document.querySelector('.MuiDrawer-paper'));
|
|
749
864
|
hideNativeLogoutNav();
|
|
865
|
+
releaseNotificationControls();
|
|
866
|
+
ensurePopupCompatibility();
|
|
750
867
|
removeLogoutButton();
|
|
751
868
|
});
|
|
752
869
|
|
|
@@ -867,6 +984,26 @@
|
|
|
867
984
|
});
|
|
868
985
|
|
|
869
986
|
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
const ensureNotificationDialogClasses = () => safe(() => {
|
|
990
|
+
document.querySelectorAll('.MuiDialog-root, .MuiModal-root, [role="presentation"]').forEach(root => {
|
|
991
|
+
const paper = root.querySelector?.('.MuiDialog-paper, [role="dialog"]');
|
|
992
|
+
if (!paper) return;
|
|
993
|
+
const txt = normalize(paper.textContent || '');
|
|
994
|
+
if (!/(benachrichtigungen|notifications|acknowledge|bestätigen|schließen|close)/i.test(txt) && !paper.querySelector('#notifications-dialog-close')) return;
|
|
995
|
+
root.classList.add('eos-notification-dialog-root');
|
|
996
|
+
paper.classList.add('eos-notification-dialog');
|
|
997
|
+
paper.querySelectorAll('button, [role="button"], a, .MuiButtonBase-root, .MuiIconButton-root').forEach(control => {
|
|
998
|
+
control.style.pointerEvents = 'auto';
|
|
999
|
+
control.style.userSelect = 'auto';
|
|
1000
|
+
if (control.getAttribute('aria-disabled') === 'true' && /schließen|close/i.test(control.textContent || control.getAttribute('aria-label') || control.getAttribute('title') || '')) {
|
|
1001
|
+
control.removeAttribute('aria-disabled');
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
|
|
870
1007
|
const ensureSettingsDialogClasses = () => safe(() => {
|
|
871
1008
|
const dialogs = Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]'));
|
|
872
1009
|
dialogs.forEach(dialog => {
|
|
@@ -920,6 +1057,15 @@
|
|
|
920
1057
|
});
|
|
921
1058
|
|
|
922
1059
|
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
const patchNotifications = () => safe(() => {
|
|
1063
|
+
// Kept for compatibility with older calls. v38 intentionally scopes this to
|
|
1064
|
+
// snackbar/toast surfaces only; no dialogs, popovers or adapter config controls.
|
|
1065
|
+
releaseNotificationControls();
|
|
1066
|
+
ensurePopupCompatibility();
|
|
1067
|
+
});
|
|
1068
|
+
|
|
923
1069
|
const applyNavCompactPreference = () => safe(() => {
|
|
924
1070
|
const compact = localStorage.getItem('nexowatt:eosNavCompact') === '1';
|
|
925
1071
|
document.documentElement.classList.toggle('eos-nav-compact', compact);
|
|
@@ -1223,10 +1369,18 @@
|
|
|
1223
1369
|
ensureRightsHelper();
|
|
1224
1370
|
ensurePermissionPresets();
|
|
1225
1371
|
ensureSettingsDialogClasses();
|
|
1372
|
+
ensurePopupCompatibility();
|
|
1226
1373
|
hideNativeLogoutNav();
|
|
1227
1374
|
hideOfficialNexoWattRepoWarning();
|
|
1375
|
+
releaseNotificationControls();
|
|
1376
|
+
ensurePopupCompatibility();
|
|
1228
1377
|
applySecurityUiGuard();
|
|
1229
1378
|
if (isAdapterConfigSurface()) {
|
|
1379
|
+
// Adapter-owned configuration pages must not be rebranded or structurally patched.
|
|
1380
|
+
// We still repair broken UTF-8/mojibake text because jsonConfig labels can be
|
|
1381
|
+
// rendered through different legacy paths. This is text-only and does not touch
|
|
1382
|
+
// adapter controls, React state, events or attributes.
|
|
1383
|
+
patchMojibakeTextNodes(document.getElementById('app-paper'));
|
|
1230
1384
|
['.MuiAppBar-root', '.MuiDrawer-paper', 'nav', '.eos-brand-badge', '.eos-top-toolbar'].forEach(selector => {
|
|
1231
1385
|
document.querySelectorAll(selector).forEach(scope => {
|
|
1232
1386
|
patchTextNodes(scope);
|
|
@@ -1255,12 +1409,18 @@
|
|
|
1255
1409
|
ensureRightsHelper();
|
|
1256
1410
|
ensurePermissionPresets();
|
|
1257
1411
|
ensureSettingsDialogClasses();
|
|
1412
|
+
ensurePopupCompatibility();
|
|
1258
1413
|
hideNativeLogoutNav();
|
|
1259
1414
|
hideOfficialNexoWattRepoWarning();
|
|
1415
|
+
releaseNotificationControls();
|
|
1416
|
+
ensurePopupCompatibility();
|
|
1260
1417
|
applySecurityUiGuard();
|
|
1261
1418
|
for (const scope of scopes.slice(0, 80)) {
|
|
1262
1419
|
if (!scope || !scope.isConnected) continue;
|
|
1263
|
-
if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper')))
|
|
1420
|
+
if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper'))) {
|
|
1421
|
+
patchMojibakeTextNodes(scope);
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1264
1424
|
patchTextNodes(scope);
|
|
1265
1425
|
patchAttributes(scope);
|
|
1266
1426
|
}
|
|
@@ -1291,14 +1451,17 @@
|
|
|
1291
1451
|
const observer = new MutationObserver(mutations => {
|
|
1292
1452
|
for (const mutation of mutations) {
|
|
1293
1453
|
if (mutation.type === 'characterData') {
|
|
1294
|
-
|
|
1454
|
+
if (isAdapterConfigSurface() && mutation.target?.parentElement?.closest?.('#app-paper')) patchMojibakeTextNode(mutation.target);
|
|
1455
|
+
else patchTextNode(mutation.target);
|
|
1295
1456
|
continue;
|
|
1296
1457
|
}
|
|
1297
1458
|
if (mutation.type !== 'childList') continue;
|
|
1298
1459
|
mutation.addedNodes.forEach(node => {
|
|
1299
1460
|
if (!node) return;
|
|
1300
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
1301
|
-
|
|
1461
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1462
|
+
if (isAdapterConfigSurface() && node.parentElement?.closest?.('#app-paper')) patchMojibakeTextNode(node);
|
|
1463
|
+
else patchTextNode(node);
|
|
1464
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) state.pendingScopes.add(node);
|
|
1302
1465
|
});
|
|
1303
1466
|
}
|
|
1304
1467
|
if (state.pendingScopes.size) scheduleScopePatch();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
window.NEXOWATT_EOS_RUNTIME_FIXES_VERSION = 'v38-popup-config-stability-fix';
|
|
5
|
+
|
|
6
|
+
const MOJIBAKE_MAP = new Map(Object.entries({
|
|
7
|
+
'dürfen': 'dürfen', 'Dürfen': 'Dürfen',
|
|
8
|
+
'für': 'für', 'Für': 'Für',
|
|
9
|
+
'müssen': 'müssen', 'Müssen': 'Müssen',
|
|
10
|
+
'können': 'können', 'Können': 'Können',
|
|
11
|
+
'möglich': 'möglich', 'Möglich': 'Möglich',
|
|
12
|
+
'Löschen': 'Löschen', 'löschen': 'löschen',
|
|
13
|
+
'schützen': 'schützen', 'Schützen': 'Schützen',
|
|
14
|
+
'Schützt': 'Schützt', 'schützt': 'schützt',
|
|
15
|
+
'Geschützte': 'Geschützte', 'geschützte': 'geschützte',
|
|
16
|
+
'Geschützter': 'Geschützter', 'geschützter': 'geschützter',
|
|
17
|
+
'geschützten': 'geschützten', 'Geschützten': 'Geschützten',
|
|
18
|
+
'ausgewählte': 'ausgewählte', 'Ausgewählte': 'Ausgewählte',
|
|
19
|
+
'ändern': 'ändern', 'Ändern': 'Ändern',
|
|
20
|
+
'über': 'über', 'Über': 'Über',
|
|
21
|
+
'Wähle': 'Wähle', 'wähle': 'wähle',
|
|
22
|
+
'öffnen': 'öffnen', 'Öffnen': 'Öffnen',
|
|
23
|
+
'schließen': 'schließen', 'Schließen': 'Schließen',
|
|
24
|
+
'Gerät': 'Gerät', 'gerät': 'gerät',
|
|
25
|
+
'Geräte': 'Geräte', 'geräte': 'geräte',
|
|
26
|
+
'Zugänge': 'Zugänge', 'zugänge': 'zugänge',
|
|
27
|
+
'Sicherheitsgründen': 'Sicherheitsgründen',
|
|
28
|
+
'Kompatibilitätsgründen': 'Kompatibilitätsgründen',
|
|
29
|
+
'Benachrichtigungen': 'Benachrichtigungen',
|
|
30
|
+
'ß': 'ß', 'Ä': 'Ä', 'Ö': 'Ö', 'Ü': 'Ü', 'ä': 'ä', 'ö': 'ö', 'ü': 'ü',
|
|
31
|
+
'–': '–', '—': '—', '„': '„', '“': '“', 'â€': '”', ' ': ' ', 'Â': ''
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const safe = fn => { try { return fn(); } catch { return undefined; } };
|
|
35
|
+
|
|
36
|
+
const repairText = value => {
|
|
37
|
+
let text = String(value || '');
|
|
38
|
+
if (!/[ÃÂâ]/.test(text)) return text;
|
|
39
|
+
for (const [from, to] of MOJIBAKE_MAP) {
|
|
40
|
+
if (text.includes(from)) text = text.split(from).join(to);
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const skip = el => {
|
|
46
|
+
const tag = el?.tagName;
|
|
47
|
+
return tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA' || tag === 'CODE' || tag === 'PRE';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const repairSecurityText = root => safe(() => {
|
|
51
|
+
const base = root && root.nodeType ? root : document.body || document.documentElement;
|
|
52
|
+
if (!base) return;
|
|
53
|
+
const walker = document.createTreeWalker(base, NodeFilter.SHOW_TEXT, {
|
|
54
|
+
acceptNode(node) {
|
|
55
|
+
return skip(node.parentElement) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
let node;
|
|
59
|
+
while ((node = walker.nextNode())) {
|
|
60
|
+
const before = node.nodeValue || '';
|
|
61
|
+
const after = repairText(before);
|
|
62
|
+
if (after !== before) node.nodeValue = after;
|
|
63
|
+
}
|
|
64
|
+
const attrSelector = '[title],[aria-label],[placeholder],[value]';
|
|
65
|
+
const elements = base.nodeType === Node.ELEMENT_NODE && base.matches?.(attrSelector)
|
|
66
|
+
? [base]
|
|
67
|
+
: Array.from(base.querySelectorAll?.(attrSelector) || []);
|
|
68
|
+
elements.forEach(el => {
|
|
69
|
+
if (skip(el)) return;
|
|
70
|
+
['title', 'aria-label', 'placeholder', 'value'].forEach(attr => {
|
|
71
|
+
if (!el.hasAttribute?.(attr)) return;
|
|
72
|
+
const before = el.getAttribute(attr) || '';
|
|
73
|
+
const after = repairText(before);
|
|
74
|
+
if (after !== before) el.setAttribute(attr, after);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const isNotificationSurface = el => !!el?.closest?.([
|
|
80
|
+
'.MuiSnackbar-root',
|
|
81
|
+
'.SnackbarItem-root',
|
|
82
|
+
'.SnackbarItem-wrappedRoot',
|
|
83
|
+
'.notistack-Snackbar',
|
|
84
|
+
'.Toastify__toast',
|
|
85
|
+
'.eos-notification-safe'
|
|
86
|
+
].join(','));
|
|
87
|
+
|
|
88
|
+
const isCloseControl = el => {
|
|
89
|
+
const label = `${el?.textContent || ''} ${el?.getAttribute?.('aria-label') || ''} ${el?.getAttribute?.('title') || ''}`.toLowerCase();
|
|
90
|
+
return /close|schlie(?:ß|ss)en|dismiss|ausblenden|ok|verstanden|x$/.test(label)
|
|
91
|
+
|| !!el?.querySelector?.('svg[data-testid*="Close"], svg[data-testid*="Clear"], .material-icons');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const restoreCloseButton = button => safe(() => {
|
|
95
|
+
if (!button || !isCloseControl(button)) return;
|
|
96
|
+
button.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete', 'eos-hidden-logout', 'eos-native-logout-hidden');
|
|
97
|
+
button.removeAttribute('aria-disabled');
|
|
98
|
+
button.removeAttribute('data-eos-security-blocked');
|
|
99
|
+
if ('disabled' in button && button.disabled && !button.dataset.eosOriginalDisabled) button.disabled = false;
|
|
100
|
+
button.style.pointerEvents = 'auto';
|
|
101
|
+
button.style.visibility = 'visible';
|
|
102
|
+
button.style.opacity = '1';
|
|
103
|
+
if (button.style.display === 'none') button.style.display = '';
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const repairNotifications = root => safe(() => {
|
|
107
|
+
const base = root && root.nodeType ? root : document;
|
|
108
|
+
const surfaces = Array.from(base.querySelectorAll?.([
|
|
109
|
+
'.MuiSnackbar-root',
|
|
110
|
+
'.SnackbarItem-root',
|
|
111
|
+
'.SnackbarItem-wrappedRoot',
|
|
112
|
+
'.notistack-Snackbar',
|
|
113
|
+
'.Toastify__toast'
|
|
114
|
+
].join(',')) || []);
|
|
115
|
+
if (base.nodeType === Node.ELEMENT_NODE && isNotificationSurface(base)) surfaces.push(base);
|
|
116
|
+
surfaces.forEach(surface => {
|
|
117
|
+
surface.classList.add('eos-notification-safe');
|
|
118
|
+
surface.style.pointerEvents = 'auto';
|
|
119
|
+
surface.querySelectorAll('button,[role="button"],a,.MuiIconButton-root').forEach(restoreCloseButton);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const run = root => {
|
|
124
|
+
repairSecurityText(root);
|
|
125
|
+
repairNotifications(root);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const install = () => {
|
|
129
|
+
run(document.body || document.documentElement);
|
|
130
|
+
const observer = new MutationObserver(mutations => {
|
|
131
|
+
const roots = new Set();
|
|
132
|
+
for (const mutation of mutations) {
|
|
133
|
+
if (mutation.type === 'characterData') roots.add(mutation.target.parentElement || document.body);
|
|
134
|
+
mutation.addedNodes?.forEach(node => roots.add(node));
|
|
135
|
+
}
|
|
136
|
+
roots.forEach(root => run(root));
|
|
137
|
+
});
|
|
138
|
+
observer.observe(document.documentElement, { subtree: true, childList: true, characterData: true });
|
|
139
|
+
[250, 1000, 3000, 8000].forEach(ms => window.setTimeout(() => run(document.body || document.documentElement), ms));
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install, { once: true });
|
|
143
|
+
else install();
|
|
144
|
+
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v38-popup-safe-security-polish';
|
|
5
5
|
const LEGACY_ADMIN = 'admin';
|
|
6
6
|
const LEGACY_ADMIN_INSTANCE = 'admin.0';
|
|
7
7
|
const ASSET_BASE = (() => {
|
|
@@ -34,14 +34,24 @@
|
|
|
34
34
|
let text = String(value || '');
|
|
35
35
|
const map = new Map(Object.entries({
|
|
36
36
|
'dürfen': 'dürfen', 'Dürfen': 'Dürfen', 'für': 'für', 'Für': 'Für',
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
37
|
+
'müssen': 'müssen', 'Müssen': 'Müssen', 'können': 'können', 'Können': 'Können',
|
|
38
|
+
'möglich': 'möglich', 'Möglich': 'Möglich', 'Löschen': 'Löschen', 'löschen': 'löschen',
|
|
39
|
+
'schützen': 'schützen', 'Schützen': 'Schützen', 'Schützt': 'Schützt', 'schützt': 'schützt',
|
|
40
|
+
'Geschützte': 'Geschützte', 'geschützte': 'geschützte', 'Geschützter': 'Geschützter',
|
|
40
41
|
'ausgewählte': 'ausgewählte', 'Ausgewählte': 'Ausgewählte', 'ändern': 'ändern', 'Ändern': 'Ändern',
|
|
41
42
|
'über': 'über', 'Über': 'Über', 'Wähle': 'Wähle', 'wähle': 'wähle',
|
|
42
|
-
'
|
|
43
|
+
'öffnen': 'öffnen', 'Öffnen': 'Öffnen', 'schließen': 'schließen', 'Schließen': 'Schließen',
|
|
44
|
+
'Gerät': 'Gerät', 'Geräte': 'Geräte', 'Geräteliste': 'Geräteliste',
|
|
45
|
+
'ß': 'ß', 'Ä': 'Ä', 'Ö': 'Ö', 'Ü': 'Ü', 'ä': 'ä', 'ö': 'ö', 'ü': 'ü', 'Â': ''
|
|
43
46
|
}));
|
|
44
47
|
for (const [from, to] of map) if (text.includes(from)) text = text.split(from).join(to);
|
|
48
|
+
// Generic Latin1-as-UTF8 repair for labels injected by older bundles. Guard against false positives.
|
|
49
|
+
if (/[ÃÂ]/.test(text)) {
|
|
50
|
+
try {
|
|
51
|
+
const repaired = decodeURIComponent(escape(text));
|
|
52
|
+
if (/[äöüÄÖÜß]/.test(repaired) && !/[ÃÂ]/.test(repaired)) text = repaired;
|
|
53
|
+
} catch { /* keep mapped text */ }
|
|
54
|
+
}
|
|
45
55
|
return text;
|
|
46
56
|
};
|
|
47
57
|
const normalizeFlat = value => normalize(value).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
@@ -220,12 +230,14 @@
|
|
|
220
230
|
};
|
|
221
231
|
|
|
222
232
|
const applyPolicyToDom = () => {
|
|
223
|
-
|
|
233
|
+
// Text repair is safe and must also run on adapter config pages.
|
|
234
|
+
replaceTextNodes();
|
|
224
235
|
const admin = isAdminUser();
|
|
225
236
|
document.documentElement.classList.toggle('eos-security-admin-user', admin);
|
|
226
237
|
document.documentElement.classList.toggle('eos-security-non-admin-user', !admin);
|
|
227
238
|
document.documentElement.classList.toggle('eos-security-nonadmin', !admin);
|
|
228
|
-
|
|
239
|
+
releaseNotificationControls();
|
|
240
|
+
if (isAdapterConfigSurface()) return;
|
|
229
241
|
hideLegacyAdminPanels();
|
|
230
242
|
hideProtectedDeleteControls();
|
|
231
243
|
hideEosSecuritySettingsForNonAdmins();
|
|
@@ -233,6 +245,25 @@
|
|
|
233
245
|
|
|
234
246
|
const isAdapterConfigSurface = () => document.documentElement.classList.contains('eos-adapter-config-surface') || /Instanzeinstellungen:|Instance settings:|Geräteliste|Gerät hinzufügen|Gerät bearbeiten/i.test(document.body?.textContent || '');
|
|
235
247
|
|
|
248
|
+
const releaseNotificationControls = () => {
|
|
249
|
+
// v38: security UI must not modify generic dialogs or adapter popups.
|
|
250
|
+
// Only snackbar/toast surfaces are normalized for clickability.
|
|
251
|
+
const roots = [
|
|
252
|
+
'.MuiSnackbar-root', '.SnackbarItem-root', '.SnackbarItem-wrappedRoot',
|
|
253
|
+
'.notistack-Snackbar', '.Toastify__toast-container', '.Toastify__toast'
|
|
254
|
+
].join(',');
|
|
255
|
+
document.querySelectorAll(roots).forEach(box => {
|
|
256
|
+
if (box.closest('.MuiDialog-root,.MuiModal-root,.MuiPopover-root,.MuiPopper-root,.MuiMenu-root,[role="dialog"]')) return;
|
|
257
|
+
box.classList.add('eos-notification-safe');
|
|
258
|
+
box.style.pointerEvents = 'auto';
|
|
259
|
+
box.querySelectorAll('button, [role="button"], a, .MuiIconButton-root').forEach(control => {
|
|
260
|
+
control.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete');
|
|
261
|
+
control.style.pointerEvents = 'auto';
|
|
262
|
+
control.style.visibility = '';
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
236
267
|
const scheduleApply = () => {
|
|
237
268
|
if (state.scheduled) return;
|
|
238
269
|
state.scheduled = true;
|
|
@@ -271,6 +302,8 @@
|
|
|
271
302
|
document.addEventListener('click', event => {
|
|
272
303
|
const target = event.target?.closest?.('button,[role="button"],a,[role="menuitem"],.MuiMenuItem-root');
|
|
273
304
|
if (!target || isAdminUser() || isAdapterConfigSurface()) return;
|
|
305
|
+
// Native Admin dialogs, install/autocomplete poppers and adapter-owned menus must stay untouched.
|
|
306
|
+
if (target.closest?.('.MuiDialog-root,.MuiModal-root,.MuiPopover-root,.MuiPopper-root,.MuiMenu-root,.MuiAutocomplete-popper,[role="dialog"],[role="listbox"],[role="menu"]')) return;
|
|
274
307
|
const label = normalizeFlat(`${target.textContent || ''} ${target.getAttribute?.('title') || ''} ${target.getAttribute?.('aria-label') || ''}`);
|
|
275
308
|
if (/loschen|delete|remove|deinstall|uninstall/.test(label)) {
|
|
276
309
|
target.classList.add('eos-security-hidden-delete');
|
package/build/i18n/de.json
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"Password is too short (min 6 chars)": "Das Passwort ist zu kurz (mindestens 6 Zeichen)",
|
|
26
26
|
"Password repeat": "Passwort wiederholen",
|
|
27
27
|
"Password successfully changed for \"%s\"": "Passwort für „%s“ erfolgreich geändert",
|
|
28
|
-
"Schützt ausgewählte Adapter
|
|
28
|
+
"Schützt ausgewählte Adapter in der EOS Oberfläche vor kritischen Aktionen durch Installateur- und Endkundenrollen. Adaptereigene Funktionen und Updates bleiben möglich.": "Schützt ausgewählte Adapter für Installateur- und Endkundenbenutzer über Administratorrechte und EOS-Regeln. dontDelete und nondeletable bleiben false, damit Updates weiter funktionieren.",
|
|
29
29
|
"Set password": "Passwort festlegen",
|
|
30
|
-
"
|
|
30
|
+
"Blendet kritische Aktionen für Installateur- und Endkundenrollen aus. Harte ACL-Sperren werden nicht auf Runtime-Adapter gesetzt, damit BackItUp und adaptereigene Konfigurationen stabil bleiben.": "Setzt Administratorrechte auf die geschützte Adapterliste. Installateur- und Endkundenbenutzer können diese Adapter nicht stoppen, aktivieren oder löschen. Administratoren können sie weiterhin updaten und warten.",
|
|
31
31
|
"Sperr-Adresse des alten Admins": "Sperr-Adresse des alten Admins",
|
|
32
32
|
"Sperr-Port des alten Admins": "Sperr-Port des alten Admins",
|
|
33
33
|
"The password for user \"%s\" was successfully changed": "Das Passwort für den Benutzer „%s“ wurde erfolgreich geändert",
|