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.
@@ -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
+
@@ -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=23" />
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=23"></script>
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 = 'v23-official-nexowatt-repo-warning-fix';
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
  }