@kwirthmagnify/kwirth-plugin-status 0.2.5 → 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 +80 -39
  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,22 +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
- children: nodos.map((n) => ({ id: n.id, width: 230, height: 56 })),
166
- edges: aristas.map((e) => ({ id: e.id, sources: [e.source], targets: [e.target] }))
167
- });
213
+ const g = await elk.layout(elkGraphOf(nodos.map((n) => n.id), aristas));
168
214
  const pos = {};
169
215
  for (const c of g.children ?? []) pos[c.id] = { x: c.x, y: c.y };
170
216
  return Object.keys(pos).length === nodos.length ? pos : filas();
@@ -190,13 +236,13 @@
190
236
  const { nodos, aristas, canalesSueltos } = import_react2.default.useMemo(() => {
191
237
  const productores = inventory.components.filter((c) => c.kind === "provider" /* PROVIDER */ || c.kind === "pluvider" /* PLUVIDER */);
192
238
  const idsProductores = new Set(productores.map((p) => p.id));
193
- const canales = [...new Set(inventory.edges.map((e) => e.channelId))];
239
+ const canales = channelsOf(inventory.edges);
194
240
  const vecinos = /* @__PURE__ */ new Set();
195
241
  if (seleccionado) {
196
242
  vecinos.add(seleccionado);
197
243
  for (const e of inventory.edges) {
198
244
  const origen = e.providerId;
199
- const destino = `channel:${e.channelId}`;
245
+ const destino = consumerNodeId(e.consumerId);
200
246
  if (origen === seleccionado) vecinos.add(destino);
201
247
  if (destino === seleccionado) vecinos.add(origen);
202
248
  }
@@ -248,21 +294,16 @@
248
294
  }))
249
295
  ];
250
296
  const aristas2 = inventory.edges.map((e) => ({
251
- id: `${e.providerId}->${e.channelId}`,
297
+ id: `${e.providerId}->${e.consumerId}`,
252
298
  source: e.providerId,
253
- target: `channel:${e.channelId}`,
299
+ target: consumerNodeId(e.consumerId),
254
300
  /*
255
- QUIETA a propósito, aunque React Flow sepa animarlas.
256
-
257
- Una línea en movimiento se lee como "por aquí está pasando algo ahora mismo", y eso no
258
- se sabe: lo único que dice esta arista es que la suscripción existe. Animarla sería el
259
- mismo error que poner un 0 donde no hay dato — parecería información y sería decoración.
260
-
261
- Cuando los contadores de S4 midan caudal de verdad, el movimiento (o el grosor) podrá
262
- significar algo, y entonces se pone.
263
- */
301
+ Una línea en movimiento se lee como "por aquí está pasando algo ahora mismo", así que
302
+ solo se mueve cuando eso se ha MEDIDO (ver 'viva'). Quieta, lo único que dice es que la
303
+ suscripción existe. Cómo frena con auto-refresco está en el contenedor del ReactFlow.
304
+ */
264
305
  ...(() => {
265
- const tocaAlSeleccionado = Boolean(seleccionado) && (e.providerId === seleccionado || `channel:${e.channelId}` === seleccionado);
306
+ const tocaAlSeleccionado = Boolean(seleccionado) && (e.providerId === seleccionado || consumerNodeId(e.consumerId) === seleccionado);
266
307
  const viva = active.has(e.providerId);
267
308
  const color = tocaAlSeleccionado ? "#7fd8b0" : viva ? "#5fc79a" : "#4a8";
268
309
  const ancho = tocaAlSeleccionado ? 3 : viva ? 2 : 1;
@@ -275,7 +316,7 @@
275
316
  markerEnd: { type: import_react3.MarkerType.ArrowClosed, color, width: punta, height: punta }
276
317
  };
277
318
  })()
278
- })).filter((e) => idsProductores.has(e.source));
319
+ })).filter((e) => idsProductores.has(e.source) && (e.target.startsWith(CHANNEL_NODE_PREFIX) || idsProductores.has(e.target)));
279
320
  const canalesSueltos2 = inventory.edges.length - aristas2.length;
280
321
  return { nodos: nodos2, aristas: aristas2, canalesSueltos: canalesSueltos2 };
281
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.5",
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,