iobroker.eos-admin 7.9.35 → 7.9.36
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/adminWww/css/eos-branding.css +52 -0
- package/adminWww/index.html +4 -5
- package/adminWww/js/eos-branding.js +47 -14
- package/adminWww/js/eos-hard-logout.js +15 -162
- package/adminWww/js/eos-security-ui.js +4 -1
- package/build/lib/web.js +48 -64
- package/io-package.json +10 -10
- package/package.json +1 -1
- package/public/404.html +12 -0
- package/tools/nexowatt-validate-package.cjs +6 -3
|
@@ -3319,3 +3319,55 @@ 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 v36: 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
|
+
}
|
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=36" />
|
|
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=
|
|
158
|
-
<script defer src="./js/eos-security-ui.js?v=
|
|
159
|
-
<script defer src="./js/eos-
|
|
160
|
-
<script defer src="./js/eos-assistant.js?v=35"></script>
|
|
157
|
+
<script defer src="./js/eos-branding.js?v=36"></script>
|
|
158
|
+
<script defer src="./js/eos-security-ui.js?v=36"></script>
|
|
159
|
+
<script defer src="./js/eos-assistant.js?v=36"></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 = '
|
|
4
|
+
window.NEXOWATT_EOS_UI_VERSION = 'v36-native-config-session-fix';
|
|
5
5
|
|
|
6
6
|
const BRAND = 'NexoWatt EOS';
|
|
7
7
|
const EOS_MEANING = 'Energy Operation System';
|
|
@@ -398,6 +398,9 @@
|
|
|
398
398
|
const applySecurityUiGuard = () => safe(() => {
|
|
399
399
|
const policy = state.securityPolicy;
|
|
400
400
|
applySecurityClasses();
|
|
401
|
+
// Do not apply EOS security decoration inside native adapter configuration pages.
|
|
402
|
+
// Adapter UIs must remain 100% functional; backend/role checks still protect EOS actions.
|
|
403
|
+
if (isAdapterConfigSurface()) return;
|
|
401
404
|
if (!policy.loaded) return;
|
|
402
405
|
if (isAdminUser()) {
|
|
403
406
|
document.querySelectorAll('.eos-hidden-legacy-admin, .eos-protected-adapter-row').forEach(el => {
|
|
@@ -470,16 +473,33 @@
|
|
|
470
473
|
};
|
|
471
474
|
|
|
472
475
|
|
|
476
|
+
const isAdapterConfigSurface = () => document.documentElement.classList.contains('eos-adapter-config-surface');
|
|
477
|
+
|
|
473
478
|
const markAdapterConfigSurface = () => safe(() => {
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
|
|
479
|
+
const app = document.querySelector('#app-paper') || document.body;
|
|
480
|
+
const text = textOfElement(app).slice(0, 3500);
|
|
481
|
+
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
482
|
document.documentElement.classList.toggle('eos-adapter-config-surface', !!isConfig);
|
|
478
483
|
if (!isConfig) return;
|
|
479
|
-
|
|
480
|
-
|
|
484
|
+
|
|
485
|
+
// Native adapter configuration UIs are owned by the adapter. EOS must never
|
|
486
|
+
// block, rewrite or intercept their controls. This is critical for custom
|
|
487
|
+
// React/HTML configuration pages such as nexowatt-devices.
|
|
488
|
+
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
489
|
if (control.closest('.eos-assist-root, #eos-assist-root, .eos-standalone-nav-toggle')) return;
|
|
482
490
|
control.style.pointerEvents = 'auto';
|
|
491
|
+
control.removeAttribute('aria-disabled');
|
|
492
|
+
});
|
|
493
|
+
document.querySelectorAll('#app-paper .eos-protected-delete-control, #app-paper .eos-security-hidden-delete, #app-paper .eos-protected-adapter-row').forEach(el => {
|
|
494
|
+
el.classList.remove('eos-protected-delete-control', 'eos-security-hidden-delete', 'eos-protected-adapter-row');
|
|
495
|
+
el.removeAttribute('aria-disabled');
|
|
496
|
+
if (el.style) {
|
|
497
|
+
el.style.pointerEvents = '';
|
|
498
|
+
el.style.display = '';
|
|
499
|
+
el.style.visibility = '';
|
|
500
|
+
el.style.opacity = '';
|
|
501
|
+
}
|
|
502
|
+
if ('disabled' in el && !el.dataset.eosOriginalDisabled) el.disabled = false;
|
|
483
503
|
});
|
|
484
504
|
});
|
|
485
505
|
|
|
@@ -592,13 +612,10 @@
|
|
|
592
612
|
}
|
|
593
613
|
|
|
594
614
|
const installHardLogoutWatchdog = () => {
|
|
595
|
-
|
|
615
|
+
// v36: disabled. Upstream ioBroker Admin session handling is used again so
|
|
616
|
+
// the configured admin TTL is respected and native adapter config pages are
|
|
617
|
+
// not interrupted by duplicate EOS timers.
|
|
596
618
|
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
619
|
};
|
|
603
620
|
|
|
604
621
|
const ensureBrandBadge = toolbar => {
|
|
@@ -1009,6 +1026,11 @@
|
|
|
1009
1026
|
document.querySelectorAll('.eos-assist-launcher,.eos-assist-panel:not(#eos-assist-root .eos-assist-panel)').forEach(el => el.remove());
|
|
1010
1027
|
return;
|
|
1011
1028
|
}
|
|
1029
|
+
if (isAdapterConfigSurface()) {
|
|
1030
|
+
const existing = document.getElementById('eos-assist-root');
|
|
1031
|
+
if (existing) existing.classList.add('eos-assist-config-hidden');
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1012
1034
|
|
|
1013
1035
|
let root = document.getElementById('eos-assist-root');
|
|
1014
1036
|
if (!root) {
|
|
@@ -1042,6 +1064,7 @@
|
|
|
1042
1064
|
document.body.appendChild(root);
|
|
1043
1065
|
}
|
|
1044
1066
|
|
|
1067
|
+
root.classList.remove('eos-assist-config-hidden');
|
|
1045
1068
|
const ctx = assistContext();
|
|
1046
1069
|
const button = root.querySelector('.eos-assist-button');
|
|
1047
1070
|
const input = root.querySelector('.eos-assist-input');
|
|
@@ -1203,8 +1226,17 @@
|
|
|
1203
1226
|
hideNativeLogoutNav();
|
|
1204
1227
|
hideOfficialNexoWattRepoWarning();
|
|
1205
1228
|
applySecurityUiGuard();
|
|
1206
|
-
|
|
1207
|
-
|
|
1229
|
+
if (isAdapterConfigSurface()) {
|
|
1230
|
+
['.MuiAppBar-root', '.MuiDrawer-paper', 'nav', '.eos-brand-badge', '.eos-top-toolbar'].forEach(selector => {
|
|
1231
|
+
document.querySelectorAll(selector).forEach(scope => {
|
|
1232
|
+
patchTextNodes(scope);
|
|
1233
|
+
patchAttributes(scope);
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
} else {
|
|
1237
|
+
patchTextNodes(document.body || document.documentElement);
|
|
1238
|
+
patchAttributes(document.body || document.documentElement);
|
|
1239
|
+
}
|
|
1208
1240
|
};
|
|
1209
1241
|
|
|
1210
1242
|
const scopePatch = () => {
|
|
@@ -1228,6 +1260,7 @@
|
|
|
1228
1260
|
applySecurityUiGuard();
|
|
1229
1261
|
for (const scope of scopes.slice(0, 80)) {
|
|
1230
1262
|
if (!scope || !scope.isConnected) continue;
|
|
1263
|
+
if (isAdapterConfigSurface() && (scope.id === 'app-paper' || scope.closest?.('#app-paper'))) continue;
|
|
1231
1264
|
patchTextNodes(scope);
|
|
1232
1265
|
patchAttributes(scope);
|
|
1233
1266
|
}
|
|
@@ -1,165 +1,18 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function clearStoredTokens() {
|
|
21
|
-
const storages = [window.localStorage, window.sessionStorage, window._localStorage, window._sessionStorage]
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
.filter((storage, index, array) => array.indexOf(storage) === index);
|
|
24
|
-
const tokenKeyPattern = /(access[_-]?token|refresh[_-]?token|token[_-]?expires|expires[_-]?in|oauth|bearer|auth|connection)/i;
|
|
25
|
-
for (const storage of storages) {
|
|
26
|
-
try {
|
|
27
|
-
const keys = [];
|
|
28
|
-
for (let i = 0; i < storage.length; i++) {
|
|
29
|
-
const key = storage.key(i);
|
|
30
|
-
if (key && (tokenKeyPattern.test(key) || key === STORAGE_KEY)) {
|
|
31
|
-
keys.push(key);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
keys.forEach(key => storage.removeItem(key));
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// ignore blocked storage
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function clearAuthCookies() {
|
|
42
|
-
const cookieNames = ['access_token', 'refresh_token', 'connect.sid', 'ioBroker.sid'];
|
|
43
|
-
const paths = ['/', window.location.pathname.replace(/\/[^/]*$/, '/') || '/'];
|
|
44
|
-
for (const name of cookieNames) {
|
|
45
|
-
for (const path of paths) {
|
|
46
|
-
document.cookie = `${name}=; Max-Age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}; SameSite=Lax`;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function readDeadline() {
|
|
52
|
-
try {
|
|
53
|
-
const value = Number(window.localStorage.getItem(STORAGE_KEY));
|
|
54
|
-
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
55
|
-
} catch (e) {
|
|
56
|
-
return 0;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function writeDeadline(deadline) {
|
|
61
|
-
try {
|
|
62
|
-
window.localStorage.setItem(STORAGE_KEY, String(deadline));
|
|
63
|
-
} catch (e) {
|
|
64
|
-
// ignore blocked storage
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function hardLogout(reason) {
|
|
69
|
-
if (logoutStarted || isLoginPage()) return;
|
|
70
|
-
logoutStarted = true;
|
|
71
|
-
try { console.warn(LOG_PREFIX, reason || 'session expired'); } catch (e) {}
|
|
72
|
-
if (logoutTimer) {
|
|
73
|
-
clearTimeout(logoutTimer);
|
|
74
|
-
logoutTimer = null;
|
|
75
|
-
}
|
|
76
|
-
if (pollTimer) {
|
|
77
|
-
clearInterval(pollTimer);
|
|
78
|
-
pollTimer = null;
|
|
79
|
-
}
|
|
80
|
-
clearStoredTokens();
|
|
81
|
-
clearAuthCookies();
|
|
82
|
-
const logoutUrl = new URL('logout', window.location.origin + '/');
|
|
83
|
-
logoutUrl.searchParams.set('hard', '1');
|
|
84
|
-
logoutUrl.searchParams.set('ts', String(Date.now()));
|
|
85
|
-
window.location.replace(logoutUrl.href);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function scheduleHardLogout(deadline) {
|
|
89
|
-
const ms = Math.min(Math.max(deadline - Date.now() + 250, 250), MAX_TIMER_MS);
|
|
90
|
-
if (logoutTimer) clearTimeout(logoutTimer);
|
|
91
|
-
logoutTimer = setTimeout(() => hardLogout('configured login timeout reached'), ms);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function applyServerExpiration(expireInSec) {
|
|
95
|
-
const seconds = Number(expireInSec);
|
|
96
|
-
if (!Number.isFinite(seconds)) return;
|
|
97
|
-
if (seconds <= 0) {
|
|
98
|
-
hardLogout('server reported expired session');
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (seconds < MIN_TTL_SEC) return;
|
|
102
|
-
|
|
103
|
-
const now = Date.now();
|
|
104
|
-
const candidateDeadline = now + seconds * 1000;
|
|
105
|
-
const storedDeadline = readDeadline();
|
|
106
|
-
|
|
107
|
-
// Set the hard deadline once. If the server reports an earlier expiration later,
|
|
108
|
-
// tighten the deadline. Never extend it through refresh-token based renewal.
|
|
109
|
-
const deadline = !storedDeadline || candidateDeadline < storedDeadline - 5000 ? candidateDeadline : storedDeadline;
|
|
110
|
-
if (deadline !== storedDeadline) writeDeadline(deadline);
|
|
111
|
-
|
|
112
|
-
if (now >= deadline) {
|
|
113
|
-
hardLogout('stored hard deadline reached');
|
|
114
|
-
} else {
|
|
115
|
-
scheduleHardLogout(deadline);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function checkSession() {
|
|
120
|
-
if (logoutStarted) return;
|
|
121
|
-
if (isLoginPage()) {
|
|
122
|
-
clearStoredTokens();
|
|
123
|
-
clearAuthCookies();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const storedDeadline = readDeadline();
|
|
128
|
-
if (storedDeadline && Date.now() >= storedDeadline) {
|
|
129
|
-
hardLogout('stored hard deadline reached');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const response = await fetch('./session?hard=1&ts=' + Date.now(), {
|
|
135
|
-
credentials: 'include',
|
|
136
|
-
cache: 'no-store',
|
|
137
|
-
headers: { Accept: 'application/json' },
|
|
138
|
-
});
|
|
139
|
-
if (!response.ok) return;
|
|
140
|
-
const session = await response.json();
|
|
141
|
-
if (typeof session.expireInSec === 'number') {
|
|
142
|
-
applyServerExpiration(session.expireInSec);
|
|
143
|
-
}
|
|
144
|
-
} catch (e) {
|
|
145
|
-
// During update/restart the endpoint can be unavailable. Do not logout just because of a network error.
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function start() {
|
|
150
|
-
checkSession();
|
|
151
|
-
pollTimer = setInterval(checkSession, MIN_POLL_MS);
|
|
152
|
-
window.addEventListener('focus', checkSession, { passive: true });
|
|
153
|
-
document.addEventListener('visibilitychange', () => {
|
|
154
|
-
if (!document.hidden) checkSession();
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
window.NEXOWATT_EOS_HARD_LOGOUT = { version: VERSION, checkSession, hardLogout, clearStoredTokens, clearAuthCookies };
|
|
159
|
-
|
|
160
|
-
if (document.readyState === 'loading') {
|
|
161
|
-
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
162
|
-
} else {
|
|
163
|
-
start();
|
|
164
|
-
}
|
|
3
|
+
// NexoWatt EOS v36: compatibility stub.
|
|
4
|
+
// The custom hard-logout timer was removed because it could expire sessions
|
|
5
|
+
// earlier than the configured admin TTL and broke native adapter dialogs.
|
|
6
|
+
// Session handling is now delegated to the same OAuth/session flow as the
|
|
7
|
+
// upstream ioBroker Admin.
|
|
8
|
+
const VERSION = '36';
|
|
9
|
+
function noop() {}
|
|
10
|
+
window.NEXOWATT_EOS_HARD_LOGOUT = {
|
|
11
|
+
version: VERSION,
|
|
12
|
+
checkSession: noop,
|
|
13
|
+
hardLogout: noop,
|
|
14
|
+
clearStoredTokens: noop,
|
|
15
|
+
clearAuthCookies: noop,
|
|
16
|
+
disabled: true,
|
|
17
|
+
};
|
|
165
18
|
})();
|
|
@@ -220,6 +220,7 @@
|
|
|
220
220
|
};
|
|
221
221
|
|
|
222
222
|
const applyPolicyToDom = () => {
|
|
223
|
+
if (isAdapterConfigSurface()) return;
|
|
223
224
|
const admin = isAdminUser();
|
|
224
225
|
document.documentElement.classList.toggle('eos-security-admin-user', admin);
|
|
225
226
|
document.documentElement.classList.toggle('eos-security-non-admin-user', !admin);
|
|
@@ -230,6 +231,8 @@
|
|
|
230
231
|
hideEosSecuritySettingsForNonAdmins();
|
|
231
232
|
};
|
|
232
233
|
|
|
234
|
+
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
|
+
|
|
233
236
|
const scheduleApply = () => {
|
|
234
237
|
if (state.scheduled) return;
|
|
235
238
|
state.scheduled = true;
|
|
@@ -267,7 +270,7 @@
|
|
|
267
270
|
|
|
268
271
|
document.addEventListener('click', event => {
|
|
269
272
|
const target = event.target?.closest?.('button,[role="button"],a,[role="menuitem"],.MuiMenuItem-root');
|
|
270
|
-
if (!target || isAdminUser()) return;
|
|
273
|
+
if (!target || isAdminUser() || isAdapterConfigSurface()) return;
|
|
271
274
|
const label = normalizeFlat(`${target.textContent || ''} ${target.getAttribute?.('title') || ''} ${target.getAttribute?.('aria-label') || ''}`);
|
|
272
275
|
if (/loschen|delete|remove|deinstall|uninstall/.test(label)) {
|
|
273
276
|
target.classList.add('eos-security-hidden-delete');
|
package/build/lib/web.js
CHANGED
|
@@ -583,6 +583,16 @@ class Web {
|
|
|
583
583
|
this.server.app.get('/version', (_req, res) => {
|
|
584
584
|
res.status(200).send(this.adapter.version);
|
|
585
585
|
});
|
|
586
|
+
// v36: Repair stale/broken URLs generated by older EOS hard-logout builds.
|
|
587
|
+
this.server.app.use((req, res, next) => {
|
|
588
|
+
const requested = String(req.originalUrl || req.url || '');
|
|
589
|
+
if (/(?:%2f|%252f)(?:%23|%2523)|\/login\/|\/logout\/|hard=1/i.test(requested)
|
|
590
|
+
&& /index\.html|login|hard=1|origin=|%23|%2523/i.test(requested)) {
|
|
591
|
+
res.redirect(this.LOGIN_PAGE);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
next();
|
|
595
|
+
});
|
|
586
596
|
// replace socket.io
|
|
587
597
|
this.server.app.use((req, res, next) => {
|
|
588
598
|
const url = req.url.split('?')[0];
|
|
@@ -633,38 +643,12 @@ class Web {
|
|
|
633
643
|
this.server.app.use(cookieParser());
|
|
634
644
|
this.server.app.use(bodyParser.urlencoded({ extended: true }));
|
|
635
645
|
this.server.app.use(bodyParser.json());
|
|
636
|
-
//
|
|
637
|
-
// We remove refresh tokens from OAuth responses so the admin client cannot silently extend sessions.
|
|
638
|
-
const eosHardLogout = false; // v35: keep OAuth refresh flow alive; hard timeout is enforced client-side by an absolute deadline.
|
|
639
|
-
if (eosHardLogout) {
|
|
640
|
-
this.server.app.use('/oauth/token', (req, res, next) => {
|
|
641
|
-
const grantType = String(req.body?.grant_type || '');
|
|
642
|
-
if (req.method === 'POST' && grantType === 'refresh_token') {
|
|
643
|
-
res.status(401).json({
|
|
644
|
-
error: 'invalid_grant',
|
|
645
|
-
error_description: 'EOS session expired. Please log in again.',
|
|
646
|
-
eosHardLogout: true,
|
|
647
|
-
});
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const originalJson = res.json.bind(res);
|
|
651
|
-
res.json = body => {
|
|
652
|
-
if (body && typeof body === 'object' && body.access_token && body.expires_in) {
|
|
653
|
-
const expiresIn = Math.max(1, Math.round(Number(body.expires_in) || Number(this.settings.ttl) || 3600));
|
|
654
|
-
body.refresh_token = '';
|
|
655
|
-
body.refresh_token_expires_in = expiresIn;
|
|
656
|
-
body.eosHardLogout = true;
|
|
657
|
-
}
|
|
658
|
-
return originalJson(body);
|
|
659
|
-
};
|
|
660
|
-
next();
|
|
661
|
-
});
|
|
662
|
-
}
|
|
646
|
+
// v36: OAuth/session handling intentionally follows upstream ioBroker Admin.
|
|
663
647
|
this.oauth2Model = (0, webserver_1.createOAuth2Server)(this.adapter, {
|
|
664
648
|
app: this.server.app,
|
|
665
649
|
secure: this.settings.secure,
|
|
666
650
|
accessLifetime: this.settings.ttl,
|
|
667
|
-
refreshLifetime:
|
|
651
|
+
refreshLifetime: 60 * 60 * 24 * 7, // 1 week, same as upstream admin
|
|
668
652
|
noBasicAuth: this.settings.noBasicAuth,
|
|
669
653
|
loginPage: (req) => {
|
|
670
654
|
const isDev = req.url.includes('?dev');
|
|
@@ -676,44 +660,44 @@ class Web {
|
|
|
676
660
|
},
|
|
677
661
|
});
|
|
678
662
|
this.server.app.get('/session', (req, res) => {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
663
|
+
// v36: Follow upstream admin semantics again. Do not run a second
|
|
664
|
+
// EOS hard-logout timer here; the official OAuth/session handling
|
|
665
|
+
// already uses the configured access lifetime.
|
|
666
|
+
if (req.headers.cookie) {
|
|
667
|
+
const cookies = req.headers.cookie.split(';').find(c => c.trim().startsWith('access_token='));
|
|
668
|
+
let tokenCookie = cookies?.split('=')[1];
|
|
669
|
+
if (!tokenCookie && req.headers.authorization?.startsWith('Bearer ')) {
|
|
670
|
+
tokenCookie = req.headers.authorization.split(' ')[1];
|
|
671
|
+
}
|
|
672
|
+
else if (!tokenCookie && req.query?.token) {
|
|
673
|
+
tokenCookie = req.query.token;
|
|
674
|
+
}
|
|
675
|
+
if (tokenCookie) {
|
|
676
|
+
const candidates = new Set();
|
|
677
|
+
candidates.add(tokenCookie.startsWith('a:') ? tokenCookie : `a:${tokenCookie}`);
|
|
678
|
+
if (tokenCookie.length > 1)
|
|
679
|
+
candidates.add(`a:${tokenCookie[1]}`);
|
|
680
|
+
const ids = Array.from(candidates);
|
|
681
|
+
const readNext = (index) => {
|
|
682
|
+
const id = ids[index];
|
|
683
|
+
if (!id) {
|
|
684
|
+
res.json({ expireInSec: 0 });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
void this.adapter.getSession(id, (token) => {
|
|
688
|
+
if (!token?.user) {
|
|
689
|
+
readNext(index + 1);
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
res.json({ expireInSec: Math.round((token.aExp - Date.now()) / 1000) });
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
};
|
|
696
|
+
readNext(0);
|
|
699
697
|
return;
|
|
700
698
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
readNext(index + 1);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const now = Date.now();
|
|
707
|
-
const expirations = [Number(token.aExp), Number(token.rExp), Number(token.expire), Number(token.expires), Number(token.expiresAt)].filter(value => Number.isFinite(value) && value > 0);
|
|
708
|
-
const expiresAt = expirations.length ? Math.min(...expirations) : now + (this.settings.ttl || 3600) * 1000;
|
|
709
|
-
res.json({
|
|
710
|
-
expireInSec: Math.max(0, Math.floor((expiresAt - now) / 1000)),
|
|
711
|
-
hardLogout: true,
|
|
712
|
-
user: token.user,
|
|
713
|
-
});
|
|
714
|
-
});
|
|
715
|
-
};
|
|
716
|
-
readNext(0);
|
|
699
|
+
}
|
|
700
|
+
res.json({ error: 'Cannot find session' });
|
|
717
701
|
});
|
|
718
702
|
this.server.app.get(/.*\/nexowatt\/security\/(?:context|session)$/, (req, res) => {
|
|
719
703
|
void this.sendEosSecuritySession(req, res).catch(e => {
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "eos-admin",
|
|
4
|
-
"version": "7.9.
|
|
4
|
+
"version": "7.9.36",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "NexoWatt EOS Admin",
|
|
7
7
|
"de": "NexoWatt EOS Admin",
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
"connectionType": "local",
|
|
19
19
|
"dataSource": "push",
|
|
20
20
|
"news": {
|
|
21
|
+
"7.9.36": {
|
|
22
|
+
"en": "Restores official admin session handling, fixes bad login redirect paths, and enables full native adapter configuration compatibility without EOS overlays blocking adapter UI actions.",
|
|
23
|
+
"de": "Stellt die offizielle Admin-Sitzungslogik wieder her, korrigiert fehlerhafte Login-Redirect-Pfade und aktiviert volle native Adapter-Konfigurationskompatibilität ohne blockierende EOS-Overlays."
|
|
24
|
+
},
|
|
21
25
|
"7.9.35": {
|
|
22
|
-
"en": "
|
|
23
|
-
"de": "
|
|
26
|
+
"en": "Fixes EOS Admin login redirect/logout origin handling and restores reliable interaction with adapter instance configuration dialogs.",
|
|
27
|
+
"de": "Korrigiert den EOS-Admin Login-/Logout-Redirect und stellt die Bedienung von Adapter-Instanzkonfigurationen wieder zuverlässig her."
|
|
24
28
|
},
|
|
25
29
|
"7.9.31": {
|
|
26
30
|
"en": "UI cleanup: fixes EOS Security German text encoding, removes the logout tile next to the compact navigation arrow, and expands EOS Assist with AI-ready multi-path setup guidance.",
|
|
@@ -165,10 +169,6 @@
|
|
|
165
169
|
"pl": "Rozszerzony plik konfiguracyjny JSON do adaptera fregata",
|
|
166
170
|
"uk": "Розширений налаштування JSON для адаптера фригату",
|
|
167
171
|
"zh-cn": "JSON 护卫舰适配器扩展配置"
|
|
168
|
-
},
|
|
169
|
-
"7.9.35": {
|
|
170
|
-
"en": "Fixes EOS Admin login redirect/logout origin handling and restores reliable interaction with adapter instance configuration dialogs.",
|
|
171
|
-
"de": "Korrigiert den EOS-Admin Login-/Logout-Redirect und stellt die Bedienung von Adapter-Instanzkonfigurationen wieder zuverlässig her."
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
174
|
"desc": {
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
"icon": "admin.svg",
|
|
207
207
|
"messagebox": true,
|
|
208
208
|
"enabled": true,
|
|
209
|
-
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
209
|
+
"extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.36/admin/admin.svg",
|
|
210
210
|
"keywords": [
|
|
211
211
|
"NexoWatt",
|
|
212
212
|
"EOS",
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
"licensed"
|
|
218
218
|
],
|
|
219
219
|
"compact": true,
|
|
220
|
-
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
220
|
+
"readme": "https://unpkg.com/iobroker.eos-admin@7.9.36/README.md",
|
|
221
221
|
"authors": [
|
|
222
222
|
"bluefox <bluefox@ccu.io>",
|
|
223
223
|
"hobbyquaker <hq@ccu.io>"
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"nondeletable": false,
|
|
287
287
|
"allowAdapterUpdate": true,
|
|
288
288
|
"allowAdapterDelete": false,
|
|
289
|
-
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.
|
|
289
|
+
"meta": "https://unpkg.com/iobroker.eos-admin@7.9.36/io-package.json",
|
|
290
290
|
"npmPackage": "iobroker.eos-admin"
|
|
291
291
|
},
|
|
292
292
|
"native": {
|
package/package.json
CHANGED
package/public/404.html
CHANGED
|
@@ -64,6 +64,18 @@
|
|
|
64
64
|
color: #fff;
|
|
65
65
|
}
|
|
66
66
|
</style>
|
|
67
|
+
|
|
68
|
+
<script id="NEXOWATT_EOS_BAD_PATH_FIX">
|
|
69
|
+
(function () {
|
|
70
|
+
try {
|
|
71
|
+
var u = String(location.pathname + location.search + location.hash);
|
|
72
|
+
if (/(%2F%23|%252F%2523|hard=1|\/login\/|\/logout\/)/i.test(u)) {
|
|
73
|
+
location.replace('/index.html?login');
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {}
|
|
76
|
+
})();
|
|
77
|
+
</script>
|
|
78
|
+
|
|
67
79
|
</head>
|
|
68
80
|
|
|
69
81
|
<body>
|
|
@@ -56,8 +56,11 @@ if (!bootstrap.includes('window.adapterName="eos-admin"')) fail('frontend bootst
|
|
|
56
56
|
if (bootstrap.includes('window.adapterName="admin"')) fail('frontend bootstrap still contains window.adapterName="admin"');
|
|
57
57
|
|
|
58
58
|
const webBuild = fs.readFileSync(path.join(root, 'build/lib/web.js'), 'utf8');
|
|
59
|
-
|
|
60
|
-
if (!webBuild.includes(
|
|
61
|
-
if (!webBuild.includes('
|
|
59
|
+
const branding = fs.readFileSync(path.join(root, 'adminWww/js/eos-branding.js'), 'utf8');
|
|
60
|
+
if (!webBuild.includes('refreshLifetime: 60 * 60 * 24 * 7')) fail('build/lib/web.js must keep upstream-compatible refresh lifetime');
|
|
61
|
+
if (!webBuild.includes('Follow upstream admin semantics again')) fail('build/lib/web.js does not contain the v36 session compatibility fix');
|
|
62
|
+
if (index.includes('eos-hard-logout.js')) fail('adminWww/index.html must not load the removed custom hard-logout timer');
|
|
63
|
+
if (!branding.includes('isAdapterConfigSurface')) fail('eos-branding.js lacks native adapter config safe-mode detection');
|
|
64
|
+
if (!branding.includes('Adapter UIs must remain 100% functional')) fail('eos-branding.js lacks native adapter config interaction guard');
|
|
62
65
|
|
|
63
66
|
console.log('[NexoWatt EOS package validation] OK');
|