iobroker.eos-admin 7.9.27 → 7.9.28

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.
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_UI_VERSION = 'v27-header-logo-nav-assist';
4
+ window.NEXOWATT_EOS_UI_VERSION = 'v27-logo-nav-assist-polish';
5
5
 
6
6
  const BRAND = 'NexoWatt EOS';
7
7
  const EOS_MEANING = 'Energy Operation System';
@@ -72,6 +72,7 @@
72
72
  protectedAdapters: ['eos-admin', 'backitup'],
73
73
  },
74
74
  securityFetchStarted: false,
75
+ assistOpen: false,
75
76
  };
76
77
 
77
78
  const safe = fn => {
@@ -490,44 +491,6 @@
490
491
  removeLogoutButton();
491
492
  };
492
493
 
493
- const NAV_COMPACT_KEY = 'nexowatt.eos.navCompact';
494
-
495
- const setNavCompact = value => safe(() => {
496
- const active = !!value;
497
- document.documentElement.classList.toggle('eos-nav-compact', active);
498
- window.localStorage && window.localStorage.setItem(NAV_COMPACT_KEY, active ? '1' : '0');
499
- });
500
-
501
- const restoreNavCompact = () => safe(() => {
502
- const saved = window.localStorage && window.localStorage.getItem(NAV_COMPACT_KEY);
503
- if (saved === '1') document.documentElement.classList.add('eos-nav-compact');
504
- if (saved === '0') document.documentElement.classList.remove('eos-nav-compact');
505
- });
506
-
507
- const bindCompactNavToggle = header => safe(() => {
508
- if (!header) return;
509
- restoreNavCompact();
510
- const controls = Array.from(header.querySelectorAll('button, .MuiIconButton-root, [role="button"]'));
511
- controls.forEach(control => {
512
- if (control.dataset.eosCompactToggleBound === '1') return;
513
- control.dataset.eosCompactToggleBound = '1';
514
- control.setAttribute('title', 'Menü kompakt/erweitert anzeigen');
515
- control.setAttribute('aria-label', 'Menü kompakt/erweitert anzeigen');
516
- const toggle = event => {
517
- event.preventDefault();
518
- event.stopPropagation();
519
- if (event.stopImmediatePropagation) event.stopImmediatePropagation();
520
- setNavCompact(!document.documentElement.classList.contains('eos-nav-compact'));
521
- scheduleFullPatch(0);
522
- return false;
523
- };
524
- control.addEventListener('click', toggle, true);
525
- control.addEventListener('keydown', event => {
526
- if (event.key === 'Enter' || event.key === ' ') toggle(event);
527
- }, true);
528
- });
529
- });
530
-
531
494
  const hideNativeLogoutNav = () => safe(() => {
532
495
  const candidates = Array.from(document.querySelectorAll('.MuiDrawer-paper a, .MuiDrawer-paper button, .MuiDrawer-paper .MuiListItem-root, .MuiDrawer-paper .MuiListItemButton-root, .MuiDrawer-paper [role=\"button\"]'));
533
496
  candidates.forEach(el => {
@@ -556,7 +519,26 @@
556
519
  }
557
520
  if (header) {
558
521
  header.classList.add('eos-native-drawer-header');
559
- bindCompactNavToggle(header);
522
+ const toggleButton = header.querySelector('button, .MuiIconButton-root, [role="button"]');
523
+ if (toggleButton && !toggleButton.dataset.eosNavCompactToggle) {
524
+ toggleButton.dataset.eosNavCompactToggle = 'true';
525
+ toggleButton.classList.add('eos-nav-compact-toggle');
526
+ toggleButton.setAttribute('title', 'Navigation kompakt/normal umschalten');
527
+ toggleButton.setAttribute('aria-label', 'Navigation kompakt/normal umschalten');
528
+ const toggleCompact = event => {
529
+ event.preventDefault();
530
+ event.stopPropagation();
531
+ if (event.stopImmediatePropagation) event.stopImmediatePropagation();
532
+ const compact = !document.documentElement.classList.contains('eos-nav-compact');
533
+ document.documentElement.classList.toggle('eos-nav-compact', compact);
534
+ safe(() => localStorage.setItem('nexowatt:eosNavCompact', compact ? '1' : '0'));
535
+ toggleButton.setAttribute('aria-pressed', compact ? 'true' : 'false');
536
+ };
537
+ toggleButton.addEventListener('click', toggleCompact, true);
538
+ toggleButton.addEventListener('keydown', event => {
539
+ if (event.key === 'Enter' || event.key === ' ') toggleCompact(event);
540
+ }, true);
541
+ }
560
542
  const img = header.querySelector('img');
561
543
  if (img) patchImage(img);
562
544
  const avatarImg = header.querySelector('.MuiAvatar-img');
@@ -767,6 +749,116 @@
767
749
  });
768
750
  });
769
751
 
752
+
753
+ const applyNavCompactPreference = () => safe(() => {
754
+ const compact = localStorage.getItem('nexowatt:eosNavCompact') === '1';
755
+ document.documentElement.classList.toggle('eos-nav-compact', compact);
756
+ document.querySelectorAll('.eos-nav-compact-toggle').forEach(button => {
757
+ button.setAttribute('aria-pressed', compact ? 'true' : 'false');
758
+ button.setAttribute('title', compact ? 'Navigation normal anzeigen' : 'Navigation kompakt anzeigen');
759
+ });
760
+ });
761
+
762
+ const assistContext = () => {
763
+ const routes = routeInfo();
764
+ if (routes.adapters) return {
765
+ title: 'Module einrichten',
766
+ text: 'Wähle zuerst das passende Modul, prüfe Version und Instanzstatus und öffne dann die Konfiguration über die drei Punkte oder das Werkzeug-Symbol.',
767
+ steps: ['Modul suchen', 'Instanz anlegen', 'Verbindung testen', 'Datenpunkte prüfen']
768
+ };
769
+ if (routes.instances) return {
770
+ title: 'Dienste prüfen',
771
+ text: 'Kontrolliere Status, Port, Speicherverbrauch und Logmeldungen. Gestoppte Dienste erst nach Ursache und Abhängigkeiten prüfen.',
772
+ steps: ['Status ansehen', 'Log öffnen', 'Konfiguration prüfen', 'Dienst neu starten']
773
+ };
774
+ if (routes.users) return {
775
+ title: 'Zugänge & Rechte',
776
+ text: 'Installateure und Endkunden sollten nur die Module sehen, die sie wirklich bedienen dürfen. Geschützte EOS-Module bleiben Administratoren vorbehalten.',
777
+ steps: ['Benutzer wählen', 'Rolle zuordnen', 'Rechteprofil setzen', 'Löschschutz prüfen']
778
+ };
779
+ if (routeInfo().intro) return {
780
+ title: 'EOS Cockpit',
781
+ text: 'Beginne mit Systemstatus, Hosts und gesicherten Basisdiensten. Danach Module Schritt für Schritt freischalten.',
782
+ steps: ['System prüfen', 'Sicherung prüfen', 'Module planen', 'Rechte vergeben']
783
+ };
784
+ return {
785
+ title: 'EOS Bedienhilfe',
786
+ text: 'Ich unterstütze dich beim Einrichten, Prüfen und Absichern deiner EOS-Module.',
787
+ steps: ['Ziel beschreiben', 'Modul auswählen', 'Parameter prüfen', 'Test starten']
788
+ };
789
+ };
790
+
791
+ const assistAnswer = query => {
792
+ const q = normalize(query);
793
+ if (!q) return 'Beschreibe kurz, was eingerichtet werden soll, zum Beispiel: PV, Speicher, Wallbox, Modbus, Backup oder Benutzerrechte.';
794
+ if (/wallbox|evcs|lade|auto|ocpp/.test(q)) return 'Für Ladepunkte: zuerst OCPP/EVCS-Modul installieren, Verbindung zur Wallbox prüfen, Ladepunkt-ID setzen, danach Datenpunkte für Status, Leistung und Freigabe testen.';
795
+ if (/pv|solar|wechselrichter|sun2000|fronius|kostal|sma/.test(q)) return 'Für PV/Wechselrichter: IP-Adresse, Modbus/TCP oder Hersteller-API aktivieren, Abfrageintervall moderat setzen und danach Erzeugung, Bezug und Einspeisung in den Datenpunkten prüfen.';
796
+ if (/speicher|batterie|akku/.test(q)) return 'Für Speicher: Kommunikationsweg prüfen, Lade-/Entladeleistung und SoC-Datenpunkte kontrollieren und Grenzwerte erst setzen, wenn Livewerte plausibel sind.';
797
+ if (/modbus/.test(q)) return 'Für Modbus: Host/IP, Port 502, Unit-ID und Registertabelle prüfen. Danach erst lesen testen, dann Schreibrechte gezielt freischalten.';
798
+ if (/mqtt/.test(q)) return 'Für MQTT: Broker-Adresse, Zugangsdaten, Topic-Struktur und TLS prüfen. Danach mit einem Test-Topic starten und erst dann produktive Topics freigeben.';
799
+ if (/backup|sicherung|restore/.test(q)) return 'Für Sicherung: BackItUp aktiv halten, Zielpfad oder Cloud-Ziel prüfen, Test-Backup starten und Restore-Hinweise dokumentieren.';
800
+ if (/rechte|benutzer|installateur|kunde|admin/.test(q)) return 'Für Rechte: Benutzer in Rollen trennen. Installateure dürfen konfigurieren, aber geschützte EOS-Systemmodule nicht löschen. Endkunden bekommen nur Bedien- und Leserechte.';
801
+ if (/fehler|log|offline|404|timeout/.test(q)) return 'Bei Fehlern: zuerst Systemlogs öffnen, betroffene Instanz filtern, letzte Änderung prüfen, Dienst neu starten und danach Port/WebSocket/Repository prüfen.';
802
+ return 'Vorschlag: Starte mit dem passenden Modul, prüfe die Verbindung, kontrolliere die erzeugten Datenpunkte und sichere danach die Rechte. Für konkrete Hilfe nenne bitte Modulname und Zielgerät.';
803
+ };
804
+
805
+ const ensureEosAssist = () => safe(() => {
806
+ const hasApp = !!document.getElementById('app-paper');
807
+ if (!hasApp || isLoginView()) {
808
+ document.getElementById('eos-assist-root')?.remove();
809
+ return;
810
+ }
811
+ let root = document.getElementById('eos-assist-root');
812
+ if (!root) {
813
+ root = document.createElement('section');
814
+ root.id = 'eos-assist-root';
815
+ root.className = 'eos-assist-root';
816
+ document.body.appendChild(root);
817
+ }
818
+ const ctx = assistContext();
819
+ root.classList.toggle('eos-assist-open', !!state.assistOpen);
820
+ root.innerHTML = `
821
+ <button class="eos-assist-button" type="button" aria-expanded="${state.assistOpen ? 'true' : 'false'}">
822
+ <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Einrichtungshilfe</small>
823
+ </button>
824
+ <div class="eos-assist-panel" role="dialog" aria-label="EOS Assist Einrichtungshilfe">
825
+ <div class="eos-assist-head">
826
+ <div><strong>${ctx.title}</strong><span>${ctx.text}</span></div>
827
+ <button type="button" class="eos-assist-close" aria-label="EOS Assist schließen">×</button>
828
+ </div>
829
+ <div class="eos-assist-steps">${ctx.steps.map(step => `<span>${step}</span>`).join('')}</div>
830
+ <label class="eos-assist-input-label">Was möchtest du einrichten?</label>
831
+ <div class="eos-assist-input-row"><input class="eos-assist-input" placeholder="z. B. Wallbox, PV, Modbus, Rechte..." /><button type="button" class="eos-assist-send">Fragen</button></div>
832
+ <div class="eos-assist-answer">${assistAnswer('')}</div>
833
+ </div>
834
+ `;
835
+ const openButton = root.querySelector('.eos-assist-button');
836
+ const closeButton = root.querySelector('.eos-assist-close');
837
+ const input = root.querySelector('.eos-assist-input');
838
+ const send = root.querySelector('.eos-assist-send');
839
+ const answer = root.querySelector('.eos-assist-answer');
840
+ openButton?.addEventListener('click', event => {
841
+ event.preventDefault();
842
+ state.assistOpen = !state.assistOpen;
843
+ root.classList.toggle('eos-assist-open', state.assistOpen);
844
+ openButton.setAttribute('aria-expanded', state.assistOpen ? 'true' : 'false');
845
+ if (state.assistOpen) setTimeout(() => input?.focus(), 40);
846
+ });
847
+ closeButton?.addEventListener('click', event => {
848
+ event.preventDefault();
849
+ state.assistOpen = false;
850
+ root.classList.remove('eos-assist-open');
851
+ openButton?.setAttribute('aria-expanded', 'false');
852
+ });
853
+ const ask = () => {
854
+ if (answer) answer.textContent = assistAnswer(input?.value || '');
855
+ };
856
+ send?.addEventListener('click', ask);
857
+ input?.addEventListener('keydown', event => {
858
+ if (event.key === 'Enter') ask();
859
+ });
860
+ });
861
+
770
862
  const patchDocumentMeta = () => safe(() => {
771
863
  document.title = BRAND_LONG;
772
864
  const theme = document.querySelector('meta[name="theme-color"]');
@@ -784,6 +876,8 @@
784
876
  patchDocumentMeta();
785
877
  patchLogin();
786
878
  patchShell();
879
+ applyNavCompactPreference();
880
+ ensureEosAssist();
787
881
  ensureRightsHelper();
788
882
  ensurePermissionPresets();
789
883
  ensureSettingsDialogClasses();
@@ -801,6 +895,8 @@
801
895
  normalizeBadAddressAfterLogin();
802
896
  patchLogin();
803
897
  patchShell();
898
+ applyNavCompactPreference();
899
+ ensureEosAssist();
804
900
  ensureRightsHelper();
805
901
  ensurePermissionPresets();
806
902
  ensureSettingsDialogClasses();
@@ -859,7 +955,6 @@
859
955
  });
860
956
 
861
957
  forceLoginGlobals();
862
- restoreNavCompact();
863
958
  if (document.readyState === 'loading') {
864
959
  document.addEventListener('DOMContentLoaded', () => {
865
960
  fullPatch();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v26-security-visibility-guard';
4
+ const VERSION = 'v27-security-visibility-guard';
5
5
  const LEGACY_ADMIN = 'admin';
6
6
  const LEGACY_ADMIN_INSTANCE = 'admin.0';
7
7
  const SECURITY_URL = '/nexowatt/security/session';
package/build/lib/web.js CHANGED
@@ -491,14 +491,6 @@ class Web {
491
491
  legacyAdminAdapter: 'admin',
492
492
  legacyAdminInstance: 'admin.0',
493
493
  protectedAdapters: this.getEosProtectedAdapterNames(),
494
- eosAssistantEnabled: this.settings.eosAssistantEnabled !== false,
495
- eosAssistantMode: this.settings.eosAssistantMode || 'local',
496
- eosAssistantAdminOnly: this.settings.eosAssistantAdminOnly === true,
497
- assistant: {
498
- enabled: this.settings.eosAssistantEnabled !== false,
499
- mode: this.settings.eosAssistantMode || 'local',
500
- adminOnly: this.settings.eosAssistantAdminOnly === true,
501
- },
502
494
  });
503
495
  }
504
496
 
@@ -729,10 +721,6 @@ class Web {
729
721
  hideLegacyAdminFromNonAdmins: true,
730
722
  restrictProtectedAdapterControls: true,
731
723
  protectedAdapters: ['eos-admin'],
732
- eosAssistantEnabled: true,
733
- eosAssistantMode: 'local',
734
- eosAssistantAdminOnly: false,
735
- assistant: { enabled: true, mode: 'local', adminOnly: false },
736
724
  });
737
725
  });
738
726
  };
package/build/main.js CHANGED
@@ -828,13 +828,8 @@ class Admin extends adapter_core_1.Adapter {
828
828
  }
829
829
  let changed = false;
830
830
  obj.common = obj.common || {};
831
- if (options.keepDontDelete) {
832
- if (obj.common.dontDelete !== true) {
833
- obj.common.dontDelete = true;
834
- changed = true;
835
- }
836
- }
837
- else if (obj.common.dontDelete === true && id !== `system.adapter.${EOS_ADMIN_ADAPTER_NAME}`) {
831
+ // v28: do not use common.dontDelete for EOS protection because it can block adapter upgrades.
832
+ if (obj.common.dontDelete === true) {
838
833
  delete obj.common.dontDelete;
839
834
  changed = true;
840
835
  }
@@ -870,7 +865,7 @@ class Admin extends adapter_core_1.Adapter {
870
865
  const isEosAdmin = adapter === EOS_ADMIN_ADAPTER_NAME;
871
866
  const adminOnlyAcl = isEosAdmin || this.shouldApplyAdminOnlyAclToProtectedAdapters();
872
867
  let changed = await this.ensureObjectProtectionPolicy(`system.adapter.${adapter}`, {
873
- keepDontDelete: isEosAdmin,
868
+ keepDontDelete: false,
874
869
  adminOnlyAcl,
875
870
  });
876
871
  try {
@@ -880,7 +875,7 @@ class Admin extends adapter_core_1.Adapter {
880
875
  });
881
876
  for (const row of instances.rows) {
882
877
  changed = (await this.ensureObjectProtectionPolicy(row.id, {
883
- keepDontDelete: isEosAdmin,
878
+ keepDontDelete: false,
884
879
  adminOnlyAcl,
885
880
  })) || changed;
886
881
  }
@@ -889,7 +884,7 @@ class Admin extends adapter_core_1.Adapter {
889
884
  this.log.warn(`Cannot protect instances of adapter "${adapter}": ${e instanceof Error ? e.message : e}`);
890
885
  }
891
886
  if (changed) {
892
- this.log.info(`EOS delete/ACL guard applied to adapter "${adapter}"`);
887
+ this.log.info(`EOS ACL/UI delete guard applied to adapter "${adapter}"`);
893
888
  }
894
889
  }
895
890
  async ensureLegacyAdminLocked() {
@@ -0,0 +1,43 @@
1
+ # NexoWatt EOS Admin v28 Update-Fix
2
+
3
+ Diese Version behebt den Update-Stopp des eigenständigen `eos-admin` Adapters.
4
+
5
+ ## Ursachen
6
+
7
+ Es gab zwei technische Ursachen:
8
+
9
+ 1. Der gebaute Adapter-Frontend-Bundle enthielt noch den alten Self-Update-Check `adapterName === "admin"`. Dadurch nutzte `eos-admin` beim Klick auf Update nicht den Webserver-Updatepfad, sondern den normalen Terminal-Befehl. Beim Update der gerade laufenden Oberfläche kann das hängen bleiben.
10
+ 2. Frühere Builds konnten `common.dontDelete=true` auf `system.adapter.eos-admin` setzen. Das schützt zwar gegen Löschen, kann aber den ioBroker-Upgrade-Ablauf stören, weil Updates Adapterobjekte ersetzen oder neu schreiben müssen.
11
+
12
+ ## Lösung
13
+
14
+ - Der gebaute Adapter-Bundle nutzt jetzt `adapterName === "eos-admin"`.
15
+ - Der Webserver-Updater sendet jetzt `adapterName: "eos-admin"` an den js-controller.
16
+ - `eos-admin` wird aus „Update alle“ herausgefiltert, damit die laufende Oberfläche nicht über den normalen Terminal-Befehl aktualisiert wird.
17
+ - `common.stopBeforeUpdate=false` im Repository und im Adapter-Metadata.
18
+ - `common.dontDelete=false` und `common.nondeletable=false`.
19
+ - Löschschutz erfolgt über ACLs, EOS UI-Regeln und Security Guard, nicht über objektbasierte Update-Blocker.
20
+
21
+ ## Reparatur bestehender Installationen
22
+
23
+ Vor dem Update von älteren v24–v27 Installationen einmalig die alten Sperrflags entfernen:
24
+
25
+ ```bash
26
+ cd /opt/iobroker
27
+
28
+ iobroker object set system.adapter.eos-admin common.dontDelete=false || true
29
+ iobroker object set system.adapter.eos-admin common.nondeletable=false || true
30
+ iobroker object set system.adapter.eos-admin common.stopBeforeUpdate=false || true
31
+
32
+ iobroker object set system.adapter.eos-admin.0 common.dontDelete=false || true
33
+ iobroker object set system.adapter.eos-admin.0 common.nondeletable=false || true
34
+
35
+ iobroker update https://iobroker.live/repo/repo-nexowatt.json
36
+ iobroker upgrade eos-admin https://iobroker.live/repo/repo-nexowatt.json
37
+ iobroker upload eos-admin
38
+ iobroker restart eos-admin.0
39
+ ```
40
+
41
+ ## Löschschutz
42
+
43
+ Der harte Objekt-Flag `dontDelete` wird in v28 bewusst entfernt, weil er den Updatepfad blockieren kann. Der Schutz gegen Löschen erfolgt über EOS-Admin-Rechte, ACL/Guard und ausgeblendete Löschaktionen für Nicht-Administratoren. `nondeletable` bleibt `false`, damit Updates immer möglich bleiben.