@kwirthmagnify/kwirth-plugin-status 0.2.6 → 0.2.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.
Files changed (3) hide show
  1. package/back.js +13 -3
  2. package/front.js +76 -46
  3. package/package.json +1 -1
package/back.js CHANGED
@@ -41,10 +41,19 @@ var require_kwirth_common_back = __commonJS({
41
41
  var index_exports = {};
42
42
  __export(index_exports, {
43
43
  StatusChannel: () => StatusChannel,
44
- default: () => index_default
44
+ default: () => index_default,
45
+ toStatusEdges: () => toStatusEdges
45
46
  });
46
47
  module.exports = __toCommonJS(index_exports);
47
48
  var import_kwirth_common_back = __toESM(require_kwirth_common_back(), 1);
49
+ var toStatusEdges = (subscriptions) => {
50
+ const edges = [];
51
+ for (const s of subscriptions) {
52
+ const consumerId = s.consumerId ?? s.channelId;
53
+ if (consumerId) edges.push({ providerId: s.providerId, consumerId, since: s.since });
54
+ }
55
+ return edges;
56
+ };
48
57
  var statsOf = (p) => {
49
58
  if (!p.getStats) return void 0;
50
59
  try {
@@ -257,7 +266,7 @@ var StatusChannel = class {
257
266
  this.subscriptionsOf = () => {
258
267
  if (!this.clusterInfo.getSubscriptions) return [];
259
268
  try {
260
- return this.clusterInfo.getSubscriptions() ?? [];
269
+ return toStatusEdges(this.clusterInfo.getSubscriptions() ?? []);
261
270
  } catch {
262
271
  return [];
263
272
  }
@@ -300,5 +309,6 @@ var StatusChannel = class {
300
309
  var index_default = StatusChannel;
301
310
  // Annotate the CommonJS export names for ESM import in node:
302
311
  0 && (module.exports = {
303
- StatusChannel
312
+ StatusChannel,
313
+ toStatusEdges
304
314
  });
package/front.js CHANGED
@@ -96,6 +96,9 @@
96
96
  var import_material = __toESM(require_material(), 1);
97
97
  var StatusIcon = (props) => /* @__PURE__ */ import_react.default.createElement(import_material.SvgIcon, { ...props, viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react.default.createElement("g", { fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("rect", { x: "2.6", y: "4.6", width: "18.8", height: "14.8", rx: "2.2" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M6 12h2.6l1.5-3.4 2.6 6.8 1.5-3.4H18" })));
98
98
 
99
+ // src/common/StatusTypes.ts
100
+ var PROVIDER_CONSUMER_PREFIX = "provider:";
101
+
99
102
  // src/front/StatusData.ts
100
103
  var countUnbrokeredConsumers = (components) => components.reduce((n, c) => {
101
104
  if (c.subscribers === void 0 || c.knownConsumers === void 0) return n;
@@ -122,6 +125,56 @@
122
125
  var import_react2 = __toESM(require_react(), 1);
123
126
  var import_material2 = __toESM(require_material(), 1);
124
127
  var import_react3 = __toESM(require_react2(), 1);
128
+
129
+ // src/front/StatusGraph.ts
130
+ var CHANNEL_NODE_PREFIX = "channel:";
131
+ var isProviderConsumer = (consumerId) => consumerId.startsWith(PROVIDER_CONSUMER_PREFIX);
132
+ var consumerNodeId = (consumerId) => isProviderConsumer(consumerId) ? consumerId.slice(PROVIDER_CONSUMER_PREFIX.length) : `${CHANNEL_NODE_PREFIX}${consumerId}`;
133
+ var channelsOf = (edges) => [...new Set(edges.filter((e) => !isProviderConsumer(e.consumerId)).map((e) => e.consumerId))];
134
+ var layerOf = (nodeId, targets) => {
135
+ if (nodeId.startsWith(CHANNEL_NODE_PREFIX)) return "LAST" /* LAST */;
136
+ return targets.has(nodeId) ? void 0 : "FIRST" /* FIRST */;
137
+ };
138
+ var elkGraphOf = (nodeIds, lines) => {
139
+ const targets = new Set(lines.map((l) => l.target));
140
+ return {
141
+ id: "root",
142
+ layoutOptions: {
143
+ "elk.algorithm": "layered",
144
+ /*
145
+ De arriba abajo: los productores en la capa de arriba y los consumidores debajo.
146
+ Se lee como un diagrama de flujo —el dato cae— y aprovecha el ancho de la pantalla,
147
+ que es donde sobra sitio cuando hay muchos nodos.
148
+ */
149
+ "elk.direction": "DOWN",
150
+ "elk.spacing.nodeNode": "40",
151
+ "elk.layered.spacing.nodeNodeBetweenLayers": "110",
152
+ /*
153
+ UN solo grafo, no uno por componente. De serie elk coloca cada componente conexo por
154
+ su cuenta y luego los apila: un par suelto como sugarless -> sugarless salia en su
155
+ propio bloque de dos filas, con su canal por ENCIMA de productores del bloque grande.
156
+ */
157
+ "elk.separateConnectedComponents": "false"
158
+ },
159
+ /*
160
+ The layer of each node (see layerOf): channels at the bottom, producers that consume nothing
161
+ at the top, and a provider that consumes another provider left free, so elk puts it below
162
+ what it reads and every line goes down.
163
+ */
164
+ children: nodeIds.map((id) => {
165
+ const layer = layerOf(id, targets);
166
+ return {
167
+ id,
168
+ width: 230,
169
+ height: 56,
170
+ ...layer ? { layoutOptions: { "elk.layered.layering.layerConstraint": layer } } : {}
171
+ };
172
+ }),
173
+ edges: lines.map((l) => ({ id: l.id, sources: [l.source], targets: [l.target] }))
174
+ };
175
+ };
176
+
177
+ // src/front/StatusDiagram.tsx
125
178
  var COLOR = {
126
179
  ["active" /* ACTIVE */]: "#2e7d32",
127
180
  ["idle" /* IDLE */]: "#616161",
@@ -132,16 +185,24 @@
132
185
  ["unknown" /* UNKNOWN */]: "#616161"
133
186
  };
134
187
  var colocar = async (nodos, aristas) => {
188
+ const destinos = new Set(aristas.map((a) => a.target));
135
189
  const filas = () => {
136
190
  const pos = {};
137
- const productores = nodos.filter((n) => n.data.esProductor);
138
- const consumidores = nodos.filter((n) => !n.data.esProductor);
139
- productores.forEach((n, i) => {
140
- pos[n.id] = { x: i * 260, y: 0 };
141
- });
142
- consumidores.forEach((n, i) => {
143
- pos[n.id] = { x: i * 260, y: 220 };
144
- });
191
+ const fila = (n) => {
192
+ switch (layerOf(n.id, destinos)) {
193
+ case "FIRST" /* FIRST */:
194
+ return 0;
195
+ case "LAST" /* LAST */:
196
+ return 2;
197
+ default:
198
+ return 1;
199
+ }
200
+ };
201
+ for (const f of [0, 1, 2]) {
202
+ nodos.filter((n) => fila(n) === f).forEach((n, i) => {
203
+ pos[n.id] = { x: i * 260, y: f * 220 };
204
+ });
205
+ }
145
206
  return pos;
146
207
  };
147
208
  const loadElk = window.__kwirth__?.loadElk;
@@ -149,38 +210,7 @@
149
210
  try {
150
211
  const ELK = await loadElk();
151
212
  const elk = new ELK();
152
- const g = await elk.layout({
153
- id: "root",
154
- layoutOptions: {
155
- "elk.algorithm": "layered",
156
- /*
157
- De arriba abajo: los productores en la capa de arriba y los consumidores debajo.
158
- Se lee como un diagrama de flujo —el dato cae— y aprovecha el ancho de la pantalla,
159
- que es donde sobra sitio cuando hay muchos nodos.
160
- */
161
- "elk.direction": "DOWN",
162
- "elk.spacing.nodeNode": "40",
163
- "elk.layered.spacing.nodeNodeBetweenLayers": "110",
164
- /*
165
- UN solo grafo, no uno por componente. De serie elk coloca cada componente conexo por
166
- su cuenta y luego los apila: un par suelto como sugarless -> sugarless salia en su
167
- propio bloque de dos filas, con su canal por ENCIMA de productores del bloque grande.
168
- */
169
- "elk.separateConnectedComponents": "false"
170
- },
171
- /*
172
- Y la fila de cada nodo, fijada: productores en la primera capa, consumidores en la ultima.
173
- Sin esto, un productor sin aristas (plugin:montag) es un nodo sin salidas y el layering
174
- lo puede bajar con los consumidores.
175
- */
176
- children: nodos.map((n) => ({
177
- id: n.id,
178
- width: 230,
179
- height: 56,
180
- layoutOptions: { "elk.layered.layering.layerConstraint": n.data.esProductor ? "FIRST" : "LAST" }
181
- })),
182
- edges: aristas.map((e) => ({ id: e.id, sources: [e.source], targets: [e.target] }))
183
- });
213
+ const g = await elk.layout(elkGraphOf(nodos.map((n) => n.id), aristas));
184
214
  const pos = {};
185
215
  for (const c of g.children ?? []) pos[c.id] = { x: c.x, y: c.y };
186
216
  return Object.keys(pos).length === nodos.length ? pos : filas();
@@ -206,13 +236,13 @@
206
236
  const { nodos, aristas, canalesSueltos } = import_react2.default.useMemo(() => {
207
237
  const productores = inventory.components.filter((c) => c.kind === "provider" /* PROVIDER */ || c.kind === "pluvider" /* PLUVIDER */);
208
238
  const idsProductores = new Set(productores.map((p) => p.id));
209
- const canales = [...new Set(inventory.edges.map((e) => e.channelId))];
239
+ const canales = channelsOf(inventory.edges);
210
240
  const vecinos = /* @__PURE__ */ new Set();
211
241
  if (seleccionado) {
212
242
  vecinos.add(seleccionado);
213
243
  for (const e of inventory.edges) {
214
244
  const origen = e.providerId;
215
- const destino = `channel:${e.channelId}`;
245
+ const destino = consumerNodeId(e.consumerId);
216
246
  if (origen === seleccionado) vecinos.add(destino);
217
247
  if (destino === seleccionado) vecinos.add(origen);
218
248
  }
@@ -264,16 +294,16 @@
264
294
  }))
265
295
  ];
266
296
  const aristas2 = inventory.edges.map((e) => ({
267
- id: `${e.providerId}->${e.channelId}`,
297
+ id: `${e.providerId}->${e.consumerId}`,
268
298
  source: e.providerId,
269
- target: `channel:${e.channelId}`,
299
+ target: consumerNodeId(e.consumerId),
270
300
  /*
271
301
  Una línea en movimiento se lee como "por aquí está pasando algo ahora mismo", así que
272
302
  solo se mueve cuando eso se ha MEDIDO (ver 'viva'). Quieta, lo único que dice es que la
273
303
  suscripción existe. Cómo frena con auto-refresco está en el contenedor del ReactFlow.
274
304
  */
275
305
  ...(() => {
276
- const tocaAlSeleccionado = Boolean(seleccionado) && (e.providerId === seleccionado || `channel:${e.channelId}` === seleccionado);
306
+ const tocaAlSeleccionado = Boolean(seleccionado) && (e.providerId === seleccionado || consumerNodeId(e.consumerId) === seleccionado);
277
307
  const viva = active.has(e.providerId);
278
308
  const color = tocaAlSeleccionado ? "#7fd8b0" : viva ? "#5fc79a" : "#4a8";
279
309
  const ancho = tocaAlSeleccionado ? 3 : viva ? 2 : 1;
@@ -286,7 +316,7 @@
286
316
  markerEnd: { type: import_react3.MarkerType.ArrowClosed, color, width: punta, height: punta }
287
317
  };
288
318
  })()
289
- })).filter((e) => idsProductores.has(e.source));
319
+ })).filter((e) => idsProductores.has(e.source) && (e.target.startsWith(CHANNEL_NODE_PREFIX) || idsProductores.has(e.target)));
290
320
  const canalesSueltos2 = inventory.edges.length - aristas2.length;
291
321
  return { nodos: nodos2, aristas: aristas2, canalesSueltos: canalesSueltos2 };
292
322
  }, [inventory, active, seleccionado, colores.fondoNodo, colores.fondoCanal, colores.texto, colores.bordeCanal]);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "id": "status",
5
5
  "name": "@kwirthmagnify/kwirth-plugin-status",
6
6
  "displayName": "Kwirth Status",
7
- "version": "0.2.6",
7
+ "version": "0.2.7",
8
8
  "description": "A look inside Kwirth: what is installed, how it is doing and who consumes what",
9
9
  "icon": "<svg viewBox='0 0 24 24'><g fill='none' stroke='currentColor' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><rect x='2.6' y='4.6' width='18.8' height='14.8' rx='2.2'/><path d='M6 12h2.6l1.5-3.4 2.6 6.8 1.5-3.4H18'/></g></svg>",
10
10
  "requiresRestart": false,