iobroker.eos-admin 7.9.29 → 7.9.30

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,203 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
-
4
- window.NEXOWATT_EOS_ASSIST_VERSION = 'v27-local-setup-guide';
5
-
6
- const ASSET_BASE = (() => {
7
- const script = document.currentScript?.src || document.querySelector('script[src*="eos-assistant.js"]')?.src || window.location.href;
8
- return new URL('../', script).href;
9
- })();
10
- const asset = path => new URL(path.replace(/^\.\//, ''), ASSET_BASE).href;
11
- const LOGO = asset('img/eos/nexowatt-192.png');
12
- const STORE_KEY = 'nexowatt.eos.assist.open';
13
- const state = { checked: false, allowed: true };
14
-
15
- const fetchAssistantPolicy = async () => {
16
- if (state.checked) return state.allowed;
17
- state.checked = true;
18
- try {
19
- const url = new URL('eos/security/status', ASSET_BASE).href;
20
- const response = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
21
- if (!response.ok) return state.allowed;
22
- const json = await response.json();
23
- const assistant = json.assistant || {};
24
- const enabled = assistant.enabled !== false && json.eosAssistantEnabled !== false;
25
- const adminOnly = assistant.adminOnly === true || json.eosAssistantAdminOnly === true;
26
- const isAdmin = json.isAdmin === true || json.isAdministrator === true || json.isEosAdminGroup === true;
27
- state.allowed = enabled && (!adminOnly || isAdmin);
28
- } catch {
29
- state.allowed = true;
30
- }
31
- return state.allowed;
32
- };
33
-
34
- const normalize = value => String(value || '')
35
- .toLowerCase()
36
- .normalize('NFD')
37
- .replace(/[\u0300-\u036f]/g, '')
38
- .replace(/\s+/g, ' ')
39
- .trim();
40
-
41
- const route = () => {
42
- const hash = window.location.hash || '';
43
- const text = normalize(document.body?.innerText || '');
44
- if (hash.includes('tab-adapters') || /module|adapter/.test(text.slice(0, 4000))) return 'modules';
45
- if (hash.includes('tab-instances') || /dienste|instanzen/.test(text.slice(0, 4000))) return 'services';
46
- if (hash.includes('tab-logs') || /systemlogs|protokolle|log-grosse/.test(text.slice(0, 4000))) return 'logs';
47
- if (hash.includes('tab-users') || /zugange & rechte|benutzer|rollen/.test(text.slice(0, 4000))) return 'rights';
48
- if (hash.includes('tab-objects') || /datenpunkte|objekte/.test(text.slice(0, 4000))) return 'objects';
49
- if (hash.includes('tab-hosts') || /system-hosts|hosts/.test(text.slice(0, 4000))) return 'hosts';
50
- return 'overview';
51
- };
52
-
53
- const contextTitle = () => ({
54
- modules: 'Module & Adapter',
55
- services: 'Dienste & Instanzen',
56
- logs: 'Systemlogs',
57
- rights: 'Zugänge & Rechte',
58
- objects: 'Datenpunkte',
59
- hosts: 'System-Hosts',
60
- overview: 'EOS Cockpit',
61
- }[route()] || 'EOS Cockpit');
62
-
63
- const routeAdvice = () => ({
64
- modules: 'Hier installierst und aktualisierst du Module. Prüfe vor einer Installation immer: Zweck, benötigte Instanzen, Version, Abhängigkeiten und ob das Modul zu den geschützten EOS-Komponenten gehört.',
65
- services: 'Hier siehst du laufende Dienste. Wichtig sind Status, Speicherverbrauch, Log-Level und ob eine Instanz automatisch startet. Fehlerhafte Dienste zuerst öffnen, Log prüfen, dann gezielt neu starten.',
66
- logs: 'Hier erkennst du die Ursache vieler Probleme. Filtere nach Quelle und Level. Wiederholte Warnungen sind meist wichtiger als einzelne Meldungen.',
67
- rights: 'Hier steuerst du Rollen und Benutzer. Installateur- und Endkundenrollen sollten keine geschützten EOS-Systemmodule löschen, stoppen oder aktivieren können.',
68
- objects: 'Datenpunkte sind die technische Basis. Ändere Rollen, States und Alias-Strukturen nur bewusst, weil Adapter und Visualisierungen davon abhängen.',
69
- hosts: 'Hier prüfst du Systemlast, Node.js, npm, Speicher und laufende Prozesse. Bei Updateproblemen zuerst Host-Status und freien Speicher prüfen.',
70
- overview: 'Das Cockpit gibt den Überblick. Starte von hier aus mit Modulen, Diensten, Logs oder Rechten, je nachdem ob du einrichten, prüfen oder absichern möchtest.',
71
- }[route()] || 'Wähle einen Bereich aus, dann kann EOS Assist dir die nächsten Schritte erklären.');
72
-
73
- const answerFor = question => {
74
- const q = normalize(question);
75
- const r = route();
76
- if (!q) return `Aktueller Bereich: ${contextTitle()}\n\n${routeAdvice()}\n\nDu kannst z. B. fragen: „Wie richte ich Modbus ein?“, „Warum ist ein Dienst rot?“ oder „Welche Rechte braucht der Installateur?“`;
77
-
78
- if (/modbus|wechselrichter|tcp|rs485/.test(q)) {
79
- return 'Modbus-Einrichtung:\n1. Modul installieren und Instanz anlegen.\n2. IP/Port oder seriellen Adapter prüfen.\n3. Unit-ID, Registerbereich und Byte-Reihenfolge dokumentieren.\n4. Dienst starten und Systemlogs auf Timeout/CRC prüfen.\n5. Erst wenn Werte stabil laufen, Datenpunkte in EOS Cockpit verwenden.';
80
- }
81
- if (/shelly|relay|schalter|steckdose/.test(q)) {
82
- return 'Shelly-/Schaltmodul-Einrichtung:\n1. Gerät im gleichen Netzwerk erreichbar machen.\n2. Authentifizierung im Gerät setzen, damit keine Warnung wie „not protected via restricted login“ entsteht.\n3. Adapterinstanz konfigurieren und neu starten.\n4. In Datenpunkten prüfen, ob Power, Energy und Relay-State sauber aktualisieren.';
83
- }
84
- if (/ev|wallbox|laden|ladepunkt|ocpp/.test(q)) {
85
- return 'Ladepunkt-/EVCS-Einrichtung:\n1. OCPP/Wallbox-Modul installieren.\n2. Ladepunkt-ID, Endpoint und Authentifizierung prüfen.\n3. Dienst starten und Verbindung im Log kontrollieren.\n4. Danach Ladeleistung, Fahrzeugstatus und Freigabe-Datenpunkte mit dem EOS Energiemanagement verknüpfen.';
86
- }
87
- if (/backup|backitup|sicherung|restore/.test(q)) {
88
- return 'Backup-Empfehlung:\n1. BackItUp bleibt ein geschütztes EOS-Systemmodul.\n2. Vor Updates immer ein Backup starten.\n3. Restore-Ziel und Speicherort prüfen.\n4. Installateur-/Endkundenrollen sollten BackItUp nicht löschen oder deaktivieren dürfen.';
89
- }
90
- if (/recht|rolle|benutzer|installateur|kunde|admin/.test(q)) {
91
- return 'Rechte-Empfehlung:\n1. Administrator: volle Wartung und Systemschutz.\n2. Installateur: Module konfigurieren, aber geschützte Adapter nicht löschen.\n3. Endkunde: Bedienung und Ansicht, keine Systemmodule.\n4. Geschützte Adapter legt der Administrator im EOS-Sicherheitsbereich fest.';
92
- }
93
- if (/log|fehler|warn|error|timeout|offline/.test(q)) {
94
- return 'Fehleranalyse:\n1. In Systemlogs nach Quelle und Level filtern.\n2. Wiederholte Meldungen priorisieren.\n3. Bei Timeout Netzwerk/IP/Port prüfen.\n4. Bei Auth-Warnungen Zugangsdaten im Gerät und Adapter vergleichen.\n5. Danach nur die betroffene Instanz neu starten, nicht das komplette System.';
95
- }
96
- if (/update|aktualisieren|npm|repository|repo/.test(q)) {
97
- return 'Update-Prüfung:\n1. NexoWatt-Repository aktiv setzen.\n2. Version im Repository und npm vergleichen.\n3. Bei EOS Admin: iobroker upgrade eos-admin, danach iobroker upload eos-admin.\n4. Browser mit Strg+F5 neu laden.\n5. Vor größeren Updates Backup erstellen.';
98
- }
99
- if (/dienst|instanz|start|stop|neustart/.test(q)) {
100
- return 'Dienst prüfen:\n1. Status ansehen: grün/läuft, rot/gestoppt, gelb/Warnung.\n2. Speicherverbrauch und Log-Level prüfen.\n3. Bei Fehlern zuerst Log öffnen.\n4. Dann Instanz neu starten.\n5. Wenn der Fehler wiederkommt, Konfiguration und Abhängigkeiten prüfen.';
101
- }
102
- return `Ich habe deine Frage lokal eingeordnet.\n\nBereich: ${contextTitle()}\n\nEmpfohlene nächsten Schritte:\n1. Ziel klären: installieren, konfigurieren, Fehler suchen oder absichern.\n2. Betroffenes Modul/Dienst auswählen.\n3. Logs und Status prüfen.\n4. Änderung speichern und nur die betroffene Instanz neu starten.\n\nHinweis: Diese erste EOS-Assistenz arbeitet lokal ohne Cloud. Eine echte KI-Anbindung kann später über einen NexoWatt- oder MCP-Dienst ergänzt werden.`;
103
- };
104
-
105
- const setOpen = open => {
106
- document.documentElement.classList.toggle('eos-assist-open', !!open);
107
- try { localStorage.setItem(STORE_KEY, open ? '1' : '0'); } catch { /* ignore */ }
108
- };
109
-
110
- const renderAnswer = (panel, question) => {
111
- const out = panel.querySelector('.eos-assist-answer');
112
- if (out) out.textContent = answerFor(question);
113
- const ctx = panel.querySelector('.eos-assist-context');
114
- if (ctx) ctx.textContent = contextTitle();
115
- const hint = panel.querySelector('.eos-assist-hint');
116
- if (hint) hint.textContent = routeAdvice();
117
- };
118
-
119
- const ensureAssistant = () => {
120
- if (!document.body || document.querySelector('.eos-assist-launcher')) return;
121
- if (document.documentElement.classList.contains('eos-login')) return;
122
- if (!document.getElementById('app-paper')) return;
123
-
124
- const launcher = document.createElement('button');
125
- launcher.type = 'button';
126
- launcher.className = 'eos-assist-launcher';
127
- launcher.innerHTML = '<span>✦</span><span>EOS Assist</span>';
128
- launcher.setAttribute('aria-label', 'EOS Assist öffnen');
129
-
130
- const panel = document.createElement('aside');
131
- panel.className = 'eos-assist-panel';
132
- panel.setAttribute('aria-label', 'EOS Assist');
133
- panel.innerHTML = `
134
- <div class="eos-assist-head">
135
- <span class="eos-assist-logo"><img src="${LOGO}" alt="NexoWatt EOS" /></span>
136
- <span><strong>EOS Assist</strong><small><span class="eos-assist-context"></span> · geführte Einrichtung</small></span>
137
- <button type="button" class="eos-assist-close" aria-label="Schließen">×</button>
138
- </div>
139
- <div class="eos-assist-body">
140
- <div class="eos-assist-card"><strong>Aktueller Hinweis</strong><p class="eos-assist-hint"></p></div>
141
- <div class="eos-assist-actions">
142
- <button type="button" data-question="Wie richte ich dieses Modul ein?">Modul einrichten</button>
143
- <button type="button" data-question="Wie prüfe ich Fehler in den Logs?">Fehler prüfen</button>
144
- <button type="button" data-question="Welche Rechte braucht der Installateur?">Rechte erklären</button>
145
- <button type="button" data-question="Was muss ich vor einem Update beachten?">Update-Check</button>
146
- </div>
147
- <div class="eos-assist-input-row">
148
- <input class="eos-assist-input" type="text" placeholder="Frage eingeben, z. B. Modbus einrichten" />
149
- <button type="button" class="eos-assist-send">Fragen</button>
150
- </div>
151
- <div class="eos-assist-card eos-assist-answer"></div>
152
- <div class="eos-assist-foot">Lokale Assistenz ohne Cloud. Für echte KI-Automation kann später ein NexoWatt-Dienst angebunden werden.</div>
153
- </div>
154
- `;
155
-
156
- document.body.appendChild(launcher);
157
- document.body.appendChild(panel);
158
- renderAnswer(panel, '');
159
-
160
- launcher.addEventListener('click', () => setOpen(!document.documentElement.classList.contains('eos-assist-open')));
161
- panel.querySelector('.eos-assist-close')?.addEventListener('click', () => setOpen(false));
162
- panel.querySelector('.eos-assist-send')?.addEventListener('click', () => renderAnswer(panel, panel.querySelector('.eos-assist-input')?.value || ''));
163
- panel.querySelector('.eos-assist-input')?.addEventListener('keydown', event => {
164
- if (event.key === 'Enter') renderAnswer(panel, event.currentTarget.value || '');
165
- });
166
- panel.querySelectorAll('button[data-question]').forEach(button => {
167
- button.addEventListener('click', () => {
168
- const question = button.getAttribute('data-question') || '';
169
- const input = panel.querySelector('.eos-assist-input');
170
- if (input) input.value = question;
171
- renderAnswer(panel, question);
172
- });
173
- });
174
-
175
- try { if (localStorage.getItem(STORE_KEY) === '1') setOpen(true); } catch { /* ignore */ }
176
- };
177
-
178
- const removeOnLogin = () => {
179
- if (!document.documentElement.classList.contains('eos-login')) return;
180
- document.querySelectorAll('.eos-assist-launcher,.eos-assist-panel').forEach(el => el.remove());
181
- };
182
-
183
- const run = () => {
184
- removeOnLogin();
185
- void fetchAssistantPolicy().then(allowed => {
186
- if (!allowed) {
187
- document.querySelectorAll('.eos-assist-launcher,.eos-assist-panel').forEach(el => el.remove());
188
- return;
189
- }
190
- ensureAssistant();
191
- const panel = document.querySelector('.eos-assist-panel');
192
- if (panel) renderAnswer(panel, panel.querySelector('.eos-assist-input')?.value || '');
193
- });
194
- };
195
-
196
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run, { once: true });
197
- else run();
198
- window.addEventListener('hashchange', () => setTimeout(run, 80));
199
- window.addEventListener('load', () => setTimeout(run, 120), { once: true });
200
- new MutationObserver(() => {
201
- if (!document.querySelector('.eos-assist-launcher')) run();
202
- }).observe(document.documentElement, { childList: true, subtree: true });
3
+ // v30: EOS Assist is rendered by eos-branding.js so it can share route/security state.
4
+ // This file stays as a lightweight compatibility hook for cache-safe deployments.
5
+ window.NEXOWATT_EOS_ASSIST_VERSION = 'v30-integrated-with-eos-branding';
203
6
  })();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_UI_VERSION = 'v27-logo-nav-assist-polish';
4
+ window.NEXOWATT_EOS_UI_VERSION = 'v30-nav-assist-security-polish';
5
5
 
6
6
  const BRAND = 'NexoWatt EOS';
7
7
  const EOS_MEANING = 'Energy Operation System';
@@ -329,21 +329,40 @@
329
329
  };
330
330
 
331
331
  const hideSecuritySettingsForNonAdmin = () => {
332
- if (isAdminUser()) return;
332
+ const securityNeedles = /(nexowatt\s+sicherheit|eos\s+security|eos\s+systemschutz|legacy\s+admin|alter\s+admin|gesch(?:u|ü)tzte\s+(system)?adapter|administratorgruppen|protected\s+adapters|admin\s+groups)/i;
333
+ const markAdminOnly = el => {
334
+ if (!el || isAdminUser()) return;
335
+ el.dataset.eosSecurityAdminOnly = 'true';
336
+ el.classList.add('eos-security-admin-only-field');
337
+ el.setAttribute('aria-hidden', 'true');
338
+ };
339
+ if (isAdminUser()) {
340
+ document.querySelectorAll('[data-eos-security-admin-only="true"], .eos-security-admin-only-field').forEach(el => {
341
+ el.removeAttribute('data-eos-security-admin-only');
342
+ el.classList.remove('eos-security-admin-only-field');
343
+ el.removeAttribute('aria-hidden');
344
+ });
345
+ return;
346
+ }
347
+ Array.from(document.querySelectorAll('[role="tab"], .MuiTab-root, button, [role="button"]')).forEach(tab => {
348
+ if (!securityNeedles.test(textOfElement(tab))) return;
349
+ const inDialog = !!tab.closest('.MuiDialog-paper, [role="dialog"]');
350
+ if (inDialog) markAdminOnly(tab);
351
+ });
333
352
  Array.from(document.querySelectorAll('.MuiDialog-paper, [role="dialog"]')).forEach(dialog => {
334
353
  const dialogText = textOfElement(dialog);
335
- if (!/(eos security|nexowatt security|legacy admin|alter admin|protected adapters|geschutzte adapter|eos admin groups)/i.test(dialogText)) return;
354
+ if (!securityNeedles.test(dialogText)) return;
336
355
  dialog.classList.add('eos-security-settings-restricted');
337
- 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;
338
- Array.from(dialog.querySelectorAll('label, legend, h2, h3, h4, .MuiTypography-root, .MuiFormLabel-root')).forEach(label => {
339
- if (!needles.test(label.textContent || '')) return;
340
- const row = label.closest('.MuiGrid2-root, .MuiGrid-root, .MuiFormControl-root, .MuiBox-root, .MuiPaper-root') || label.parentElement;
341
- if (row && row !== dialog) row.classList.add('eos-security-admin-only-field');
356
+ Array.from(dialog.querySelectorAll('label, legend, h2, h3, h4, .MuiTypography-root, .MuiFormLabel-root, .MuiTab-root, [role="tab"], .MuiGrid-root, .MuiGrid2-root, .MuiFormControl-root')).forEach(el => {
357
+ if (!securityNeedles.test(textOfElement(el))) return;
358
+ const row = el.closest('.MuiGrid2-root, .MuiGrid-root, .MuiFormControl-root, .MuiBox-root, .MuiPaper-root, [role="tabpanel"]') || el.parentElement;
359
+ if (row && row !== dialog) markAdminOnly(row);
360
+ markAdminOnly(el);
342
361
  });
343
362
  if (!dialog.querySelector('.eos-security-restricted-note')) {
344
363
  const note = document.createElement('div');
345
364
  note.className = 'eos-security-restricted-note';
346
- note.innerHTML = '<strong>EOS Systemschutz aktiv</strong><span>Diese Sicherheitseinstellungen sind nur für Administratoren sichtbar und änderbar.</span>';
365
+ note.innerHTML = '<strong>EOS Systemschutz</strong><span>Dieser Bereich ist nur für Administratoren sichtbar. Geschützte Adapter und der alte Admin werden zentral durch NexoWatt EOS verwaltet.</span>';
347
366
  const content = dialog.querySelector('.MuiDialogContent-root') || dialog;
348
367
  content.insertBefore(note, content.firstElementChild || null);
349
368
  }
@@ -492,16 +511,17 @@
492
511
  };
493
512
 
494
513
  const hideNativeLogoutNav = () => safe(() => {
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\"]'));
514
+ const candidates = Array.from(document.querySelectorAll('.MuiDrawer-paper a, .MuiDrawer-paper button, .MuiDrawer-paper .MuiListItem-root, .MuiDrawer-paper .MuiListItemButton-root, .MuiDrawer-paper [role="button"], nav a, nav button, nav [role="button"]'));
496
515
  candidates.forEach(el => {
497
- const text = normalize(el.textContent || el.getAttribute('aria-label') || el.getAttribute('title') || '');
498
- const href = String(el.getAttribute('href') || '');
499
- const isLogout = /^(abmelden|logout|ra_logout)$/.test(text) || /(?:^|[/?#])logout(?:[/?#]|$)/i.test(href);
516
+ const text = normalize(`${el.textContent || ''} ${el.getAttribute?.('aria-label') || ''} ${el.getAttribute?.('title') || ''}`);
517
+ const href = String(el.getAttribute?.('href') || '');
518
+ const isLogout = /(?:^|\b)(abmelden|logout|ra_logout)(?:\b|$)/.test(text) || /(?:^|[/?#])logout(?:[/?#]|$)/i.test(href);
500
519
  if (!isLogout) return;
501
- const item = el.closest('.MuiListItem-root, li') || el.closest('.MuiListItemButton-root, a, button') || el;
502
- item.classList.add('eos-hidden-logout');
520
+ const item = el.closest('.MuiListItem-root, li, .MuiButtonBase-root, .MuiListItemButton-root') || el.closest('a, button, [role="button"]') || el;
521
+ item.classList.add('eos-hidden-logout', 'eos-native-logout-hidden');
503
522
  item.setAttribute('aria-hidden', 'true');
504
523
  item.setAttribute('tabindex', '-1');
524
+ if (item.style) item.style.display = 'none';
505
525
  });
506
526
  });
507
527
 
@@ -806,57 +826,133 @@
806
826
  const hasApp = !!document.getElementById('app-paper');
807
827
  if (!hasApp || isLoginView()) {
808
828
  document.getElementById('eos-assist-root')?.remove();
829
+ document.querySelectorAll('.eos-assist-launcher,.eos-assist-panel:not(#eos-assist-root .eos-assist-panel)').forEach(el => el.remove());
809
830
  return;
810
831
  }
832
+
811
833
  let root = document.getElementById('eos-assist-root');
812
834
  if (!root) {
813
835
  root = document.createElement('section');
814
836
  root.id = 'eos-assist-root';
815
837
  root.className = 'eos-assist-root';
838
+ root.innerHTML = `
839
+ <button class="eos-assist-button" type="button" aria-expanded="false">
840
+ <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Einrichtungshilfe</small>
841
+ </button>
842
+ <div class="eos-assist-panel" role="dialog" aria-label="EOS Assist Einrichtungshilfe">
843
+ <div class="eos-assist-head">
844
+ <span class="eos-assist-logo"><img src="${LOGO}" alt="NexoWatt EOS" /></span>
845
+ <div><strong class="eos-assist-title"></strong><span class="eos-assist-text"></span></div>
846
+ <button type="button" class="eos-assist-close" aria-label="EOS Assist schließen">×</button>
847
+ </div>
848
+ <div class="eos-assist-steps"></div>
849
+ <div class="eos-assist-actions">
850
+ <button type="button" data-question="Wie richte ich dieses Modul ein?">Modul einrichten</button>
851
+ <button type="button" data-question="Wie prüfe ich Fehler in den Logs?">Fehler prüfen</button>
852
+ <button type="button" data-question="Welche Rechte braucht der Installateur?">Rechte erklären</button>
853
+ <button type="button" data-question="Was muss ich vor einem Update beachten?">Update-Check</button>
854
+ </div>
855
+ <label class="eos-assist-input-label">Was möchtest du einrichten?</label>
856
+ <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>
857
+ <div class="eos-assist-answer"></div>
858
+ <div class="eos-assist-foot">Lokale Bedienhilfe ohne Cloud. Eine echte KI-Anbindung kann später über einen NexoWatt-Dienst ergänzt werden.</div>
859
+ </div>
860
+ `;
816
861
  document.body.appendChild(root);
817
862
  }
863
+
818
864
  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');
865
+ const button = root.querySelector('.eos-assist-button');
837
866
  const input = root.querySelector('.eos-assist-input');
838
- const send = root.querySelector('.eos-assist-send');
839
867
  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
- });
868
+ root.querySelector('.eos-assist-title').textContent = ctx.title;
869
+ root.querySelector('.eos-assist-text').textContent = ctx.text;
870
+ root.querySelector('.eos-assist-steps').innerHTML = ctx.steps.map(step => `<span>${step}</span>`).join('');
871
+ if (answer && !answer.textContent) answer.textContent = assistAnswer('');
872
+ root.classList.toggle('eos-assist-open', !!state.assistOpen);
873
+ button?.setAttribute('aria-expanded', state.assistOpen ? 'true' : 'false');
874
+
875
+ if (!root.dataset.eosAssistBound) {
876
+ root.dataset.eosAssistBound = 'true';
877
+ root.addEventListener('click', event => {
878
+ const target = event.target?.closest?.('button');
879
+ if (!target || !root.contains(target)) return;
880
+ if (target.classList.contains('eos-assist-button')) {
881
+ event.preventDefault();
882
+ event.stopPropagation();
883
+ state.assistOpen = !state.assistOpen;
884
+ root.classList.toggle('eos-assist-open', state.assistOpen);
885
+ target.setAttribute('aria-expanded', state.assistOpen ? 'true' : 'false');
886
+ if (state.assistOpen) setTimeout(() => root.querySelector('.eos-assist-input')?.focus(), 50);
887
+ return;
888
+ }
889
+ if (target.classList.contains('eos-assist-close')) {
890
+ event.preventDefault();
891
+ state.assistOpen = false;
892
+ root.classList.remove('eos-assist-open');
893
+ root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
894
+ return;
895
+ }
896
+ if (target.classList.contains('eos-assist-send')) {
897
+ event.preventDefault();
898
+ const value = root.querySelector('.eos-assist-input')?.value || '';
899
+ const out = root.querySelector('.eos-assist-answer');
900
+ if (out) out.textContent = assistAnswer(value);
901
+ return;
902
+ }
903
+ if (target.hasAttribute('data-question')) {
904
+ event.preventDefault();
905
+ const question = target.getAttribute('data-question') || '';
906
+ const field = root.querySelector('.eos-assist-input');
907
+ if (field) field.value = question;
908
+ const out = root.querySelector('.eos-assist-answer');
909
+ if (out) out.textContent = assistAnswer(question);
910
+ }
911
+ }, true);
912
+ root.addEventListener('keydown', event => {
913
+ if (event.key !== 'Enter' || !event.target?.classList?.contains('eos-assist-input')) return;
914
+ const out = root.querySelector('.eos-assist-answer');
915
+ if (out) out.textContent = assistAnswer(event.target.value || '');
916
+ }, true);
917
+ }
918
+ });
919
+
920
+
921
+
922
+ const installAssistDelegatedClick = () => safe(() => {
923
+ if (window.__NEXOWATT_EOS_ASSIST_BRANDING_CLICK__) return;
924
+ window.__NEXOWATT_EOS_ASSIST_BRANDING_CLICK__ = true;
925
+ document.addEventListener('click', event => {
926
+ const target = event.target?.closest?.('.eos-assist-button,.eos-assist-close,.eos-assist-send');
927
+ if (!target) return;
928
+ const root = document.getElementById('eos-assist-root') || target.closest('.eos-assist-root');
929
+ if (!root) return;
930
+ const input = root.querySelector('.eos-assist-input');
931
+ const answer = root.querySelector('.eos-assist-answer');
932
+ if (target.matches('.eos-assist-button')) {
933
+ event.preventDefault();
934
+ event.stopPropagation();
935
+ event.stopImmediatePropagation?.();
936
+ state.assistOpen = !root.classList.contains('eos-assist-open');
937
+ root.classList.toggle('eos-assist-open', state.assistOpen);
938
+ target.setAttribute('aria-expanded', state.assistOpen ? 'true' : 'false');
939
+ if (state.assistOpen) setTimeout(() => input?.focus(), 40);
940
+ } else if (target.matches('.eos-assist-close')) {
941
+ event.preventDefault();
942
+ event.stopPropagation();
943
+ event.stopImmediatePropagation?.();
944
+ state.assistOpen = false;
945
+ root.classList.remove('eos-assist-open');
946
+ root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
947
+ } else if (target.matches('.eos-assist-send')) {
948
+ event.preventDefault();
949
+ event.stopPropagation();
950
+ event.stopImmediatePropagation?.();
951
+ if (answer) answer.textContent = assistAnswer(input?.value || '');
952
+ state.assistOpen = true;
953
+ root.classList.add('eos-assist-open');
954
+ }
955
+ }, true);
860
956
  });
861
957
 
862
958
  const patchDocumentMeta = () => safe(() => {
@@ -877,6 +973,7 @@
877
973
  patchLogin();
878
974
  patchShell();
879
975
  applyNavCompactPreference();
976
+ installAssistDelegatedClick();
880
977
  ensureEosAssist();
881
978
  ensureRightsHelper();
882
979
  ensurePermissionPresets();
@@ -896,6 +993,7 @@
896
993
  patchLogin();
897
994
  patchShell();
898
995
  applyNavCompactPreference();
996
+ installAssistDelegatedClick();
899
997
  ensureEosAssist();
900
998
  ensureRightsHelper();
901
999
  ensurePermissionPresets();