iobroker.eos-admin 7.9.23 → 7.9.26
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/README.md +26 -1
- package/admin/jsonConfig.json5 +143 -0
- package/adminWww/assets/Adapters-B5_jQ7DE.js +1 -1
- package/adminWww/assets/Instances-YdaGnS5a.js +1 -1
- package/adminWww/css/eos-branding.css +245 -0
- package/adminWww/index.html +3 -2
- package/adminWww/js/eos-branding.js +221 -1
- package/adminWww/js/eos-security-ui.js +212 -0
- package/build/lib/testPassword.js.map +1 -1
- package/build/lib/web.js +196 -18
- package/build/lib/web.js.map +1 -1
- package/build/main.js +332 -1
- package/build/main.js.map +1 -1
- package/io-package.json +50 -10
- package/package.json +1 -1
- package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +1 -1
- package/tools/nexowatt-generate-repo-entry.cjs +12 -23
- package/tools/nexowatt-patch-repo.cjs +14 -22
|
@@ -2203,3 +2203,248 @@ html.eos-app .eos-hidden-logout * {
|
|
|
2203
2203
|
.eos-hidden-nexowatt-repo-warning {
|
|
2204
2204
|
display: none !important;
|
|
2205
2205
|
}
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
/* NexoWatt EOS security visibility */
|
|
2209
|
+
.eos-hidden-legacy-admin {
|
|
2210
|
+
display: none !important;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.eos-hide-legacy-admin-active [data-id="admin"],
|
|
2214
|
+
.eos-hide-legacy-admin-active [data-id="admin.0"],
|
|
2215
|
+
.eos-hide-legacy-admin-active [data-name="admin"] {
|
|
2216
|
+
display: none !important;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
/* v25: role-aware EOS security UI guard */
|
|
2220
|
+
html.eos-security-nonadmin .eos-hidden-legacy-admin {
|
|
2221
|
+
display: none !important;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
html.eos-security-nonadmin .eos-protected-adapter-row .eos-protected-delete-control,
|
|
2225
|
+
html.eos-security-nonadmin .eos-protected-delete-control {
|
|
2226
|
+
opacity: 0.28 !important;
|
|
2227
|
+
pointer-events: none !important;
|
|
2228
|
+
filter: grayscale(1) !important;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
html.eos-security-nonadmin .eos-protected-delete-dialog .eos-protected-delete-control {
|
|
2232
|
+
display: none !important;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
html.eos-security-nonadmin .eos-security-admin-only-field {
|
|
2236
|
+
display: none !important;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
.eos-security-restricted-note {
|
|
2240
|
+
margin: 10px 0 18px !important;
|
|
2241
|
+
padding: 14px 16px !important;
|
|
2242
|
+
border-radius: 16px !important;
|
|
2243
|
+
border: 1px solid rgba(45, 255, 182, 0.26) !important;
|
|
2244
|
+
background: rgba(4, 18, 28, 0.78) !important;
|
|
2245
|
+
color: rgba(232, 255, 248, 0.95) !important;
|
|
2246
|
+
box-shadow: 0 0 22px rgba(0, 229, 168, 0.13) !important;
|
|
2247
|
+
display: flex !important;
|
|
2248
|
+
flex-direction: column !important;
|
|
2249
|
+
gap: 4px !important;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
.eos-security-restricted-note strong {
|
|
2253
|
+
color: #53ffd0 !important;
|
|
2254
|
+
font-weight: 800 !important;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.eos-security-restricted-note span {
|
|
2258
|
+
color: rgba(219, 246, 240, 0.78) !important;
|
|
2259
|
+
font-size: 0.9rem !important;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
/* NexoWatt EOS v25 security visibility guard */
|
|
2264
|
+
.eos-security-hidden,
|
|
2265
|
+
.eos-security-hidden-delete {
|
|
2266
|
+
display: none !important;
|
|
2267
|
+
}
|
|
2268
|
+
.eos-security-protected-adapter {
|
|
2269
|
+
position: relative;
|
|
2270
|
+
}
|
|
2271
|
+
.eos-security-protected-adapter::after {
|
|
2272
|
+
content: 'EOS geschützt';
|
|
2273
|
+
position: absolute;
|
|
2274
|
+
right: 10px;
|
|
2275
|
+
top: 10px;
|
|
2276
|
+
z-index: 2;
|
|
2277
|
+
font-size: 10px;
|
|
2278
|
+
letter-spacing: .06em;
|
|
2279
|
+
text-transform: uppercase;
|
|
2280
|
+
padding: 3px 8px;
|
|
2281
|
+
border-radius: 999px;
|
|
2282
|
+
color: rgba(225, 255, 246, .88);
|
|
2283
|
+
background: rgba(5, 22, 28, .72);
|
|
2284
|
+
border: 1px solid rgba(55, 230, 180, .22);
|
|
2285
|
+
pointer-events: none;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
/* === NexoWatt EOS v26: Header identity polish =============================
|
|
2289
|
+
Header-Logo links stärker sichtbar, leicht nach oben gesetzt und sauber vom
|
|
2290
|
+
Text getrennt. Benutzername rechts wird kontrastreich/weiß dargestellt. */
|
|
2291
|
+
html.eos-app .eos-top-toolbar {
|
|
2292
|
+
overflow: visible !important;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
html.eos-app .eos-brand-badge {
|
|
2296
|
+
grid-template-columns: 76px minmax(170px, 1fr) 12px !important;
|
|
2297
|
+
min-width: 326px !important;
|
|
2298
|
+
width: 326px !important;
|
|
2299
|
+
height: 62px !important;
|
|
2300
|
+
min-height: 62px !important;
|
|
2301
|
+
padding: 3px 16px 3px 5px !important;
|
|
2302
|
+
column-gap: 13px !important;
|
|
2303
|
+
align-items: center !important;
|
|
2304
|
+
transform: translateY(-2px) !important;
|
|
2305
|
+
background:
|
|
2306
|
+
radial-gradient(circle at 26px 31px, rgba(0, 255, 136, 0.30), transparent 35px),
|
|
2307
|
+
linear-gradient(135deg, rgba(0, 255, 136, 0.18), rgba(0, 212, 255, 0.075) 58%, rgba(2, 9, 20, 0.45)) !important;
|
|
2308
|
+
border-color: rgba(0, 255, 136, 0.55) !important;
|
|
2309
|
+
box-shadow:
|
|
2310
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.10),
|
|
2311
|
+
inset 0 0 24px rgba(0, 255, 136, 0.08),
|
|
2312
|
+
0 10px 24px rgba(0, 0, 0, 0.28),
|
|
2313
|
+
0 0 22px rgba(0, 255, 136, 0.18) !important;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
html.eos-app .eos-brand-badge-mark {
|
|
2317
|
+
width: 64px !important;
|
|
2318
|
+
height: 64px !important;
|
|
2319
|
+
min-width: 64px !important;
|
|
2320
|
+
min-height: 64px !important;
|
|
2321
|
+
margin-left: -1px !important;
|
|
2322
|
+
transform: translateY(-3px) !important;
|
|
2323
|
+
border-radius: 22px !important;
|
|
2324
|
+
background:
|
|
2325
|
+
radial-gradient(circle at 48% 46%, rgba(45, 255, 188, 0.20), rgba(0, 22, 22, 0.92) 63%, rgba(1, 9, 17, 0.98) 100%) !important;
|
|
2326
|
+
border: 1px solid rgba(111, 255, 215, 0.76) !important;
|
|
2327
|
+
box-shadow:
|
|
2328
|
+
inset 0 0 22px rgba(0, 255, 136, 0.16),
|
|
2329
|
+
0 0 0 1px rgba(255, 255, 255, 0.06),
|
|
2330
|
+
0 0 20px rgba(0, 255, 136, 0.54),
|
|
2331
|
+
0 0 38px rgba(0, 212, 255, 0.17) !important;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
html.eos-app .eos-brand-badge-logo {
|
|
2335
|
+
width: 57px !important;
|
|
2336
|
+
height: 57px !important;
|
|
2337
|
+
min-width: 57px !important;
|
|
2338
|
+
min-height: 57px !important;
|
|
2339
|
+
opacity: 1 !important;
|
|
2340
|
+
transform: translateY(-1px) !important;
|
|
2341
|
+
filter:
|
|
2342
|
+
brightness(2.18)
|
|
2343
|
+
contrast(1.16)
|
|
2344
|
+
saturate(1.42)
|
|
2345
|
+
drop-shadow(0 0 1px rgba(245, 255, 252, 0.92))
|
|
2346
|
+
drop-shadow(0 0 10px rgba(0, 255, 136, 0.78))
|
|
2347
|
+
drop-shadow(0 0 20px rgba(0, 212, 255, 0.24)) !important;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
html.eos-app .eos-brand-badge-copy {
|
|
2351
|
+
padding-left: 1px !important;
|
|
2352
|
+
min-width: 0 !important;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
html.eos-app .eos-brand-badge-copy strong {
|
|
2356
|
+
color: #ffffff !important;
|
|
2357
|
+
font-size: 14px !important;
|
|
2358
|
+
line-height: 1.0 !important;
|
|
2359
|
+
text-shadow: 0 1px 0 rgba(0,0,0,.45), 0 0 10px rgba(0,255,136,.18) !important;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
html.eos-app .eos-brand-badge-copy small {
|
|
2363
|
+
color: rgba(239, 255, 250, 0.88) !important;
|
|
2364
|
+
font-size: 7.9px !important;
|
|
2365
|
+
line-height: 1.08 !important;
|
|
2366
|
+
text-shadow: 0 1px 0 rgba(0,0,0,.42) !important;
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
html.eos-app .eos-brand-led {
|
|
2370
|
+
background: #18ff9a !important;
|
|
2371
|
+
box-shadow: 0 0 12px rgba(0,255,136,.95), 0 0 24px rgba(0,212,255,.18) !important;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
/* Right account/user area in the native app bar: keep names readable. */
|
|
2375
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root,
|
|
2376
|
+
html.eos-app .eos-top-toolbar {
|
|
2377
|
+
color: #f4fbff !important;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
html.eos-app .eos-top-toolbar > :not(.eos-brand-badge),
|
|
2381
|
+
html.eos-app .eos-top-toolbar > :not(.eos-brand-badge) *,
|
|
2382
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root .MuiTypography-root,
|
|
2383
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root a,
|
|
2384
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root button,
|
|
2385
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root span:not(.eos-brand-led):not(.eos-brand-badge-mark) {
|
|
2386
|
+
color: #f4fbff !important;
|
|
2387
|
+
text-shadow: 0 1px 0 rgba(0,0,0,.55), 0 0 9px rgba(0,255,136,.14) !important;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root .MuiAvatar-root,
|
|
2391
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root img[alt*="admin" i],
|
|
2392
|
+
html.eos-app .MuiAppBar-root .MuiToolbar-root img[alt*="user" i] {
|
|
2393
|
+
opacity: 1 !important;
|
|
2394
|
+
filter: brightness(1.25) saturate(1.25) drop-shadow(0 0 10px rgba(0,255,136,.42)) !important;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
@media (max-width: 1220px) {
|
|
2398
|
+
html.eos-app .eos-brand-badge {
|
|
2399
|
+
min-width: 80px !important;
|
|
2400
|
+
width: 80px !important;
|
|
2401
|
+
grid-template-columns: 1fr !important;
|
|
2402
|
+
justify-items: center !important;
|
|
2403
|
+
padding: 4px 7px !important;
|
|
2404
|
+
transform: translateY(-1px) !important;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
html.eos-app .eos-brand-badge-mark {
|
|
2408
|
+
width: 64px !important;
|
|
2409
|
+
height: 64px !important;
|
|
2410
|
+
min-width: 64px !important;
|
|
2411
|
+
min-height: 64px !important;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
html.eos-app .eos-brand-badge-logo {
|
|
2415
|
+
width: 57px !important;
|
|
2416
|
+
height: 57px !important;
|
|
2417
|
+
min-width: 57px !important;
|
|
2418
|
+
min-height: 57px !important;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
html.eos-app .eos-brand-badge-copy,
|
|
2422
|
+
html.eos-app .eos-brand-led {
|
|
2423
|
+
display: none !important;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
@media (max-width: 720px) {
|
|
2428
|
+
html.eos-app .eos-brand-badge {
|
|
2429
|
+
min-width: 64px !important;
|
|
2430
|
+
width: 64px !important;
|
|
2431
|
+
height: 56px !important;
|
|
2432
|
+
min-height: 56px !important;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
html.eos-app .eos-brand-badge-mark {
|
|
2436
|
+
width: 52px !important;
|
|
2437
|
+
height: 52px !important;
|
|
2438
|
+
min-width: 52px !important;
|
|
2439
|
+
min-height: 52px !important;
|
|
2440
|
+
transform: translateY(-1px) !important;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
html.eos-app .eos-brand-badge-logo {
|
|
2444
|
+
width: 47px !important;
|
|
2445
|
+
height: 47px !important;
|
|
2446
|
+
min-width: 47px !important;
|
|
2447
|
+
min-height: 47px !important;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
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=26" />
|
|
35
35
|
<link
|
|
36
36
|
rel="manifest"
|
|
37
37
|
href="manifest.json"
|
|
@@ -154,7 +154,8 @@
|
|
|
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=
|
|
157
|
+
<script defer src="./js/eos-branding.js?v=26"></script>
|
|
158
|
+
<script defer src="./js/eos-security-ui.js?v=26"></script>
|
|
158
159
|
</head>
|
|
159
160
|
<body>
|
|
160
161
|
<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 = 'v26-header-identity-polish';
|
|
5
5
|
|
|
6
6
|
const BRAND = 'NexoWatt EOS';
|
|
7
7
|
const EOS_MEANING = 'Energy Operation System';
|
|
@@ -64,6 +64,14 @@
|
|
|
64
64
|
scopePatchScheduled: false,
|
|
65
65
|
pendingScopes: new Set(),
|
|
66
66
|
lastFullPatch: 0,
|
|
67
|
+
securityPolicy: {
|
|
68
|
+
loaded: false,
|
|
69
|
+
isAdmin: false,
|
|
70
|
+
hideLegacyAdminForNonAdmins: true,
|
|
71
|
+
restrictProtectedAdapterControls: true,
|
|
72
|
+
protectedAdapters: ['eos-admin', 'backitup'],
|
|
73
|
+
},
|
|
74
|
+
securityFetchStarted: false,
|
|
67
75
|
};
|
|
68
76
|
|
|
69
77
|
const safe = fn => {
|
|
@@ -163,6 +171,214 @@
|
|
|
163
171
|
};
|
|
164
172
|
};
|
|
165
173
|
|
|
174
|
+
const normalizeIdentifier = value => String(value || '')
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.normalize('NFD')
|
|
177
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
178
|
+
.replace(/^iobroker\./, '')
|
|
179
|
+
.replace(/^system\.adapter\./, '')
|
|
180
|
+
.replace(/\.\d+$/, '')
|
|
181
|
+
.replace(/[^a-z0-9_.-]+/g, ' ')
|
|
182
|
+
.trim();
|
|
183
|
+
|
|
184
|
+
const adapterPattern = adapter => {
|
|
185
|
+
const escaped = String(adapter || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
186
|
+
return new RegExp(`(?:^|[^a-z0-9_-])${escaped}(?:$|[^a-z0-9_-])`, 'i');
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const textOfElement = el => {
|
|
190
|
+
if (!el) return '';
|
|
191
|
+
const attrs = ['data-adapter-name', 'data-adapter', 'data-id', 'id', 'title', 'aria-label', 'alt', 'href', 'src'];
|
|
192
|
+
const values = [el.textContent || ''];
|
|
193
|
+
attrs.forEach(attr => {
|
|
194
|
+
if (el.getAttribute && el.hasAttribute(attr)) values.push(el.getAttribute(attr) || '');
|
|
195
|
+
});
|
|
196
|
+
return values.join(' ');
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const securityEndpointUrls = () => [
|
|
200
|
+
new URL('nexowatt/security/context', ASSET_BASE).href,
|
|
201
|
+
new URL('nexowatt/security/session', ASSET_BASE).href,
|
|
202
|
+
new URL('eos/security/status', ASSET_BASE).href,
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
const normalizeSecurityPolicy = policy => {
|
|
206
|
+
const protectedAdapters = new Set(['eos-admin', 'backitup']);
|
|
207
|
+
(Array.isArray(policy?.protectedAdapters) ? policy.protectedAdapters : []).forEach(item => {
|
|
208
|
+
const adapter = typeof item === 'string' ? normalizeIdentifier(item) : normalizeIdentifier(item?.adapter || item?.name);
|
|
209
|
+
if (adapter) protectedAdapters.add(adapter);
|
|
210
|
+
});
|
|
211
|
+
const isAdmin = !!(policy?.isAdmin || policy?.isAdminGroup || policy?.isEosAdminGroup || policy?.isAdministrator);
|
|
212
|
+
return {
|
|
213
|
+
loaded: true,
|
|
214
|
+
user: policy?.user || null,
|
|
215
|
+
groups: Array.isArray(policy?.groups) ? policy.groups : [],
|
|
216
|
+
isAdmin,
|
|
217
|
+
hideLegacyAdminForNonAdmins: policy?.hideLegacyAdminForNonAdmins !== false && policy?.hideLegacyAdminFromNonAdmins !== false,
|
|
218
|
+
restrictProtectedAdapterControls: policy?.restrictProtectedAdapterControls !== false,
|
|
219
|
+
protectedAdapters: [...protectedAdapters].sort(),
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const applySecurityClasses = () => {
|
|
224
|
+
const policy = state.securityPolicy;
|
|
225
|
+
document.documentElement.classList.toggle('eos-security-loaded', !!policy.loaded);
|
|
226
|
+
document.documentElement.classList.toggle('eos-security-admin', !!policy.isAdmin);
|
|
227
|
+
document.documentElement.classList.toggle('eos-security-nonadmin', policy.loaded && !policy.isAdmin);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const fetchSecurityPolicy = async () => {
|
|
231
|
+
if (state.securityFetchStarted) return;
|
|
232
|
+
state.securityFetchStarted = true;
|
|
233
|
+
for (const url of securityEndpointUrls()) {
|
|
234
|
+
try {
|
|
235
|
+
const response = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
|
|
236
|
+
if (!response.ok) continue;
|
|
237
|
+
const json = await response.json();
|
|
238
|
+
if (!json || json.error) continue;
|
|
239
|
+
state.securityPolicy = normalizeSecurityPolicy(json);
|
|
240
|
+
applySecurityClasses();
|
|
241
|
+
scheduleFullPatch(0);
|
|
242
|
+
return;
|
|
243
|
+
} catch (e) {
|
|
244
|
+
// Security endpoint may be unavailable during login or old cache. Fallback below.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
state.securityPolicy = normalizeSecurityPolicy({ isAdmin: false, protectedAdapters: ['eos-admin', 'backitup'] });
|
|
248
|
+
applySecurityClasses();
|
|
249
|
+
scheduleFullPatch(0);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const isAdminUser = () => !!state.securityPolicy?.isAdmin;
|
|
253
|
+
|
|
254
|
+
const isLegacyAdminContainer = el => {
|
|
255
|
+
const text = textOfElement(el);
|
|
256
|
+
if (/\badmin\.0\b/i.test(text) || /\bsystem\.adapter\.admin\.0\b/i.test(text)) return true;
|
|
257
|
+
const candidates = Array.from(el.querySelectorAll ? el.querySelectorAll('*') : []);
|
|
258
|
+
candidates.push(el);
|
|
259
|
+
return candidates.some(node => {
|
|
260
|
+
const value = textOfElement(node).trim();
|
|
261
|
+
return /^admin$/i.test(value) || /^iobroker\.admin$/i.test(value) || /^system\.adapter\.admin$/i.test(value);
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const containerMentionsAdapter = (container, adapter) => {
|
|
266
|
+
if (!container || !adapter) return false;
|
|
267
|
+
const normalized = normalizeIdentifier(adapter);
|
|
268
|
+
if (!normalized) return false;
|
|
269
|
+
if (normalized === 'admin') return isLegacyAdminContainer(container);
|
|
270
|
+
const pattern = adapterPattern(normalized);
|
|
271
|
+
if (pattern.test(textOfElement(container))) return true;
|
|
272
|
+
return Array.from(container.querySelectorAll ? container.querySelectorAll('[title],[aria-label],[data-adapter-name],[data-adapter],[data-id]') : [])
|
|
273
|
+
.some(el => pattern.test(textOfElement(el)));
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const getSecurityContainers = () => Array.from(document.querySelectorAll([
|
|
277
|
+
'#app-paper .MuiCard-root',
|
|
278
|
+
'#app-paper tr.MuiTableRow-root',
|
|
279
|
+
'#app-paper tr',
|
|
280
|
+
'#app-paper .MuiAccordion-root',
|
|
281
|
+
'#app-paper [role="row"]',
|
|
282
|
+
'#app-paper [role="treeitem"]',
|
|
283
|
+
'#app-paper .MuiDataGrid-row',
|
|
284
|
+
'#app-paper .MuiTreeItem-root',
|
|
285
|
+
'#app-paper .MuiFormControlLabel-root',
|
|
286
|
+
'#app-paper .MuiListItem-root',
|
|
287
|
+
'#app-paper .MuiListItemButton-root',
|
|
288
|
+
].join(','))).filter(el => !el.closest('.MuiDialog-paper'));
|
|
289
|
+
|
|
290
|
+
const isDeleteControl = el => {
|
|
291
|
+
const text = normalize(el.textContent || el.getAttribute?.('title') || el.getAttribute?.('aria-label') || '');
|
|
292
|
+
const title = normalize(el.getAttribute?.('title') || el.closest?.('[title]')?.getAttribute('title') || '');
|
|
293
|
+
const svg = el.querySelector?.('svg[data-testid*="Delete"], svg[data-testid*="Remove"], svg[data-testid*="Clear"]');
|
|
294
|
+
return !!svg || /\b(delete|remove|uninstall|del|loschen|entfernen|deinstallieren)\b/.test(`${text} ${title}`);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const lockDeleteControls = container => {
|
|
298
|
+
Array.from(container.querySelectorAll('button, [role="button"], a')).forEach(control => {
|
|
299
|
+
if (!isDeleteControl(control)) return;
|
|
300
|
+
control.classList.add('eos-protected-delete-control');
|
|
301
|
+
control.setAttribute('aria-disabled', 'true');
|
|
302
|
+
control.setAttribute('title', 'Nur Administratoren dürfen geschützte EOS-Systemmodule löschen');
|
|
303
|
+
if ('disabled' in control) control.disabled = true;
|
|
304
|
+
control.addEventListener('click', event => {
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
event.stopImmediatePropagation();
|
|
307
|
+
}, true);
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const protectDeleteDialogs = () => {
|
|
312
|
+
if (isAdminUser() || state.securityPolicy.restrictProtectedAdapterControls === false) return;
|
|
313
|
+
const protectedAdapters = state.securityPolicy.protectedAdapters || [];
|
|
314
|
+
Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]')).forEach(dialog => {
|
|
315
|
+
const text = textOfElement(dialog);
|
|
316
|
+
if (!/(delete|remove|loschen|entfernen|deinstallieren|del\s+)/i.test(text)) return;
|
|
317
|
+
const protectedHit = protectedAdapters.some(adapter => containerMentionsAdapter(dialog, adapter));
|
|
318
|
+
if (!protectedHit) return;
|
|
319
|
+
dialog.classList.add('eos-protected-delete-dialog');
|
|
320
|
+
Array.from(dialog.querySelectorAll('button')).forEach(button => {
|
|
321
|
+
const label = normalize(button.textContent || button.getAttribute('aria-label') || '');
|
|
322
|
+
if (/^(ok|ja|yes|delete|remove|loschen|entfernen|deinstallieren)$/.test(label)) {
|
|
323
|
+
button.disabled = true;
|
|
324
|
+
button.classList.add('eos-protected-delete-control');
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const hideSecuritySettingsForNonAdmin = () => {
|
|
331
|
+
if (isAdminUser()) return;
|
|
332
|
+
Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]')).forEach(dialog => {
|
|
333
|
+
const dialogText = textOfElement(dialog);
|
|
334
|
+
if (!/(eos security|nexowatt security|legacy admin|alter admin|protected adapters|geschutzte adapter|eos admin groups)/i.test(dialogText)) return;
|
|
335
|
+
dialog.classList.add('eos-security-settings-restricted');
|
|
336
|
+
const needles = /(eos security|nexowatt security|legacy admin|alter admin|protected adapters|geschutzte adapter|eos admin groups|lock legacy admin|hide legacy admin|restrict protected adapter)/i;
|
|
337
|
+
Array.from(dialog.querySelectorAll('label, legend, h2, h3, h4, .MuiTypography-root, .MuiFormLabel-root')).forEach(label => {
|
|
338
|
+
if (!needles.test(label.textContent || '')) return;
|
|
339
|
+
const row = label.closest('.MuiGrid2-root, .MuiGrid-root, .MuiFormControl-root, .MuiBox-root, .MuiPaper-root') || label.parentElement;
|
|
340
|
+
if (row && row !== dialog) row.classList.add('eos-security-admin-only-field');
|
|
341
|
+
});
|
|
342
|
+
if (!dialog.querySelector('.eos-security-restricted-note')) {
|
|
343
|
+
const note = document.createElement('div');
|
|
344
|
+
note.className = 'eos-security-restricted-note';
|
|
345
|
+
note.innerHTML = '<strong>EOS Systemschutz aktiv</strong><span>Diese Sicherheitseinstellungen sind nur für Administratoren sichtbar und änderbar.</span>';
|
|
346
|
+
const content = dialog.querySelector('.MuiDialogContent-root') || dialog;
|
|
347
|
+
content.insertBefore(note, content.firstElementChild || null);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const applySecurityUiGuard = () => safe(() => {
|
|
353
|
+
const policy = state.securityPolicy;
|
|
354
|
+
applySecurityClasses();
|
|
355
|
+
if (!policy.loaded) return;
|
|
356
|
+
if (isAdminUser()) {
|
|
357
|
+
document.querySelectorAll('.eos-hidden-legacy-admin, .eos-protected-adapter-row').forEach(el => {
|
|
358
|
+
el.classList.remove('eos-hidden-legacy-admin', 'eos-protected-adapter-row');
|
|
359
|
+
el.removeAttribute('aria-hidden');
|
|
360
|
+
});
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const containers = getSecurityContainers();
|
|
365
|
+
containers.forEach(container => {
|
|
366
|
+
if (policy.hideLegacyAdminForNonAdmins !== false && isLegacyAdminContainer(container)) {
|
|
367
|
+
container.classList.add('eos-hidden-legacy-admin');
|
|
368
|
+
container.setAttribute('aria-hidden', 'true');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (policy.restrictProtectedAdapterControls !== false) {
|
|
372
|
+
const protectedHit = (policy.protectedAdapters || []).some(adapter => adapter !== 'admin' && containerMentionsAdapter(container, adapter));
|
|
373
|
+
if (protectedHit) {
|
|
374
|
+
container.classList.add('eos-protected-adapter-row');
|
|
375
|
+
lockDeleteControls(container);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
protectDeleteDialogs();
|
|
380
|
+
hideSecuritySettingsForNonAdmin();
|
|
381
|
+
});
|
|
166
382
|
|
|
167
383
|
const isLoginView = () => safe(() => {
|
|
168
384
|
const hasApp = !!document.getElementById('app-paper');
|
|
@@ -534,6 +750,7 @@
|
|
|
534
750
|
ensureSettingsDialogClasses();
|
|
535
751
|
hideNativeLogoutNav();
|
|
536
752
|
hideOfficialNexoWattRepoWarning();
|
|
753
|
+
applySecurityUiGuard();
|
|
537
754
|
patchTextNodes(document.body || document.documentElement);
|
|
538
755
|
patchAttributes(document.body || document.documentElement);
|
|
539
756
|
};
|
|
@@ -550,6 +767,7 @@
|
|
|
550
767
|
ensureSettingsDialogClasses();
|
|
551
768
|
hideNativeLogoutNav();
|
|
552
769
|
hideOfficialNexoWattRepoWarning();
|
|
770
|
+
applySecurityUiGuard();
|
|
553
771
|
for (const scope of scopes.slice(0, 80)) {
|
|
554
772
|
if (!scope || !scope.isConnected) continue;
|
|
555
773
|
patchTextNodes(scope);
|
|
@@ -605,11 +823,13 @@
|
|
|
605
823
|
if (document.readyState === 'loading') {
|
|
606
824
|
document.addEventListener('DOMContentLoaded', () => {
|
|
607
825
|
fullPatch();
|
|
826
|
+
fetchSecurityPolicy();
|
|
608
827
|
installObserver();
|
|
609
828
|
[250, 1000, 2500, 5000].forEach(scheduleFullPatch);
|
|
610
829
|
}, { once: true });
|
|
611
830
|
} else {
|
|
612
831
|
fullPatch();
|
|
832
|
+
fetchSecurityPolicy();
|
|
613
833
|
installObserver();
|
|
614
834
|
[250, 1000, 2500, 5000].forEach(scheduleFullPatch);
|
|
615
835
|
}
|