iobroker.eos-admin 7.9.36 → 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.
- 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 +99 -1
- package/adminWww/index.html +4 -4
- package/adminWww/js/eos-branding.js +171 -6
- package/adminWww/js/eos-runtime-fixes.js +144 -0
- package/adminWww/js/eos-security-ui.js +37 -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 +10 -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 v37: 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,101 @@ 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
|
+
/* === 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
|
+
}
|
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=37" />
|
|
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=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>
|
|
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 = '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,7 @@
|
|
|
398
486
|
const applySecurityUiGuard = () => safe(() => {
|
|
399
487
|
const policy = state.securityPolicy;
|
|
400
488
|
applySecurityClasses();
|
|
489
|
+
releaseNotificationControls();
|
|
401
490
|
// Do not apply EOS security decoration inside native adapter configuration pages.
|
|
402
491
|
// Adapter UIs must remain 100% functional; backend/role checks still protect EOS actions.
|
|
403
492
|
if (isAdapterConfigSurface()) return;
|
|
@@ -747,6 +836,7 @@
|
|
|
747
836
|
}
|
|
748
837
|
patchDrawerHeader(document.querySelector('.MuiDrawer-paper'));
|
|
749
838
|
hideNativeLogoutNav();
|
|
839
|
+
patchNotifications();
|
|
750
840
|
removeLogoutButton();
|
|
751
841
|
});
|
|
752
842
|
|
|
@@ -867,6 +957,26 @@
|
|
|
867
957
|
});
|
|
868
958
|
|
|
869
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
|
+
|
|
870
980
|
const ensureSettingsDialogClasses = () => safe(() => {
|
|
871
981
|
const dialogs = Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]'));
|
|
872
982
|
dialogs.forEach(dialog => {
|
|
@@ -920,6 +1030,28 @@
|
|
|
920
1030
|
});
|
|
921
1031
|
|
|
922
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
|
+
|
|
923
1055
|
const applyNavCompactPreference = () => safe(() => {
|
|
924
1056
|
const compact = localStorage.getItem('nexowatt:eosNavCompact') === '1';
|
|
925
1057
|
document.documentElement.classList.toggle('eos-nav-compact', compact);
|
|
@@ -1223,10 +1355,17 @@
|
|
|
1223
1355
|
ensureRightsHelper();
|
|
1224
1356
|
ensurePermissionPresets();
|
|
1225
1357
|
ensureSettingsDialogClasses();
|
|
1358
|
+
ensureNotificationDialogClasses();
|
|
1226
1359
|
hideNativeLogoutNav();
|
|
1227
1360
|
hideOfficialNexoWattRepoWarning();
|
|
1361
|
+
patchNotifications();
|
|
1228
1362
|
applySecurityUiGuard();
|
|
1229
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'));
|
|
1230
1369
|
['.MuiAppBar-root', '.MuiDrawer-paper', 'nav', '.eos-brand-badge', '.eos-top-toolbar'].forEach(selector => {
|
|
1231
1370
|
document.querySelectorAll(selector).forEach(scope => {
|
|
1232
1371
|
patchTextNodes(scope);
|
|
@@ -1255,12 +1394,17 @@
|
|
|
1255
1394
|
ensureRightsHelper();
|
|
1256
1395
|
ensurePermissionPresets();
|
|
1257
1396
|
ensureSettingsDialogClasses();
|
|
1397
|
+
ensureNotificationDialogClasses();
|
|
1258
1398
|
hideNativeLogoutNav();
|
|
1259
1399
|
hideOfficialNexoWattRepoWarning();
|
|
1400
|
+
patchNotifications();
|
|
1260
1401
|
applySecurityUiGuard();
|
|
1261
1402
|
for (const scope of scopes.slice(0, 80)) {
|
|
1262
1403
|
if (!scope || !scope.isConnected) continue;
|
|
1263
|
-
if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper')))
|
|
1404
|
+
if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper'))) {
|
|
1405
|
+
patchMojibakeTextNodes(scope);
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1264
1408
|
patchTextNodes(scope);
|
|
1265
1409
|
patchAttributes(scope);
|
|
1266
1410
|
}
|
|
@@ -1291,14 +1435,17 @@
|
|
|
1291
1435
|
const observer = new MutationObserver(mutations => {
|
|
1292
1436
|
for (const mutation of mutations) {
|
|
1293
1437
|
if (mutation.type === 'characterData') {
|
|
1294
|
-
|
|
1438
|
+
if (isAdapterConfigSurface() && mutation.target?.parentElement?.closest?.('#app-paper')) patchMojibakeTextNode(mutation.target);
|
|
1439
|
+
else patchTextNode(mutation.target);
|
|
1295
1440
|
continue;
|
|
1296
1441
|
}
|
|
1297
1442
|
if (mutation.type !== 'childList') continue;
|
|
1298
1443
|
mutation.addedNodes.forEach(node => {
|
|
1299
1444
|
if (!node) return;
|
|
1300
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
1301
|
-
|
|
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);
|
|
1302
1449
|
});
|
|
1303
1450
|
}
|
|
1304
1451
|
if (state.pendingScopes.size) scheduleScopePatch();
|
|
@@ -1327,3 +1474,21 @@
|
|
|
1327
1474
|
window.addEventListener('load', () => scheduleFullPatch(0), { once: true });
|
|
1328
1475
|
window.addEventListener('hashchange', () => scheduleFullPatch(0));
|
|
1329
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
|
+
})();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
window.NEXOWATT_EOS_RUNTIME_FIXES_VERSION = 'v37-notification-backitup-security-text-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
|
+
'.MuiAlert-root',
|
|
82
|
+
'[role="alert"]',
|
|
83
|
+
'[role="status"]',
|
|
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
|
+
'.MuiAlert-root',
|
|
111
|
+
'[role="alert"]',
|
|
112
|
+
'[role="status"]',
|
|
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 = 'v37-security-text-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,24 @@
|
|
|
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
|
+
// Never block notification/toast close actions. These controls are owned by
|
|
250
|
+
// the native Admin UI and must remain clickable regardless of EOS security rules.
|
|
251
|
+
document.querySelectorAll('.MuiSnackbar-root, .MuiAlert-root, .MuiSnackbarContent-root, [role="alert"], .Toastify__toast, .notistack-Snackbar').forEach(box => {
|
|
252
|
+
box.classList.add('eos-notification-safe');
|
|
253
|
+
box.style.pointerEvents = 'auto';
|
|
254
|
+
box.querySelectorAll('button, [role="button"], a, .MuiIconButton-root').forEach(control => {
|
|
255
|
+
control.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete');
|
|
256
|
+
control.removeAttribute('disabled');
|
|
257
|
+
control.removeAttribute('aria-disabled');
|
|
258
|
+
if ('disabled' in control) control.disabled = false;
|
|
259
|
+
control.style.pointerEvents = 'auto';
|
|
260
|
+
control.style.display = '';
|
|
261
|
+
control.style.visibility = '';
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
|
|
236
266
|
const scheduleApply = () => {
|
|
237
267
|
if (state.scheduled) return;
|
|
238
268
|
state.scheduled = true;
|
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",
|