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.
- package/adminWww/assets/Objects-DPan0bzw.js +1 -1
- package/adminWww/assets/Objects-DPw50Write.js +1 -1
- package/adminWww/assets/Objects-DPw51NoLock.js +878 -0
- package/adminWww/assets/Objects-DPw52Fast.js +878 -0
- package/adminWww/assets/bootstrap-DPWrite51.js +317 -0
- package/adminWww/assets/bootstrap-DPWrite52.js +317 -0
- package/adminWww/assets/hostInit-DPWrite51.js +2 -0
- package/adminWww/assets/hostInit-DPWrite52.js +2 -0
- package/adminWww/assets/index-D2ymscJA.js +2 -2
- package/adminWww/assets/index-DPAdapter50.js +2 -2
- package/adminWww/assets/index-DPAdapter51.js +950 -0
- package/adminWww/assets/index-DPAdapter52.js +950 -0
- package/adminWww/assets/index-DPWrite51.js +2 -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 +257 -201
- 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-DPWrite51.js +2 -0
- package/adminWww/remoteEntry-DPWrite52.js +2 -0
- package/build/main.js +55 -0
- package/io-package.json +8 -4
- 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,123 +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
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
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(',', '.')
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (type === '
|
|
200
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
393
|
+
window.alert(`EOS: Datenpunkt konnte nicht geschrieben werden:\n${error?.message || error}`);
|
|
301
394
|
}
|
|
302
395
|
};
|
|
303
396
|
|
|
304
|
-
const
|
|
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 (
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (!
|
|
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
|
-
}
|
|
423
|
+
};
|
|
372
424
|
|
|
373
|
-
const
|
|
425
|
+
const handlePointerOver = event => {
|
|
374
426
|
if (!setSurfaceState()) return;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
+
})();
|