iobroker.eos-admin 7.10.6 → 7.10.8

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.
Files changed (31) hide show
  1. package/INSTALL_TEST_V7.10.7.md +9 -0
  2. package/MERGE_UPDATE_README_V7.10.7.md +7 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +19 -19
  4. package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +26 -24
  5. package/PUBLISH_STABLE_V7.10.7.md +9 -0
  6. package/README_STABILITY_V7.10.7.md +7 -0
  7. package/RELEASE_ACCEPTANCE_V7.10.7.md +12 -0
  8. package/RELEASE_NOTES_V7.10.7.md +19 -0
  9. package/{SECURITY_ROLE_MATRIX_V7.10.6.md → SECURITY_ROLE_MATRIX_V7.10.7.md} +4 -2
  10. package/adminWww/css/nexowatt-native-shell.css +332 -0
  11. package/adminWww/index.html +14 -14
  12. package/adminWww/js/eos-role-bootstrap.js +4 -3
  13. package/adminWww/js/eos-role-ui.js +34 -18
  14. package/adminWww/js/nexowatt-native-shell.js +104 -2
  15. package/adminWww/static/js/{nexowatt-stable-v7106.js → nexowatt-stable-v7108.js} +2 -2
  16. package/build/lib/web.js +3 -0
  17. package/io-package.json +10 -5
  18. package/package.json +16 -12
  19. package/tools/nexowatt-installer-ems-selftest.cjs +44 -0
  20. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  21. package/tools/nexowatt-role-security-selftest.cjs +19 -6
  22. package/tools/nexowatt-stable-v7103-selftest.cjs +2 -2
  23. package/tools/nexowatt-user-area-selftest.cjs +1 -1
  24. package/tools/nexowatt-v7108-ui-foundation-selftest.cjs +64 -0
  25. package/tools/nexowatt-validate-package.cjs +9 -7
  26. package/INSTALL_TEST_V7.10.6.md +0 -8
  27. package/MERGE_UPDATE_README_V7.10.6.md +0 -7
  28. package/PUBLISH_STABLE_V7.10.6.md +0 -8
  29. package/README_STABILITY_V7.10.6.md +0 -5
  30. package/RELEASE_ACCEPTANCE_V7.10.6.md +0 -11
  31. package/RELEASE_NOTES_V7.10.6.md +0 -14
@@ -8,7 +8,7 @@
8
8
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
9
9
  <meta http-equiv="Pragma" content="no-cache" />
10
10
  <meta http-equiv="Expires" content="0" />
11
- <script src="./js/eos-manual-write-policy.js?v=7106"></script>
11
+ <script src="./js/eos-manual-write-policy.js?v=7108"></script>
12
12
  <script type="module" src="./assets/hostInit-v84.js?v=84"></script>
13
13
 
14
14
  <meta charset="utf-8" />
@@ -36,8 +36,8 @@
36
36
  rel="stylesheet"
37
37
  href="css/leaflet.css"
38
38
  />
39
- <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=7106" />
40
- <link rel="stylesheet" href="./css/eos-ems-overview.css?v=7106" />
39
+ <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=7108" />
40
+ <link rel="stylesheet" href="./css/eos-ems-overview.css?v=7108" />
41
41
  <link rel="stylesheet" href="./css/eos-auto-update.css?v=7103" />
42
42
  <link
43
43
  rel="manifest"
@@ -160,22 +160,22 @@
160
160
  </script>
161
161
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
162
162
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
163
- <script defer src="./js/eos-role-bootstrap.js?v=7106" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
164
- <script defer src="./js/eos-policy-client.js?v=7106"></script>
165
- <script defer src="./js/eos-branding-sanitizer.js?v=7106"></script>
166
- <script defer src="./js/nexowatt-native-shell.js?v=7106"></script>
167
- <script defer src="./js/eos-native-security.js?v=7106"></script>
168
- <script defer src="./js/eos-basic-settings.js?v=7106"></script>
169
- <script defer src="./js/eos-role-ui.js?v=7106"></script>
170
- <script defer src="./js/eos-ems-overview.js?v=7106"></script>
163
+ <script defer src="./js/eos-role-bootstrap.js?v=7108" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
164
+ <script defer src="./js/eos-policy-client.js?v=7108"></script>
165
+ <script defer src="./js/eos-branding-sanitizer.js?v=7108"></script>
166
+ <script defer src="./js/nexowatt-native-shell.js?v=7108"></script>
167
+ <script defer src="./js/eos-native-security.js?v=7108"></script>
168
+ <script defer src="./js/eos-basic-settings.js?v=7108"></script>
169
+ <script defer src="./js/eos-role-ui.js?v=7108"></script>
170
+ <script defer src="./js/eos-ems-overview.js?v=7108"></script>
171
171
 
172
172
  <script defer src="./js/eos-auto-update.js?v=7103"></script>
173
- <script defer src="./js/eos-assistant.js?v=7106"></script>
173
+ <script defer src="./js/eos-assistant.js?v=7108"></script>
174
174
  </head>
175
175
  <body>
176
176
  <noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
177
177
  <div id="root"></div>
178
- <script defer src="static/js/nexowatt-stable-login.js?v=7106"></script>
179
- <script defer src="static/js/nexowatt-stable-v7106.js?v=7106"></script>
178
+ <script defer src="static/js/nexowatt-stable-login.js?v=7108"></script>
179
+ <script defer src="static/js/nexowatt-stable-v7108.js?v=7108"></script>
180
180
  </body>
181
181
  </html>
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7106-admin-authoritative-rbac';
4
+ const VERSION = 'v7108-modern-core-surfaces-rbac';
5
5
  const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
6
6
  const entry = script?.dataset?.eosEntry || '';
7
7
  let launched = false;
@@ -25,11 +25,12 @@
25
25
  const isCustomerBackupTab = tab => /^(?:tab-)?(?:nexowatt-backup|eos-backup|nexowatt-sicherung)(?:-|$)/.test(normalize(tab));
26
26
  const isReleasedEndUserTab = tab => /^(?:tab-)?(?:nexowatt-ui|nexowatt-cockpit|eos-cockpit|eos-dashboard|kunden-cockpit|endkunden-cockpit|lovelace|jarvis|vis|iqontrol|material)(?:-|$)/.test(normalize(tab));
27
27
  const isOfficialReserveTab = tab => /^(?:tab-)?(?:admin|backitup)(?:-\d+)?$/.test(normalize(tab));
28
+ const isGloballyHiddenTab = tab => normalize(tab) === 'tab-enums';
28
29
  const isInstallerDenied = tab => /(?:tab-users|tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|users|hosts|files|console|terminal|security)/.test(normalize(tab)) || isOfficialReserveTab(tab);
29
- const isAllowed = (role, route) => route !== 'easy' && (role === 'admin'
30
+ const isAllowed = (role, route) => route !== 'easy' && !isGloballyHiddenTab(route) && (role === 'admin'
30
31
  || (role === 'installer'
31
32
  ? route === 'tab-intro' || isCustomerBackupTab(route) || !isInstallerDenied(route)
32
- : route === 'tab-intro' || route === 'tab-enums' || route === 'tab-objects' || isCustomerBackupTab(route) || isReleasedEndUserTab(route)));
33
+ : route === 'tab-intro' || route === 'tab-objects' || isCustomerBackupTab(route) || isReleasedEndUserTab(route)));
33
34
  const defaultTab = () => 'tab-intro';
34
35
  const currentRoute = () => {
35
36
  const hash = decodeURIComponent(window.location.hash || '').toLowerCase();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7106-admin-authoritative-rbac';
4
+ const VERSION = 'v7108-modern-core-surfaces-rbac';
5
5
  window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
6
6
 
7
7
  const state = {
@@ -45,6 +45,7 @@
45
45
  if (policy?.isInstaller || /installateur|installer|installation|inbetriebnahme|techniker|technician|integrator|partner/.test(raw)) return 'installer';
46
46
  return 'enduser';
47
47
  };
48
+ const isGloballyHiddenTab = tab => normalize(tab) === 'tab-enums';
48
49
  const isOfficialAdminTab = tab => /^tab-admin(?:-\d+)?$/.test(normalize(tab));
49
50
  const isOfficialBackupTab = tab => /^tab-backitup(?:-\d+)?$/.test(normalize(tab));
50
51
  const isOfficialReserveTab = tab => isOfficialAdminTab(tab) || isOfficialBackupTab(tab);
@@ -53,7 +54,7 @@
53
54
  const endUserDeniedByText = value => /(module|dienste|systemlogs|logs|zugange|rechte|benutzer|users|gruppen|groups|system hosts|system-hosts|hosts|dateien|files|konsole|console|terminal|xterm|adapter|instances?|instanzen|geraetesuche|discovery|systemschutz|security)/.test(normalize(value));
54
55
  const isEndUserTab = (tab, label = '') => {
55
56
  const clean = normalize(tab); const text = `${clean} ${normalize(label)}`;
56
- if (clean === 'tab-intro' || clean === 'tab-enums' || clean === 'tab-objects' || isCustomerBackupTab(clean)) return true;
57
+ if (clean === 'tab-intro' || clean === 'tab-objects' || isCustomerBackupTab(clean)) return true;
57
58
  if (/^tab-(nexowatt-ui|nexowatt-cockpit|eos-cockpit|eos-dashboard|kunden-cockpit|endkunden-cockpit|lovelace|jarvis|vis|iqontrol|material)(?:-|$)/.test(clean)) return true;
58
59
  return !!clean && !endUserDeniedByText(text) && endUserAllowedByText(text);
59
60
  };
@@ -64,7 +65,7 @@
64
65
  return /(?:tab-users|tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|users|benutzer|gruppen|groups|system-hosts|system hosts|hosts|dateien|files|konsole|console|terminal|xterm|systemschutz|security)/.test(text);
65
66
  };
66
67
  const isRouteAllowed = (role, route, label = '') => {
67
- if (route === 'easy') return false;
68
+ if (route === 'easy' || isGloballyHiddenTab(route)) return false;
68
69
  if (role === 'admin') return true;
69
70
  if (isOfficialReserveTab(route)) return false;
70
71
  if (role === 'installer') return route === 'tab-intro' || (route !== 'tab-users' && !isInstallerDenied(route, label));
@@ -136,6 +137,8 @@
136
137
  return normalize(`${button?.getAttribute?.('aria-label') || ''} ${button?.getAttribute?.('title') || ''} ${testIds}`);
137
138
  };
138
139
  const isSystemSettingsButton = button => /buildicon|system settings|systemeinstellungen|systeeminstellingen/.test(toolbarButtonSignature(button));
140
+ const isHeaderSystemSettingsButton = button => button?.dataset?.eosSystemSettingsControl === '1'
141
+ && !!button.closest?.('.eos-top-toolbar,.MuiAppBar-root .MuiToolbar-root');
139
142
  const isExpertModeButton = button => button?.dataset?.eosExpertControl === '1' || /expert|experten/.test(toolbarButtonSignature(button));
140
143
  const markAdminOnlyToolbarControls = () => safe(() => {
141
144
  const toolbar = document.querySelector('.eos-top-toolbar,.MuiAppBar-root .MuiToolbar-root');
@@ -227,7 +230,6 @@
227
230
  ['Dienste', 'Instanzen starten, stoppen und diagnostizieren', 'tab-instances', 'services'],
228
231
  ['Module', 'Module installieren, aktualisieren und konfigurieren', 'tab-adapters', 'modules'],
229
232
  ['Datenpunkte', 'Messwerte prüfen und freigegebene Werte schreiben', 'tab-objects', 'datapoints'],
230
- ['Struktur', 'Räume und Funktionen für Smart Home zuordnen', 'tab-enums', 'structure'],
231
233
  ['Geräte', 'Geräteintegration und Inbetriebnahme öffnen', 'tab-devicemanager', 'devices'],
232
234
  ['Basis-Einstellungen', 'Standort, Sprache und Anlagenparameter', 'basic-settings', 'settings'],
233
235
  ...(backupTab ? [['NexoWatt Sicherung', 'Sicherung und Wiederherstellung des EOS-Systems', backupTab, 'backup']] : []),
@@ -237,7 +239,6 @@
237
239
  return [
238
240
  ['EOS Cockpit', 'Energie, Laden und Gebäude bedienen', uiTab, 'eos'],
239
241
  ['Datenpunkte', 'Messwerte und Zustände ausschließlich lesen', 'tab-objects', 'datapoints'],
240
- ['Smart Home', 'Räume, Funktionen und freigegebene Geräte zuordnen', 'tab-enums', 'structure'],
241
242
  ...(backupTab ? [['NexoWatt Sicherung', 'Eigene Sicherungen ausführen und wiederherstellen', backupTab, 'backup']] : []),
242
243
  ];
243
244
  };
@@ -483,12 +484,26 @@
483
484
 
484
485
 
485
486
  const privilegedFrameObservers = new WeakMap();
486
- const privilegedActionPattern = /^(?:ems apps?(?: offnen| öffnen)?|app center|app-center|appcenter|simulation|simulator|lizenz|license)$/;
487
- const isPrivilegedAction = element => {
487
+ const privilegedActionKind = element => {
488
488
  const signature = normalize(textOf(element));
489
489
  const href = normalize(element?.getAttribute?.('href') || element?.href || element?.getAttribute?.('data-href') || '');
490
- return privilegedActionPattern.test(signature)
491
- || /(?:^|[\/#?&_.-])(?:appcenter|app-center|simulation|simulator|license|lizenz)(?:$|[\/#?&=_.-])/.test(href);
490
+ if (/^(?:ems apps?(?: offnen| öffnen)?|app center|app-center|appcenter)$/.test(signature)
491
+ || /(?:^|[\/#?&_.-])(?:appcenter|app-center)(?:$|[\/#?&=_.-])/.test(href)) return 'ems';
492
+ if (/^(?:simulation|simulator)$/.test(signature)
493
+ || /(?:^|[\/#?&_.-])(?:simulation|simulator)(?:$|[\/#?&=_.-])/.test(href)) return 'simulation';
494
+ if (/^(?:lizenz|license)$/.test(signature)
495
+ || /(?:^|[\/#?&_.-])(?:license|lizenz)(?:$|[\/#?&=_.-])/.test(href)) return 'license';
496
+ return '';
497
+ };
498
+ const isPrivilegedAction = element => !!privilegedActionKind(element);
499
+ const canUsePrivilegedAction = element => {
500
+ const action = privilegedActionKind(element);
501
+ if (!action) return true;
502
+ if (action === 'ems') {
503
+ return state.role === 'admin'
504
+ || (state.role === 'installer' && state.policy?.capabilities?.emsAppCenter !== false);
505
+ }
506
+ return state.role === 'admin';
492
507
  };
493
508
  const setPrivilegedActionHidden = (element, hidden) => {
494
509
  if (!element) return;
@@ -507,19 +522,18 @@
507
522
  };
508
523
  const applyPrivilegedActionsInDocument = doc => safe(() => {
509
524
  if (!doc?.documentElement) return;
510
- const admin = state.role === 'admin';
511
525
  doc.querySelectorAll('[data-eos-privileged-action-hidden="1"]').forEach(element => {
512
- if (admin || !isPrivilegedAction(element)) setPrivilegedActionHidden(element, false);
526
+ if (!isPrivilegedAction(element) || canUsePrivilegedAction(element)) setPrivilegedActionHidden(element, false);
513
527
  });
514
528
  for (const element of doc.querySelectorAll('a,button,[role="button"],[role="tab"],.MuiButtonBase-root')) {
515
- if (isPrivilegedAction(element)) setPrivilegedActionHidden(element, !admin);
529
+ if (isPrivilegedAction(element)) setPrivilegedActionHidden(element, !canUsePrivilegedAction(element));
516
530
  }
517
531
  if (!doc.documentElement.dataset.eosPrivilegedClickGuard) {
518
532
  doc.documentElement.dataset.eosPrivilegedClickGuard = '1';
519
533
  doc.addEventListener('click', event => {
520
- if (state.role === 'admin' || state.role === 'unknown') return;
534
+ if (state.role === 'unknown') return;
521
535
  const target = event.target?.closest?.('a,button,[role="button"],[role="tab"],.MuiButtonBase-root');
522
- if (!target || !isPrivilegedAction(target)) return;
536
+ if (!target || !isPrivilegedAction(target) || canUsePrivilegedAction(target)) return;
523
537
  event.preventDefault();
524
538
  event.stopImmediatePropagation();
525
539
  }, true);
@@ -544,9 +558,9 @@
544
558
  });
545
559
  });
546
560
  const scrubInheritedUiAdminSession = () => safe(() => {
547
- if (state.role === 'admin' || state.role === 'unknown') return;
561
+ if (state.role !== 'enduser') return;
548
562
  const user = String(state.policy?.user || state.policy?.userId || '').replace(/^system\.user\./, '') || state.role;
549
- const key = `eosUiAdminSessionScrubbed:v7106:${user}:${state.role}`;
563
+ const key = `eosUiAdminSessionScrubbed:v7108:${user}:${state.role}`;
550
564
  if (sessionStorage.getItem(key) === '1') return;
551
565
  sessionStorage.setItem(key, '1');
552
566
  const frame = document.createElement('iframe');
@@ -676,7 +690,7 @@
676
690
  if (clickedButton && isExpertModeButton(clickedButton)) {
677
691
  event.preventDefault(); event.stopImmediatePropagation(); lockExpertMode(); return;
678
692
  }
679
- if (clickedButton && isSystemSettingsButton(clickedButton)) {
693
+ if (clickedButton && isHeaderSystemSettingsButton(clickedButton)) {
680
694
  event.preventDefault(); event.stopImmediatePropagation();
681
695
  if (state.role === 'installer') window.NEXOWATT_EOS_BASIC_SETTINGS?.open?.();
682
696
  return;
@@ -688,7 +702,7 @@
688
702
  const target = event.target?.closest?.('[data-eos-admin-only-control="1"],[data-eos-system-settings-control="1"],[data-eos-role-tab],a[href*="#tab-"],a[href*="#/tab-"]');
689
703
  if (!target) return;
690
704
  if (target.matches('[data-eos-admin-only-control="1"]')) { event.preventDefault(); event.stopImmediatePropagation(); return; }
691
- if (target.matches('[data-eos-system-settings-control="1"]') && state.role === 'installer') {
705
+ if (isHeaderSystemSettingsButton(target) && state.role === 'installer') {
692
706
  event.preventDefault(); event.stopImmediatePropagation(); window.NEXOWATT_EOS_BASIC_SETTINGS?.open?.(); return;
693
707
  }
694
708
  const roleTab = target.getAttribute('data-eos-role-tab');
@@ -709,6 +723,8 @@
709
723
  getRole: () => state.role,
710
724
  getPolicy: () => state.policy,
711
725
  isRouteAllowed,
726
+ isGloballyHiddenTab,
727
+ isHeaderSystemSettingsButton,
712
728
  defaultTab,
713
729
  isOfficialReserveTab,
714
730
  isCustomerBackupTab,
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7106-nexowatt-native-shell-native-overview';
4
+ const VERSION = 'v7108-nexowatt-native-shell-modern-core-surfaces';
5
5
  const previous = window.NEXOWATT_NATIVE_SHELL;
6
6
  if (previous?.version === VERSION) return;
7
7
  previous?.destroy?.();
@@ -25,7 +25,27 @@
25
25
  const safe = fn => {
26
26
  try { return fn(); } catch (_) { return undefined; }
27
27
  };
28
-
28
+ const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, match => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[match] || match));
29
+ const TECHNICAL_SURFACES = Object.freeze({
30
+ instances: {
31
+ eyebrow: 'EOS SYSTEMDIENSTE',
32
+ title: 'Dienste & Instanzen',
33
+ text: 'Laufzeit, Status, Ressourcen und Konfiguration aller aktiven EOS-Dienste auf einen Blick.',
34
+ icon: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="6" rx="2"/><rect x="4" y="14" width="16" height="6" rx="2"/><path d="M8 7h.01M8 17h.01M12 7h5M12 17h5"/></svg>',
35
+ },
36
+ objects: {
37
+ eyebrow: 'EOS DATENEBENE',
38
+ title: 'Datenpunkte & Zustände',
39
+ text: 'Messwerte, Status und freigegebene Steuerwerte in einer klaren technischen Ansicht.',
40
+ icon: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="6" cy="12" r="2.5"/><circle cx="18" cy="6" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M8.3 10.9 15.7 7M8.3 13.1l7.4 3.9"/></svg>',
41
+ },
42
+ logs: {
43
+ eyebrow: 'EOS DIAGNOSE',
44
+ title: 'Systemlogs',
45
+ text: 'Live-Protokoll für Adapter, Dienste und Systemereignisse mit klarer Priorisierung.',
46
+ icon: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5h14M5 10h14M5 15h9M5 20h6"/><circle cx="18" cy="17" r="3"/><path d="m20.2 19.2 1.8 1.8"/></svg>',
47
+ },
48
+ });
29
49
 
30
50
  // Navigation labels and icons are rendered natively by Drawer.tsx.
31
51
  // This runtime only positions the shell and never rewrites React content.
@@ -177,6 +197,85 @@
177
197
  <div class="eos-overview-role" data-nexowatt-overview-role="${role}"><span class="eos-overview-status-dot"></span>${config.label}</div>`;
178
198
  };
179
199
 
200
+ const roleLabel = () => {
201
+ const role = String(window.NEXOWATT_EOS_ACCESS_ROLE || 'unknown').toLowerCase();
202
+ if (role === 'admin') return 'Admin / Service';
203
+ if (role === 'installer' || role === 'installateur') return 'Installateur';
204
+ return 'Endkunde';
205
+ };
206
+
207
+ const classifyTechnicalRows = (paper, surface) => {
208
+ if (!paper || !surface) return;
209
+ if (surface === 'instances') {
210
+ paper.querySelectorAll('.MuiAccordion-root,tbody tr,.MuiTableRow-root,[role="row"]').forEach(row => {
211
+ if (row.closest?.('.eos-technical-surface-header') || row.classList.contains('MuiTableHead-root')) return;
212
+ row.classList.add('eos-service-row');
213
+ const expanded = row.classList.contains('Mui-expanded') || !!row.querySelector?.('[aria-expanded="true"]');
214
+ row.classList.toggle('eos-service-row-expanded', expanded);
215
+ });
216
+ return;
217
+ }
218
+ if (surface === 'objects') {
219
+ paper.querySelectorAll('[role="row"],.MuiTableRow-root,.MuiDataGrid-row').forEach(row => {
220
+ if (row.closest?.('.eos-technical-surface-header')) return;
221
+ row.classList.add('eos-datapoint-row');
222
+ });
223
+ return;
224
+ }
225
+ if (surface === 'logs') {
226
+ paper.querySelectorAll('tbody tr,.MuiTableRow-root,[role="row"]').forEach(row => {
227
+ if (row.closest?.('.eos-technical-surface-header')) return;
228
+ const text = String(row.textContent || '').toLowerCase();
229
+ let level = 'info';
230
+ if (/\b(error|fatal|fehler|kritisch|critical)\b/.test(text)) level = 'error';
231
+ else if (/\b(warn|warning|warnung)\b/.test(text)) level = 'warn';
232
+ else if (/\b(debug|trace)\b/.test(text)) level = 'debug';
233
+ row.classList.remove('eos-log-row-error','eos-log-row-warn','eos-log-row-info','eos-log-row-debug');
234
+ row.classList.add('eos-log-row', `eos-log-row-${level}`);
235
+ row.dataset.eosLogLevel = level;
236
+ });
237
+ }
238
+ };
239
+
240
+ const ensureTechnicalSurface = () => {
241
+ const paper = document.getElementById('app-paper');
242
+ if (!paper || isLogin()) return;
243
+ const surface = routeKey(currentTab());
244
+ const config = TECHNICAL_SURFACES[surface];
245
+ const existing = document.getElementById('eos-technical-surface-header');
246
+ if (!config) {
247
+ existing?.remove();
248
+ paper.removeAttribute('data-eos-technical-surface');
249
+ return;
250
+ }
251
+ paper.setAttribute('data-eos-technical-surface', surface);
252
+ let header = existing;
253
+ if (!header) {
254
+ header = document.createElement('section');
255
+ header.id = 'eos-technical-surface-header';
256
+ header.className = 'eos-technical-surface-header';
257
+ header.setAttribute('aria-live', 'polite');
258
+ paper.insertBefore(header, paper.firstChild || null);
259
+ }
260
+ if (header.dataset.eosSurface !== surface || header.dataset.eosRole !== roleLabel()) {
261
+ header.dataset.eosSurface = surface;
262
+ header.dataset.eosRole = roleLabel();
263
+ header.innerHTML = `
264
+ <div class="eos-technical-surface-icon">${config.icon}</div>
265
+ <div class="eos-technical-surface-copy">
266
+ <span>${escapeHtml(config.eyebrow)}</span>
267
+ <h1>${escapeHtml(config.title)}</h1>
268
+ <p>${escapeHtml(config.text)}</p>
269
+ </div>
270
+ <div class="eos-technical-surface-meta">
271
+ <i aria-hidden="true"></i>
272
+ <strong>${escapeHtml(roleLabel())}</strong>
273
+ <small>${surface === 'logs' ? 'Live-Diagnose' : surface === 'instances' ? 'Betriebsbereit' : 'Zentrale Datenansicht'}</small>
274
+ </div>`;
275
+ }
276
+ classifyTechnicalRows(paper, surface);
277
+ };
278
+
180
279
  const apply = () => {
181
280
  if (destroyed) return;
182
281
  setShellClasses();
@@ -184,8 +283,10 @@
184
283
  ensureBrand();
185
284
  ensureNavigationContainer();
186
285
  ensureModernOverview();
286
+ ensureTechnicalSurface();
187
287
  } else {
188
288
  document.getElementById('eos-native-overview-hero')?.remove();
289
+ document.getElementById('eos-technical-surface-header')?.remove();
189
290
  }
190
291
  };
191
292
 
@@ -231,6 +332,7 @@
231
332
  unsubscribeDom = null;
232
333
  if (retryTimer) clearTimeout(retryTimer);
233
334
  retryTimer = 0;
335
+ document.getElementById('eos-technical-surface-header')?.remove();
234
336
  },
235
337
  };
236
338
  })();
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- const VERSION = 'v7106-scroll-standard-password';
3
+ const VERSION = 'v7108-scroll-standard-password';
4
4
  const ensureScroll = () => {
5
5
  const root = document.documentElement;
6
6
  const paper = document.getElementById('app-paper');
@@ -23,5 +23,5 @@
23
23
  const schedule=()=>{if(scheduled)return;scheduled=true;requestAnimationFrame(()=>{scheduled=false;apply();});};
24
24
  const start=()=>{apply();new MutationObserver(schedule).observe(document.documentElement,{subtree:true,childList:true});window.addEventListener('hashchange',schedule);window.addEventListener('resize',schedule,{passive:true});};
25
25
  document.readyState==='loading'?document.addEventListener('DOMContentLoaded',start,{once:true}):start();
26
- window.NEXOWATT_EOS_STABLE_V7101=Object.freeze({version:VERSION,refresh:apply,ensureScroll});
26
+ window.NEXOWATT_EOS_STABLE_V7108=Object.freeze({version:VERSION,refresh:apply,ensureScroll});
27
27
  })();
package/build/lib/web.js CHANGED
@@ -650,6 +650,9 @@ class Web {
650
650
  objectDiagnostics: technical,
651
651
  logDiagnostics: technical,
652
652
  basicSystemSettings: technical,
653
+ emsAppCenter: technical,
654
+ simulation: role === 'admin',
655
+ licenseAdministration: role === 'admin',
653
656
  fullSystemSettings: role === 'admin',
654
657
  expertMode: role === 'admin',
655
658
  userManagement: role === 'admin',
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "eos-admin",
4
- "version": "7.10.6",
4
+ "version": "7.10.8",
5
5
  "titleLang": {
6
6
  "en": "NexoWatt EOS Admin",
7
7
  "de": "NexoWatt EOS Admin",
@@ -18,6 +18,11 @@
18
18
  "connectionType": "local",
19
19
  "dataSource": "push",
20
20
  "news": {
21
+ "7.10.7": {
22
+ "de": "Stable 7.10.7 gibt dem Installateur den Zugriff auf „EMS Apps öffnen“ und das EMS App-Center frei. Simulation und Lizenzverwaltung bleiben ausschließlich dem Administrator vorbehalten. Endkundenbereich, Nur-Lese-Datenpunkte und Adminrechte bleiben unverändert.",
23
+ "en": "Stable 7.10.7 grants the Installer access to “Open EMS Apps” and the EMS App-Center. Simulation and license administration remain Administrator-only. End User read-only datapoints and all Administrator rights remain unchanged.",
24
+ "nl": "Stable 7.10.7 geeft de installateur toegang tot “EMS Apps openen” en het EMS App-Center. Simulatie en licentiebeheer blijven uitsluitend voor beheerders. Alleen-lezen datapunten voor eindgebruikers en alle beheerdersrechten blijven ongewijzigd."
25
+ },
21
26
  "7.10.6": {
22
27
  "de": "Stable 7.10.6 bereinigt ausschließlich den Endkundenbereich: Datenpunkte bleiben sichtbar und nur lesbar, Bearbeiten-/Löschen-/Einstellungsaktionen werden ausgeblendet, und die Systeminfokarte erhält Plattform, RAM, Node.js und NPM über einen authentifizierten Nur-Lese-Endpunkt. Die Administratoroberfläche und sämtliche Adminrechte bleiben unverändert.",
23
28
  "en": "Stable 7.10.6 cleans up the End User area only: datapoints stay visible and read-only, edit/delete/settings actions are hidden, and the system information card receives platform, RAM, Node.js and NPM through an authenticated read-only endpoint. The administrator UI and all Admin rights remain unchanged.",
@@ -359,7 +364,7 @@
359
364
  "icon": "admin.svg",
360
365
  "messagebox": true,
361
366
  "enabled": true,
362
- "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.6/admin/admin.svg",
367
+ "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.8/admin/admin.svg",
363
368
  "keywords": [
364
369
  "NexoWatt",
365
370
  "EOS",
@@ -370,7 +375,7 @@
370
375
  "licensed"
371
376
  ],
372
377
  "compact": true,
373
- "readme": "https://unpkg.com/iobroker.eos-admin@7.10.6/README.md",
378
+ "readme": "https://unpkg.com/iobroker.eos-admin@7.10.8/README.md",
374
379
  "authors": [
375
380
  "NexoWatt"
376
381
  ],
@@ -438,7 +443,7 @@
438
443
  "nondeletable": false,
439
444
  "allowAdapterUpdate": true,
440
445
  "allowAdapterDelete": false,
441
- "meta": "https://unpkg.com/iobroker.eos-admin@7.10.6/io-package.json",
446
+ "meta": "https://unpkg.com/iobroker.eos-admin@7.10.8/io-package.json",
442
447
  "npmPackage": "iobroker.eos-admin",
443
448
  "packetName": "iobroker.eos-admin",
444
449
  "publisher": "NexoWatt"
@@ -1215,5 +1220,5 @@
1215
1220
  }
1216
1221
  }
1217
1222
  },
1218
- "version": "7.10.6"
1223
+ "version": "7.10.8"
1219
1224
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
3
  "description": "NexoWatt EOS Admin – repaired stable baseline with restart-safe automatic updates, native account passwords and persistent cockpit scrolling.",
4
- "version": "7.10.6",
4
+ "version": "7.10.8",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -133,16 +133,18 @@
133
133
  "NEXOWATT_EOS_PREBUILT_MANIFEST.json",
134
134
  "tools/nexowatt-prebuilt-release-selftest.cjs",
135
135
  "tools/nexowatt-stable-v7103-selftest.cjs",
136
- "README_STABILITY_V7.10.6.md",
137
- "RELEASE_NOTES_V7.10.6.md",
138
- "PUBLISH_STABLE_V7.10.6.md",
139
- "INSTALL_TEST_V7.10.6.md",
140
- "RELEASE_ACCEPTANCE_V7.10.6.md",
141
- "MERGE_UPDATE_README_V7.10.6.md",
136
+ "README_STABILITY_V7.10.7.md",
137
+ "RELEASE_NOTES_V7.10.7.md",
138
+ "PUBLISH_STABLE_V7.10.7.md",
139
+ "INSTALL_TEST_V7.10.7.md",
140
+ "RELEASE_ACCEPTANCE_V7.10.7.md",
141
+ "MERGE_UPDATE_README_V7.10.7.md",
142
142
  "tools/nexowatt-auto-update-placement-selftest.cjs",
143
143
  "tools/nexowatt-role-security-selftest.cjs",
144
- "SECURITY_ROLE_MATRIX_V7.10.6.md",
145
- "tools/nexowatt-user-area-selftest.cjs"
144
+ "SECURITY_ROLE_MATRIX_V7.10.7.md",
145
+ "tools/nexowatt-user-area-selftest.cjs",
146
+ "tools/nexowatt-installer-ems-selftest.cjs",
147
+ "tools/nexowatt-v7108-ui-foundation-selftest.cjs"
146
148
  ],
147
149
  "scripts": {
148
150
  "npm": "npm i -f && cd src-admin && npm i -f",
@@ -177,7 +179,7 @@
177
179
  "nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
178
180
  "nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
179
181
  "nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
180
- "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7103 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs && node tools/nexowatt-auto-update-placement-selftest.cjs && node tools/nexowatt-role-security-selftest.cjs && node tools/nexowatt-user-area-selftest.cjs",
182
+ "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7103 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs && node tools/nexowatt-auto-update-placement-selftest.cjs && node tools/nexowatt-role-security-selftest.cjs && node tools/nexowatt-user-area-selftest.cjs && node tools/nexowatt-installer-ems-selftest.cjs && node tools/nexowatt-v7108-ui-foundation-selftest.cjs",
181
183
  "nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
182
184
  "clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
183
185
  "precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
@@ -206,7 +208,9 @@
206
208
  "seal:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs --write",
207
209
  "check:eos-stable-v7103": "node tools/nexowatt-stable-v7103-selftest.cjs",
208
210
  "test:eos-auto-update-placement": "node tools/nexowatt-auto-update-placement-selftest.cjs",
209
- "check:eos-role-security": "node tools/nexowatt-role-security-selftest.cjs"
211
+ "check:eos-role-security": "node tools/nexowatt-role-security-selftest.cjs",
212
+ "test:eos-installer-ems": "node tools/nexowatt-installer-ems-selftest.cjs",
213
+ "check:eos-v7108-ui": "node tools/nexowatt-v7108-ui-foundation-selftest.cjs"
210
214
  },
211
215
  "author": "NexoWatt",
212
216
  "private": false,
@@ -217,7 +221,7 @@
217
221
  },
218
222
  "nexowattReleasePolicy": {
219
223
  "distTag": "latest",
220
- "purpose": "accepted stable NexoWatt EOS End User read-only UI and system information correction 7.10.6"
224
+ "purpose": "stable NexoWatt EOS Admin 7.10.8 modern core surfaces and installer service controls"
221
225
  },
222
226
  "publisher": "NexoWatt"
223
227
  }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const root = path.resolve(__dirname, '..');
9
+ const read = file => fs.readFileSync(path.join(root, file), 'utf8');
10
+ const json = file => JSON.parse(read(file));
11
+
12
+ const pkg = json('package.json');
13
+ const sourceRole = read('src-admin/public/js/eos-role-ui.js');
14
+ const runtimeRole = read('adminWww/js/eos-role-ui.js');
15
+ const sourceWeb = read('src/lib/web.ts');
16
+ const runtimeWeb = read('build/lib/web.js');
17
+
18
+ assert.equal(pkg.version, '7.10.8', 'Installer EMS release must use npm version 7.10.8');
19
+ assert.equal(runtimeRole, sourceRole, 'built and source role runtimes must be identical');
20
+
21
+ for (const code of [sourceRole, runtimeRole]) {
22
+ assert.match(code, /const privilegedActionKind = element =>/, 'privileged action classifier missing');
23
+ assert.match(code, /return 'ems';/, 'EMS action classification missing');
24
+ assert.match(code, /return 'simulation';/, 'Simulation action classification missing');
25
+ assert.match(code, /return 'license';/, 'License action classification missing');
26
+ assert.match(code, /action === 'ems'/, 'EMS-specific access branch missing');
27
+ assert.match(
28
+ code,
29
+ /state\.role === 'installer' && state\.policy\?\.capabilities\?\.emsAppCenter !== false/,
30
+ 'Installer is not allowed to open EMS App-Center',
31
+ );
32
+ assert.match(code, /return state\.role === 'admin';/, 'Simulation and License must remain Admin-only');
33
+ assert.match(code, /!canUsePrivilegedAction\(element\)/, 'UI visibility does not use the role-aware action permission');
34
+ assert.match(code, /\|\| canUsePrivilegedAction\(target\)\) return;/, 'click guard does not use the role-aware action permission');
35
+ assert.match(code, /if \(state\.role !== 'enduser'\) return;/, 'port 8188 session scrubbing must apply only to End User');
36
+ }
37
+
38
+ for (const code of [sourceWeb, runtimeWeb]) {
39
+ assert.match(code, /emsAppCenter: technical/, 'backend capability must grant EMS App-Center to Admin and Installer');
40
+ assert.match(code, /simulation: role === 'admin'/, 'Simulation capability must remain Admin-only');
41
+ assert.match(code, /licenseAdministration: role === 'admin'/, 'License capability must remain Admin-only');
42
+ }
43
+
44
+ console.log('[NexoWatt EOS installer EMS] OK (Installer may use EMS App-Center; Simulation/License remain Admin-only; End User remains blocked)');
@@ -16,7 +16,7 @@ for (const file of [
16
16
  'MERGE_UPDATE.ps1',
17
17
  'MERGE_UPDATE.cmd',
18
18
  'MERGE_UPDATE.sh',
19
- 'MERGE_UPDATE_README_V7.10.6.md',
19
+ 'MERGE_UPDATE_README_V7.10.7.md',
20
20
  'tools/nexowatt-apply-ems-overview.cjs',
21
21
  'tools/nexowatt-sync-release-version.cjs',
22
22
  'tools/nexowatt-version-sync-selftest.cjs',