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.
@@ -8,9 +8,11 @@ import { $ } from './basis.js';
8
8
  import { txt, tr } from './sprache.js';
9
9
  import './enums.js';
10
10
  import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
11
- import { kindZustaende, aliasQuellen, holeWerte } from './werte.js';
11
+ import { kindZustaende, aliasQuellen, holeWerte,
12
+ mitVorspann, schreibQuelle
13
+ } from './werte.js';
12
14
  import { erkenneEntwurf } from './erkennung.js';
13
- import { vorschlag , pruefeVorlage } from './vorlagen.js';
15
+ import { vorschlag , pruefeVorlage, zeigeAbozahl } from './vorlagen.js';
14
16
 
15
17
  import { zeichneErgebnis } from './ergebnis.js';
16
18
 
@@ -101,11 +103,7 @@ export function baueEntwurf(kanal) {
101
103
  alias.0.Badezimmer.Rollladen.CLOSE). Belegt ist das Schreiben
102
104
  durch `common.write` oder durch eine vorhandene Schreibformel -
103
105
  wer eine hinterlegt hat, wollte schreiben. */
104
- srcW: istAlias
105
- ? (q.einfach
106
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
107
- : (q.write || ''))
108
- : (c.write ? id : ''),
106
+ srcW: istAlias ? schreibQuelle(o) : (c.write ? id : ''),
109
107
  f: (typeof a.read === 'string') ? a.read : '',
110
108
  fw: (typeof a.write === 'string') ? a.write : '',
111
109
  caption: istAlias ? txt(c.name) : kanalBeschriftung(kanal, kurzName),
@@ -132,8 +130,8 @@ export function baueEntwurf(kanal) {
132
130
  entstanden ist - dieselbe Regel wie in `waehle`. Ohne sie durfte eine
133
131
  spaeter angelegte Vorlage beim Umschalten auf die Alle-Ansicht das
134
132
  fertige Geraet still uebernehmen (26.08.2026). */
135
- export function alleUndVorlage(kanal, vorlagenId) {
136
- var v = vorschlag(kanal, vorlagenId);
133
+ export function alleUndVorlage(kanal, vorlagenId, instanz) {
134
+ var v = vorschlag(kanal, vorlagenId, instanz);
137
135
  var roh = baueEntwurf(kanal);
138
136
  if (!v) { return roh; }
139
137
 
@@ -215,6 +213,120 @@ export function knotenDa(id) {
215
213
  return kindZustaende(id).length > 0;
216
214
  }
217
215
 
216
+ /* Aus welchen Knoten liest dieser Alias, und wie oft aus jedem?
217
+
218
+ Dieselbe Zaehlung wie in `quelleVon` - nur dass die dort das Ergebnis
219
+ auf einen Sieger eindampft. Fuer die Anzeige zaehlt aber gerade das,
220
+ was dabei verlorengeht: dass es mehr als einen gibt. Ricardos
221
+ `alias.0.Solar.Netz` liest drei Punkte aus `0_userdata` und zwei aus
222
+ `mqtt-client`; der Sprungknopf fuehrte zur Mehrheit und sagte kein
223
+ Wort ueber den Rest (Ricardo, 08.09.2026).
224
+
225
+ Absteigend nach Anzahl, bei Gleichstand alphabetisch - sonst
226
+ wechselte die Reihenfolge bei jedem Neuzeichnen. */
227
+ /* Zu welcher Quelle des Alias gehoert der Knoten, an dem ich stehe?
228
+
229
+ Die Antwort ist nicht „die Mehrheit", sondern „die hier". Sie traegt
230
+ zwei Dinge: den Rueckweg zum Alias (er gehoert an jede Quelle, nicht
231
+ nur an die groesste) und den Bezugspunkt der Zeilenmarken - markiert
232
+ wird, was NICHT von hier kommt (Ricardo, 08.09.2026).
233
+
234
+ Getroffen wird ein Eintrag der Verteilung auf drei Weisen: genau er,
235
+ ein Knoten unter ihm (`…Stromzaehler.stat` unter der zusammengefassten
236
+ Sparte) oder sein unmittelbarer Elternknoten - dort steht man am
237
+ Geraet, waehrend die Quelle eine Ebene tiefer liegt. Nur eine Ebene:
238
+ sonst bekaeme `mqtt-client.0.SmartHome` den Knopf und jede Ebene
239
+ darueber gleich mit. */
240
+ export function quelleHier(aliasId, knoten) {
241
+ if (!aliasId || !knoten) { return null; }
242
+ var treffer = null;
243
+ quellenVerteilung(aliasId).forEach(function (q) {
244
+ if (treffer) { return; }
245
+ if (q.id === knoten) { treffer = q.id; return; }
246
+ if (knoten.indexOf(q.id + '.') === 0) { treffer = q.id; return; }
247
+ var t = q.id.split('.');
248
+ t.pop();
249
+ if (t.join('.') === knoten) { treffer = q.id; }
250
+ });
251
+ return treffer;
252
+ }
253
+
254
+ export function quellenVerteilung(aliasId) {
255
+ var zaehler = {};
256
+ kindZustaende(aliasId).forEach(function (id) {
257
+ var q = aliasQuellen(S.objects[id]);
258
+ /* Einmal je DATENPUNKT, nicht je Quellangabe. `quelleVon` zaehlt
259
+ Lese- und Schreibquelle getrennt - fuer den Mehrheitsentscheid
260
+ gleichgueltig, fuer eine Anzeige nicht: an einem Alias mit fuenf
261
+ Punkten stand „6 und 4" statt „3 und 2". Wo Lesen und Schreiben
262
+ auseinandergehen, zaehlt die Lesequelle: aus ihr kommt der Wert,
263
+ den man sieht. */
264
+ var x = q.read || q.write;
265
+ if (!x) { return; }
266
+ var t = String(x).split('.');
267
+ t.pop();
268
+ var eltern = t.join('.');
269
+ if (eltern) { zaehler[eltern] = (zaehler[eltern] || 0) + 1; }
270
+ });
271
+ var sortiere = function (liste) {
272
+ return liste.sort(function (a, b) { return (b.n - a.n) || (a.id < b.id ? -1 : 1); });
273
+ };
274
+ var liste = sortiere(Object.keys(zaehler).map(function (k) { return { id: k, n: zaehler[k] }; }));
275
+
276
+ /* Sparten desselben Geraets sind keine zweite Quelle.
277
+
278
+ Ein Tasmota-Alias liest aus `…Licht.stat` und `…Licht.tele` - das
279
+ sind zwei Knoten, aber ein Geraet, und „2 Quellen" waere dort
280
+ schlicht falsch. Alles, was unterhalb der Hauptquelle liegt, zaehlt
281
+ deshalb zu ihr. Uebrig bleiben nur eigene Zweige - bei Ricardos
282
+ `alias.0.Solar.Netz` `0_userdata` und `mqtt-client` (08.09.2026). */
283
+ var haupt = quelleVon(aliasId);
284
+ if (!haupt) { return liste; }
285
+ var raus = [], hauptN = 0;
286
+ liste.forEach(function (q) {
287
+ if (q.id === haupt || q.id.indexOf(haupt + '.') === 0) { hauptN += q.n; return; }
288
+ raus.push(q);
289
+ });
290
+ if (hauptN) { raus.push({ id: haupt, n: hauptN }); }
291
+
292
+ /* Dasselbe noch einmal unter den Nebenquellen.
293
+
294
+ Der Schritt darueber fasst nur gegen die HAUPTquelle zusammen. Bei
295
+ `alias.0.Solar.Balkon.Einspeisung` liegen aber zwei der Nebenquellen
296
+ ebenfalls beieinander - `…Solar_Balkon.stat` und `…Solar_Balkon.tele`
297
+ sind ein Tasmota, und es standen „4 Quellen" statt drei (Ricardo,
298
+ 08.09.2026).
299
+
300
+ Zusammengefasst wird nur, was wirklich zusammengehoert: mehrere
301
+ Knoten unter demselben Elternknoten, und das auch nur, wenn sie
302
+ blosse Kennungsebenen ohne eigenes Objekt sind (so legt MQTT seine
303
+ Sparten an) oder ihr Elternknoten ein `device` ist (so liegen die
304
+ Kanaele bei Homematic). Zwei `channel` in einem Ordner - etwa
305
+ `0_userdata.0.Solar.Netz` und `…Solar.Balkon` - bleiben getrennt:
306
+ das sind zwei Geraete, keine zwei Sparten. */
307
+ var nachEltern = {};
308
+ raus.forEach(function (q) {
309
+ var t = q.id.split('.');
310
+ t.pop();
311
+ var eltern = t.join('.');
312
+ (nachEltern[eltern] = nachEltern[eltern] || []).push(q);
313
+ });
314
+ var zusammen = [];
315
+ Object.keys(nachEltern).forEach(function (eltern) {
316
+ var gruppe = nachEltern[eltern];
317
+ var ohneObjekt = gruppe.every(function (q) { return !S.objects[q.id]; });
318
+ var elternGeraet = !!(S.objects[eltern] && S.objects[eltern].type === 'device');
319
+ if (gruppe.length < 2 || !eltern || (!ohneObjekt && !elternGeraet)) {
320
+ zusammen = zusammen.concat(gruppe);
321
+ return;
322
+ }
323
+ var summe = 0;
324
+ gruppe.forEach(function (q) { summe += q.n; });
325
+ zusammen.push({ id: eltern, n: summe });
326
+ });
327
+ return sortiere(zusammen);
328
+ }
329
+
218
330
  export function quelleVon(aliasId) {
219
331
  var o = S.objects[aliasId];
220
332
  var q = o && o.native && o.native.quelle;
@@ -436,7 +548,14 @@ export function steuerKanaele(dev) {
436
548
  var pflicht = f[0].states.filter(function (st) { return st.id; })
437
549
  .map(function (st) { return st.id; });
438
550
  if (!f[0].states.some(function (st) { return st.required && st.id; })) { return; }
439
- raus.push({ id: id, typ: f[0].type, name: eigenerKanalName(id) || id.slice(pre.length),
551
+ /* `name` ist der Anzeigename und darf alles enthalten, was ein Mensch
552
+ hinschreibt. `kennung` ist, was als Objekt-ID taugt — dieselbe
553
+ Saeuberung wie ueberall sonst (T19). Getrennt, weil beides
554
+ gebraucht wird: die Liste zeigt den Namen, das Ziel nimmt die
555
+ Kennung. */
556
+ var kn = eigenerKanalName(id);
557
+ raus.push({ id: id, typ: f[0].type, name: kn || id.slice(pre.length),
558
+ kennung: kennungtauglich(kn || '') || id.slice(pre.length),
440
559
  pflicht: pflicht });
441
560
  });
442
561
  kanalGeraeteCache[dev] = raus;
@@ -529,6 +648,20 @@ export function entwurfFuer(id, gemeinsam, gemAn) {
529
648
  return merkeHakenVorgabe(e2);
530
649
  }
531
650
 
651
+ /* Alle laufenden Abos abmelden.
652
+
653
+ Stand nur in `waehle` — und damit meldete kein einziger der Wege ab,
654
+ die die Auswahl fallen lassen: der Moduswechsel, das Zuklappen eines
655
+ Zweigs, das Abschalten des Expertenmodus, das Loeschen eines Alias.
656
+ Danach lief ein Abo auf ein Geraet, das rechts gar nicht mehr steht,
657
+ der Zaehler in der Kopfleiste behauptete „ein Abo", und `S.werte`
658
+ fuellte sich weiter (gemessen 09.09.2026). */
659
+ export function aboLoesen() {
660
+ (S.abo || []).forEach(function (m) { socket.emit('unsubscribe', m); });
661
+ S.abo = [];
662
+ zeigeAbozahl();
663
+ }
664
+
532
665
  export function waehle(id, fertig) {
533
666
  /* Wer links etwas anderes anklickt, will es rechts sehen - auch wenn
534
667
  der Fokus noch in einem Feld der rechten Seite steht. `tipptGerade`
@@ -573,9 +706,118 @@ export function waehle(id, fertig) {
573
706
  sel.scrollIntoView({ block: 'nearest' });
574
707
  }
575
708
 
576
- if (S.abo) { socket.emit('unsubscribe', S.abo); }
577
- S.abo = id + '.*';
578
- socket.emit('subscribe', S.abo);
709
+ /* Abonniert wird, was auch angezeigt wird.
710
+
711
+ Bisher nur `<knoten>.*`. An einer Quelle stimmt das - die Werte in
712
+ der Liste kommen von dort. Am Alias nicht: dort stehen die Werte der
713
+ QUELLEN, und die bekamen kein Ereignis. Die Zahlen wurden einmal
714
+ beim Auswaehlen geholt und standen dann still; an
715
+ `alias.0.Solar.Balkon.Energie`, wo sich im Sekundentakt etwas
716
+ aendert, fiel es auf (Ricardo, 08.09.2026).
717
+
718
+ Deshalb am Alias zusaetzlich seine Quellen - `quellenVerteilung`
719
+ weiss ja, welche das sind. Und deshalb eine Liste: sonst bliebe
720
+ beim naechsten Wechsel die Haelfte abonniert und die Werkbank
721
+ bekaeme mit der Zeit Ereignisse fuer das halbe System. Die Zahl der
722
+ laufenden Abos steht in der Kopfleiste - dort sieht man sofort, ob
723
+ sich etwas ansammelt. */
724
+ aboLoesen();
725
+ var muster = [id + '.*'];
726
+ if (id.indexOf('alias.') === 0) {
727
+ /* Aus den Quellen, nicht aus der Anzeige-Verteilung.
728
+
729
+ `quellenVerteilung` fasst zusammen, was die rechte Seite als
730
+ „Quellen" zeigt — dafuer bildet sie den ELTERNknoten jeder
731
+ Quelle. Bei einem flachen MQTT-Geraet ist das der halbe Adapter:
732
+ gemessen 09.09.2026 wurde daraus `mqtt-client.0.*` mit 322
733
+ Objekten, an einem einzigen Alias. Und `S.werte` wird nie
734
+ geleert, das sammelt sich also.
735
+
736
+ Abonniert wird deshalb der Kanal jeder wirklich gelesenen Quelle.
737
+ Wo die Quellen ohnehin gebuendelt liegen, bleibt es bei einem
738
+ Muster; wo nicht, sind es ein paar mehr — aber jedes davon eng. */
739
+ var kanaele = [];
740
+ kindZustaende(id).forEach(function (kid) {
741
+ var q = aliasQuellen(S.objects[kid]);
742
+ [q.read, q.write].forEach(function (x) {
743
+ if (!x) { return; }
744
+ var t = String(x).split('.');
745
+ t.pop();
746
+ var kn = t.join('.');
747
+ if (kn && kanaele.indexOf(kn) === -1) { kanaele.push(kn); }
748
+ });
749
+ });
750
+ /* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
751
+
752
+ Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
753
+ Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
754
+ unter `SmartHome`, und `SmartHome.*` sind 320 Objekte. Ab dieser
755
+ Schwelle werden die Quellen einzeln abonniert — ein paar Abos
756
+ mehr, dafuer genau die, deren Werte auch angezeigt werden. */
757
+ var SCHWELLE = 30;
758
+ var wieBreit = function (knoten) {
759
+ var n = 0;
760
+ S.keysSorted.forEach(function (k0) { if (k0.indexOf(knoten + '.') === 0) { n++; } });
761
+ return n;
762
+ };
763
+ var drinSchon = function (was) {
764
+ return muster.some(function (m0) {
765
+ var p0 = m0.slice(-2) === '.*' ? m0.slice(0, -2) : m0;
766
+ return was === p0 || was.indexOf(p0 + '.') === 0;
767
+ });
768
+ };
769
+
770
+ /* Sparten desselben Geraets zu einem Muster zusammenfassen.
771
+
772
+ Ein Tasmota-Alias liest aus `…Licht.stat`, `…Licht.tele` und
773
+ `…Licht.cmnd` — drei Kanaele, ein Geraet, und in der Kopfleiste
774
+ gehoert dort „ein Abo" zu stehen, nicht drei. Zusammengefasst wird
775
+ aber nur, wo wirklich MEHRERE Kanaele unter demselben Knoten
776
+ haengen: Sonst waere aus `…Garten.Weihnachten_aussen` schlicht
777
+ `…Garten` geworden — breiter als noetig, ohne dass etwas
778
+ zusammenfaellt. */
779
+ for (var runde = 0; runde < 4; runde++) {
780
+ var unter = {};
781
+ kanaele.forEach(function (kn) {
782
+ var t = kn.split('.');
783
+ if (t.length <= 3) { return; }
784
+ var ob = t.slice(0, -1).join('.');
785
+ (unter[ob] = unter[ob] || []).push(kn);
786
+ });
787
+ var geaendert = false;
788
+ Object.keys(unter).forEach(function (ob) {
789
+ if (unter[ob].length < 2 || wieBreit(ob) > SCHWELLE) { return; }
790
+ kanaele = kanaele.filter(function (kn) { return unter[ob].indexOf(kn) === -1; });
791
+ if (kanaele.indexOf(ob) === -1) { kanaele.push(ob); }
792
+ geaendert = true;
793
+ });
794
+ if (!geaendert) { break; }
795
+ }
796
+
797
+ /* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
798
+
799
+ Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
800
+ Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
801
+ unter `SmartHome`, und `SmartHome.*` sind 320 Objekte (gemessen
802
+ 09.09.2026; vorher wurde daraus sogar `mqtt-client.0.*` mit 322).
803
+ Ab dieser Schwelle werden die Quellen einzeln abonniert — ein paar
804
+ Abos mehr, dafuer genau die, deren Werte auch angezeigt werden. */
805
+ kanaele.forEach(function (kn) {
806
+ if (drinSchon(kn)) { return; }
807
+ if (wieBreit(kn) > SCHWELLE) { return; }
808
+ muster.push(kn + '.*');
809
+ });
810
+ /* Was jetzt noch von keinem Muster gedeckt ist, kommt einzeln. */
811
+ kindZustaende(id).forEach(function (kid) {
812
+ var q = aliasQuellen(S.objects[kid]);
813
+ [q.read, q.write].forEach(function (x) {
814
+ if (x && !drinSchon(x) && muster.indexOf(x) === -1) { muster.push(x); }
815
+ });
816
+ });
817
+ }
818
+ S.abo = muster;
819
+ muster.forEach(function (m) { socket.emit('subscribe', m); });
820
+ zeigeAbozahl();
579
821
 
580
822
  /* Erst die Werte, dann der Vorschlag. Die Erkennung schaut in den
581
823
  Rohwert von tele.SENSOR hinein — ohne Werte haelt sie ein Geraet
@@ -600,7 +842,17 @@ export function waehle(id, fertig) {
600
842
  var werteDa = false;
601
843
  holeWerte(id, quellen, function () {
602
844
  werteDa = true;
603
- if (id !== S.current) { return; }
845
+ if (id !== S.current) {
846
+ /* Der Knoten hat gewechselt, waehrend die Werte unterwegs waren.
847
+ Am Entwurf ist hier nichts mehr zu tun — der Rueckruf muss aber
848
+ trotzdem kommen. Sonst bleibt `so59Laeuft` oder `abfrageLaeuft`
849
+ fuer immer gesetzt (der Knopf steht dauerhaft auf „laeuft"), und
850
+ der Trockenlauf nach dem Anlegen fehlender Sendepunkte geht nie
851
+ auf. Zwoelf Sekunden Wartezeit bei einer SetOption59-Abfrage
852
+ sind reichlich Gelegenheit dafuer (09.09.2026). */
853
+ if (typeof fertig === 'function') { fertig(); }
854
+ return;
855
+ }
604
856
 
605
857
  /* Wer schon Hand angelegt hat, behaelt seinen Entwurf.
606
858
 
@@ -812,13 +1064,40 @@ export function geraeteDarunter(wurzel) {
812
1064
  angelegt" und erzeugt beim Klick ein zweites Geraet, waehrend das
813
1065
  erste verwaist stehenbleibt. Beim Karbonator lag der Alias unter
814
1066
  alias.0.Strom, vorgeschlagen wurde alias.0.SmartHome. */
815
- export function aliasFuer(quelle) {
816
- var treffer = null;
817
- S.keysSorted.forEach(function (id) {
818
- if (treffer || id.indexOf('alias.') !== 0) { return; }
1067
+ /* Rueckwaertskarte Quelle -> Alias, einmal je Indexstand.
1068
+
1069
+ `aliasFuer` lief zweimal ueber alle Kennungen — und wird je
1070
+ Neuzeichnen mehrfach gerufen. Die Karte entsteht bei der ersten Frage
1071
+ nach einem Indexwechsel und beantwortet alle weiteren aus dem
1072
+ Gedaechtnis. Erkannt wird der Wechsel an Laenge und erster Kennung;
1073
+ `indexNeu()` sortiert ohnehin bei jeder Aenderung neu. */
1074
+ var alsQuelleKarte = null, alsQuelleStand = '';
1075
+ function alsQuelleKarten() {
1076
+ var stand = S.keysSorted.length + '|' + (S.keysSorted[0] || '');
1077
+ if (alsQuelleKarte && alsQuelleStand === stand) { return alsQuelleKarte; }
1078
+ var fest = {}, zweig = {};
1079
+ mitVorspann('alias.').forEach(function (id) {
819
1080
  var o = S.objects[id];
820
- if (o && o.native && o.native.quelle === quelle) { treffer = id; }
1081
+ if (!o) { return; }
1082
+ if (o.native && o.native.quelle && fest[o.native.quelle] === undefined) {
1083
+ fest[o.native.quelle] = id;
1084
+ }
1085
+ if (o.type !== 'state') { return; }
1086
+ var q = aliasQuellen(o);
1087
+ [q.read, q.write].forEach(function (x) {
1088
+ if (!x) { return; }
1089
+ var kanal = id.split('.').slice(0, -1).join('.');
1090
+ (zweig[x] = zweig[x] || []).push(kanal);
1091
+ });
821
1092
  });
1093
+ alsQuelleKarte = { fest: fest, zweig: zweig };
1094
+ alsQuelleStand = stand;
1095
+ return alsQuelleKarte;
1096
+ }
1097
+
1098
+ export function aliasFuer(quelle) {
1099
+ var karte = alsQuelleKarten();
1100
+ var treffer = karte.fest[quelle] || null;
822
1101
  if (treffer) { return treffer; }
823
1102
 
824
1103
  /* Welcher Aliaskanal liest oder schreibt am haeufigsten in diesen
@@ -827,17 +1106,19 @@ export function aliasFuer(quelle) {
827
1106
  erste Treffer. */
828
1107
  var zaehler = {}, bester = null, wieviel = 0;
829
1108
  var pre = quelle + '.';
830
- S.keysSorted.forEach(function (id) {
831
- if (id.indexOf('alias.') !== 0) { return; }
832
- var o = S.objects[id];
833
- if (!o || o.type !== 'state') { return; }
834
- var q = aliasQuellen(o);
835
- var passt = (q.read && q.read.indexOf(pre) === 0) ||
836
- (q.write && q.write.indexOf(pre) === 0);
837
- if (!passt) { return; }
838
- var eltern = id.slice(0, id.lastIndexOf('.'));
839
- zaehler[eltern] = (zaehler[eltern] || 0) + 1;
840
- if (zaehler[eltern] > wieviel) { wieviel = zaehler[eltern]; bester = eltern; }
1109
+ /* Aus der Karte: welche Aliaskanaele lesen oder schreiben in diesen
1110
+ Zweig? Die Quellen stehen dort als volle Kennung, gesucht ist alles
1111
+ darunter — also die Kennungen des Zweigs abklappern statt aller
1112
+ Aliase. */
1113
+ var karte2 = alsQuelleKarten();
1114
+ var kanaele = [];
1115
+ Object.keys(karte2.zweig).forEach(function (q0) {
1116
+ if (q0.indexOf(pre) !== 0) { return; }
1117
+ karte2.zweig[q0].forEach(function (kanal) { kanaele.push(kanal); });
1118
+ });
1119
+ kanaele.forEach(function (kanal) {
1120
+ zaehler[kanal] = (zaehler[kanal] || 0) + 1;
1121
+ if (zaehler[kanal] > wieviel) { wieviel = zaehler[kanal]; bester = kanal; }
841
1122
  });
842
1123
  return bester;
843
1124
  }
@@ -897,9 +1178,7 @@ export function bestandVorrang(e, aliasId) {
897
1178
  s.f = (typeof a.read === 'string') ? a.read : '';
898
1179
  s.fw = (typeof a.write === 'string') ? a.write : '';
899
1180
  if (q.read) { s.srcR = q.read; }
900
- s.srcW = q.einfach
901
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
902
- : (q.write || '');
1181
+ s.srcW = schreibQuelle(o);
903
1182
  s.on = true;
904
1183
  s.ausBestand = true;
905
1184
  });
@@ -936,9 +1215,7 @@ export function bestandsAbweichung(s, aliasId) {
936
1215
  f: (typeof a.read === 'string') ? a.read : '',
937
1216
  fw: (typeof a.write === 'string') ? a.write : '',
938
1217
  srcR: q.read || '',
939
- srcW: q.einfach
940
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
941
- : (q.write || '')
1218
+ srcW: schreibQuelle(o)
942
1219
  };
943
1220
  var raus = [];
944
1221
  ['role', 'typ', 'unit', 'f', 'fw', 'srcR', 'srcW'].forEach(function (k) {
@@ -948,13 +1225,6 @@ export function bestandsAbweichung(s, aliasId) {
948
1225
  return raus;
949
1226
  }
950
1227
 
951
- /* Einen einzelnen Wert aus dem gespeicherten Alias zurueckholen. */
952
- export function bestandsWert(s, aliasId, feld) {
953
- var abw = bestandsAbweichung(s, aliasId);
954
- var tr0 = null;
955
- abw.forEach(function (x) { if (x.feld === feld) { tr0 = x; } });
956
- return tr0 ? tr0.bestand : null;
957
- }
958
1228
 
959
1229
  /* Wo weicht die Vorlage vom Bestand ab? Liefert je Zeile die Felder, die
960
1230
  „Vorlage anwenden" aendern wuerde - die Beschriftung ausgenommen. */
package/admin/js/enums.js CHANGED
@@ -9,15 +9,58 @@ import { socket } from './verbindung.js';
9
9
  suchen haben. */
10
10
  export var enums = {};
11
11
 
12
+ /* Wissen wir gerade, was in den Aufzaehlungen steht?
13
+
14
+ Das ist keine Feinheit, sondern die Bedingung dafuer, ueberhaupt eine
15
+ schreiben zu duerfen. Frueher setzte ein Ladefehler `enums` auf `{}`
16
+ und verschwieg es. Danach hielt die Werkbank jede Aufzaehlung fuer
17
+ nicht vorhanden - der Katalog bot `garden` weiter an, weil der
18
+ Dublettenfilter gegen nichts pruefte, und `enumAenderungen` baute sie
19
+ als NEU aus der Vorlage. `setObject` ersetzt ein Objekt vollstaendig:
20
+ Gemessen 09.09.2026 am Testsystem verlor `enum.rooms.garden` dabei
21
+ drei Mitglieder und behielt eines, dazu die Herkunftsmarke, die ihn
22
+ spaeter auch noch loeschbar macht. Bei Ricardo waeren das die aus der
23
+ CCU gespiegelten Raeume.
24
+
25
+ Solange diese Marke steht, wird keine Aufzaehlung angefasst. */
26
+ var unbekannt = false;
27
+ export function enumsUnbekannt() { return unbekannt; }
28
+
29
+ /* Ein Aussetzer soll die Werkbank nicht fuer den Rest der Sitzung
30
+ lahmlegen. Dreimal mit wachsendem Abstand, dann ist Schluss - eine
31
+ Schleife ohne Ende waere schlimmer als der Fehler. */
32
+ var versuch = 0;
33
+ var wiederTimer = null;
34
+
12
35
  export function ladeEnums(danach) {
36
+ if (wiederTimer) { clearTimeout(wiederTimer); wiederTimer = null; }
13
37
  socket.emit('getObjectView', 'system', 'enum',
14
- { startkey: 'enum.', endkey: 'enum.\u9999' }, function (err, doc) {
15
- enums = {};
16
- if (!err && doc && doc.rows) {
17
- doc.rows.forEach(function (r) {
18
- if (r.value && r.id) { enums[r.id] = r.value; }
19
- });
38
+ { startkey: 'enum.', endkey: 'enum.香' }, function (err, doc) {
39
+ if (err || !doc || !doc.rows) {
40
+ /* Den alten Stand behalten. Er ist vielleicht veraltet, aber
41
+ veraltet ist naeher an der Wahrheit als leer. */
42
+ unbekannt = true;
43
+ console.warn('[alias-workbench] Aufzaehlungen nicht geladen: ' +
44
+ (err || 'keine Antwort'));
45
+ if (versuch < 3) {
46
+ versuch++;
47
+ wiederTimer = setTimeout(function () {
48
+ wiederTimer = null;
49
+ ladeEnums();
50
+ }, versuch * 4000);
51
+ }
52
+ if (typeof danach === 'function') { danach(err || 'leer'); }
53
+ return;
20
54
  }
21
- if (typeof danach === 'function') { danach(); }
55
+ /* Erst sammeln, dann tauschen: ein Fehler mittendrin liesse sonst
56
+ einen halben Vorrat stehen. */
57
+ var frisch = {};
58
+ doc.rows.forEach(function (r) {
59
+ if (r.value && r.id) { frisch[r.id] = r.value; }
60
+ });
61
+ enums = frisch;
62
+ unbekannt = false;
63
+ versuch = 0;
64
+ if (typeof danach === 'function') { danach(null); }
22
65
  });
23
66
  }