iobroker.alias-workbench 0.8.3 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,33 @@
1
- /* Die uebersetzten Namen der Muster, angezapft beim Geraete-Adapter.
1
+ /* Die uebersetzten Namen der Muster, angezapft beim Admin.
2
2
 
3
- Der type-detector kennt nur rohe Typnamen (socket, thermostat). Der
4
- Geraete-Adapter fuehrt fuer genau diese Typen fertige Uebersetzungen
5
- in elf Sprachen - in seinem Bundle, ein Block je Sprache, und jeder
6
- Block gibt sich am Eintrag "type-Device type" selbst zu erkennen.
7
- Wir holen den Block der Oberflaechensprache und schlagen darin nach;
8
- eine eigene Liste wuerde bei jedem type-detector-Update altern
9
- (Ricardos Punkt 13, 25.08.2026 - Weg 2, wie der Raum/Funktion-Katalog).
3
+ Der type-detector kennt nur rohe Typnamen (socket, thermostat) und
4
+ bringt selbst KEINE Uebersetzung mit - er ist eine Bibliothek
5
+ (`@iobroker/type-detector`), kein Adapter. Uebersetzt werden seine
6
+ Typen nur in den Sprachdateien der Adapter, die ihn benutzen, und
7
+ jeder schleppt seine eigene Kopie mit. Ein Block je Sprache, jeder
8
+ gibt sich am Eintrag "type-Device type" selbst zu erkennen; wir holen
9
+ den der Oberflaechensprache und schlagen darin nach. Eine eigene
10
+ Liste wuerde bei jedem type-detector-Update altern (Ricardos Punkt 13,
11
+ 25.08.2026 - Weg 2, wie der Raum/Funktion-Katalog).
10
12
 
11
- Bricht die Kette - Adapter fehlt, Bundle umgebaut - bleibt die Karte
12
- leer und ueberall steht der rohe Name. Kein Fehler, nur weniger
13
- Komfort. Beide Bundle-Fassungen werden verstanden: die neue (4.x)
14
- schreibt Werte in Backticks, die alte (1.x, produktiv) als
15
- JSON-Strings. */
13
+ Gefragt wird ausschliesslich der ADMIN. Bis zum 08.09.2026 war es der
14
+ Geraete-Adapter; Ricardos Begruendung fuer den Wechsel: Der Admin ist
15
+ immer installiert, `devices` nicht, und er wird besser gepflegt. Was
16
+ er nicht uebersetzt, bleibt eben roh - gemessen am 08.09.2026:
17
+ Admin 8.0.11 kennt alle 53 Typen, Admin 7.8.23 nur 44.
18
+
19
+ Der Weg dorthin geht ueber zwei Verzeichnisse, weil keines allein
20
+ reicht: `index.html` nennt bei Admin 8 das richtige Buendel, bei
21
+ Admin 7 aber nur vier Bootstrap-Haeppchen, und die fuehren nicht
22
+ weiter (34 Dateien durchsucht, nichts gefunden). Dort steht die Karte
23
+ in einer Datei, die erst `mf-manifest.json` nennt - der Admin laedt
24
+ seine Oberflaeche ueber Module Federation. Also beide Listen
25
+ zusammen, kurze Dateien zuerst.
26
+
27
+ Bricht die Kette - Aufbau geaendert, Datei zu gross -, bleibt die
28
+ Karte leer und ueberall steht der rohe Name. Kein Fehler, nur weniger
29
+ Komfort. Beide Schreibweisen werden verstanden: Werte in Backticks
30
+ (Admin 8) und als JSON-Strings (Admin 7). */
16
31
 
17
32
  import { sprache } from './sprache.js';
18
33
  import { holeText } from './basis.js';
@@ -44,26 +59,76 @@ function ziehe(src) {
44
59
  return Object.keys(raus).length ? raus : null;
45
60
  }
46
61
 
62
+ /* Welche Dateien der Admin ueberhaupt ausliefert. Zwei Verzeichnisse,
63
+ zusammengelegt und ohne Doppel - siehe Kopf: keines allein reicht
64
+ fuer beide Admin-Fassungen. */
65
+ function adminDateien() {
66
+ var raus = [];
67
+ function dazu(f) {
68
+ /* Fuehrendes `./` weg, sonst zaehlt dieselbe Datei zweimal: das
69
+ Manifest schreibt `assets/lib-….js`, das index.html
70
+ `./assets/lib-….js`. Ohne dieses Abschneiden stuenden 30 Eintraege
71
+ in einer Liste, die nach 25 abgeschnitten wird - und die gesuchte
72
+ Datei faellt genauso heraus wie damals bei der Sortierung nach
73
+ Namenslaenge. */
74
+ f = f.replace(/^\.\//, '');
75
+ if (raus.indexOf(f) === -1) { raus.push(f); }
76
+ }
77
+ /* Gesucht werden PFADE, keine blossen Dateinamen.
78
+
79
+ Beide Verzeichnisse schreiben den Ordner dazu, wo einer gehoert:
80
+ `"sync":["assets/lib-DSjRYjfP.js"]` im Manifest, `assets/…js` im
81
+ index.html. Wer nur den Dateinamen aufsammelt und stur `assets/`
82
+ davorsetzt, erfindet zwei Pfade, die es nicht gibt: das Manifest
83
+ nennt unter `remoteEntry` und `ssrRemoteEntry` je einen Namen mit
84
+ ausdruecklich leerem `"path":""` - die zwei Dateien liegen unter `/`.
85
+ Das gab beim Laden zwei 404 in der Konsole (A1, gefunden 08.09.2026
86
+ produktiv). Geholfen haben sie ohnehin nie: hinter remoteEntry steckt
87
+ der Einstieg von Module Federation, nicht die Sprachkarte.
88
+
89
+ Der Schraegstrich im Muster ist zugleich die Bedingung - ein nackter
90
+ Name kommt so gar nicht erst in die Liste, und ein Admin, der seine
91
+ Buendel eines Tages woanders ablegt, wird trotzdem gefunden. */
92
+ var pfade = /[A-Za-z0-9_\-.]+\/[A-Za-z0-9_\-./]+\.js/g;
93
+ return holeText('/mf-manifest.json')['catch'](function () { return ''; })
94
+ .then(function (mf) {
95
+ (mf.match(pfade) || []).forEach(dazu);
96
+ return holeText('/index.html')['catch'](function () { return ''; });
97
+ })
98
+ .then(function (html) {
99
+ (html.match(pfade) || []).forEach(dazu);
100
+ /* Reihenfolge bleibt, wie die Verzeichnisse sie nennen. Erst
101
+ wurde nach Namenslaenge sortiert - uebernommen vom
102
+ Aufzaehlungskatalog, wo es das Riesenbuendel nach hinten
103
+ schiebt. Hier war es genau falsch: der Name der gesuchten Datei
104
+ ist bei Admin 8 mit 143 Zeichen der laengste ueberhaupt, sie
105
+ rutschte ans Ende und fiel aus der Auswahl. Ungeordnet steht
106
+ sie an fuenfter Stelle von 21, bei Admin 7 an vierter von 19 -
107
+ beide Verzeichnisse nennen das Wichtige zuerst (gemessen
108
+ 08.09.2026). Das Groessenlimit unten haelt die dicken Buendel
109
+ ohnehin heraus. */
110
+ return raus.slice(0, 25);
111
+ });
112
+ }
113
+
47
114
  export function ladeMusterNamen(nachher) {
48
115
  if (geholt) { return; }
49
116
  geholt = true;
50
- holeText('/adapter/devices/tab.html').then(function (html) {
51
- /* Die tab.html nennt mehrere Bausteine (Bootstrap-Haeppchen,
52
- Vendor, Hauptbundle). Die Karte steckt im Hauptbundle - das
53
- heisst in beiden bekannten Fassungen index-<hash>.js, also
54
- dieses zuerst, die uebrigen als Rueckfall der Reihe nach. */
55
- var dateien = (html.match(/assets\/[A-Za-z0-9_\-.]+\.js/g) || [])
56
- .filter(function (x, i, a) { return a.indexOf(x) === i; })
57
- .sort(function (a, b) {
58
- return (b.indexOf('assets/index-') === 0 ? 1 : 0)
59
- - (a.indexOf('assets/index-') === 0 ? 1 : 0);
60
- })
61
- .slice(0, 8);
117
+ adminDateien().then(function (dateien) {
62
118
  if (!dateien.length) { throw new Error('kein Bundle'); }
63
119
  return dateien.reduce(function (kette, f) {
64
120
  return kette.then(function (fertig) {
65
121
  if (fertig) { return true; }
66
- return holeText('/adapter/devices/' + f).then(function (src) {
122
+ return holeText('/' + f).then(function (src) {
123
+ /* Die Grenze liegt hoeher als beim Aufzaehlungskatalog (3 MB):
124
+ bei Admin 8 steckt die Karte ausgerechnet im grossen
125
+ GUI-Buendel, gemessen 8,4 MB (08.09.2026). Mit der alten
126
+ Grenze wurde genau diese Datei uebersprungen und produktiv
127
+ blieb jeder Beiname roh. Teuer ist es nicht: derselbe Admin
128
+ hat das Buendel gerade selbst geladen, der zweite Griff
129
+ kommt aus dem Zwischenspeicher des Browsers. Alles darueber
130
+ ist kein Buendel mehr, sondern ein Versehen. */
131
+ if (src.length > 12000000) { return false; }
67
132
  var karte = ziehe(src);
68
133
  if (karte) { musterNamen = karte; return true; }
69
134
  return false;
@@ -112,6 +112,16 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
112
112
  var markeCache = {};
113
113
  function bewerte(m) {
114
114
  if (markeCache[m] !== undefined) { return markeCache[m]; }
115
+ /* Die angebotene Liste ist gefiltert — der gewaehlte Typ nicht:
116
+ `mBeschriften` ruft `markeFuer(e.want)` direkt, und `e.want` kommt
117
+ ungeprueft aus `v.geraetetyp`. Eine eingelesene Vorlage mit dem
118
+ Musternamen eines neueren type-detector liess `musterVon(m).states`
119
+ werfen, und der Entwurf wurde gar nicht erst gezeichnet — die
120
+ rechte Seite blieb leer (gemessen 09.09.2026). */
121
+ if (!musterVon(m)) {
122
+ markeCache[m] = { mark: tr('pattern.doesNotFit'), passt: false, belegt: 0 };
123
+ return markeCache[m];
124
+ }
115
125
  /* Die Vorschau muss vorhersagen, was das Auswaehlen wirklich tut.
116
126
  Beim Vorschlag werden Lampe und Steckdose samt Rollen neu gebaut -
117
127
  also auch hier gegen den umgebauten Entwurf pruefen. Sonst stuende
@@ -81,6 +81,19 @@ export function nachziehenErledigt() {
81
81
  nachziehBaum = false;
82
82
  }
83
83
 
84
+ /* Nach einem Abbruch sind beim Server alle Abos weg.
85
+
86
+ `subscribeObjects` und `subscribe` gelten nur fuer die Sitzung, die
87
+ sie gestellt hat — der WebSocket-Shim des Admin erneuert von sich aus
88
+ nichts (das Wort `subscribe` kommt in seinem Quelltext kein einziges
89
+ Mal vor). Der `objectChange`-Horcher wird hier ausdruecklich NICHT
90
+ noch einmal angemeldet: der haengt am Socket-Objekt und hat den
91
+ Abbruch ueberlebt. Nur die Anmeldung beim Server fehlt. */
92
+ export function abosErneuern() {
93
+ if (horcht) { socket.emit('subscribeObjects', '*'); }
94
+ (S.abo || []).forEach(function (m) { socket.emit('subscribe', m); });
95
+ }
96
+
84
97
  export function horcheAufObjekte() {
85
98
  if (horcht) { return; }
86
99
  horcht = true;
@@ -95,11 +108,20 @@ export function horcheAufObjekte() {
95
108
  var kern = function (o) {
96
109
  return o ? JSON.stringify([o.type, o.common, o.native]) : null;
97
110
  };
98
- var vorher = kern(S.objects[id]);
99
111
  var nachher = kern(obj);
100
- if (vorher === nachher) { return; }
101
112
 
102
- /* Aufzaehlungen gehen einen eigenen Weg.
113
+ /* Aufzaehlungen zuerst — und zwar VOR dem Vergleich mit `S.objects`.
114
+
115
+ Der Vergleich unten fragt `S.objects[id]`, und dort liegen
116
+ Aufzaehlungen nie. Beim Loeschen einer Aufzaehlung sind alter und
117
+ neuer Wert deshalb beide `null`, der Frueheinstieg griff, und
118
+ `delete enums[id]` wurde nie erreicht. Die geloeschte Aufzaehlung
119
+ blieb im Vorrat stehen, und der naechste Alias mit diesem Raum
120
+ kopierte sie samt aller alten Mitglieder zurueck an ihren Platz.
121
+ Gemessen 09.09.2026 am Testsystem: `enum.rooms.WBP7` war am System
122
+ geloescht und waere mit zwei fremden Mitgliedern wieder entstanden.
123
+
124
+ Aufzaehlungen gehen einen eigenen Weg.
103
125
 
104
126
  Sie gehoeren nicht in `objects` - dort stuenden sie im Geraetebaum,
105
127
  wo sie nichts zu suchen haben. Nachziehen muss man sie aber, und
@@ -128,6 +150,10 @@ export function horcheAufObjekte() {
128
150
  return;
129
151
  }
130
152
 
153
+ /* Alles Uebrige: nur was zaehlt, nicht jeder Schreibvorgang. */
154
+ var vorher = kern(S.objects[id]);
155
+ if (vorher === nachher) { return; }
156
+
131
157
  var gabEs = S.objects[id] !== undefined;
132
158
  if (obj) { S.objects[id] = obj; } else { delete S.objects[id]; }
133
159
  if (gabEs !== (S.objects[id] !== undefined)) { nachziehBaum = true; }
@@ -68,7 +68,7 @@ export function pruefungDopplung(e) {
68
68
  export function pruefungSenden(e) {
69
69
  var ziele = e.states.filter(function (x) { return x.on && x.srcW; });
70
70
  if (!ziele.length) { return { s: 'mut', t: tr('check.publish'), d: tr('check.noWritePath') }; }
71
- var mqtt = [], stumm = [], fehlt = [];
71
+ var mqtt = [], stumm = [], fehlt = [], unklar = [];
72
72
  ziele.forEach(function (x) {
73
73
  /* Drei Faelle, nicht zwei: den Punkt gibt es gar nicht, es gibt ihn
74
74
  und er ist stumm, oder er sendet. Der erste kam dazu, seit die
@@ -77,11 +77,19 @@ export function pruefungSenden(e) {
77
77
  var m = mqttEinstellung(x.srcW);
78
78
  if (!m) { return; }
79
79
  mqtt.push(x);
80
- if (!m.publish) { stumm.push(x.n); }
80
+ /* Nur ein ausdrueckliches Nein ist ein Nein. `null` heisst
81
+ unbekannt: Objekte von ioBroker.mqtt und sonoff tragen das Thema
82
+ nur in `native`, ohne `custom`-Block — ueber ihre Sendefaehigkeit
83
+ sagt das nichts. Vorher stand dort fest `false`, und die Pruefung
84
+ meldete rot „kann nicht senden", ohne dass sich etwas daran
85
+ aendern liess (gemessen 09.09.2026). */
86
+ if (m.publish === false) { stumm.push(x.n); }
87
+ else if (m.publish === null) { unklar.push(x.n); }
81
88
  });
82
89
  if (fehlt.length) { return { s: 'bad', t: tr('check.publish'), d: tr('check.sendPointMissing', fehlt.join(', ')) }; }
83
90
  if (!mqtt.length) { return { s: 'mut', t: tr('check.publish'), d: tr('check.notMqtt') }; }
84
91
  if (stumm.length) { return { s: 'bad', t: tr('check.publish'), d: tr('check.cannotSend', stumm.join(', ')) }; }
92
+ if (unklar.length) { return { s: 'mut', t: tr('check.publish'), d: tr('check.sendUnknown', unklar.join(', ')) }; }
85
93
  return { s: 'ok', t: tr('check.publish'), d: tr('check.canSend', mqtt.length) };
86
94
  }
87
95
 
@@ -162,7 +162,15 @@ export function tauschPlan(kanal, neuKanal) {
162
162
  }
163
163
  if (!zeile.neuW && zeile.altW) {
164
164
  var gw = geraten(zeile.altW, neuKanal);
165
- if (gw) { zeile.neuW = gw; }
165
+ /* Was hier geraten wird, muss ein anderer Punkt sein als die
166
+ Lesequelle. Bei getrennten Quellen heissen Melder und Befehl oft
167
+ gleich - `stat.POWER` und `cmnd.POWER` -, und das Raten greift
168
+ nach dem Namen: aus der Schreibquelle `cmnd.POWER` wurde am
169
+ neuen Geraet `stat.POWER`, also genau der Melder. Der Alias
170
+ haette danach seinen Befehl ins Status-Thema geschickt; das
171
+ Geraet schaltet nicht und die Rueckmeldung ist verfaelscht
172
+ (gemessen 09.09.2026). Lieber kein Treffer als dieser. */
173
+ if (gw && gw !== zeile.neuR) { zeile.neuW = gw; }
166
174
  }
167
175
  /* Bei einfacher Quelle gilt die Lesequelle fuer beides. */
168
176
  if (zeile.einfach && zeile.neuR) { zeile.neuW = zeile.neuR; }
@@ -191,7 +199,19 @@ export function tauschObjekte(kanal, neuKanal, plan, weg) {
191
199
  delete neu.ts; delete neu.from; delete neu.user; delete neu.acl; delete neu._id;
192
200
  var a = neu.common.alias || {};
193
201
  if (z.neuW && z.neuW !== z.neuR) { a.id = { read: z.neuR, write: z.neuW }; }
194
- else { a.id = z.neuR; }
202
+ else if (z.einfach) { a.id = z.neuR; }
203
+ else {
204
+ /* Getrennte Quellen ohne Schreibtreffer: die Objektform bleibt.
205
+
206
+ Eine blosse Zeichenkette heisst fuer den js-controller „lesen
207
+ und schreiben auf dieselbe Kennung". Aus einem Alias, der den
208
+ Zustand liest und den Befehl woanders hinschickt, wurde damit
209
+ einer, der auf den Melder schreibt - und aus einem bewusst nur
210
+ lesenden Punkt bei `common.write: true` sogar ein schreibender.
211
+ Ohne Treffer bleibt `write` einfach weg: dann schreibt der Alias
212
+ nirgendwohin, und das ist die ehrliche Auskunft. */
213
+ a.id = { read: z.neuR };
214
+ }
195
215
  neu.common.alias = a;
196
216
  raus.push({ id: z.id, neu: false, obj: neu });
197
217
  });
@@ -461,6 +481,15 @@ function zeichneTausch() {
461
481
  rechts.appendChild(paarZeile(tr('write.diffNew'),
462
482
  z.neuR + (z.neuW && z.neuW !== z.neuR ? ' → ' + z.neuW : ''),
463
483
  'sn', gleich ? 'var(--ink-3)' : 'var(--ok)'));
484
+ /* Lesequelle gefunden, Schreibquelle nicht — das gehoert gesagt.
485
+ Sonst steht nur die neue Lesequelle in Gruen da, und dass der
486
+ Punkt danach nirgendwohin schreibt, merkt man erst am Geraet. */
487
+ if (z.altW && z.altW !== z.altR && !z.neuW) {
488
+ var kw = el('div');
489
+ kw.style.color = 'var(--bad)';
490
+ kw.textContent = tr('swap.noWriteMatch');
491
+ rechts.appendChild(kw);
492
+ }
464
493
  } else {
465
494
  var fehlt = el('div');
466
495
  fehlt.style.color = 'var(--bad)';