iobroker.eos-admin 7.9.51 → 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.
- package/adminWww/assets/Objects-DPw52Fast.js +878 -0
- package/adminWww/assets/bootstrap-DPWrite52.js +317 -0
- package/adminWww/assets/hostInit-DPWrite52.js +2 -0
- package/adminWww/assets/index-DPAdapter52.js +950 -0
- package/adminWww/assets/index-DPWrite52.js +2 -0
- package/adminWww/index.html +9 -8
- package/adminWww/js/eos-branding.js +19 -1
- package/adminWww/js/eos-objects-state-tools.js +180 -255
- package/adminWww/js/eos-performance-guard.js +37 -0
- package/adminWww/js/eos-role-ui.js +5 -4
- package/adminWww/js/eos-security-ui.js +13 -5
- package/adminWww/mf-manifest.json +1 -1
- package/adminWww/remoteEntry-DPWrite52.js +2 -0
- package/io-package.json +7 -7
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
window.NEXOWATT_EOS_OBJECTS_STATE_TOOLS_VERSION = '
|
|
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
|
|
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,133 +23,86 @@
|
|
|
20
23
|
return active;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
const
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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');
|
|
83
64
|
};
|
|
84
65
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
el.classList?.remove('eos-security-hidden-delete', 'eos-protected-delete-control', 'eos-disabled-by-security');
|
|
97
|
-
el.removeAttribute?.('data-eos-security-blocked');
|
|
98
|
-
el.removeAttribute?.('aria-disabled');
|
|
99
|
-
if (el.style?.pointerEvents === 'none') el.style.pointerEvents = 'auto';
|
|
100
|
-
if (el.style?.visibility === 'hidden') el.style.visibility = 'visible';
|
|
101
|
-
if (el.style?.display === 'none' && !el.classList?.contains('eos-native-logout-hidden')) el.style.display = '';
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const annotateValueCells = root => safe(() => {
|
|
106
|
-
if (!setSurfaceState()) return;
|
|
107
|
-
const base = root && root.nodeType ? root : document;
|
|
108
|
-
collect(base, valueCellSelector).forEach(cell => {
|
|
109
|
-
const id = cell.getAttribute('data-eos-object-value-cell') || cell.closest?.('[id]')?.getAttribute('id') || '';
|
|
110
|
-
const writable = isWritableCell(cell);
|
|
111
|
-
const visibleValue = normalizeText(cell.textContent || '') || '(leer)';
|
|
112
|
-
const cache = `${id}|${writable ? 1 : 0}|${visibleValue}`;
|
|
113
|
-
|
|
114
|
-
cell.style.pointerEvents = 'auto';
|
|
115
|
-
cell.style.cursor = writable ? 'pointer' : 'default';
|
|
116
|
-
cell.classList.add('eos-object-value-cell');
|
|
117
|
-
cell.classList.toggle('eos-object-value-writable', writable);
|
|
118
|
-
cell.classList.toggle('eos-object-value-readonly', !writable);
|
|
119
|
-
if (writable) cell.setAttribute('tabindex', '0');
|
|
120
|
-
|
|
121
|
-
if (cell.getAttribute('data-eos-value-cache') !== cache) {
|
|
122
|
-
cell.setAttribute('data-eos-value-cache', cache);
|
|
123
|
-
cell.setAttribute('data-eos-current-visible-value', visibleValue);
|
|
124
|
-
const nextTitle = [
|
|
125
|
-
id ? `ID: ${id}` : null,
|
|
126
|
-
`Aktueller Wert: ${visibleValue}`,
|
|
127
|
-
`Status: ${writable ? 'beschreibbar' : 'nur lesbar'}`,
|
|
128
|
-
writable ? 'Klicken: Wert schreiben' : 'Nur lesbarer Wert'
|
|
129
|
-
].filter(Boolean).join('\n');
|
|
130
|
-
if (cell.getAttribute('title') !== nextTitle) cell.setAttribute('title', nextTitle);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
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
|
+
};
|
|
134
77
|
|
|
135
78
|
const parseBoolean = value => {
|
|
136
79
|
if (value === true || value === 1) return true;
|
|
137
80
|
if (value === false || value === 0 || value == null) return false;
|
|
138
81
|
const normalized = String(value).trim().toLowerCase();
|
|
139
|
-
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';
|
|
140
83
|
};
|
|
141
84
|
|
|
142
85
|
const isNullToken = value => /^\s*(\(null\)|null|undefined)?\s*$/i.test(String(value ?? ''));
|
|
143
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
|
+
|
|
144
100
|
const coerceValue = (rawValue, type) => {
|
|
145
101
|
const raw = String(rawValue ?? '');
|
|
146
102
|
if (isNullToken(raw) && type !== 'string' && type !== 'json') return null;
|
|
147
103
|
if (type === 'boolean') return parseBoolean(raw);
|
|
148
104
|
if (type === 'number') {
|
|
149
|
-
const normalized = raw.replace(',', '.')
|
|
105
|
+
const normalized = stripUnit(raw).replace(',', '.');
|
|
150
106
|
const number = Number(normalized);
|
|
151
107
|
if (!Number.isFinite(number)) throw new Error(`Ungültige Zahl: ${raw}`);
|
|
152
108
|
return number;
|
|
@@ -166,14 +122,6 @@
|
|
|
166
122
|
return String(value);
|
|
167
123
|
};
|
|
168
124
|
|
|
169
|
-
const hasNativeWriteDialog = () => {
|
|
170
|
-
const dialogs = Array.from(document.querySelectorAll('.MuiDialog-root,[role="dialog"]'));
|
|
171
|
-
return dialogs.some(dialog => /write value|wert schreiben|set value|wert setzen/i.test(dialog.textContent || ''));
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
let cachedEosAdminInstance = null;
|
|
176
|
-
|
|
177
125
|
const formatWriteError = error => {
|
|
178
126
|
if (!error) return 'unbekannter Fehler';
|
|
179
127
|
if (typeof error === 'string') return error;
|
|
@@ -181,15 +129,14 @@
|
|
|
181
129
|
try { return JSON.stringify(error); } catch (_) { return String(error); }
|
|
182
130
|
};
|
|
183
131
|
|
|
132
|
+
let cachedEosAdminInstance = null;
|
|
184
133
|
const normalizeInstanceId = value => {
|
|
185
134
|
if (!value) return null;
|
|
186
135
|
if (typeof value === 'string') {
|
|
187
136
|
const id = value.replace(/^system\.adapter\./, '').trim();
|
|
188
137
|
return /^eos-admin\.\d+$/.test(id) ? id : null;
|
|
189
138
|
}
|
|
190
|
-
if (typeof value === 'object')
|
|
191
|
-
return normalizeInstanceId(value._id || value.id || value.instance || value.namespace);
|
|
192
|
-
}
|
|
139
|
+
if (typeof value === 'object') return normalizeInstanceId(value._id || value.id || value.instance || value.namespace);
|
|
193
140
|
return null;
|
|
194
141
|
};
|
|
195
142
|
|
|
@@ -197,28 +144,21 @@
|
|
|
197
144
|
if (cachedEosAdminInstance) return cachedEosAdminInstance;
|
|
198
145
|
const explicit = normalizeInstanceId(window.NEXOWATT_EOS_ADMIN_INSTANCE || window.adminInstance || window.adapterInstance);
|
|
199
146
|
if (explicit) return (cachedEosAdminInstance = explicit);
|
|
200
|
-
|
|
201
147
|
if (socket && typeof socket.getCurrentInstance === 'function') {
|
|
202
148
|
try {
|
|
203
|
-
const current = normalizeInstanceId(await socket.getCurrentInstance());
|
|
149
|
+
const current = normalizeInstanceId(await withTimeout(socket.getCurrentInstance(), 700, null));
|
|
204
150
|
if (current) return (cachedEosAdminInstance = current);
|
|
205
|
-
} catch (_) {
|
|
206
|
-
// ignore and try instance list/fallback
|
|
207
|
-
}
|
|
151
|
+
} catch (_) { /* fallback below */ }
|
|
208
152
|
}
|
|
209
|
-
|
|
210
153
|
if (socket && typeof socket.getAdapterInstances === 'function') {
|
|
211
154
|
try {
|
|
212
|
-
const instances = await socket.getAdapterInstances('eos-admin');
|
|
155
|
+
const instances = await withTimeout(socket.getAdapterInstances('eos-admin'), 900, []);
|
|
213
156
|
const list = Array.isArray(instances) ? instances : [];
|
|
214
157
|
const alive = list.find(instance => normalizeInstanceId(instance) && instance?.common?.enabled !== false);
|
|
215
158
|
const id = normalizeInstanceId(alive || list[0]);
|
|
216
159
|
if (id) return (cachedEosAdminInstance = id);
|
|
217
|
-
} catch (_) {
|
|
218
|
-
// ignore and use default
|
|
219
|
-
}
|
|
160
|
+
} catch (_) { /* fallback below */ }
|
|
220
161
|
}
|
|
221
|
-
|
|
222
162
|
return (cachedEosAdminInstance = 'eos-admin.0');
|
|
223
163
|
};
|
|
224
164
|
|
|
@@ -228,48 +168,48 @@
|
|
|
228
168
|
|
|
229
169
|
if (socket && typeof socket.sendTo === 'function') {
|
|
230
170
|
const result = socket.sendTo(instance, command, message);
|
|
231
|
-
if (result && typeof result.then === 'function') return result;
|
|
171
|
+
if (result && typeof result.then === 'function') return await withTimeout(result, 7000, { ok: false, error: 'sendTo Timeout' });
|
|
232
172
|
if (result !== undefined) return result;
|
|
233
173
|
}
|
|
234
174
|
|
|
235
175
|
const rawSocket = socket?._socket || socket?.socket || window.socket;
|
|
236
176
|
if (rawSocket && typeof rawSocket.emit === 'function') {
|
|
237
|
-
return await new Promise((resolve, reject) => {
|
|
177
|
+
return await withTimeout(new Promise((resolve, reject) => {
|
|
238
178
|
try {
|
|
239
179
|
rawSocket.emit('sendTo', instance, command, message, response => resolve(response));
|
|
240
180
|
} catch (error) {
|
|
241
181
|
reject(error);
|
|
242
182
|
}
|
|
243
|
-
});
|
|
183
|
+
}), 7000, { ok: false, error: 'raw sendTo Timeout' });
|
|
244
184
|
}
|
|
245
185
|
|
|
246
186
|
throw new Error('socket.sendTo ist nicht verfügbar');
|
|
247
187
|
};
|
|
248
188
|
|
|
249
189
|
const writeStateUnrestricted = async (socket, id, state) => {
|
|
250
|
-
let directError = null;
|
|
251
190
|
if (!socket) throw new Error('Socket ist nicht bereit');
|
|
252
191
|
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
await socket.setState(id, state);
|
|
256
|
-
return { ok: true, via: 'socket' };
|
|
257
|
-
} catch (error) {
|
|
258
|
-
directError = error;
|
|
259
|
-
}
|
|
260
|
-
} else {
|
|
261
|
-
directError = new Error('socket.setState ist nicht verfügbar');
|
|
262
|
-
}
|
|
263
|
-
|
|
192
|
+
let backendError = null;
|
|
264
193
|
try {
|
|
265
194
|
const response = await callEosAdmin(socket, 'eos:writeState', { id, state });
|
|
266
195
|
if (response === 'permissionError') throw new Error('sendTo permissionError');
|
|
267
196
|
if (response && response.ok === false) throw new Error(response.error || 'EOS Backend hat das Schreiben abgelehnt');
|
|
268
197
|
if (response && response.error) throw new Error(response.error);
|
|
269
198
|
return { ok: true, via: 'eos-admin' };
|
|
270
|
-
} catch (
|
|
271
|
-
|
|
199
|
+
} catch (error) {
|
|
200
|
+
backendError = error;
|
|
272
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`);
|
|
273
213
|
};
|
|
274
214
|
|
|
275
215
|
window.NEXOWATT_EOS_WRITE_STATE_UNRESTRICTED = writeStateUnrestricted;
|
|
@@ -279,60 +219,90 @@
|
|
|
279
219
|
const style = document.createElement('style');
|
|
280
220
|
style.id = 'eos-dp-write-style';
|
|
281
221
|
style.textContent = `
|
|
282
|
-
|
|
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}`;
|
|
283
224
|
document.head.appendChild(style);
|
|
284
225
|
};
|
|
285
226
|
|
|
286
|
-
const
|
|
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
|
+
};
|
|
287
236
|
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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();
|
|
295
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
|
+
};
|
|
296
270
|
|
|
271
|
+
const openWriteDialog = async (cell, event) => {
|
|
272
|
+
const id = resolveIdFromCell(cell);
|
|
273
|
+
if (!id || busyIds.has(id)) return;
|
|
297
274
|
busyIds.add(id);
|
|
298
|
-
|
|
299
|
-
|
|
275
|
+
injectDialogStyle();
|
|
276
|
+
cell?.classList?.add('eos-dp-write-pulse');
|
|
277
|
+
window.setTimeout(() => cell?.classList?.remove('eos-dp-write-pulse'), 650);
|
|
300
278
|
|
|
301
279
|
try {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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})`);
|
|
309
287
|
busyIds.delete(id);
|
|
310
288
|
return;
|
|
311
289
|
}
|
|
312
|
-
const common = obj?.common || {};
|
|
313
290
|
if (common.type === 'file') {
|
|
291
|
+
showToast('EOS: Datei-Datenpunkte werden nicht als normaler State-Wert geschrieben.');
|
|
314
292
|
busyIds.delete(id);
|
|
315
|
-
window.alert('EOS: Datei-Datenpunkte werden über die Datei-/Objektansicht geöffnet und nicht als State-Wert geschrieben.');
|
|
316
293
|
return;
|
|
317
294
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (!type || type === 'undefined') type = 'string';
|
|
321
|
-
if (type === 'object') type = 'json';
|
|
322
|
-
const current = state?.val;
|
|
323
|
-
const states = common.states && typeof common.states === 'object' ? common.states : null;
|
|
324
|
-
|
|
325
|
-
if (/^button/.test(role)) {
|
|
295
|
+
|
|
296
|
+
if (/^button/.test(role) && (type === 'boolean' || type === 'string' || !type)) {
|
|
326
297
|
await writeStateUnrestricted(socket, id, { val: true, ack: false, q: 0 });
|
|
298
|
+
showToast(`Befehl geschrieben: ${id}`);
|
|
327
299
|
busyIds.delete(id);
|
|
328
300
|
return;
|
|
329
301
|
}
|
|
330
302
|
|
|
331
|
-
injectDialogStyle();
|
|
332
303
|
closeExistingDialog();
|
|
333
|
-
|
|
334
304
|
const backdrop = document.createElement('div');
|
|
335
|
-
backdrop.className =
|
|
305
|
+
backdrop.className = DIALOG_CLASS;
|
|
336
306
|
const dialog = document.createElement('div');
|
|
337
307
|
dialog.className = 'eos-dp-write-dialog';
|
|
338
308
|
dialog.setAttribute('role', 'dialog');
|
|
@@ -344,13 +314,14 @@
|
|
|
344
314
|
<label class="eos-dp-write-label">Neuer Wert</label>
|
|
345
315
|
<div class="eos-dp-write-input-wrap"></div>
|
|
346
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>
|
|
347
318
|
<div class="eos-dp-write-error" hidden></div>
|
|
348
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>`;
|
|
349
320
|
backdrop.appendChild(dialog);
|
|
350
321
|
document.body.appendChild(backdrop);
|
|
351
322
|
|
|
352
323
|
dialog.querySelector('.eos-dp-write-id').textContent = id;
|
|
353
|
-
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'}`;
|
|
354
325
|
|
|
355
326
|
const inputWrap = dialog.querySelector('.eos-dp-write-input-wrap');
|
|
356
327
|
let input;
|
|
@@ -365,8 +336,8 @@
|
|
|
365
336
|
input.value = current == null ? Object.keys(states)[0] || '' : String(current);
|
|
366
337
|
} else if (type === 'boolean') {
|
|
367
338
|
input = document.createElement('select');
|
|
368
|
-
input.innerHTML = '<option value="true">true</option><option value="false">false</option>';
|
|
369
|
-
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');
|
|
370
341
|
} else if (type === 'json') {
|
|
371
342
|
input = document.createElement('textarea');
|
|
372
343
|
input.value = stateToString(current);
|
|
@@ -393,13 +364,13 @@
|
|
|
393
364
|
writeBtn.disabled = true;
|
|
394
365
|
cancelBtn.disabled = true;
|
|
395
366
|
try {
|
|
396
|
-
let value = input.value;
|
|
397
367
|
const effectiveType = states ? (common.type || type) : type;
|
|
398
|
-
value = coerceValue(value, effectiveType);
|
|
368
|
+
const value = coerceValue(input.value, effectiveType);
|
|
399
369
|
await writeStateUnrestricted(socket, id, { val: value, ack: !!ackInput.checked, q: 0 });
|
|
400
370
|
close();
|
|
371
|
+
showToast(`Geschrieben: ${id}`);
|
|
401
372
|
} catch (error) {
|
|
402
|
-
errorEl.textContent = `Schreiben fehlgeschlagen
|
|
373
|
+
errorEl.textContent = `Schreiben fehlgeschlagen:\n${error?.message || error}`;
|
|
403
374
|
errorEl.hidden = false;
|
|
404
375
|
writeBtn.disabled = false;
|
|
405
376
|
cancelBtn.disabled = false;
|
|
@@ -419,102 +390,56 @@
|
|
|
419
390
|
window.setTimeout(() => input.focus(), 30);
|
|
420
391
|
} catch (error) {
|
|
421
392
|
busyIds.delete(id);
|
|
422
|
-
window.alert(`EOS: Datenpunkt konnte nicht geschrieben werden
|
|
393
|
+
window.alert(`EOS: Datenpunkt konnte nicht geschrieben werden:\n${error?.message || error}`);
|
|
423
394
|
}
|
|
424
395
|
};
|
|
425
396
|
|
|
426
|
-
const
|
|
397
|
+
const handleClickCapture = event => {
|
|
427
398
|
if (!setSurfaceState()) return;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (!setSurfaceState()) return;
|
|
436
|
-
if (root) pendingRoots.add(root);
|
|
437
|
-
if (scheduled) return;
|
|
438
|
-
scheduled = true;
|
|
439
|
-
const execute = () => {
|
|
440
|
-
scheduled = false;
|
|
441
|
-
const roots = pendingRoots.size ? Array.from(pendingRoots) : [document];
|
|
442
|
-
pendingRoots.clear();
|
|
443
|
-
roots.slice(0, 80).forEach(run);
|
|
444
|
-
};
|
|
445
|
-
if ('requestIdleCallback' in window) window.requestIdleCallback(execute, { timeout: 500 });
|
|
446
|
-
else window.requestAnimationFrame(execute);
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
const handlePointerAssist = event => {
|
|
450
|
-
if (!setSurfaceState()) return;
|
|
451
|
-
const cell = event.target?.closest?.('.eos-object-value-cell');
|
|
452
|
-
if (isWritableCell(cell)) cell.classList.add('eos-object-value-hover');
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const handlePointerLeave = event => {
|
|
456
|
-
event.target?.closest?.('.eos-object-value-cell')?.classList.remove('eos-object-value-hover');
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const shouldSkipFallback = cell => !!cell?.querySelector?.('.MuiSwitch-root,.admin-button,button,.copyButton');
|
|
460
|
-
|
|
461
|
-
const scheduleFallbackWriter = event => {
|
|
462
|
-
if (!setSurfaceState()) return;
|
|
463
|
-
const cell = event.target?.closest?.('.eos-object-value-cell,[data-eos-object-value-cell]');
|
|
464
|
-
if (!cell || !isStateValueCandidate(cell) || shouldSkipFallback(cell)) return;
|
|
465
|
-
const id = cell.getAttribute('data-eos-object-value-cell') || '';
|
|
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);
|
|
466
406
|
if (!id) return;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}, 180);
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
event.stopPropagation();
|
|
409
|
+
event.stopImmediatePropagation?.();
|
|
410
|
+
openWriteDialog(cell, event);
|
|
472
411
|
};
|
|
473
412
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (!
|
|
480
|
-
const cell = event.target?.closest?.('.eos-object-value-cell,[data-eos-object-value-cell]');
|
|
481
|
-
if (cell) {
|
|
482
|
-
releaseNativeControls(cell);
|
|
483
|
-
scheduleFallbackWriter(event);
|
|
484
|
-
}
|
|
485
|
-
}, true);
|
|
486
|
-
document.addEventListener('click', event => {
|
|
487
|
-
if (!setSurfaceState()) return;
|
|
488
|
-
const nativeInteractive = event.target?.closest?.(`${interactiveSelector},${nativeLayerSelector},.eos-object-value-cell,[data-eos-object-value-cell]`);
|
|
489
|
-
if (nativeInteractive) {
|
|
490
|
-
releaseNativeControls(nativeInteractive);
|
|
491
|
-
scheduleFallbackWriter(event);
|
|
492
|
-
window.setTimeout(() => schedule(nativeInteractive), 80);
|
|
493
|
-
}
|
|
494
|
-
}, false);
|
|
495
|
-
document.addEventListener('keydown', event => {
|
|
496
|
-
if (!setSurfaceState()) return;
|
|
497
|
-
if (event.key !== 'Enter') return;
|
|
498
|
-
const cell = event.target?.closest?.('.eos-object-value-cell,[data-eos-object-value-cell]');
|
|
499
|
-
if (!cell || !isStateValueCandidate(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;
|
|
500
419
|
event.preventDefault();
|
|
420
|
+
event.stopPropagation();
|
|
421
|
+
event.stopImmediatePropagation?.();
|
|
501
422
|
openWriteDialog(cell, event);
|
|
502
|
-
}
|
|
423
|
+
};
|
|
503
424
|
|
|
504
|
-
const
|
|
425
|
+
const handlePointerOver = event => {
|
|
505
426
|
if (!setSurfaceState()) return;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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');
|
|
510
432
|
}
|
|
511
|
-
}
|
|
433
|
+
};
|
|
512
434
|
|
|
513
435
|
const start = () => {
|
|
514
436
|
setSurfaceState();
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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);
|
|
518
443
|
};
|
|
519
444
|
|
|
520
445
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
|