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.
- package/admin/i18n/de.json +19 -0
- package/admin/i18n/en.json +19 -0
- package/admin/i18n/es.json +19 -0
- package/admin/i18n/fr.json +19 -0
- package/admin/i18n/it.json +19 -0
- package/admin/i18n/nl.json +19 -0
- package/admin/i18n/pl.json +19 -0
- package/admin/i18n/pt.json +19 -0
- package/admin/i18n/ru.json +19 -0
- package/admin/i18n/uk.json +19 -0
- package/admin/i18n/zh-cn.json +19 -0
- package/admin/jsonConfig.json5 +19 -19
- package/adminWww/css/eos-branding.css +375 -0
- package/adminWww/index.html +4 -4
- package/adminWww/js/eos-assistant.js +3 -200
- package/adminWww/js/eos-branding.js +152 -54
- package/adminWww/js/eos-security-ui.js +105 -48
- package/build/i18n/de.json +19 -0
- package/build/i18n/en.json +28 -9
- package/io-package.json +10 -8
- package/package.json +143 -143
|
@@ -1,203 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
|
|
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 = '
|
|
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
|
-
|
|
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 (
|
|
354
|
+
if (!securityNeedles.test(dialogText)) return;
|
|
336
355
|
dialog.classList.add('eos-security-settings-restricted');
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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('
|
|
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.
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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();
|