iobroker.eos-admin 7.9.50 → 7.9.52

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,12 +1,15 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_OBJECTS_STATE_TOOLS_VERSION = 'v50-dp-write-hardfix';
4
+ window.NEXOWATT_EOS_OBJECTS_STATE_TOOLS_VERSION = 'v52-fast-unrestricted-dp-write';
5
5
 
6
6
  const ACTIVE_CLASS = 'eos-objects-surface';
7
- const safe = fn => { try { return fn(); } catch (_) { return undefined; } };
7
+ const DIALOG_CLASS = 'eos-dp-write-backdrop';
8
8
  const busyIds = new Set();
9
9
 
10
+ const safe = fn => { try { return fn(); } catch (_) { return undefined; } };
11
+ const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));
12
+
10
13
  const isObjectsRoute = () => {
11
14
  const hash = String(window.location.hash || '');
12
15
  if (/#\/?tab-objects\b|#tab-objects\b/.test(hash)) return true;
@@ -20,123 +23,86 @@
20
23
  return active;
21
24
  };
22
25
 
23
- const interactiveSelector = [
26
+ const valueCellSelector = [
27
+ '.eos-object-value-cell',
28
+ '[data-eos-object-value-cell]',
29
+ '[title*="Wert klicken"]',
30
+ '[title*="Wert schreiben"]',
31
+ '[title*="Write value"]'
32
+ ].join(',');
33
+
34
+ const interactiveIgnoreSelector = [
35
+ `.${DIALOG_CLASS}`,
24
36
  'button',
25
37
  '[role="button"]',
26
38
  'a[href]',
27
39
  'input',
28
40
  'select',
29
41
  'textarea',
30
- '.MuiButton-root',
31
42
  '.MuiIconButton-root',
32
43
  '.MuiCheckbox-root',
33
- '.MuiSwitch-root',
34
- '.MuiSwitch-switchBase',
35
44
  '.MuiMenuItem-root',
36
45
  '.MuiAutocomplete-option',
37
- '.MuiSelect-select',
38
- '.admin-button',
39
46
  '.copyButton'
40
47
  ].join(',');
41
48
 
42
- const nativeLayerSelector = [
43
- '.MuiDialog-root',
44
- '.MuiModal-root',
45
- '.MuiPopover-root',
46
- '.MuiPopper-root',
47
- '.MuiTooltip-popper',
48
- '[role="dialog"]',
49
- '[role="listbox"]',
50
- '[role="menu"]',
51
- '[role="tooltip"]'
52
- ].join(',');
53
-
54
- const valueCellSelector = [
55
- '.eos-object-value-cell',
56
- '[data-eos-object-value-cell]',
57
- '[title*="Wert schreiben"]',
58
- '[title*="Wert klicken"]',
59
- '[title*="Button sofort testen"]'
60
- ].join(',');
61
-
62
- const collect = (base, selector) => {
63
- const out = [];
64
- if (!base) return out;
65
- if (base.nodeType === Node.ELEMENT_NODE && base.matches?.(selector)) out.push(base);
66
- if (base.querySelectorAll) out.push(...Array.from(base.querySelectorAll(selector)));
67
- return out;
68
- };
69
-
70
- const getSocket = () => window.NEXOWATT_EOS_OBJECTS_SOCKET || window.NEXOWATT_EOS_SOCKET || window.socket || null;
71
-
72
49
  const normalizeText = value => String(value ?? '').replace(/\s+/g, ' ').trim();
73
50
 
74
- const isWritableCell = cell => {
75
- if (!cell) return false;
76
- if (cell.getAttribute('data-eos-object-writable') === '1') return true;
77
- const title = `${cell.getAttribute('title') || ''} ${cell.textContent || ''}`;
78
- return /wert\s*(klicken|schreiben)|write\s*value|sofort\s*testen/i.test(title);
51
+ const getSocket = () =>
52
+ window.NEXOWATT_EOS_OBJECTS_SOCKET ||
53
+ window.NEXOWATT_EOS_SOCKET ||
54
+ window.socket ||
55
+ null;
56
+
57
+ const waitForSocket = async () => {
58
+ for (let i = 0; i < 40; i += 1) {
59
+ const socket = getSocket();
60
+ if (socket && (typeof socket.sendTo === 'function' || typeof socket.setState === 'function')) return socket;
61
+ await delay(50);
62
+ }
63
+ throw new Error('Socket ist noch nicht bereit');
79
64
  };
80
65
 
81
- const releaseNativeControls = root => safe(() => {
82
- if (!setSurfaceState()) return;
83
- const base = root && root.nodeType ? root : document;
84
- collect(base, `${interactiveSelector},${nativeLayerSelector}`).forEach(el => {
85
- if (!el || el.closest?.('#eos-assist-root,.eos-assist-root')) return;
86
- el.classList?.remove('eos-security-hidden-delete', 'eos-protected-delete-control', 'eos-disabled-by-security');
87
- el.removeAttribute?.('data-eos-security-blocked');
88
- el.removeAttribute?.('aria-disabled');
89
- if (el.style?.pointerEvents === 'none') el.style.pointerEvents = 'auto';
90
- if (el.style?.visibility === 'hidden') el.style.visibility = 'visible';
91
- if (el.style?.display === 'none' && !el.classList?.contains('eos-native-logout-hidden')) el.style.display = '';
92
- });
93
- });
94
-
95
- const annotateValueCells = root => safe(() => {
96
- if (!setSurfaceState()) return;
97
- const base = root && root.nodeType ? root : document;
98
- collect(base, valueCellSelector).forEach(cell => {
99
- const id = cell.getAttribute('data-eos-object-value-cell') || cell.closest?.('[id]')?.getAttribute('id') || '';
100
- const writable = isWritableCell(cell);
101
- const visibleValue = normalizeText(cell.textContent || '') || '(leer)';
102
- const cache = `${id}|${writable ? 1 : 0}|${visibleValue}`;
103
-
104
- cell.style.pointerEvents = 'auto';
105
- cell.style.cursor = writable ? 'pointer' : 'default';
106
- cell.classList.add('eos-object-value-cell');
107
- cell.classList.toggle('eos-object-value-writable', writable);
108
- cell.classList.toggle('eos-object-value-readonly', !writable);
109
- if (writable) cell.setAttribute('tabindex', '0');
110
-
111
- if (cell.getAttribute('data-eos-value-cache') !== cache) {
112
- cell.setAttribute('data-eos-value-cache', cache);
113
- cell.setAttribute('data-eos-current-visible-value', visibleValue);
114
- const nextTitle = [
115
- id ? `ID: ${id}` : null,
116
- `Aktueller Wert: ${visibleValue}`,
117
- `Status: ${writable ? 'beschreibbar' : 'nur lesbar'}`,
118
- writable ? 'Klicken: Wert schreiben' : 'Nur lesbarer Wert'
119
- ].filter(Boolean).join('\n');
120
- if (cell.getAttribute('title') !== nextTitle) cell.setAttribute('title', nextTitle);
121
- }
122
- });
123
- });
66
+ const withTimeout = async (promise, ms, fallback = null) => {
67
+ let timer;
68
+ try {
69
+ return await Promise.race([
70
+ Promise.resolve(promise),
71
+ new Promise(resolve => { timer = window.setTimeout(() => resolve(fallback), ms); }),
72
+ ]);
73
+ } finally {
74
+ if (timer) window.clearTimeout(timer);
75
+ }
76
+ };
124
77
 
125
78
  const parseBoolean = value => {
126
79
  if (value === true || value === 1) return true;
127
80
  if (value === false || value === 0 || value == null) return false;
128
81
  const normalized = String(value).trim().toLowerCase();
129
- return normalized === 'true' || normalized === '1' || normalized === 'on' || normalized === 'yes' || normalized === 'ja';
82
+ return normalized === 'true' || normalized === '1' || normalized === 'on' || normalized === 'yes' || normalized === 'ja' || normalized === 'ein';
130
83
  };
131
84
 
132
85
  const isNullToken = value => /^\s*(\(null\)|null|undefined)?\s*$/i.test(String(value ?? ''));
133
86
 
87
+ const stripUnit = value => String(value ?? '')
88
+ .replace(/^\s*\((null|undefined)\)\s*$/i, '$1')
89
+ .replace(/\s+(?:%|°C|°F|K|W|kW|Wh|kWh|VA|VAr|A|V|Hz|bar|mbar|Pa|hPa|l|L|m³|m3|s|min|h|d)$/i, '')
90
+ .trim();
91
+
92
+ const inferTypeFromValue = value => {
93
+ const text = stripUnit(value);
94
+ if (/^(true|false|0|1|on|off|yes|no|ja|nein|ein|aus)$/i.test(text)) return 'boolean';
95
+ if (/^-?\d+(?:[.,]\d+)?$/.test(text)) return 'number';
96
+ if (/^\s*[\[{]/.test(String(value || ''))) return 'json';
97
+ return 'string';
98
+ };
99
+
134
100
  const coerceValue = (rawValue, type) => {
135
101
  const raw = String(rawValue ?? '');
136
102
  if (isNullToken(raw) && type !== 'string' && type !== 'json') return null;
137
103
  if (type === 'boolean') return parseBoolean(raw);
138
104
  if (type === 'number') {
139
- const normalized = raw.replace(',', '.').trim();
105
+ const normalized = stripUnit(raw).replace(',', '.');
140
106
  const number = Number(normalized);
141
107
  if (!Number.isFinite(number)) throw new Error(`Ungültige Zahl: ${raw}`);
142
108
  return number;
@@ -156,61 +122,187 @@
156
122
  return String(value);
157
123
  };
158
124
 
159
- const hasNativeWriteDialog = () => {
160
- const dialogs = Array.from(document.querySelectorAll('.MuiDialog-root,[role="dialog"]'));
161
- return dialogs.some(dialog => /write value|wert schreiben|set value|wert setzen/i.test(dialog.textContent || ''));
125
+ const formatWriteError = error => {
126
+ if (!error) return 'unbekannter Fehler';
127
+ if (typeof error === 'string') return error;
128
+ if (error.message) return error.message;
129
+ try { return JSON.stringify(error); } catch (_) { return String(error); }
130
+ };
131
+
132
+ let cachedEosAdminInstance = null;
133
+ const normalizeInstanceId = value => {
134
+ if (!value) return null;
135
+ if (typeof value === 'string') {
136
+ const id = value.replace(/^system\.adapter\./, '').trim();
137
+ return /^eos-admin\.\d+$/.test(id) ? id : null;
138
+ }
139
+ if (typeof value === 'object') return normalizeInstanceId(value._id || value.id || value.instance || value.namespace);
140
+ return null;
141
+ };
142
+
143
+ const resolveEosAdminInstance = async socket => {
144
+ if (cachedEosAdminInstance) return cachedEosAdminInstance;
145
+ const explicit = normalizeInstanceId(window.NEXOWATT_EOS_ADMIN_INSTANCE || window.adminInstance || window.adapterInstance);
146
+ if (explicit) return (cachedEosAdminInstance = explicit);
147
+ if (socket && typeof socket.getCurrentInstance === 'function') {
148
+ try {
149
+ const current = normalizeInstanceId(await withTimeout(socket.getCurrentInstance(), 700, null));
150
+ if (current) return (cachedEosAdminInstance = current);
151
+ } catch (_) { /* fallback below */ }
152
+ }
153
+ if (socket && typeof socket.getAdapterInstances === 'function') {
154
+ try {
155
+ const instances = await withTimeout(socket.getAdapterInstances('eos-admin'), 900, []);
156
+ const list = Array.isArray(instances) ? instances : [];
157
+ const alive = list.find(instance => normalizeInstanceId(instance) && instance?.common?.enabled !== false);
158
+ const id = normalizeInstanceId(alive || list[0]);
159
+ if (id) return (cachedEosAdminInstance = id);
160
+ } catch (_) { /* fallback below */ }
161
+ }
162
+ return (cachedEosAdminInstance = 'eos-admin.0');
163
+ };
164
+
165
+ const callEosAdmin = async (socket, command, message) => {
166
+ const instance = await resolveEosAdminInstance(socket);
167
+ if (!instance) throw new Error('EOS Admin Instanz konnte nicht ermittelt werden');
168
+
169
+ if (socket && typeof socket.sendTo === 'function') {
170
+ const result = socket.sendTo(instance, command, message);
171
+ if (result && typeof result.then === 'function') return await withTimeout(result, 7000, { ok: false, error: 'sendTo Timeout' });
172
+ if (result !== undefined) return result;
173
+ }
174
+
175
+ const rawSocket = socket?._socket || socket?.socket || window.socket;
176
+ if (rawSocket && typeof rawSocket.emit === 'function') {
177
+ return await withTimeout(new Promise((resolve, reject) => {
178
+ try {
179
+ rawSocket.emit('sendTo', instance, command, message, response => resolve(response));
180
+ } catch (error) {
181
+ reject(error);
182
+ }
183
+ }), 7000, { ok: false, error: 'raw sendTo Timeout' });
184
+ }
185
+
186
+ throw new Error('socket.sendTo ist nicht verfügbar');
187
+ };
188
+
189
+ const writeStateUnrestricted = async (socket, id, state) => {
190
+ if (!socket) throw new Error('Socket ist nicht bereit');
191
+
192
+ let backendError = null;
193
+ try {
194
+ const response = await callEosAdmin(socket, 'eos:writeState', { id, state });
195
+ if (response === 'permissionError') throw new Error('sendTo permissionError');
196
+ if (response && response.ok === false) throw new Error(response.error || 'EOS Backend hat das Schreiben abgelehnt');
197
+ if (response && response.error) throw new Error(response.error);
198
+ return { ok: true, via: 'eos-admin' };
199
+ } catch (error) {
200
+ backendError = error;
201
+ }
202
+
203
+ if (typeof socket.setState === 'function') {
204
+ try {
205
+ await withTimeout(socket.setState(id, state), 7000);
206
+ return { ok: true, via: 'socket' };
207
+ } catch (directError) {
208
+ throw new Error(`EOS-Backend: ${formatWriteError(backendError)}\nDirekt: ${formatWriteError(directError)}`);
209
+ }
210
+ }
211
+
212
+ throw new Error(`EOS-Backend: ${formatWriteError(backendError)}\nDirekt: socket.setState ist nicht verfügbar`);
162
213
  };
163
214
 
215
+ window.NEXOWATT_EOS_WRITE_STATE_UNRESTRICTED = writeStateUnrestricted;
216
+
164
217
  const injectDialogStyle = () => {
165
218
  if (document.getElementById('eos-dp-write-style')) return;
166
219
  const style = document.createElement('style');
167
220
  style.id = 'eos-dp-write-style';
168
221
  style.textContent = `
169
- .eos-dp-write-backdrop{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:18px;font-family:Roboto,Arial,sans-serif}.eos-dp-write-dialog{width:min(620px,calc(100vw - 36px));max-height:calc(100vh - 36px);overflow:auto;background:#061827;color:#fff;border:1px solid rgba(0,255,180,.45);border-radius:16px;box-shadow:0 18px 60px rgba(0,0,0,.65),0 0 34px rgba(0,255,180,.22);padding:18px}.eos-dp-write-dialog h3{margin:0 0 8px;font-size:19px;color:#18f4c0}.eos-dp-write-id{font-family:monospace;font-size:12px;opacity:.82;word-break:break-all;margin-bottom:12px}.eos-dp-write-meta{font-size:12px;opacity:.76;margin-bottom:12px}.eos-dp-write-dialog label{display:block;font-size:13px;margin:12px 0 6px}.eos-dp-write-dialog input[type=text],.eos-dp-write-dialog textarea,.eos-dp-write-dialog select{box-sizing:border-box;width:100%;background:#07111d;color:#fff;border:1px solid rgba(255,255,255,.28);border-radius:8px;padding:10px;font-size:15px;outline:none}.eos-dp-write-dialog textarea{min-height:130px;font-family:monospace}.eos-dp-write-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:18px}.eos-dp-write-actions button{border:0;border-radius:10px;padding:9px 16px;font-weight:700;cursor:pointer}.eos-dp-write-primary{background:#00d6a1;color:#03130f}.eos-dp-write-secondary{background:#273746;color:#fff}.eos-dp-write-actions button:disabled{opacity:.55;cursor:wait}.eos-dp-write-error{color:#ff7676;margin-top:10px;font-size:13px;white-space:pre-wrap}.eos-object-value-cell.eos-dp-write-pulse{outline:2px solid #00d6a1;outline-offset:-2px}`;
222
+ html.${ACTIVE_CLASS} .eos-object-value-cell,[data-eos-object-value-cell]{pointer-events:auto!important;cursor:pointer!important}
223
+ .eos-dp-write-backdrop{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:18px;font-family:Roboto,Arial,sans-serif}.eos-dp-write-dialog{width:min(640px,calc(100vw - 36px));max-height:calc(100vh - 36px);overflow:auto;background:#061827;color:#fff;border:1px solid rgba(0,255,180,.45);border-radius:16px;box-shadow:0 18px 60px rgba(0,0,0,.65),0 0 34px rgba(0,255,180,.22);padding:18px}.eos-dp-write-dialog h3{margin:0 0 8px;font-size:19px;color:#18f4c0}.eos-dp-write-id{font-family:monospace;font-size:12px;opacity:.82;word-break:break-all;margin-bottom:12px}.eos-dp-write-meta{font-size:12px;opacity:.76;margin-bottom:12px}.eos-dp-write-dialog label{display:block;font-size:13px;margin:12px 0 6px}.eos-dp-write-dialog input[type=text],.eos-dp-write-dialog textarea,.eos-dp-write-dialog select{box-sizing:border-box;width:100%;background:#07111d;color:#fff;border:1px solid rgba(255,255,255,.28);border-radius:8px;padding:10px;font-size:15px;outline:none}.eos-dp-write-dialog textarea{min-height:130px;font-family:monospace}.eos-dp-write-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:18px;flex-wrap:wrap}.eos-dp-write-actions button{border:0;border-radius:10px;padding:9px 16px;font-weight:700;cursor:pointer}.eos-dp-write-primary{background:#00d6a1;color:#03130f}.eos-dp-write-secondary{background:#273746;color:#fff}.eos-dp-write-actions button:disabled{opacity:.55;cursor:wait}.eos-dp-write-error{color:#ff7676;margin-top:10px;font-size:13px;white-space:pre-wrap}.eos-dp-write-hint{font-size:12px;opacity:.72;margin-top:8px}.eos-dp-write-toast{position:fixed;right:18px;bottom:84px;z-index:9050;background:#05251e;border:1px solid rgba(0,255,180,.45);box-shadow:0 8px 28px rgba(0,0,0,.45);color:#fff;border-radius:12px;padding:10px 14px;font:13px Roboto,Arial,sans-serif}.eos-object-value-cell.eos-dp-write-pulse{outline:2px solid #00d6a1;outline-offset:-2px}`;
170
224
  document.head.appendChild(style);
171
225
  };
172
226
 
173
- const closeExistingDialog = () => document.querySelector('.eos-dp-write-backdrop')?.remove();
227
+ const showToast = (message, ms = 1800) => {
228
+ injectDialogStyle();
229
+ document.querySelectorAll('.eos-dp-write-toast').forEach(el => el.remove());
230
+ const toast = document.createElement('div');
231
+ toast.className = 'eos-dp-write-toast';
232
+ toast.textContent = message;
233
+ document.body.appendChild(toast);
234
+ window.setTimeout(() => toast.remove(), ms);
235
+ };
174
236
 
175
- const openWriteDialog = async (cell, event) => {
176
- const id = cell?.getAttribute('data-eos-object-value-cell') || cell?.closest?.('[id]')?.id || '';
177
- if (!id || busyIds.has(id)) return;
178
- const socket = getSocket();
179
- if (!socket || typeof socket.setState !== 'function') {
180
- window.alert('EOS: Socket für Datenpunkt-Schreiben ist noch nicht bereit. Bitte Seite einmal hart neu laden (Strg+F5).');
181
- return;
237
+ const closeExistingDialog = () => document.querySelector(`.${DIALOG_CLASS}`)?.remove();
238
+
239
+ const resolveIdFromCell = cell => {
240
+ if (!cell) return '';
241
+ const direct = cell.getAttribute?.('data-eos-object-value-cell') || cell.closest?.('[data-eos-object-value-cell]')?.getAttribute('data-eos-object-value-cell');
242
+ if (direct) return direct.trim();
243
+ const row = cell.closest?.('[data-eos-object-id],[data-id],[data-nodeid],[role="treeitem"],[role="row"],tr,li');
244
+ const attrs = ['data-eos-object-id', 'data-id', 'data-nodeid', 'id'];
245
+ for (const attr of attrs) {
246
+ const value = row?.getAttribute?.(attr);
247
+ if (value && value.includes('.')) return value.replace(/^.*?(?=(?:system|alias|enum|javascript|mqtt|modbus|e3dc|0_userdata|nexowatt|ems|hm-rpc|zigbee|shelly|sonoff|mihome|knx|fhem|sql|influxdb)\.)/, '').trim();
182
248
  }
249
+ return '';
250
+ };
251
+
252
+ const readObjectAndState = async (socket, id, visibleValue) => {
253
+ const objectPromise = typeof socket.getObject === 'function' ? socket.getObject(id) : Promise.resolve(null);
254
+ const statePromise = typeof socket.getState === 'function' ? socket.getState(id) : Promise.resolve(null);
255
+ const [objResult, stateResult] = await Promise.allSettled([
256
+ withTimeout(objectPromise, 1800, null),
257
+ withTimeout(statePromise, 1800, null),
258
+ ]);
259
+ const obj = objResult.status === 'fulfilled' ? objResult.value : null;
260
+ const state = stateResult.status === 'fulfilled' ? stateResult.value : null;
261
+ const common = obj?.common || {};
262
+ const current = state && Object.prototype.hasOwnProperty.call(state, 'val') ? state.val : stripUnit(visibleValue);
263
+ let type = common.type || typeof current;
264
+ if (!type || type === 'undefined' || type === 'object') type = type === 'object' ? 'json' : inferTypeFromValue(current);
265
+ if (type === 'array') type = 'json';
266
+ const role = String(common.role || '');
267
+ const states = common.states && typeof common.states === 'object' ? common.states : null;
268
+ return { obj, state, common, current, type, role, states };
269
+ };
183
270
 
271
+ const openWriteDialog = async (cell, event) => {
272
+ const id = resolveIdFromCell(cell);
273
+ if (!id || busyIds.has(id)) return;
184
274
  busyIds.add(id);
185
- cell?.classList.add('eos-dp-write-pulse');
186
- window.setTimeout(() => cell?.classList.remove('eos-dp-write-pulse'), 900);
275
+ injectDialogStyle();
276
+ cell?.classList?.add('eos-dp-write-pulse');
277
+ window.setTimeout(() => cell?.classList?.remove('eos-dp-write-pulse'), 650);
187
278
 
188
279
  try {
189
- const [objResult, stateResult] = await Promise.allSettled([
190
- typeof socket.getObject === 'function' ? socket.getObject(id) : Promise.resolve(null),
191
- typeof socket.getState === 'function' ? socket.getState(id) : Promise.resolve(null),
192
- ]);
193
- const obj = objResult.status === 'fulfilled' ? objResult.value : null;
194
- const state = stateResult.status === 'fulfilled' ? stateResult.value : null;
195
- const common = obj?.common || {};
196
- const role = String(common.role || '');
197
- let type = common.type || typeof state?.val;
198
- if (!type || type === 'undefined') type = 'string';
199
- if (type === 'object') type = 'json';
200
- const current = state?.val;
201
- const states = common.states && typeof common.states === 'object' ? common.states : null;
202
-
203
- if (/^button/.test(role)) {
204
- await socket.setState(id, { val: true, ack: false, q: 0 });
280
+ const socket = await waitForSocket();
281
+ const visibleValue = normalizeText(cell?.textContent || '');
282
+ const meta = await readObjectAndState(socket, id, visibleValue);
283
+ const { obj, common, current, type, role, states } = meta;
284
+
285
+ if (obj && obj.type && obj.type !== 'state') {
286
+ showToast(`EOS: "${id}" ist kein State (${obj.type})`);
287
+ busyIds.delete(id);
288
+ return;
289
+ }
290
+ if (common.type === 'file') {
291
+ showToast('EOS: Datei-Datenpunkte werden nicht als normaler State-Wert geschrieben.');
205
292
  busyIds.delete(id);
206
293
  return;
207
294
  }
208
295
 
209
- injectDialogStyle();
210
- closeExistingDialog();
296
+ if (/^button/.test(role) && (type === 'boolean' || type === 'string' || !type)) {
297
+ await writeStateUnrestricted(socket, id, { val: true, ack: false, q: 0 });
298
+ showToast(`Befehl geschrieben: ${id}`);
299
+ busyIds.delete(id);
300
+ return;
301
+ }
211
302
 
303
+ closeExistingDialog();
212
304
  const backdrop = document.createElement('div');
213
- backdrop.className = 'eos-dp-write-backdrop';
305
+ backdrop.className = DIALOG_CLASS;
214
306
  const dialog = document.createElement('div');
215
307
  dialog.className = 'eos-dp-write-dialog';
216
308
  dialog.setAttribute('role', 'dialog');
@@ -222,13 +314,14 @@
222
314
  <label class="eos-dp-write-label">Neuer Wert</label>
223
315
  <div class="eos-dp-write-input-wrap"></div>
224
316
  <label style="display:flex;align-items:center;gap:8px;margin-top:12px"><input class="eos-dp-write-ack" type="checkbox"> ack=true schreiben</label>
317
+ <div class="eos-dp-write-hint">EOS schreibt direkt über die Backend-Bridge. common.write/read blockiert hier nicht mehr.</div>
225
318
  <div class="eos-dp-write-error" hidden></div>
226
319
  <div class="eos-dp-write-actions"><button class="eos-dp-write-secondary" type="button">Abbrechen</button><button class="eos-dp-write-primary" type="button">Schreiben</button></div>`;
227
320
  backdrop.appendChild(dialog);
228
321
  document.body.appendChild(backdrop);
229
322
 
230
323
  dialog.querySelector('.eos-dp-write-id').textContent = id;
231
- dialog.querySelector('.eos-dp-write-meta').textContent = `Typ: ${type}${role ? ` · Rolle: ${role}` : ''}${common.unit ? ` · Einheit: ${common.unit}` : ''}`;
324
+ dialog.querySelector('.eos-dp-write-meta').textContent = `Typ: ${type}${role ? ` · Rolle: ${role}` : ''}${common.unit ? ` · Einheit: ${common.unit}` : ''}${obj ? '' : ' · Objekt lädt langsam / Fallback aktiv'}`;
232
325
 
233
326
  const inputWrap = dialog.querySelector('.eos-dp-write-input-wrap');
234
327
  let input;
@@ -243,8 +336,8 @@
243
336
  input.value = current == null ? Object.keys(states)[0] || '' : String(current);
244
337
  } else if (type === 'boolean') {
245
338
  input = document.createElement('select');
246
- input.innerHTML = '<option value="true">true</option><option value="false">false</option>';
247
- input.value = parseBoolean(current) ? 'true' : 'false';
339
+ input.innerHTML = '<option value="true">true</option><option value="false">false</option><option value="null">null</option>';
340
+ input.value = current == null ? 'null' : (parseBoolean(current) ? 'true' : 'false');
248
341
  } else if (type === 'json') {
249
342
  input = document.createElement('textarea');
250
343
  input.value = stateToString(current);
@@ -271,13 +364,13 @@
271
364
  writeBtn.disabled = true;
272
365
  cancelBtn.disabled = true;
273
366
  try {
274
- let value = input.value;
275
367
  const effectiveType = states ? (common.type || type) : type;
276
- value = coerceValue(value, effectiveType);
277
- await socket.setState(id, { val: value, ack: !!ackInput.checked, q: 0 });
368
+ const value = coerceValue(input.value, effectiveType);
369
+ await writeStateUnrestricted(socket, id, { val: value, ack: !!ackInput.checked, q: 0 });
278
370
  close();
371
+ showToast(`Geschrieben: ${id}`);
279
372
  } catch (error) {
280
- errorEl.textContent = `Schreiben fehlgeschlagen: ${error?.message || error}`;
373
+ errorEl.textContent = `Schreiben fehlgeschlagen:\n${error?.message || error}`;
281
374
  errorEl.hidden = false;
282
375
  writeBtn.disabled = false;
283
376
  cancelBtn.disabled = false;
@@ -297,93 +390,56 @@
297
390
  window.setTimeout(() => input.focus(), 30);
298
391
  } catch (error) {
299
392
  busyIds.delete(id);
300
- window.alert(`EOS: Datenpunkt konnte nicht geschrieben werden: ${error?.message || error}`);
393
+ window.alert(`EOS: Datenpunkt konnte nicht geschrieben werden:\n${error?.message || error}`);
301
394
  }
302
395
  };
303
396
 
304
- const run = root => {
305
- if (!setSurfaceState()) return;
306
- releaseNativeControls(root);
307
- annotateValueCells(root);
308
- };
309
-
310
- let scheduled = false;
311
- const pendingRoots = new Set();
312
- const schedule = root => {
397
+ const handleClickCapture = event => {
313
398
  if (!setSurfaceState()) return;
314
- if (root) pendingRoots.add(root);
315
- if (scheduled) return;
316
- scheduled = true;
317
- const execute = () => {
318
- scheduled = false;
319
- const roots = pendingRoots.size ? Array.from(pendingRoots) : [document];
320
- pendingRoots.clear();
321
- roots.slice(0, 80).forEach(run);
322
- };
323
- if ('requestIdleCallback' in window) window.requestIdleCallback(execute, { timeout: 500 });
324
- else window.requestAnimationFrame(execute);
325
- };
326
-
327
- const handlePointerAssist = event => {
328
- if (!setSurfaceState()) return;
329
- const cell = event.target?.closest?.('.eos-object-value-cell');
330
- if (isWritableCell(cell)) cell.classList.add('eos-object-value-hover');
331
- };
332
-
333
- const handlePointerLeave = event => {
334
- event.target?.closest?.('.eos-object-value-cell')?.classList.remove('eos-object-value-hover');
335
- };
336
-
337
- const shouldSkipFallback = cell => !!cell?.querySelector?.('.MuiSwitch-root,.admin-button,button,.copyButton');
338
-
339
- const scheduleFallbackWriter = event => {
340
- if (!setSurfaceState()) return;
341
- const cell = event.target?.closest?.('.eos-object-value-cell,[data-eos-object-value-cell]');
342
- if (!cell || !isWritableCell(cell) || shouldSkipFallback(cell)) return;
343
- const id = cell.getAttribute('data-eos-object-value-cell') || '';
344
- window.setTimeout(() => {
345
- if (!setSurfaceState()) return;
346
- if (!cell.isConnected || hasNativeWriteDialog()) return;
347
- openWriteDialog(cell, event);
348
- }, 260);
399
+ if (event.defaultPrevented || event.button !== 0) return;
400
+ if (event.ctrlKey || event.metaKey || event.altKey) return;
401
+ if (event.target?.closest?.(`.${DIALOG_CLASS}`)) return;
402
+ if (event.target?.closest?.(interactiveIgnoreSelector) && !event.target?.closest?.(valueCellSelector)) return;
403
+ const cell = event.target?.closest?.(valueCellSelector);
404
+ if (!cell) return;
405
+ const id = resolveIdFromCell(cell);
406
+ if (!id) return;
407
+ event.preventDefault();
408
+ event.stopPropagation();
409
+ event.stopImmediatePropagation?.();
410
+ openWriteDialog(cell, event);
349
411
  };
350
412
 
351
- window.addEventListener('hashchange', () => window.setTimeout(() => schedule(document), 30));
352
- window.addEventListener('popstate', () => window.setTimeout(() => schedule(document), 30));
353
- document.addEventListener('mouseover', handlePointerAssist, true);
354
- document.addEventListener('mouseout', handlePointerLeave, true);
355
- document.addEventListener('click', event => {
356
- if (!setSurfaceState()) return;
357
- const nativeInteractive = event.target?.closest?.(`${interactiveSelector},${nativeLayerSelector},.eos-object-value-cell,[data-eos-object-value-cell]`);
358
- if (nativeInteractive) {
359
- releaseNativeControls(nativeInteractive);
360
- scheduleFallbackWriter(event);
361
- window.setTimeout(() => schedule(nativeInteractive), 80);
362
- }
363
- }, false);
364
- document.addEventListener('keydown', event => {
365
- if (!setSurfaceState()) return;
366
- if (event.key !== 'Enter') return;
367
- const cell = event.target?.closest?.('.eos-object-value-cell,[data-eos-object-value-cell]');
368
- if (!cell || !isWritableCell(cell) || shouldSkipFallback(cell)) return;
413
+ const handleKeydownCapture = event => {
414
+ if (!setSurfaceState() || event.key !== 'Enter') return;
415
+ const cell = event.target?.closest?.(valueCellSelector);
416
+ if (!cell) return;
417
+ const id = resolveIdFromCell(cell);
418
+ if (!id) return;
369
419
  event.preventDefault();
420
+ event.stopPropagation();
421
+ event.stopImmediatePropagation?.();
370
422
  openWriteDialog(cell, event);
371
- }, true);
423
+ };
372
424
 
373
- const observer = new MutationObserver(records => {
425
+ const handlePointerOver = event => {
374
426
  if (!setSurfaceState()) return;
375
- for (const rec of records) {
376
- rec.addedNodes?.forEach(node => {
377
- if (node.nodeType === Node.ELEMENT_NODE) schedule(node);
378
- });
427
+ const cell = event.target?.closest?.(valueCellSelector);
428
+ if (cell && resolveIdFromCell(cell)) {
429
+ cell.classList.add('eos-object-value-cell');
430
+ cell.setAttribute('tabindex', '0');
431
+ if (!cell.getAttribute('title')) cell.setAttribute('title', 'Klicken: Wert schreiben');
379
432
  }
380
- });
433
+ };
381
434
 
382
435
  const start = () => {
383
436
  setSurfaceState();
384
- schedule(document);
385
- observer.observe(document.documentElement, { childList: true, subtree: true });
386
- window.setInterval(() => schedule(document), 60000);
437
+ injectDialogStyle();
438
+ document.addEventListener('click', handleClickCapture, true);
439
+ document.addEventListener('keydown', handleKeydownCapture, true);
440
+ document.addEventListener('pointerover', handlePointerOver, true);
441
+ window.addEventListener('hashchange', setSurfaceState);
442
+ window.addEventListener('popstate', setSurfaceState);
387
443
  };
388
444
 
389
445
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
@@ -0,0 +1,37 @@
1
+ (() => {
2
+ 'use strict';
3
+
4
+ if (window.NEXOWATT_EOS_PERFORMANCE_GUARD_VERSION) return;
5
+ window.NEXOWATT_EOS_PERFORMANCE_GUARD_VERSION = 'v52-fast-ui';
6
+
7
+ const filteredConsole = (method, matcher) => {
8
+ const original = console[method]?.bind(console);
9
+ if (!original || original.__eosPerformanceGuard) return;
10
+ const wrapped = (...args) => {
11
+ const first = String(args[0] ?? '');
12
+ if (matcher(first)) return;
13
+ return original(...args);
14
+ };
15
+ wrapped.__eosPerformanceGuard = true;
16
+ console[method] = wrapped;
17
+ };
18
+
19
+ filteredConsole('log', first => /^Translate:\s+/.test(first));
20
+ filteredConsole('warn', first => /^Please (?:add to|modify) "system\.adapter\./.test(first) && /common\.adminUI=/.test(first));
21
+
22
+ const disableI18nWarnings = () => {
23
+ try {
24
+ if (typeof window.i18nDisableWarning === 'function') window.i18nDisableWarning(true);
25
+ } catch (_) {
26
+ // ignore
27
+ }
28
+ };
29
+
30
+ disableI18nWarnings();
31
+ let tries = 0;
32
+ const timer = window.setInterval(() => {
33
+ tries += 1;
34
+ disableI18nWarnings();
35
+ if (tries >= 40 || typeof window.i18nDisableWarning === 'function') window.clearInterval(timer);
36
+ }, 250);
37
+ })();