iobroker.alias-workbench 0.9.9 → 0.9.11
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/LICENSE +1 -1
- package/README.md +29 -0
- package/admin/css/werkbank.css +88 -3
- package/admin/js/baum.js +8 -0
- package/admin/js/detail.js +53 -6
- package/admin/js/entwurf.js +60 -20
- package/admin/js/ergebnis.js +83 -5
- package/admin/js/erkennung.js +46 -2
- package/admin/js/mqtt.js +23 -2
- package/admin/js/musterwahl.js +208 -31
- package/admin/js/objekte.js +31 -0
- package/admin/js/pruefungen.js +28 -2
- package/admin/js/quellentausch.js +179 -19
- package/admin/js/schreiben.js +194 -52
- package/admin/js/sprache.js +6 -1
- package/admin/js/verwendung.js +478 -0
- package/admin/js/vorlagen.js +87 -8
- package/admin/js/vorlagenblatt.js +199 -20
- package/admin/js/vorschlagen.js +50 -17
- package/admin/js/werte.js +157 -5
- package/admin/js/zuordnung.js +32 -36
- package/admin/js/zustand.js +3 -0
- package/admin/js/zustandsliste.js +59 -7
- package/admin/sprachen/de.json +67 -3
- package/admin/sprachen/en.json +67 -3
- package/admin/vorlagen/messpunkt.json +2 -1
- package/admin/vorlagen/tasmota-lampe.json +2 -1
- package/admin/vorlagen/tasmota-mehrfach.json +2 -1
- package/admin/vorlagen/tasmota-rgb.json +2 -1
- package/admin/vorlagen/tasmota-steckdose.json +2 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/* Wer nennt diese Kennung sonst noch?
|
|
2
|
+
|
|
3
|
+
Ein Adapter kann eine fremde Kennung nicht kennen, ohne sie irgendwo
|
|
4
|
+
hingeschrieben zu haben. Dafuer gibt es vier Ablagen: ein eigenes
|
|
5
|
+
Objekt, eine Datei, eine Aufzaehlung, oder `common.custom` am Punkt
|
|
6
|
+
selbst. Alle vier sieht dieser Kasten nach.
|
|
7
|
+
|
|
8
|
+
Gemessen am produktiven System (23584 Objekte der vier geladenen
|
|
9
|
+
Arten, 10,1 MB): Alias-Kennungen nennen dort ausschliesslich die
|
|
10
|
+
javascript-Skripte, zwei matter-Bridges, `enum.functions` und
|
|
11
|
+
`enum.rooms` - dazu die vis-Ansichten im Dateibaum (71 Aliase in
|
|
12
|
+
`vis.0`, 38 in `vis-2.0`). Es sind also nicht hundert Adapter,
|
|
13
|
+
sondern eine Handvoll Orte.
|
|
14
|
+
|
|
15
|
+
Was draussen bleibt, und warum:
|
|
16
|
+
|
|
17
|
+
* Adapter-Oberflaechen (`*.admin`, Widgetsaetze, Icon-Pakete). Das
|
|
18
|
+
sind 160 der 177 MB Text im Dateibaum, auf jedem ioBroker dieselben
|
|
19
|
+
- und sie enthalten Beispiele, die wie echte Daten aussehen
|
|
20
|
+
(`alias.0.Floor.Room.MotionSensor` in `javascript.admin`). Ohne
|
|
21
|
+
diese Grenze faende die Werkbank ihre eigenen hochgeladenen
|
|
22
|
+
Quelldateien als Verwender ihrer selbst. Gesucht wird deshalb nur
|
|
23
|
+
in Ordnern, die auf `.<Zahl>` enden - dort liegt, was der Nutzer
|
|
24
|
+
selbst gebaut hat (bei Ricardo 66 Dateien, 15 MB).
|
|
25
|
+
* Zustandswerte. `energiefluss-erweitert.0.configuration` ist ein
|
|
26
|
+
Datenpunkt, dessen *Wert* ein JSON voller Kennungen ist; im Objekt
|
|
27
|
+
steht davon nichts. Dafuer muesste man Werte lesen - ein eigener
|
|
28
|
+
Griff, bewusst noch nicht getan.
|
|
29
|
+
* Zusammengebaute Kennungen (`'alias.0.' + raum + '.Licht'`) und
|
|
30
|
+
Wildcard-Abos. Die findet keine Textsuche, und der Kasten sagt das.
|
|
31
|
+
|
|
32
|
+
Bewusst kein Urteil: dass die Quelle noch irgendwo genannt wird, ist
|
|
33
|
+
kein Fehler. Ein Skript, das sie beschreibt, gehoert genau dorthin.
|
|
34
|
+
Gemessen: von 376 Aliasen mit Quelle wurde bei 61 die Quelle in einem
|
|
35
|
+
Skript weiter direkt genannt, und ein guter Teil davon zu Recht. Eine
|
|
36
|
+
Textsuche kann lesend und schreibend nicht unterscheiden - deshalb
|
|
37
|
+
steht hier eine Liste zum Nachsehen und keine Warnung. */
|
|
38
|
+
|
|
39
|
+
import { S } from './zustand.js';
|
|
40
|
+
import { socket } from './verbindung.js';
|
|
41
|
+
import { el } from './basis.js';
|
|
42
|
+
import { tr, txt } from './sprache.js';
|
|
43
|
+
import { enums } from './enums.js';
|
|
44
|
+
|
|
45
|
+
/* --- Skripte ---------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
Einmal holen und behalten. Der Verlegen-Dialog suchte bisher bei jedem
|
|
48
|
+
Neuzeichnen neu - bei 67 Skripten ist das jedes Mal dieselbe Antwort.
|
|
49
|
+
Der Vorrat gilt fuer die Sitzung; „neu einlesen" am Kasten wirft ihn
|
|
50
|
+
weg. */
|
|
51
|
+
var skriptVorrat = null;
|
|
52
|
+
|
|
53
|
+
function holeSkripte(fertig) {
|
|
54
|
+
if (skriptVorrat) { return fertig(skriptVorrat); }
|
|
55
|
+
var fertigMit = function (rows) {
|
|
56
|
+
skriptVorrat = rows;
|
|
57
|
+
fertig(skriptVorrat);
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
socket.emit('getObjectView', 'script', 'javascript',
|
|
61
|
+
{ startkey: 'script.js.', endkey: 'script.js.香' }, function (err, doc) {
|
|
62
|
+
if (err || !doc || !doc.rows) { return fertigMit([]); }
|
|
63
|
+
fertigMit(doc.rows.map(function (r) {
|
|
64
|
+
var o = r.value || {};
|
|
65
|
+
return {
|
|
66
|
+
id: r.id,
|
|
67
|
+
name: txt((o.common || {}).name) || String(r.id).split('.').pop(),
|
|
68
|
+
an: !!(o.common && o.common.enabled),
|
|
69
|
+
quelle: String((o.common || {}).source || '')
|
|
70
|
+
};
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
} catch { fertigMit([]); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Die Auswertung ohne Rueckruf - der Vorrat liegt dann schon da. */
|
|
77
|
+
function trefferFuer(kennung) {
|
|
78
|
+
var treffer = [];
|
|
79
|
+
(skriptVorrat || []).forEach(function (s) {
|
|
80
|
+
if (!kennung || s.quelle.indexOf(kennung) === -1) { return; }
|
|
81
|
+
var stellen = [];
|
|
82
|
+
s.quelle.split('\n').forEach(function (z, i) {
|
|
83
|
+
if (z.indexOf(kennung) > -1) { stellen.push(i + 1); }
|
|
84
|
+
});
|
|
85
|
+
treffer.push({ id: s.id, name: s.name, an: s.an, zeilen: stellen });
|
|
86
|
+
});
|
|
87
|
+
return treffer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Alle Skripte, in deren Quelltext die Kennung im Klartext steht. */
|
|
91
|
+
export function sucheInSkripten(kennung, fertig) {
|
|
92
|
+
if (!kennung) { return fertig([]); }
|
|
93
|
+
holeSkripte(function () { fertig(trefferFuer(kennung)); });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Mehrere Kennungen auf einmal - der Loeschdialog nimmt beim Haken
|
|
97
|
+
„alle Ausgaenge" mehrere Wurzeln mit. Ein Skript, das zwei davon
|
|
98
|
+
nennt, soll trotzdem einmal in der Liste stehen. */
|
|
99
|
+
export function sucheInSkriptenMehrere(kennungen, fertig) {
|
|
100
|
+
holeSkripte(function () {
|
|
101
|
+
var nach = {};
|
|
102
|
+
(kennungen || []).forEach(function (k) {
|
|
103
|
+
trefferFuer(k).forEach(function (t) {
|
|
104
|
+
var v = nach[t.id];
|
|
105
|
+
if (!v) { nach[t.id] = { id: t.id, name: t.name, an: t.an, zeilen: t.zeilen.slice() }; return; }
|
|
106
|
+
t.zeilen.forEach(function (z) { if (v.zeilen.indexOf(z) === -1) { v.zeilen.push(z); } });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
fertig(Object.keys(nach).map(function (k) {
|
|
110
|
+
nach[k].zeilen.sort(function (a, b) { return a - b; });
|
|
111
|
+
return nach[k];
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function gehoertZu(id, praefix) {
|
|
117
|
+
return id === praefix || id.indexOf(praefix + '.') === 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* --- Objekte und Dateien ---------------------------------------------
|
|
121
|
+
|
|
122
|
+
Beide liefern dasselbe: „an dieser Stelle steht diese Kennung". Also
|
|
123
|
+
ein gemeinsamer Index, gebaut aus kennungsartigen Zeichenketten.
|
|
124
|
+
|
|
125
|
+
Der Ausdruck endet von selbst an der ersten Nicht-Kennungsstelle -
|
|
126
|
+
damit ist der Treffer hinten verankert. Ohne das faende die Suche
|
|
127
|
+
nach `hm-rpc.2.NEQ1660737` auch ein `…NEQ16607370`. */
|
|
128
|
+
var KENNUNG = /[A-Za-z0-9_-]+\.\d+\.[A-Za-z0-9_.\-À-ɏ]+/g;
|
|
129
|
+
|
|
130
|
+
/* token -> [{ art, id, name, ort, pfad }] */
|
|
131
|
+
var fundIndex = null;
|
|
132
|
+
|
|
133
|
+
function tokenAus(text, eintrag, ziel) {
|
|
134
|
+
var t;
|
|
135
|
+
KENNUNG.lastIndex = 0;
|
|
136
|
+
while ((t = KENNUNG.exec(text)) !== null) {
|
|
137
|
+
var k = t[0];
|
|
138
|
+
var liste = ziel.get(k);
|
|
139
|
+
if (!liste) { liste = []; ziel.set(k, liste); }
|
|
140
|
+
if (liste.indexOf(eintrag) === -1) { liste.push(eintrag); }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Die Objekte liegen schon im Speicher - `ladeObjekte` holt device,
|
|
145
|
+
channel, folder und state samt `native`. Das matter-Objekt, das seine
|
|
146
|
+
Bridge-Mitglieder auffuehrt, ist ein `channel` und damit von Anfang an
|
|
147
|
+
dabei. Es kostet also keinen neuen Zugriff, nur einen Durchlauf. */
|
|
148
|
+
function objekteIndizieren(ziel) {
|
|
149
|
+
Object.keys(S.objects).forEach(function (id) {
|
|
150
|
+
/* Die Diagnosepunkte einer Instanz (`system.adapter.mqtt-client.0.cpu`)
|
|
151
|
+
nennen nie eine fremde Kennung - sie tragen nur eine, die wie eine
|
|
152
|
+
aussieht. */
|
|
153
|
+
if (id.indexOf('system.adapter.') === 0) { return; }
|
|
154
|
+
var o = S.objects[id];
|
|
155
|
+
var text;
|
|
156
|
+
try { text = JSON.stringify(o); } catch { return; }
|
|
157
|
+
tokenAus(text, {
|
|
158
|
+
art: 'obj',
|
|
159
|
+
id: id,
|
|
160
|
+
name: txt((o.common || {}).name) || id.split('.').pop()
|
|
161
|
+
}, ziel);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Dateien, die nie eine Kennung tragen - und wenn doch, dann als Bild. */
|
|
166
|
+
var BINAER = /\.(png|jpe?g|gif|bmp|ico|webp|mp3|mp4|wav|ogg|woff2?|ttf|eot|otf|zip|gz|tgz|7z|pdf)$/i;
|
|
167
|
+
var MAX_DATEI = 8 * 1024 * 1024;
|
|
168
|
+
|
|
169
|
+
function lies(ort, pfad) {
|
|
170
|
+
return new Promise(function (fertig) {
|
|
171
|
+
try {
|
|
172
|
+
socket.emit('readFile', ort, pfad, function (err, daten) {
|
|
173
|
+
fertig((!err && typeof daten === 'string') ? daten : null);
|
|
174
|
+
});
|
|
175
|
+
} catch { fertig(null); }
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function liesOrdner(ort, pfad) {
|
|
180
|
+
return new Promise(function (fertig) {
|
|
181
|
+
try {
|
|
182
|
+
socket.emit('readDir', ort, pfad, function (err, liste) {
|
|
183
|
+
fertig((!err && Array.isArray(liste)) ? liste : []);
|
|
184
|
+
});
|
|
185
|
+
} catch { fertig([]); }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Nur Instanzordner: `vis.0`, `vis-2.0`, `web.0`, `0_userdata.0` … Was
|
|
190
|
+
nicht auf `.<Zahl>` endet, ist Lieferbestandteil eines Adapters -
|
|
191
|
+
siehe der Kopf dieser Datei. */
|
|
192
|
+
var INSTANZ = /^[A-Za-z0-9_-]+\.\d+$/;
|
|
193
|
+
|
|
194
|
+
async function dateienIndizieren(ziel) {
|
|
195
|
+
var wurzel = await liesOrdner('', '');
|
|
196
|
+
var orte = wurzel.filter(function (x) {
|
|
197
|
+
return x && x.isDir && INSTANZ.test(x.file);
|
|
198
|
+
}).map(function (x) { return x.file; });
|
|
199
|
+
|
|
200
|
+
for (var i = 0; i < orte.length; i++) {
|
|
201
|
+
var ort = orte[i];
|
|
202
|
+
var offen = [''];
|
|
203
|
+
var dateien = [];
|
|
204
|
+
while (offen.length) {
|
|
205
|
+
var hier = offen.shift();
|
|
206
|
+
var liste = await liesOrdner(ort, hier);
|
|
207
|
+
liste.forEach(function (x) {
|
|
208
|
+
if (!x || !x.file) { return; }
|
|
209
|
+
var p = hier ? hier + '/' + x.file : x.file;
|
|
210
|
+
if (x.isDir) { offen.push(p); return; }
|
|
211
|
+
if (BINAER.test(p)) { return; }
|
|
212
|
+
if (x.stats && x.stats.size > MAX_DATEI) { return; }
|
|
213
|
+
dateien.push(p);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/* In kleinen Buendeln lesen: nacheinander dauert bei 66 Dateien
|
|
217
|
+
spuerbar, alle auf einmal ueberfaehrt den Socket. */
|
|
218
|
+
for (var j = 0; j < dateien.length; j += 4) {
|
|
219
|
+
var teil = dateien.slice(j, j + 4);
|
|
220
|
+
var inhalte = await Promise.all(teil.map(function (p) { return lies(ort, p); }));
|
|
221
|
+
inhalte.forEach(function (text, k) {
|
|
222
|
+
if (text) { tokenAus(text, { art: 'datei', ort: ort, pfad: teil[k] }, ziel); }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function holeFundorte(fertig) {
|
|
229
|
+
if (fundIndex) { return fertig(fundIndex); }
|
|
230
|
+
var ziel = new Map();
|
|
231
|
+
objekteIndizieren(ziel);
|
|
232
|
+
var schluss = function () { fundIndex = ziel; fertig(fundIndex); };
|
|
233
|
+
dateienIndizieren(ziel).then(schluss, schluss);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Fundstellen zu einem Praefix - ohne den Punkt selbst, ohne alles
|
|
237
|
+
unter ihm, und ohne die Aliase: die stehen in einer eigenen Zeile. */
|
|
238
|
+
function fundstellen(praefix, art) {
|
|
239
|
+
if (!praefix || !fundIndex) { return []; }
|
|
240
|
+
var raus = [];
|
|
241
|
+
var gesehen = {};
|
|
242
|
+
fundIndex.forEach(function (liste, token) {
|
|
243
|
+
if (!gehoertZu(token, praefix)) { return; }
|
|
244
|
+
liste.forEach(function (e) {
|
|
245
|
+
if (e.art !== art) { return; }
|
|
246
|
+
if (art === 'obj') {
|
|
247
|
+
if (e.id.indexOf('alias.') === 0) { return; }
|
|
248
|
+
if (gehoertZu(e.id, praefix) || gehoertZu(praefix, e.id)) { return; }
|
|
249
|
+
}
|
|
250
|
+
var schluessel = art === 'obj' ? e.id : e.ort + '/' + e.pfad;
|
|
251
|
+
if (gesehen[schluessel]) { return; }
|
|
252
|
+
gesehen[schluessel] = true;
|
|
253
|
+
raus.push(e);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
return raus;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* --- Was ohne Suche schon dasteht ------------------------------------ */
|
|
260
|
+
|
|
261
|
+
/* Aufzaehlungen, die den Punkt oder einen darunter als Mitglied fuehren. */
|
|
262
|
+
function aufzaehlungenMit(praefix) {
|
|
263
|
+
if (!praefix) { return []; }
|
|
264
|
+
return Object.keys(enums).filter(function (k) {
|
|
265
|
+
return ((enums[k].common || {}).members || []).some(function (m) {
|
|
266
|
+
return gehoertZu(m, praefix);
|
|
267
|
+
});
|
|
268
|
+
}).map(function (k) {
|
|
269
|
+
return { id: k, name: txt((enums[k].common || {}).name) || k.split('.').pop() };
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Was am Punkt selbst haengt: Aufzeichnung (history, influxdb, sql),
|
|
274
|
+
Smart-Namen (iot, alexa). Das steht im Objekt und ist deshalb gratis. */
|
|
275
|
+
function customAn(praefix) {
|
|
276
|
+
if (!praefix) { return []; }
|
|
277
|
+
var raus = [];
|
|
278
|
+
Object.keys(S.objects).forEach(function (id) {
|
|
279
|
+
if (!gehoertZu(id, praefix)) { return; }
|
|
280
|
+
var c = (S.objects[id].common || {}).custom;
|
|
281
|
+
if (!c) { return; }
|
|
282
|
+
var adapter = Object.keys(c);
|
|
283
|
+
if (adapter.length) { raus.push({ id: id, adapter: adapter.join(', ') }); }
|
|
284
|
+
});
|
|
285
|
+
return raus;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Weitere Aliase, die aus derselben Quelle lesen oder in sie schreiben. */
|
|
289
|
+
function aliaseAuf(quelle, ausser) {
|
|
290
|
+
if (!quelle) { return []; }
|
|
291
|
+
var kanaele = {};
|
|
292
|
+
Object.keys(S.objects).forEach(function (id) {
|
|
293
|
+
if (id.indexOf('alias.') !== 0) { return; }
|
|
294
|
+
var a = ((S.objects[id].common || {}).alias || {}).id;
|
|
295
|
+
if (!a) { return; }
|
|
296
|
+
var ziele = (typeof a === 'string') ? [a] : [a.read, a.write];
|
|
297
|
+
if (!ziele.some(function (z) { return z && gehoertZu(z, quelle); })) { return; }
|
|
298
|
+
var kanal = id.split('.').slice(0, -1).join('.');
|
|
299
|
+
if (ausser && gehoertZu(kanal, ausser)) { return; }
|
|
300
|
+
kanaele[kanal] = true;
|
|
301
|
+
});
|
|
302
|
+
return Object.keys(kanaele);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* --- Anzeige --------------------------------------------------------- */
|
|
306
|
+
|
|
307
|
+
/* Der Ordner, in dem das Skript liegt: aus script.js.common.Raum.Name
|
|
308
|
+
bleibt „Raum". Bei 67 Skripten sagt der blosse Name oft nicht genug -
|
|
309
|
+
„Licht" gibt es dreimal, in drei Raeumen. */
|
|
310
|
+
function skriptOrdner(id) {
|
|
311
|
+
return String(id).split('.').slice(3, -1).join(' / ');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function skriptListe(treffer) {
|
|
315
|
+
var ul = el('ul');
|
|
316
|
+
treffer.forEach(function (t) {
|
|
317
|
+
var li = el('li');
|
|
318
|
+
var ordner = skriptOrdner(t.id);
|
|
319
|
+
if (ordner) { li.appendChild(el('span', 'ord', ordner + ' / ')); }
|
|
320
|
+
li.appendChild(el('span', 'id', t.name));
|
|
321
|
+
li.appendChild(document.createTextNode(
|
|
322
|
+
(t.an ? '' : ' (' + tr('move.scriptOff') + ')') + ' — ' +
|
|
323
|
+
(t.zeilen.length === 1
|
|
324
|
+
? tr('move.scriptLines', t.zeilen[0])
|
|
325
|
+
: tr('move.scriptLinesN', t.zeilen.join(', ')))));
|
|
326
|
+
ul.appendChild(li);
|
|
327
|
+
});
|
|
328
|
+
return ul;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function zweiteilig(treffer, vorn, hinten) {
|
|
332
|
+
var ul = el('ul');
|
|
333
|
+
treffer.forEach(function (t) {
|
|
334
|
+
var li = el('li');
|
|
335
|
+
li.appendChild(el('span', 'id', vorn(t)));
|
|
336
|
+
li.appendChild(document.createTextNode(' — '));
|
|
337
|
+
li.appendChild(el('span', 'ord', hinten(t)));
|
|
338
|
+
ul.appendChild(li);
|
|
339
|
+
});
|
|
340
|
+
return ul;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Was zuletzt nachgesehen wurde. `zeichneErgebnis` baut die Ansicht bei
|
|
344
|
+
jeder Aenderung neu auf - ohne dieses Gedaechtnis waere das Ergebnis
|
|
345
|
+
nach dem ersten Hakenklick wieder weg und der Knopf stuende erneut da. */
|
|
346
|
+
var zuletzt = { id: null, daten: null };
|
|
347
|
+
|
|
348
|
+
function sammle(alias, quelle) {
|
|
349
|
+
return {
|
|
350
|
+
alias: {
|
|
351
|
+
id: alias,
|
|
352
|
+
skripte: trefferFuer(alias),
|
|
353
|
+
enums: aufzaehlungenMit(alias),
|
|
354
|
+
objekte: fundstellen(alias, 'obj'),
|
|
355
|
+
dateien: fundstellen(alias, 'datei'),
|
|
356
|
+
custom: customAn(alias)
|
|
357
|
+
},
|
|
358
|
+
quelle: quelle ? {
|
|
359
|
+
id: quelle,
|
|
360
|
+
skripte: trefferFuer(quelle),
|
|
361
|
+
enums: aufzaehlungenMit(quelle),
|
|
362
|
+
objekte: fundstellen(quelle, 'obj'),
|
|
363
|
+
dateien: fundstellen(quelle, 'datei'),
|
|
364
|
+
aliase: aliaseAuf(quelle, alias)
|
|
365
|
+
} : null
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function kennungen(liste) {
|
|
370
|
+
var w = document.createElement('div');
|
|
371
|
+
liste.forEach(function (x, i) {
|
|
372
|
+
if (i) { w.appendChild(document.createTextNode(', ')); }
|
|
373
|
+
w.appendChild(el('span', 'id', x));
|
|
374
|
+
});
|
|
375
|
+
return w;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function zeile(wo, was, inhalt) {
|
|
379
|
+
var r = el('div', 'vwzeile');
|
|
380
|
+
r.appendChild(el('div', 'k', was));
|
|
381
|
+
r.appendChild(inhalt || el('div', 'leer', tr('use.none')));
|
|
382
|
+
wo.appendChild(r);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function nennungen(wo, teil, kopf) {
|
|
386
|
+
var k = el('div', 'vwkopf');
|
|
387
|
+
k.appendChild(el('span', 't', kopf));
|
|
388
|
+
k.appendChild(el('span', 'id', teil.id));
|
|
389
|
+
wo.appendChild(k);
|
|
390
|
+
|
|
391
|
+
zeile(wo, tr('use.scripts'), teil.skripte.length ? skriptListe(teil.skripte) : null);
|
|
392
|
+
zeile(wo, tr('use.files'), teil.dateien.length
|
|
393
|
+
? zweiteilig(teil.dateien, function (t) { return t.ort; }, function (t) { return t.pfad; })
|
|
394
|
+
: null);
|
|
395
|
+
zeile(wo, tr('use.objects'), teil.objekte.length
|
|
396
|
+
? zweiteilig(teil.objekte, function (t) { return t.name; }, function (t) { return t.id; })
|
|
397
|
+
: null);
|
|
398
|
+
zeile(wo, tr('use.enums'), teil.enums.length
|
|
399
|
+
? el('div', null, teil.enums.map(function (x) { return x.name; }).join(', '))
|
|
400
|
+
: null);
|
|
401
|
+
if (teil.custom) {
|
|
402
|
+
zeile(wo, tr('use.custom'), teil.custom.length
|
|
403
|
+
? el('div', null, teil.custom.map(function (x) {
|
|
404
|
+
return x.id.split('.').pop() + ' → ' + x.adapter;
|
|
405
|
+
}).join(', '))
|
|
406
|
+
: null);
|
|
407
|
+
}
|
|
408
|
+
if (teil.aliase) {
|
|
409
|
+
zeile(wo, tr('use.otherAliases'), teil.aliase.length ? kennungen(teil.aliase) : null);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function baueVerwendungKarte(host, alias, quelle) {
|
|
414
|
+
if (!alias) { return; }
|
|
415
|
+
|
|
416
|
+
var k = el('div', 'card');
|
|
417
|
+
var kopf = el('div', 'ch');
|
|
418
|
+
kopf.appendChild(el('span', 'typ', tr('use.head')));
|
|
419
|
+
var knopf = el('button', 'btn');
|
|
420
|
+
knopf.style.fontSize = '11.5px';
|
|
421
|
+
kopf.appendChild(knopf);
|
|
422
|
+
k.appendChild(kopf);
|
|
423
|
+
|
|
424
|
+
var koerper = el('div');
|
|
425
|
+
k.appendChild(koerper);
|
|
426
|
+
host.appendChild(k);
|
|
427
|
+
|
|
428
|
+
var hinweis = function (text, warnen) {
|
|
429
|
+
var w = el('div', 'vwnote');
|
|
430
|
+
w.appendChild(el('div', warnen ? 'aside w' : 'aside', text));
|
|
431
|
+
koerper.appendChild(w);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
var malen = function (daten) {
|
|
435
|
+
koerper.textContent = '';
|
|
436
|
+
nennungen(koerper, daten.alias, tr('use.aliasHead'));
|
|
437
|
+
if (daten.quelle) {
|
|
438
|
+
nennungen(koerper, daten.quelle, tr('use.sourceHead'));
|
|
439
|
+
hinweis(tr('use.sourceHint'), true);
|
|
440
|
+
}
|
|
441
|
+
hinweis(tr('use.limits'), false);
|
|
442
|
+
knopf.textContent = tr('use.again');
|
|
443
|
+
knopf.disabled = false;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
var nachsehen = function () {
|
|
447
|
+
knopf.disabled = true;
|
|
448
|
+
knopf.textContent = tr('use.searching');
|
|
449
|
+
holeSkripte(function () {
|
|
450
|
+
holeFundorte(function () {
|
|
451
|
+
zuletzt = { id: alias, daten: sammle(alias, quelle) };
|
|
452
|
+
malen(zuletzt.daten);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
/* Nur der zweite Druck liest neu ein.
|
|
458
|
+
|
|
459
|
+
Der Vorrat gilt fuer die ganze Sitzung und fuer jeden Alias - wer
|
|
460
|
+
nach dem Nachsehen zum naechsten Alias wechselt, soll die 15 MB
|
|
461
|
+
nicht noch einmal holen. Gemessen am Produktivsystem: der erste
|
|
462
|
+
Lauf 1,6 s, jeder weitere aus dem Vorrat. Erst „neu einlesen" am
|
|
463
|
+
schon gefuellten Kasten wirft ihn weg. */
|
|
464
|
+
knopf.addEventListener('click', function () {
|
|
465
|
+
if (zuletzt.id === alias && zuletzt.daten) {
|
|
466
|
+
skriptVorrat = null;
|
|
467
|
+
fundIndex = null;
|
|
468
|
+
}
|
|
469
|
+
nachsehen();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (zuletzt.id === alias && zuletzt.daten) {
|
|
473
|
+
malen(zuletzt.daten);
|
|
474
|
+
} else {
|
|
475
|
+
knopf.textContent = tr('use.look');
|
|
476
|
+
hinweis(tr('use.lookHint'), false);
|
|
477
|
+
}
|
|
478
|
+
}
|
package/admin/js/vorlagen.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { S } from './zustand.js';
|
|
8
8
|
import { D, $, el } from './basis.js';
|
|
9
9
|
import { hatPunkt, jsonVon, feldWert, kindZustaende, feldFormel } from './werte.js';
|
|
10
|
-
import { eigenerKanalName, merkeHakenVorgabe } from './entwurf.js';
|
|
10
|
+
import { eigenerKanalName, merkeHakenVorgabe, kennungtauglich } from './entwurf.js';
|
|
11
11
|
import { tasmotaBefehle } from './mqtt.js';
|
|
12
12
|
import { socket } from './verbindung.js';
|
|
13
13
|
|
|
@@ -271,7 +271,17 @@ export function setzeInstanz(text, v, n) {
|
|
|
271
271
|
export function ausgangName(e, n) {
|
|
272
272
|
if (e && e.kanal) {
|
|
273
273
|
var eigen = eigenerKanalName(e.kanal + '.' + n);
|
|
274
|
-
|
|
274
|
+
/* Der Kanalname wird hier zur KENNUNG - also durch dieselbe
|
|
275
|
+
Saeuberung wie ueberall sonst.
|
|
276
|
+
|
|
277
|
+
Vorher stand hier nur `replace(/\./g, '_')`. Leerzeichen und
|
|
278
|
+
Kommas blieben damit stehen, und der js-controller lehnt so etwas
|
|
279
|
+
nicht ab, er saeubert still: aus dem Kanalnamen „Licht, Bar" wurde
|
|
280
|
+
`alias.0.…Licht_ Bar`, waehrend die Werkbank „Licht, Bar" anzeigte
|
|
281
|
+
und in die Aufzaehlungen eintrug - eine Kennung, die es nie gab
|
|
282
|
+
(G31, gemessen 12.09.2026). Der Name selbst bleibt unberuehrt: er
|
|
283
|
+
steht als Beschriftung im Objekt. */
|
|
284
|
+
if (eigen) { return kennungtauglich(eigen) || eigen.replace(/\./g, '_'); }
|
|
275
285
|
}
|
|
276
286
|
if (!e || !e.kanalnameRoh) { return 'POWER' + n; }
|
|
277
287
|
return String(e.kanalnameRoh).split(e.platzhalter || '%N%').join(n);
|
|
@@ -452,7 +462,8 @@ export function schreibZiel(z, kanal, auf) {
|
|
|
452
462
|
einzige Zeile ein Fehlgriff. */
|
|
453
463
|
export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
454
464
|
var auf = function (t) { return setzeInstanz(t, v, instanz); };
|
|
455
|
-
var e = { kanal: kanal, states: [],
|
|
465
|
+
var e = { kanal: kanal, states: [], pflichtFehlt: null,
|
|
466
|
+
want: v.geraetetyp, wantAuto: v.geraetetyp,
|
|
456
467
|
alleMuster: false, vorschlag: true, roh: false,
|
|
457
468
|
vorlage: v.id, vorlageVersion: v.version, tplName: sprachtext(v.name),
|
|
458
469
|
grund: (gruende || []).slice(), uebersprungen: [],
|
|
@@ -532,6 +543,7 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
|
532
543
|
var ziel0 = schreibZiel(z, kanal, auf);
|
|
533
544
|
if (!ziel0) {
|
|
534
545
|
e.uebersprungen.push(tr('tpl.skippedSource', z.name, auf(z.schreiben)));
|
|
546
|
+
if (!z.optional) { e.pflichtFehlt = z.name; }
|
|
535
547
|
return;
|
|
536
548
|
}
|
|
537
549
|
e.states.push({
|
|
@@ -553,6 +565,18 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
|
553
565
|
e.uebersprungen.push(gescheitert.length > 1
|
|
554
566
|
? tr('tpl.skippedNowhere', z.name, gescheitert.join(tr('tpl.nor')))
|
|
555
567
|
: tr('tpl.skippedSource', z.name, gescheitert[0] || auf(z.lesen)));
|
|
568
|
+
/* Eine Pflichtzeile, die ihre Quelle nicht findet, heisst: die
|
|
569
|
+
Vorlage passt zu diesem Geraet nicht. Die Erkennung sieht das
|
|
570
|
+
nicht — sie prueft, ob die genannten *Punkte* da sind, nicht ob
|
|
571
|
+
die Zeilen ihre *Felder darin* finden. Genau daran gewann eine
|
|
572
|
+
von Hand gebaute Stromzaehler-Vorlage an siebzehn Tasmotas und
|
|
573
|
+
lieferte dort eine einzige, obendrein abgewaehlte Zeile, waehrend
|
|
574
|
+
die Steckdosen-Vorlage elf gebaut haette (Ricardo, 14.09.2026).
|
|
575
|
+
|
|
576
|
+
Optionale Zeilen zaehlen nicht: sie duerfen fehlen, dafuer sind
|
|
577
|
+
sie optional. Gemessen am Testsystem betrifft die Regel keine
|
|
578
|
+
einzige der fuenfzehn uebrigen Vorlagen. */
|
|
579
|
+
if (!z.optional) { e.pflichtFehlt = z.name; }
|
|
556
580
|
return;
|
|
557
581
|
}
|
|
558
582
|
|
|
@@ -614,9 +638,34 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
|
614
638
|
});
|
|
615
639
|
});
|
|
616
640
|
|
|
617
|
-
|
|
641
|
+
/* `auchOhneTreffer` gilt beim Von-Hand-Waehlen: wer eine Vorlage
|
|
642
|
+
ausdruecklich auswaehlt, bekommt sie auch dann, wenn eine
|
|
643
|
+
Pflichtzeile fehlt — samt der Liste in „uebersprungen". */
|
|
644
|
+
return ((e.states.length && !e.pflichtFehlt) || auchOhneTreffer) ? e : null;
|
|
618
645
|
}
|
|
619
646
|
|
|
647
|
+
/* ---- Rueckfall: Anfang ---------------------------------------------
|
|
648
|
+
|
|
649
|
+
Die erste Vorlage nehmen, die wirklich etwas baut.
|
|
650
|
+
|
|
651
|
+
Bewusst ohne jede Abhaengigkeit: `anwenden` wird hereingereicht. So
|
|
652
|
+
laesst sich die Reihenfolge ohne Browser und ohne Objektdatenbank
|
|
653
|
+
pruefen, und test/vorpruefung.js schneidet diesen Block heraus.
|
|
654
|
+
|
|
655
|
+
`uebergangen` sammelt die Vorlagen, die vorne lagen und trotzdem
|
|
656
|
+
nichts ergaben — sie gehoeren ins „Warum", sonst wundert man sich,
|
|
657
|
+
warum die punktbeste nicht griff. */
|
|
658
|
+
export function ersteAnwendbare(treffer, anwenden) {
|
|
659
|
+
var uebergangen = [];
|
|
660
|
+
for (var i = 0; i < treffer.length; i++) {
|
|
661
|
+
var e = anwenden(treffer[i]);
|
|
662
|
+
if (e) { return { gewaehlt: treffer[i], e: e, uebergangen: uebergangen }; }
|
|
663
|
+
uebergangen.push(treffer[i]);
|
|
664
|
+
}
|
|
665
|
+
return { gewaehlt: null, e: null, uebergangen: uebergangen };
|
|
666
|
+
}
|
|
667
|
+
/* ---- Rueckfall: Ende ---------------------------------------------- */
|
|
668
|
+
|
|
620
669
|
/* Beste Vorlage suchen und anwenden. */
|
|
621
670
|
export function vorschlag(kanal, vorlagenId, instanz) {
|
|
622
671
|
if (!S.VORLAGEN.length) { return null; }
|
|
@@ -624,20 +673,50 @@ export function vorschlag(kanal, vorlagenId, instanz) {
|
|
|
624
673
|
var alle = S.VORLAGEN.map(function (v) { return pruefeVorlage(v, kanal, instanz); });
|
|
625
674
|
var treffer = alle.filter(function (t) { return t.passt; });
|
|
626
675
|
|
|
627
|
-
var gewaehlt = null;
|
|
676
|
+
var gewaehlt = null, e = null, uebergangen = [];
|
|
628
677
|
if (vorlagenId) {
|
|
629
678
|
/* Von Hand gewaehlt gilt auch, wenn die Erkennung nicht anspringt —
|
|
630
|
-
die Verarbeitung laesst dann weg, wofuer die Quelle fehlt.
|
|
679
|
+
die Verarbeitung laesst dann weg, wofuer die Quelle fehlt. Hier
|
|
680
|
+
gibt es auch keinen Rueckfall: wer eine Vorlage ausdruecklich
|
|
681
|
+
auswaehlt, meint genau diese und keine andere. */
|
|
631
682
|
alle.forEach(function (t) { if (t.vorlage.id === vorlagenId) { gewaehlt = t; } });
|
|
683
|
+
if (gewaehlt) {
|
|
684
|
+
e = wendeAn(gewaehlt.vorlage, kanal, gewaehlt.gruende, gewaehlt.instanz, true);
|
|
685
|
+
}
|
|
632
686
|
}
|
|
633
687
|
if (!gewaehlt) {
|
|
634
688
|
if (!treffer.length) { return null; }
|
|
635
689
|
treffer.sort(function (a, b) { return b.punkte - a.punkte; });
|
|
636
|
-
|
|
690
|
+
|
|
691
|
+
/* Die Erkennung und das Anwenden stellen zwei verschiedene Fragen.
|
|
692
|
+
`pruefeVorlage` prueft, ob die genannten *Punkte* da sind;
|
|
693
|
+
`wendeAn` braucht bestimmte *Felder darin*. Eine Vorlage kann die
|
|
694
|
+
Erkennung also gewinnen und danach keine einzige Zeile bauen —
|
|
695
|
+
`wendeAn` gibt dann `null` zurueck.
|
|
696
|
+
|
|
697
|
+
Vorher war an dieser Stelle Schluss: `treffer[0]` anwenden, und
|
|
698
|
+
wenn das nichts ergab, gar keine Vorlage. Am Geraet stand dann
|
|
699
|
+
„Keine Vorlage erkannt", waehrend die Auswahlliste daneben
|
|
700
|
+
„Tasmota-Steckdose v6 passt" meldete — zwei Urteile ueber zwei
|
|
701
|
+
verschiedene Fragen, nebeneinander in derselben Ansicht
|
|
702
|
+
(gefunden 14.09.2026 am Testsystem, C3).
|
|
703
|
+
|
|
704
|
+
Jetzt kommt die naechstbeste an die Reihe. Was uebergangen wurde,
|
|
705
|
+
steht danach im „Warum" — sonst wundert man sich, warum
|
|
706
|
+
ausgerechnet die eigene Vorlage nicht griff. */
|
|
707
|
+
var reihe = ersteAnwendbare(treffer, function (t) {
|
|
708
|
+
return wendeAn(t.vorlage, kanal, t.gruende, t.instanz, false);
|
|
709
|
+
});
|
|
710
|
+
if (!reihe.gewaehlt) { return null; }
|
|
711
|
+
gewaehlt = reihe.gewaehlt;
|
|
712
|
+
e = reihe.e;
|
|
713
|
+
uebergangen = reihe.uebergangen;
|
|
637
714
|
}
|
|
638
715
|
|
|
639
|
-
var e = wendeAn(gewaehlt.vorlage, kanal, gewaehlt.gruende, gewaehlt.instanz, !!vorlagenId);
|
|
640
716
|
if (!e) { return null; }
|
|
717
|
+
uebergangen.forEach(function (t) {
|
|
718
|
+
e.grund.push(tr('tpl.skippedEmpty', sprachtext(t.vorlage.name)));
|
|
719
|
+
});
|
|
641
720
|
if (!vorlagenId && gewaehlt.passt) {
|
|
642
721
|
var gleichAuf = treffer.filter(function (t) { return t.punkte === gewaehlt.punkte; });
|
|
643
722
|
var ohneHinweis = !gewaehlt.namenstreffer;
|