iobroker.eos-admin 7.9.96 → 7.9.98
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/INSTALL_TEST_V7.9.98.md +39 -0
- package/MERGE_UPDATE_README_V7.9.98.md +29 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +20 -18
- package/PUBLISH_STABLE_V7.9.98.md +33 -0
- package/README.md +16 -6
- package/README_STABILITY_V7.9.98.md +22 -0
- package/RELEASE_ACCEPTANCE_V7.9.98.md +48 -0
- package/RELEASE_NOTES_V7.9.98.md +32 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +1 -1
- package/adminWww/assets/bootstrap-COulQZax-v84.js +3 -3
- package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
- package/adminWww/css/eos-ems-overview.css +21 -0
- package/adminWww/css/nexowatt-native-shell.css +174 -40
- package/adminWww/index.html +15 -15
- package/adminWww/js/eos-account-management.js +26 -13
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +136 -39
- package/adminWww/js/eos-role-bootstrap.js +7 -7
- package/adminWww/js/eos-role-ui.js +2 -2
- package/adminWww/js/nexowatt-native-shell.js +9 -2
- package/adminWww/static/js/nexowatt-stable-login.js +28 -62
- package/adminWww/static/js/nexowatt-stable-v98.js +33 -0
- package/build/lib/web.js +112 -79
- package/build/main.js +1 -1
- package/io-package.json +15 -5
- package/package.json +14 -12
- package/tools/nexowatt-account-management-selftest.cjs +16 -57
- package/tools/nexowatt-account-password-flow-selftest.cjs +186 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +33 -10
- package/tools/nexowatt-login-layout-selftest.cjs +12 -81
- package/tools/nexowatt-merge-update-selftest.cjs +3 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
- package/tools/nexowatt-password-write-selftest.cjs +48 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +9 -1
- package/tools/nexowatt-stable-v98-selftest.cjs +14 -0
- package/tools/nexowatt-sync-release-version.cjs +42 -1
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +11 -11
- package/tools/nexowatt-validate-package.cjs +20 -11
- package/tools/nexowatt-version-sync-selftest.cjs +18 -1
- package/INSTALL_TEST_V7.9.96.md +0 -11
- package/MERGE_UPDATE_README_V7.9.96.md +0 -5
- package/PUBLISH_STABLE_V7.9.96.md +0 -10
- package/README_STABILITY_V7.9.96.md +0 -11
- package/RELEASE_ACCEPTANCE_V7.9.96.md +0 -10
- package/RELEASE_NOTES_V7.9.96.md +0 -12
- package/adminWww/static/js/nexowatt-stable-v96.js +0 -82
- package/tools/nexowatt-stable-v96-selftest.cjs +0 -13
|
@@ -1,50 +1,72 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
const VERSION = '
|
|
3
|
+
const VERSION = 'v98-nexowatt-ems-overview-runtime';
|
|
4
4
|
const CONTRACT = 'nexowatt-ems-overview-v1';
|
|
5
5
|
const ROOT_ID = 'eos-ems-overview-runtime';
|
|
6
6
|
const POLL_MS = 5000;
|
|
7
|
+
const RETRY_MS = 650;
|
|
8
|
+
const CONNECTION_WAIT_MS = 12000;
|
|
7
9
|
const STALE_MS = 20000;
|
|
10
|
+
const REQUIRED_UI_VERSION = '0.8.198';
|
|
8
11
|
const COLORS = { ok: '#01bc69', info: '#48b9ff', warning: '#ffbd59', error: '#ff5f72' };
|
|
9
12
|
let timer = 0;
|
|
13
|
+
let retryTimer = 0;
|
|
10
14
|
let inFlight = false;
|
|
11
15
|
let destroyed = false;
|
|
16
|
+
let socketCache = null;
|
|
17
|
+
let startedAt = Date.now();
|
|
12
18
|
|
|
13
19
|
const esc = value => String(value ?? '').replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
|
14
20
|
const text = (value, fallback = '') => String(value ?? fallback).replace(/\s+/g, ' ').trim() || fallback;
|
|
15
21
|
const num = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
|
|
16
|
-
const parse = (value, fallback) => {
|
|
17
|
-
|
|
18
|
-
try { return JSON.parse(String(value ?? '')); } catch (_) { return fallback; }
|
|
19
|
-
};
|
|
20
|
-
const severity = value => {
|
|
21
|
-
const normalized = String(value || '').toLowerCase();
|
|
22
|
-
if (normalized === 'error') return 'error';
|
|
23
|
-
if (normalized === 'warning' || normalized === 'warn') return 'warning';
|
|
24
|
-
if (normalized === 'ok') return 'ok';
|
|
25
|
-
return 'info';
|
|
26
|
-
};
|
|
22
|
+
const parse = (value, fallback) => { if (value && typeof value === 'object') return value; try { return JSON.parse(String(value ?? '')); } catch (_) { return fallback; } };
|
|
23
|
+
const severity = value => { const normalized = String(value || '').toLowerCase(); if (normalized === 'error') return 'error'; if (normalized === 'warning' || normalized === 'warn') return 'warning'; if (normalized === 'ok') return 'ok'; return 'info'; };
|
|
27
24
|
const currentTab = () => String(location.hash || '').match(/tab-[a-z0-9_-]+/i)?.[0]?.toLowerCase() || 'tab-intro';
|
|
28
25
|
const currentRole = () => String(window.NEXOWATT_EOS_ACCESS_ROLE || 'admin').toLowerCase();
|
|
29
26
|
const technical = () => ['admin', 'service', 'installer', 'installateur'].includes(currentRole());
|
|
30
|
-
const formatPower = value => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
const formatAge = timestamp => {
|
|
35
|
-
const value = num(timestamp); if (!value) return 'noch kein Regeltick';
|
|
36
|
-
const seconds = Math.max(0, Math.round((Date.now() - value) / 1000));
|
|
37
|
-
if (seconds < 2) return 'gerade eben';
|
|
38
|
-
if (seconds < 60) return `vor ${seconds} s`;
|
|
39
|
-
return `vor ${Math.round(seconds / 60)} min`;
|
|
40
|
-
};
|
|
41
|
-
const formatClock = timestamp => {
|
|
42
|
-
const value = num(timestamp); if (!value) return '—';
|
|
43
|
-
try { return new Date(value).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (_) { return '—'; }
|
|
44
|
-
};
|
|
45
|
-
const getSocket = () => window.NEXOWATT_EOS_ADMIN_SOCKET || window.NEXOWATT_EOS_SOCKET || null;
|
|
27
|
+
const formatPower = value => { const watts = num(value); const absolute = Math.abs(watts); const sign = watts < 0 ? '−' : ''; return absolute >= 1000 ? `${sign}${(absolute / 1000).toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 2 })} kW` : `${sign}${Math.round(absolute).toLocaleString('de-DE')} W`; };
|
|
28
|
+
const formatAge = timestamp => { const value = num(timestamp); if (!value) return 'noch kein Regeltick'; const seconds = Math.max(0, Math.round((Date.now() - value) / 1000)); if (seconds < 2) return 'gerade eben'; if (seconds < 60) return `vor ${seconds} s`; return `vor ${Math.round(seconds / 60)} min`; };
|
|
29
|
+
const formatClock = timestamp => { const value = num(timestamp); if (!value) return '—'; try { return new Date(value).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (_) { return '—'; } };
|
|
46
30
|
const stateVal = (states, id) => states?.[id]?.val;
|
|
47
31
|
|
|
32
|
+
const isAdminSocket = candidate => Boolean(candidate && typeof candidate.getForeignStates === 'function');
|
|
33
|
+
const rememberSocket = candidate => {
|
|
34
|
+
if (!isAdminSocket(candidate)) return null;
|
|
35
|
+
socketCache = candidate;
|
|
36
|
+
try { window.NEXOWATT_EOS_ADMIN_SOCKET = candidate; window.NEXOWATT_EOS_SOCKET = candidate; } catch (_) { /* best effort */ }
|
|
37
|
+
return candidate;
|
|
38
|
+
};
|
|
39
|
+
const socketFromFrames = () => {
|
|
40
|
+
for (const frame of [window, window.parent, window.top]) {
|
|
41
|
+
try {
|
|
42
|
+
const candidate = frame?.NEXOWATT_EOS_ADMIN_SOCKET || frame?.NEXOWATT_EOS_SOCKET;
|
|
43
|
+
if (isAdminSocket(candidate)) return rememberSocket(candidate);
|
|
44
|
+
} catch (_) { /* cross-origin frame */ }
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
const socketFromReact = () => {
|
|
49
|
+
const roots = [document.getElementById('app-paper'), document.getElementById('root')].filter(Boolean);
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const inspect = value => {
|
|
52
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function') || seen.has(value)) return null;
|
|
53
|
+
seen.add(value);
|
|
54
|
+
if (isAdminSocket(value)) return rememberSocket(value);
|
|
55
|
+
for (const key of ['socket', 'props', 'memoizedProps', 'memoizedState', 'stateNode', 'return', 'child', 'sibling']) {
|
|
56
|
+
try { const found = inspect(value[key]); if (found) return found; } catch (_) { /* ignore inaccessible property */ }
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
for (const root of roots) {
|
|
61
|
+
for (const key of Object.getOwnPropertyNames(root)) {
|
|
62
|
+
if (!key.startsWith('__react')) continue;
|
|
63
|
+
try { const found = inspect(root[key]); if (found) return found; } catch (_) { /* ignore */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
const getSocket = () => isAdminSocket(socketCache) ? socketCache : (socketFromFrames() || socketFromReact());
|
|
69
|
+
|
|
48
70
|
const normalizeInstances = (states, aliveStates) => {
|
|
49
71
|
const prefixes = new Set();
|
|
50
72
|
Object.keys(states || {}).forEach(id => { const match = id.match(/^(nexowatt-ui\.\d+)\.info\.adminOverview\./); if (match) prefixes.add(match[1]); });
|
|
@@ -68,18 +90,53 @@
|
|
|
68
90
|
if (!root) {
|
|
69
91
|
root = document.createElement('section');
|
|
70
92
|
root.id = ROOT_ID;
|
|
71
|
-
root.className = 'eos-ems-overview-card';
|
|
93
|
+
root.className = 'eos-ems-overview-card is-loading';
|
|
72
94
|
root.dataset.nexowattContract = CONTRACT;
|
|
73
95
|
}
|
|
74
96
|
const hero = document.getElementById('eos-native-overview-hero');
|
|
75
97
|
if (hero?.parentElement === paper) paper.insertBefore(root, hero.nextSibling); else paper.insertBefore(root, paper.firstChild || null);
|
|
76
98
|
return root;
|
|
77
99
|
};
|
|
78
|
-
|
|
79
|
-
const
|
|
100
|
+
const setMode = (root, mode) => { root.classList.remove('is-loading', 'is-unavailable', 'is-ready'); root.classList.add(`is-${mode}`); };
|
|
101
|
+
const loading = message => {
|
|
102
|
+
const root = ensureRoot(); if (!root) return;
|
|
103
|
+
setMode(root, 'loading');
|
|
104
|
+
root.style.setProperty('--eos-ems-status-color', COLORS.info);
|
|
105
|
+
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS</span><h2>Live-Diagnose wird verbunden</h2><p>${esc(message)}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot eos-ems-overview-state-dot--pulse"></span><strong>Verbindung</strong><small>automatischer Neuversuch</small></div></header>`;
|
|
106
|
+
};
|
|
107
|
+
const unavailable = (message, detail = '') => {
|
|
80
108
|
const root = ensureRoot(); if (!root) return;
|
|
109
|
+
setMode(root, 'unavailable');
|
|
81
110
|
root.style.setProperty('--eos-ems-status-color', COLORS.info);
|
|
82
|
-
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS</span><h2>Live-Diagnose</h2><p>${esc(message)}</p
|
|
111
|
+
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS</span><h2>Live-Diagnose nicht verfügbar</h2><p>${esc(message)}</p>${detail ? `<small class="eos-ems-overview-help">${esc(detail)}</small>` : ''}</div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>Nicht verfügbar</strong><small>read-only</small></div></header><footer class="eos-ems-overview-footer"><span>Die Kachel liest ausschließlich info.adminOverview.*.</span><span>Keine EMS-Regelung wurde verändert.</span></footer>`;
|
|
112
|
+
};
|
|
113
|
+
const semverAtLeast = (value, minimum) => {
|
|
114
|
+
const parseVersion = input => String(input || '').split('-')[0].split('.').map(part => Number.parseInt(part, 10) || 0);
|
|
115
|
+
const current = parseVersion(value); const required = parseVersion(minimum);
|
|
116
|
+
for (let index = 0; index < Math.max(current.length, required.length); index++) {
|
|
117
|
+
const left = current[index] || 0; const right = required[index] || 0;
|
|
118
|
+
if (left !== right) return left > right;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
122
|
+
const readUiAdapterInfo = async socket => {
|
|
123
|
+
try {
|
|
124
|
+
if (typeof socket.getAdapterInstances === 'function') {
|
|
125
|
+
const rows = await socket.getAdapterInstances('nexowatt-ui', true);
|
|
126
|
+
const instances = Array.isArray(rows) ? rows : [];
|
|
127
|
+
const selected = instances.find(row => row?.common?.enabled) || instances[0];
|
|
128
|
+
if (selected) return { installed: true, enabled: selected.common?.enabled !== false, version: text(selected.common?.version, ''), id: text(selected._id, '') };
|
|
129
|
+
}
|
|
130
|
+
} catch (_) { /* next fallback */ }
|
|
131
|
+
try {
|
|
132
|
+
if (typeof socket.getObjectViewSystem === 'function') {
|
|
133
|
+
const rows = await socket.getObjectViewSystem('instance', 'system.adapter.nexowatt-ui.', 'system.adapter.nexowatt-ui.香');
|
|
134
|
+
const values = Array.isArray(rows) ? rows : Object.values(rows || {});
|
|
135
|
+
const selected = values.find(row => row?.common?.enabled) || values[0];
|
|
136
|
+
if (selected) return { installed: true, enabled: selected.common?.enabled !== false, version: text(selected.common?.version, ''), id: text(selected._id, '') };
|
|
137
|
+
}
|
|
138
|
+
} catch (_) { /* permission or compatibility */ }
|
|
139
|
+
return { installed: false, version: '', enabled: false, id: '' };
|
|
83
140
|
};
|
|
84
141
|
|
|
85
142
|
const fallbackDecisions = summary => {
|
|
@@ -91,9 +148,10 @@
|
|
|
91
148
|
|
|
92
149
|
const render = data => {
|
|
93
150
|
const root = ensureRoot(); if (!root) return;
|
|
151
|
+
setMode(root, 'ready');
|
|
94
152
|
const summary = data.summary || {}; const budget = summary.budget || {}; const charging = summary.charging || {}; const storage = summary.storage || {}; const ems = summary.ems || {}; const para14a = summary.para14a || {}; const tariff = summary.tariff || {}; const forecast = summary.forecast || {}; const peak = summary.peakShaving || {};
|
|
95
153
|
const stale = data.alive === false || !data.updatedAt || Date.now() - data.updatedAt > STALE_MS;
|
|
96
|
-
const status = stale ? 'error' : severity(summary.status);
|
|
154
|
+
const status = stale ? 'error' : severity(summary.status); root.style.setProperty('--eos-ems-status-color', COLORS[status]);
|
|
97
155
|
const totalBudget = Math.max(0, num(budget.totalW)); const remaining = Math.max(0, num(budget.remainingW)); const percent = totalBudget > 0 ? Math.max(0, Math.min(100, remaining / totalBudget * 100)) : 0;
|
|
98
156
|
const isTechnical = technical(); const decisions = (Array.isArray(summary.currentDecisions) ? summary.currentDecisions : fallbackDecisions(summary)).slice(0, isTechnical ? 5 : 2); const events = isTechnical ? data.events.slice(0, 6) : [];
|
|
99
157
|
const tags = [];
|
|
@@ -110,17 +168,48 @@
|
|
|
110
168
|
root.innerHTML = `<header class="eos-ems-overview-header"><div><span class="eos-ems-overview-eyebrow">NexoWatt EMS · Live-Diagnose</span><h2>${esc(stale ? 'EMS-Diagnose ist nicht aktuell' : text(summary.headline, 'EMS arbeitet normal'))}</h2><p>${esc(stale ? 'Adapter oder Diagnosewerte sind älter als 20 Sekunden. Die Werte werden als veraltet markiert.' : text(summary.reason, 'EMS arbeitet innerhalb aller aktiven Grenzen.'))}</p></div><div class="eos-ems-overview-state"><span class="eos-ems-overview-state-dot"></span><strong>${stale ? 'Offline / veraltet' : ({ok:'Normal',info:'Information',warning:'Begrenzt',error:'Störung'}[status])}</strong><small>${esc(data.instance)} · ${esc(formatAge(data.updatedAt))}</small></div></header><div class="eos-ems-overview-metrics">${metrics}</div>${totalBudget > 0 ? `<div class="eos-ems-overview-budget" title="${percent.toFixed(1)} % Restbudget"><span style="width:${percent}%"></span></div>` : ''}${tags.length ? `<div class="eos-ems-overview-tags">${tags.map(tag => `<span class="${tag.warning ? 'is-warning' : ''}">${esc(tag.label)}</span>`).join('')}</div>` : ''}<div class="eos-ems-overview-columns"><div class="eos-ems-overview-panel"><h3>Aktuelle Regelentscheidungen</h3>${decisionsHtml}</div><div class="eos-ems-overview-panel"><h3>${isTechnical ? 'Letzte EMS-Ereignisse' : 'Systemerklärung'}</h3>${eventsHtml}</div></div><footer class="eos-ems-overview-footer"><span>Diagnose ist rein lesend. Keine Kachel besitzt Schreibhoheit.</span><a href="${esc(detailsUrl)}" target="_blank" rel="noreferrer">Vollständige EMS-Diagnose öffnen ↗</a></footer>`;
|
|
111
169
|
};
|
|
112
170
|
|
|
171
|
+
const scheduleFastRetry = () => {
|
|
172
|
+
if (destroyed || retryTimer) return;
|
|
173
|
+
retryTimer = window.setTimeout(() => { retryTimer = 0; void load(); }, RETRY_MS);
|
|
174
|
+
};
|
|
113
175
|
const load = async () => {
|
|
114
176
|
if (destroyed || inFlight || document.visibilityState === 'hidden' || currentTab() !== 'tab-intro') return;
|
|
115
177
|
const socket = getSocket();
|
|
116
|
-
if (!socket
|
|
178
|
+
if (!socket) {
|
|
179
|
+
if (Date.now() - startedAt < CONNECTION_WAIT_MS) {
|
|
180
|
+
loading('Die sichere Admin-Verbindung wird hergestellt. Die Kachel aktualisiert sich automatisch.');
|
|
181
|
+
scheduleFastRetry();
|
|
182
|
+
} else {
|
|
183
|
+
unavailable('Die Admin-Verbindung konnte nicht übernommen werden.', 'Bitte die Seite einmal mit Strg + F5 neu laden. Die übrige EOS-Oberfläche bleibt unverändert nutzbar.');
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
117
187
|
inFlight = true;
|
|
118
188
|
try {
|
|
119
|
-
const [states, alive] = await Promise.all([
|
|
189
|
+
const [states, alive] = await Promise.all([
|
|
190
|
+
socket.getForeignStates('nexowatt-ui.*.info.adminOverview.*'),
|
|
191
|
+
socket.getForeignStates('system.adapter.nexowatt-ui.*.alive'),
|
|
192
|
+
]);
|
|
120
193
|
const instances = normalizeInstances(states || {}, alive || {});
|
|
121
|
-
if (instances.length)
|
|
122
|
-
|
|
123
|
-
|
|
194
|
+
if (instances.length) {
|
|
195
|
+
render(instances[0]);
|
|
196
|
+
} else {
|
|
197
|
+
const info = await readUiAdapterInfo(socket);
|
|
198
|
+
if (!info.installed) {
|
|
199
|
+
unavailable('NexoWatt UI ist nicht installiert oder für dieses Benutzerkonto nicht sichtbar.', `Für die vollständige Kachel wird iobroker.nexowatt-ui ab ${REQUIRED_UI_VERSION} benötigt.`);
|
|
200
|
+
} else if (info.version && !semverAtLeast(info.version, REQUIRED_UI_VERSION)) {
|
|
201
|
+
unavailable(`NexoWatt UI ${info.version} ist installiert, enthält den Diagnosevertrag aber noch nicht.`, `Bitte NexoWatt UI mindestens auf ${REQUIRED_UI_VERSION} aktualisieren und die Instanz danach neu starten.`);
|
|
202
|
+
} else if (info.enabled === false) {
|
|
203
|
+
unavailable(`NexoWatt UI ${info.version || ''} ist installiert, aber deaktiviert.`, 'Bitte die UI-Instanz starten. Danach aktualisiert sich die Kachel automatisch.');
|
|
204
|
+
} else {
|
|
205
|
+
unavailable(`NexoWatt UI ${info.version || ''} ist aktiv, veröffentlicht aber noch keine info.adminOverview.*-Werte.`, 'Bitte die NexoWatt-UI-Instanz einmal neu starten. Bleibt der Channel aus, ist der AdminOverviewPublisher der installierten UI-Version zu prüfen.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const message = text(error?.message || error, 'unbekannter Fehler');
|
|
210
|
+
const permission = /permission|access|not allowed|denied/i.test(message);
|
|
211
|
+
unavailable(permission ? 'Dieses Benutzerkonto darf die EMS-Diagnosewerte derzeit nicht lesen.' : `EMS-Diagnose konnte nicht gelesen werden: ${message}`, permission ? 'Die Datenpunkte unter nexowatt-ui.*.info.adminOverview.* benötigen Leserechte für die jeweilige EOS-Rolle.' : 'Die Kachel ist rein lesend; es wurde keine EMS-Regelung verändert.');
|
|
212
|
+
} finally { inFlight = false; }
|
|
124
213
|
};
|
|
125
214
|
|
|
126
215
|
const apply = () => {
|
|
@@ -129,12 +218,20 @@
|
|
|
129
218
|
if (currentTab() !== 'tab-intro') { document.getElementById(ROOT_ID)?.remove(); return; }
|
|
130
219
|
ensureRoot(); void load();
|
|
131
220
|
};
|
|
221
|
+
const onSocketReady = event => {
|
|
222
|
+
rememberSocket(event?.detail?.socket || window.NEXOWATT_EOS_ADMIN_SOCKET || window.NEXOWATT_EOS_SOCKET);
|
|
223
|
+
startedAt = Date.now();
|
|
224
|
+
if (retryTimer) { clearTimeout(retryTimer); retryTimer = 0; }
|
|
225
|
+
void load();
|
|
226
|
+
};
|
|
132
227
|
const start = () => {
|
|
228
|
+
startedAt = Date.now();
|
|
133
229
|
apply();
|
|
134
230
|
timer = window.setInterval(apply, POLL_MS);
|
|
135
231
|
window.addEventListener('hashchange', apply);
|
|
232
|
+
window.addEventListener('nexowatt-eos-admin-socket-ready', onSocketReady);
|
|
136
233
|
document.addEventListener('visibilitychange', apply);
|
|
137
234
|
};
|
|
138
235
|
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, { once: true }) : start();
|
|
139
|
-
window.NEXOWATT_EOS_EMS_OVERVIEW = Object.freeze({ version: VERSION, contract: CONTRACT, refresh: apply, destroy() { destroyed = true; if (timer) clearInterval(timer); document.getElementById(ROOT_ID)?.remove(); } });
|
|
236
|
+
window.NEXOWATT_EOS_EMS_OVERVIEW = Object.freeze({ version: VERSION, contract: CONTRACT, refresh: apply, destroy() { destroyed = true; if (timer) clearInterval(timer); if (retryTimer) clearTimeout(retryTimer); window.removeEventListener('nexowatt-eos-admin-socket-ready', onSocketReady); document.getElementById(ROOT_ID)?.remove(); } });
|
|
140
237
|
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v98-first-login-password-native-overview';
|
|
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;
|
|
@@ -192,13 +192,13 @@
|
|
|
192
192
|
submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
|
|
193
193
|
try {
|
|
194
194
|
const response = await fetch(endpoint, {
|
|
195
|
-
method: 'POST', credentials: '
|
|
195
|
+
method: 'POST', credentials: 'include', cache: 'no-store',
|
|
196
196
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-First-Login': '1' },
|
|
197
197
|
body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
|
|
198
198
|
});
|
|
199
199
|
const data = await response.json().catch(() => ({}));
|
|
200
200
|
if (!response.ok || data.error) {
|
|
201
|
-
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy };
|
|
201
|
+
const messages = { passwordRequired: t.required, passwordMismatch: t.mismatch, passwordLength: t.length(minLength), passwordComplexity: t.complexity, passwordTooEasy: t.easy, passwordSetupNotAllowed: t.generic, invalidRequestOrigin: t.generic, passwordApiUnavailable: t.generic, passwordWriteFailed: t.generic, passwordVerificationFailed: t.generic, userObjectUnavailableAfterPasswordChange: t.generic };
|
|
202
202
|
throw new Error(messages[data.error] || t.generic);
|
|
203
203
|
}
|
|
204
204
|
setStatus(t.success, 'success');
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
|
|
266
266
|
try {
|
|
267
267
|
const response = await fetch(endpoint, {
|
|
268
|
-
method: 'POST', credentials: '
|
|
268
|
+
method: 'POST', credentials: 'include', cache: 'no-store',
|
|
269
269
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Password': '1' },
|
|
270
270
|
body: JSON.stringify({ password: password.value, passwordRepeat: repeat.value }),
|
|
271
271
|
});
|
|
@@ -473,7 +473,7 @@
|
|
|
473
473
|
setStatus(card, t.claimChecking, '');
|
|
474
474
|
try {
|
|
475
475
|
const response = await fetch(new URL('nexowatt/account/passwordless-claim', base).href, {
|
|
476
|
-
method: 'POST', credentials: '
|
|
476
|
+
method: 'POST', credentials: 'include', cache: 'no-store',
|
|
477
477
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-NexoWatt-EOS-Passwordless-Claim': '1' },
|
|
478
478
|
body: JSON.stringify({ user: account }),
|
|
479
479
|
});
|
|
@@ -500,7 +500,7 @@
|
|
|
500
500
|
const { username, password, card, submit } = loginElements();
|
|
501
501
|
if (!username || !password || !card) return;
|
|
502
502
|
card.classList.add('eos-login-card-modern');
|
|
503
|
-
//
|
|
503
|
+
// v98 compact normal-login first activation: do not add role buttons or a second panel.
|
|
504
504
|
// The account is entered in the existing Loginname field; a blank password is accepted only
|
|
505
505
|
// after the backend confirms that this exact managed account is eligible.
|
|
506
506
|
card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
|
|
@@ -634,7 +634,7 @@
|
|
|
634
634
|
const timeout = window.setTimeout(() => controller.abort(), 8000);
|
|
635
635
|
|
|
636
636
|
window.NEXOWATT_EOS_BOOTSTRAP_READY = fetch(contextUrl, {
|
|
637
|
-
credentials: '
|
|
637
|
+
credentials: 'include', cache: 'no-store', headers: { Accept: 'application/json' }, signal: controller.signal,
|
|
638
638
|
})
|
|
639
639
|
.then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); })
|
|
640
640
|
.then(policy => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v98-native-overview-ems-live-reserve-filter';
|
|
5
5
|
window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
|
|
6
6
|
|
|
7
7
|
const state = {
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
});
|
|
279
279
|
};
|
|
280
280
|
const ensureSafeOverview = () => safe(() => {
|
|
281
|
-
// 7.9.
|
|
281
|
+
// 7.9.97: never cover the real Admin Intro with a second tile surface.
|
|
282
282
|
// Installer and end-user use the native card component; security remains
|
|
283
283
|
// enforced by route/instance filters and backend permissions.
|
|
284
284
|
removeSafeOverview();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v98-nexowatt-native-shell-native-overview';
|
|
5
5
|
const previous = window.NEXOWATT_NATIVE_SHELL;
|
|
6
6
|
if (previous?.version === VERSION) return;
|
|
7
7
|
previous?.destroy?.();
|
|
@@ -150,7 +150,8 @@
|
|
|
150
150
|
const paper = document.getElementById('app-paper');
|
|
151
151
|
const role = String(window.NEXOWATT_EOS_ACCESS_ROLE || 'unknown').toLowerCase();
|
|
152
152
|
if (!paper || isLogin() || currentTab() !== 'tab-intro' || role === 'unknown') {
|
|
153
|
-
document.getElementById('eos-native-overview-hero')
|
|
153
|
+
const staleHero = document.getElementById('eos-native-overview-hero');
|
|
154
|
+
if (staleHero?.dataset.eosRuntimeOverviewHero === 'true') staleHero.remove();
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
const config = role === 'admin'
|
|
@@ -159,12 +160,18 @@
|
|
|
159
160
|
? { label: 'Installateur', text: 'Inbetriebnahme, Fehlersuche, Geräte- und EMS-Diagnose – mit deinen freigegebenen Installateurrechten.' }
|
|
160
161
|
: { label: 'Endkunde', text: 'Energie, Laden, Gebäude und die aktuellen EMS-Entscheidungen auf einen Blick.' };
|
|
161
162
|
let hero = document.getElementById('eos-native-overview-hero');
|
|
163
|
+
// A freshly built Intro.tsx owns this node. Do not rewrite React children.
|
|
164
|
+
if (hero?.dataset.eosNativeReactOverview === 'true') return;
|
|
165
|
+
// The bundled Admin-7 runtime predates the React hero. Supply one compatibility node until
|
|
166
|
+
// the next complete frontend rebuild; it uses the same role text and native-card surface.
|
|
162
167
|
if (!hero) {
|
|
163
168
|
hero = document.createElement('section');
|
|
164
169
|
hero.id = 'eos-native-overview-hero';
|
|
165
170
|
hero.className = 'eos-overview-hero eos-native-overview-hero';
|
|
171
|
+
hero.dataset.eosRuntimeOverviewHero = 'true';
|
|
166
172
|
paper.insertBefore(hero, paper.firstChild || null);
|
|
167
173
|
}
|
|
174
|
+
if (hero.dataset.eosRuntimeOverviewHero !== 'true') return;
|
|
168
175
|
hero.innerHTML = `
|
|
169
176
|
<div><span class="eos-overview-eyebrow">NexoWatt EOS</span><h1>Übersicht</h1><p>${config.text}</p></div>
|
|
170
177
|
<div class="eos-overview-role" data-nexowatt-overview-role="${role}"><span class="eos-overview-status-dot"></span>${config.label}</div>`;
|
|
@@ -1,79 +1,45 @@
|
|
|
1
|
-
/* NexoWatt EOS stable runtime
|
|
1
|
+
/* NexoWatt EOS stable runtime v98 */
|
|
2
2
|
(() => {
|
|
3
3
|
'use strict';
|
|
4
4
|
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
5
5
|
const ASSIST_TEXT = /EOS\s*Assist/i;
|
|
6
6
|
const PASSWORDLESS_TEXT = /Erstanmeldung\s+ohne\s+Passwort|first\s+sign-?in\s+without\s+password/i;
|
|
7
7
|
const isLogin = () => /(?:\?|&)login(?:[=&]|$)/i.test(location.search) || /\/login(?:[/?#]|$)/i.test(location.pathname + location.hash);
|
|
8
|
-
const visible =
|
|
8
|
+
const visible = element => !!element && element.getClientRects().length > 0;
|
|
9
9
|
const findInput = kind => {
|
|
10
10
|
const inputs = [...document.querySelectorAll('input')].filter(visible);
|
|
11
|
-
if (kind === 'password') return inputs.find(
|
|
12
|
-
return inputs.find(
|
|
11
|
+
if (kind === 'password') return inputs.find(input => input.type === 'password');
|
|
12
|
+
return inputs.find(input => input.type !== 'password' && /user|login|name/i.test(`${input.name} ${input.id} ${input.autocomplete} ${input.getAttribute('aria-label') || ''}`)) || inputs.find(input => input.type !== 'password');
|
|
13
13
|
};
|
|
14
|
-
const findSubmit = () => [...document.querySelectorAll('button,input[type="submit"]')].find(
|
|
15
|
-
const
|
|
16
|
-
if (!a || !b) return null;
|
|
17
|
-
const parents = new Set(); let n=a;
|
|
18
|
-
while(n){parents.add(n);n=n.parentElement;}
|
|
19
|
-
n=b; while(n&&!parents.has(n))n=n.parentElement;
|
|
20
|
-
return n;
|
|
21
|
-
};
|
|
22
|
-
const markLogin = () => {
|
|
14
|
+
const findSubmit = () => [...document.querySelectorAll('button,input[type="submit"]')].find(element => visible(element) && (/anmelden|login|sign in/i.test((element.textContent || element.value || '').trim()) || element.type === 'submit'));
|
|
15
|
+
const sync = () => {
|
|
23
16
|
if (!isLogin()) return;
|
|
24
|
-
document.documentElement.classList.add('nw-eos-login-
|
|
25
|
-
document.body?.classList.add('nw-eos-login-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for(let i=0;i<3 && outer?.parentElement;i++){
|
|
34
|
-
if(outer.getBoundingClientRect().width > card.getBoundingClientRect().width+20) break;
|
|
35
|
-
outer=outer.parentElement;
|
|
36
|
-
}
|
|
37
|
-
outer?.setAttribute('data-nw-login-shell','true');
|
|
38
|
-
}
|
|
39
|
-
const sync=()=>{
|
|
40
|
-
const name=(u.value||'').trim().toLowerCase();
|
|
41
|
-
const pass=p.value||'';
|
|
42
|
-
if(submit && MANAGED_USERS.has(name) && !pass){
|
|
43
|
-
submit.disabled=true;
|
|
44
|
-
submit.setAttribute('aria-disabled','true');
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
if(!u.dataset.nwV96){u.dataset.nwV96='1';u.addEventListener('input',sync);p.addEventListener('input',sync);sync();}
|
|
17
|
+
document.documentElement.classList.add('nw-eos-login-v98');
|
|
18
|
+
document.body?.classList.add('nw-eos-login-v98');
|
|
19
|
+
const username = findInput('user');
|
|
20
|
+
const password = findInput('password');
|
|
21
|
+
const submit = findSubmit();
|
|
22
|
+
const account = String(username?.value || '').trim().toLowerCase().replace(/^system\.user\./, '');
|
|
23
|
+
if (submit && MANAGED_USERS.has(account) && !String(password?.value || '')) {
|
|
24
|
+
submit.disabled = true;
|
|
25
|
+
submit.setAttribute('aria-disabled', 'true');
|
|
48
26
|
}
|
|
49
27
|
};
|
|
50
28
|
const hideLegacyUi = () => {
|
|
51
|
-
for (const
|
|
52
|
-
const
|
|
53
|
-
if(!
|
|
54
|
-
if(ASSIST_TEXT.test(
|
|
55
|
-
const target=
|
|
56
|
-
target.style.setProperty('display','none','important');
|
|
57
|
-
target.setAttribute('aria-hidden','true');
|
|
29
|
+
for (const element of document.querySelectorAll('button,a,[role="button"],div,span')) {
|
|
30
|
+
const value = (element.textContent || '').trim();
|
|
31
|
+
if (!value || value.length > 100) continue;
|
|
32
|
+
if (ASSIST_TEXT.test(value) || PASSWORDLESS_TEXT.test(value)) {
|
|
33
|
+
const target = element.closest('button,a,[role="button"],[data-eos-assist],.eos-assist') || element;
|
|
34
|
+
target.style.setProperty('display', 'none', 'important');
|
|
35
|
+
target.setAttribute('aria-hidden', 'true');
|
|
58
36
|
}
|
|
59
37
|
}
|
|
60
38
|
};
|
|
61
|
-
const apply=()=>{
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.nw-eos-login-v96 [data-nw-login-card="true"]{width:min(460px,calc(100vw - 56px))!important;min-height:540px!important;height:auto!important;max-height:none!important;overflow:visible!important;box-sizing:border-box!important;padding-bottom:26px!important}
|
|
68
|
-
.nw-eos-login-v96 [data-nw-login-card="true"] *{scrollbar-width:none}
|
|
69
|
-
.nw-eos-login-v96 [data-nw-login-card="true"] *::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}
|
|
70
|
-
.nw-eos-login-v96 [data-nw-login-card="true"] [role="alert"]{position:static!important;max-height:none!important;overflow:visible!important;white-space:normal!important}
|
|
71
|
-
[data-eos-assist],.eos-assist,.nexowatt-eos-assist{display:none!important}
|
|
72
|
-
@media(max-height:620px){.nw-eos-login-v96 [data-nw-login-shell="true"]{min-height:0!important;margin:16px auto!important}.nw-eos-login-v96 [data-nw-login-card="true"]{min-height:0!important}}
|
|
73
|
-
`;
|
|
74
|
-
(document.head||document.documentElement).appendChild(style);
|
|
75
|
-
const observer=new MutationObserver(()=>requestAnimationFrame(apply));
|
|
76
|
-
observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:['class','style']});
|
|
77
|
-
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',apply,{once:true});else apply();
|
|
78
|
-
setInterval(apply,1500);
|
|
39
|
+
const apply = () => { sync(); hideLegacyUi(); };
|
|
40
|
+
const observer = new MutationObserver(() => requestAnimationFrame(apply));
|
|
41
|
+
observer.observe(document.documentElement, { subtree: true, childList: true });
|
|
42
|
+
document.addEventListener('input', apply, true);
|
|
43
|
+
document.addEventListener('change', apply, true);
|
|
44
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', apply, { once: true }); else apply();
|
|
79
45
|
})();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const VERSION = 'v98-scroll-login-password-write';
|
|
4
|
+
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
5
|
+
const normalizeUser = value => String(value || '').trim().toLowerCase().replace(/^system\.user\./, '');
|
|
6
|
+
const apply = () => {
|
|
7
|
+
document.documentElement.classList.add('eos-assist-disabled');
|
|
8
|
+
document.querySelectorAll('.eos-assist-root,.eos-assist-header-root,[data-eos-assist-root],.eos-passwordless-launcher')
|
|
9
|
+
.forEach(element => element.remove());
|
|
10
|
+
const username = document.querySelector('input#username,input[name="username"]');
|
|
11
|
+
const password = document.querySelector('input#password,input[name="password"]');
|
|
12
|
+
const submit = document.querySelector('button[type="submit"],input[type="submit"]');
|
|
13
|
+
if (submit && MANAGED_USERS.has(normalizeUser(username?.value)) && !String(password?.value || '')) {
|
|
14
|
+
submit.disabled = true;
|
|
15
|
+
submit.classList.add('Mui-disabled');
|
|
16
|
+
submit.setAttribute('aria-disabled', 'true');
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
let scheduled = false;
|
|
20
|
+
const schedule = () => {
|
|
21
|
+
if (scheduled) return;
|
|
22
|
+
scheduled = true;
|
|
23
|
+
queueMicrotask(() => { scheduled = false; apply(); });
|
|
24
|
+
};
|
|
25
|
+
const start = () => {
|
|
26
|
+
apply();
|
|
27
|
+
new MutationObserver(schedule).observe(document.documentElement, { subtree: true, childList: true });
|
|
28
|
+
document.addEventListener('input', schedule, true);
|
|
29
|
+
document.addEventListener('change', schedule, true);
|
|
30
|
+
};
|
|
31
|
+
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, { once: true }) : start();
|
|
32
|
+
window.NEXOWATT_EOS_STABLE_V98 = Object.freeze({ version: VERSION, refresh: apply });
|
|
33
|
+
})();
|