@kwirthmagnify/kwirth-plugin-provider-debug 0.1.0

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 +272 -0
  2. package/front.js +428 -0
  3. package/package.json +12 -0
package/back.js ADDED
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // kwirth-back-globals:@kwirthmagnify/kwirth-common-back
34
+ var require_kwirth_common_back = __commonJS({
35
+ "kwirth-back-globals:@kwirthmagnify/kwirth-common-back"(exports2, module2) {
36
+ module2.exports = global.__kwirth_back__.kwirthCommonBack;
37
+ }
38
+ });
39
+
40
+ // src/back/index.ts
41
+ var index_exports = {};
42
+ __export(index_exports, {
43
+ default: () => index_default
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+ var import_kwirth_common_back = __toESM(require_kwirth_common_back(), 1);
47
+ var ProviderDebugChannel = class {
48
+ constructor(clusterInfo, backChannelObject) {
49
+ this.channelId = "provider-debug";
50
+ /**
51
+ * Deliberadamente vacío: el core solo instancia y arranca los providers que algún canal
52
+ * declara aquí, así que este canal se limita a depurar los que ya están en marcha por
53
+ * cuenta de otros plugins. Declarar providers concretos los arrancaría como efecto
54
+ * colateral de tener instalado un depurador, que es justo lo que no queremos.
55
+ */
56
+ this.requirements = { storage: false, providers: [] };
57
+ this.webSockets = [];
58
+ this.getChannelData = () => ({
59
+ id: "provider-debug",
60
+ routable: false,
61
+ pauseable: true,
62
+ modifiable: false,
63
+ // F1: para cambiar de provider se para y se rearranca la instancia
64
+ reconnectable: true,
65
+ metrics: false,
66
+ sources: [import_kwirth_common_back.EClusterType.KUBERNETES, import_kwirth_common_back.EClusterType.DOCKER],
67
+ endpoints: [],
68
+ websocket: false,
69
+ cluster: true,
70
+ // los providers son cluster-wide, no cuelgan de un pod
71
+ resourced: false
72
+ });
73
+ this.getChannelScopeLevel = (scope) => ["", "none", "cluster"].indexOf(scope);
74
+ this.startChannel = async () => {
75
+ };
76
+ // ---- registro: los canales cluster llegan aquí vía addObject('*all') ----
77
+ this.addObject = async (webSocket, instanceConfig, _ns, _pod, _container) => {
78
+ let socket = this.webSockets.find((s) => s.ws === webSocket);
79
+ if (!socket) {
80
+ const len = this.webSockets.push({ ws: webSocket, lastRefresh: Date.now(), instances: [] });
81
+ socket = this.webSockets[len - 1];
82
+ }
83
+ if (socket.instances.find((i) => i.instanceId === instanceConfig.instance)) return true;
84
+ const configData = instanceConfig.data ?? { providerId: "", subscriptionData: "" };
85
+ const instance = {
86
+ instanceId: instanceConfig.instance,
87
+ accessKey: (0, import_kwirth_common_back.accessKeyDeserialize)(instanceConfig.accessKey),
88
+ providerId: configData.providerId ?? "",
89
+ paused: false
90
+ };
91
+ socket.instances.push(instance);
92
+ this.sendProviders(socket, instance);
93
+ if (instance.providerId) this.subscribe(socket, instance, configData.subscriptionData);
94
+ return true;
95
+ };
96
+ this.deleteObject = async (webSocket, instanceConfig, _ns, _pod, _container) => {
97
+ this.removeInstance(webSocket, instanceConfig.instance);
98
+ return true;
99
+ };
100
+ this.pauseContinueInstance = (webSocket, instanceConfig, action) => {
101
+ const instance = this.getInstance(webSocket, instanceConfig.instance);
102
+ if (!instance) {
103
+ this.sendSignalMessage(webSocket, action, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.ERROR, instanceConfig.instance, "Provider debug instance not found");
104
+ return;
105
+ }
106
+ if (action === import_kwirth_common_back.EInstanceMessageAction.PAUSE) instance.paused = true;
107
+ if (action === import_kwirth_common_back.EInstanceMessageAction.CONTINUE) instance.paused = false;
108
+ };
109
+ this.modifyInstance = (_webSocket, _instanceConfig) => {
110
+ };
111
+ this.containsInstance = (instanceId) => this.webSockets.some((socket) => socket.instances.some((i) => i.instanceId === instanceId));
112
+ this.containsAsset = (_webSocket, _podNamespace, _podName, _containerName) => false;
113
+ this.stopInstance = (webSocket, instanceConfig) => {
114
+ const instance = this.getInstance(webSocket, instanceConfig.instance);
115
+ if (instance) {
116
+ this.removeInstance(webSocket, instanceConfig.instance);
117
+ this.sendSignalMessage(webSocket, import_kwirth_common_back.EInstanceMessageAction.STOP, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.INFO, instanceConfig.instance, "Provider debug instance stopped");
118
+ } else {
119
+ this.sendSignalMessage(webSocket, import_kwirth_common_back.EInstanceMessageAction.STOP, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.ERROR, instanceConfig.instance, "Provider debug instance not found");
120
+ }
121
+ };
122
+ this.removeInstance = (webSocket, instanceId) => {
123
+ const socket = this.webSockets.find((s) => s.ws === webSocket);
124
+ if (!socket) return;
125
+ const pos = socket.instances.findIndex((i) => i.instanceId === instanceId);
126
+ if (pos < 0) return;
127
+ this.unsubscribe(socket.instances[pos]);
128
+ socket.instances.splice(pos, 1);
129
+ };
130
+ this.processCommand = async (webSocket, instanceMessage) => {
131
+ if (instanceMessage.flow === import_kwirth_common_back.EInstanceMessageFlow.IMMEDIATE) return false;
132
+ const instance = this.getInstance(webSocket, instanceMessage.instance);
133
+ if (!instance) {
134
+ this.sendSignalMessage(webSocket, instanceMessage.action, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.ERROR, instanceMessage.instance, "Provider debug instance not found");
135
+ return false;
136
+ }
137
+ return true;
138
+ };
139
+ this.containsConnection = (webSocket) => Boolean(this.webSockets.find((s) => s.ws === webSocket));
140
+ this.removeConnection = (webSocket) => {
141
+ const pos = this.webSockets.findIndex((s) => s.ws === webSocket);
142
+ if (pos < 0) return;
143
+ for (const instance of this.webSockets[pos].instances) this.unsubscribe(instance);
144
+ this.webSockets.splice(pos, 1);
145
+ };
146
+ this.refreshConnection = (webSocket) => {
147
+ const socket = this.webSockets.find((s) => s.ws === webSocket);
148
+ if (socket) {
149
+ socket.lastRefresh = Date.now();
150
+ return true;
151
+ }
152
+ return false;
153
+ };
154
+ this.updateConnection = (newWebSocket, instanceId) => {
155
+ for (const entry of this.webSockets) {
156
+ if (entry.instances.find((i) => i.instanceId === instanceId)) {
157
+ entry.ws = newWebSocket;
158
+ return true;
159
+ }
160
+ }
161
+ return false;
162
+ };
163
+ // ---- suscripción a un provider en marcha ---------------------------------
164
+ this.subscribe = (socket, instance, rawSubscriptionData) => {
165
+ const provider = this.clusterInfo.providers?.find((p) => p.id === instance.providerId);
166
+ if (!provider) {
167
+ this.sendSignalMessage(socket.ws, import_kwirth_common_back.EInstanceMessageAction.START, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.ERROR, instance.instanceId, `Provider '${instance.providerId}' is not running`);
168
+ return;
169
+ }
170
+ let subscriptionData = {};
171
+ if (rawSubscriptionData && rawSubscriptionData.trim() !== "") {
172
+ try {
173
+ subscriptionData = JSON.parse(rawSubscriptionData);
174
+ } catch (err) {
175
+ this.sendSignalMessage(socket.ws, import_kwirth_common_back.EInstanceMessageAction.START, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.ERROR, instance.instanceId, `Subscription payload is not valid JSON: ${String(err)}`);
176
+ return;
177
+ }
178
+ }
179
+ const subscriber = {
180
+ processProviderEvent: (providerId, obj) => this.deliver(socket, instance, providerId, obj)
181
+ };
182
+ instance.subscriber = subscriber;
183
+ instance.provider = provider;
184
+ provider.addSubscriber(subscriber, subscriptionData);
185
+ this.backChannelObject.logInfo?.(`Provider debug instance ${instance.instanceId} subscribed to provider '${instance.providerId}'`);
186
+ this.sendSignalMessage(socket.ws, import_kwirth_common_back.EInstanceMessageAction.START, import_kwirth_common_back.EInstanceMessageFlow.RESPONSE, import_kwirth_common_back.ESignalMessageLevel.INFO, instance.instanceId, `Subscribed to provider '${instance.providerId}'`);
187
+ };
188
+ this.unsubscribe = (instance) => {
189
+ if (instance.provider && instance.subscriber) {
190
+ instance.provider.removeSubscriber(instance.subscriber);
191
+ this.backChannelObject.logInfo?.(`Provider debug instance ${instance.instanceId} unsubscribed from provider '${instance.providerId}'`);
192
+ }
193
+ instance.provider = void 0;
194
+ instance.subscriber = void 0;
195
+ };
196
+ this.deliver = (socket, instance, providerId, obj) => {
197
+ if (instance.paused) return;
198
+ const msg = {
199
+ msgtype: "providerdebugmessageresponse",
200
+ channel: this.channelId,
201
+ action: import_kwirth_common_back.EInstanceMessageAction.NONE,
202
+ flow: import_kwirth_common_back.EInstanceMessageFlow.UNSOLICITED,
203
+ type: import_kwirth_common_back.EInstanceMessageType.DATA,
204
+ instance: instance.instanceId,
205
+ payloadType: "event" /* EVENT */,
206
+ event: { ts: Date.now(), providerId, event: obj }
207
+ };
208
+ socket.ws.send(JSON.stringify(msg));
209
+ };
210
+ /**
211
+ * getSubscriptionHelp() es opcional en IProvider y lo implementa quien quiere, así que se llama
212
+ * a la defensiva: ni existir es un error, ni lo es que reviente. Un provider mal escrito no
213
+ * puede tumbar el catálogo del resto.
214
+ */
215
+ this.helpOf = (provider) => {
216
+ if (typeof provider.getSubscriptionHelp !== "function") return void 0;
217
+ try {
218
+ const help = provider.getSubscriptionHelp();
219
+ if (!help || typeof help.usage !== "string" || typeof help.example !== "object") return void 0;
220
+ return help;
221
+ } catch (err) {
222
+ this.backChannelObject.logWarning?.(`Provider '${provider.id}' failed to report its subscription help: ${String(err)}`);
223
+ return void 0;
224
+ }
225
+ };
226
+ this.sendProviders = (socket, instance) => {
227
+ const running = this.clusterInfo.providers ?? [];
228
+ const providers = running.map((p) => {
229
+ const help = this.helpOf(p);
230
+ return {
231
+ id: p.id,
232
+ providesRouter: p.providesRouter,
233
+ ...p.routerAlias ? { routerAlias: p.routerAlias } : {},
234
+ ...help ? { help } : {}
235
+ };
236
+ });
237
+ const msg = {
238
+ msgtype: "providerdebugmessageresponse",
239
+ channel: this.channelId,
240
+ action: import_kwirth_common_back.EInstanceMessageAction.NONE,
241
+ flow: import_kwirth_common_back.EInstanceMessageFlow.UNSOLICITED,
242
+ type: import_kwirth_common_back.EInstanceMessageType.DATA,
243
+ instance: instance.instanceId,
244
+ payloadType: "providers" /* PROVIDERS */,
245
+ providers
246
+ };
247
+ socket.ws.send(JSON.stringify(msg));
248
+ };
249
+ this.sendSignalMessage = (ws, action, flow, level, instanceId, text) => {
250
+ const resp = { action, flow, channel: this.channelId, instance: instanceId, type: import_kwirth_common_back.EInstanceMessageType.SIGNAL, text, level };
251
+ ws.send(JSON.stringify(resp));
252
+ };
253
+ this.clusterInfo = clusterInfo;
254
+ this.backChannelObject = backChannelObject;
255
+ }
256
+ /**
257
+ * Nunca se invoca: el canal no se registra a sí mismo como subscriber, cada instancia
258
+ * registra su propio proxy (ver IInstance.subscriber).
259
+ */
260
+ processProviderEvent(_providerId, _obj) {
261
+ }
262
+ endpointRequest(_endpoint, _req, _res, _accessKey) {
263
+ }
264
+ websocketRequest(_newWebSocket, _instanceId, _instanceConfig) {
265
+ }
266
+ getInstance(webSocket, instanceId) {
267
+ const socket = this.webSockets.find((entry) => entry.ws === webSocket);
268
+ if (socket) return socket.instances.find((i) => i.instanceId === instanceId);
269
+ return void 0;
270
+ }
271
+ };
272
+ var index_default = ProviderDebugChannel;
package/front.js ADDED
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+ (() => {
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // kwirth-globals:@kwirthmagnify/kwirth-common
30
+ var require_kwirth_common = __commonJS({
31
+ "kwirth-globals:@kwirthmagnify/kwirth-common"(exports, module) {
32
+ var _m = window.__kwirth__.kwirthCommon;
33
+ var _d = _m != null && "default" in Object(_m) ? _m.default : _m;
34
+ if (typeof _d !== "function" && _d != null && typeof _d.default !== "undefined") _d = _d.default;
35
+ module.exports = Object.assign({}, typeof _m === "object" && _m !== null ? _m : {}, { default: _d, __esModule: true });
36
+ }
37
+ });
38
+
39
+ // kwirth-globals:@kwirthmagnify/kwirth-common-front
40
+ var require_kwirth_common_front = __commonJS({
41
+ "kwirth-globals:@kwirthmagnify/kwirth-common-front"(exports, module) {
42
+ var _m = window.__kwirth__.kwirthCommonFront;
43
+ var _d = _m != null && "default" in Object(_m) ? _m.default : _m;
44
+ if (typeof _d !== "function" && _d != null && typeof _d.default !== "undefined") _d = _d.default;
45
+ module.exports = Object.assign({}, typeof _m === "object" && _m !== null ? _m : {}, { default: _d, __esModule: true });
46
+ }
47
+ });
48
+
49
+ // kwirth-globals:react
50
+ var require_react = __commonJS({
51
+ "kwirth-globals:react"(exports, module) {
52
+ var _m = window.__kwirth__.React;
53
+ var _d = _m != null && "default" in Object(_m) ? _m.default : _m;
54
+ if (typeof _d !== "function" && _d != null && typeof _d.default !== "undefined") _d = _d.default;
55
+ module.exports = Object.assign({}, typeof _m === "object" && _m !== null ? _m : {}, { default: _d, __esModule: true });
56
+ }
57
+ });
58
+
59
+ // kwirth-globals:@mui/material
60
+ var require_material = __commonJS({
61
+ "kwirth-globals:@mui/material"(exports, module) {
62
+ var _m = window.__kwirth__.MUI.material;
63
+ var _d = _m != null && "default" in Object(_m) ? _m.default : _m;
64
+ if (typeof _d !== "function" && _d != null && typeof _d.default !== "undefined") _d = _d.default;
65
+ module.exports = Object.assign({}, typeof _m === "object" && _m !== null ? _m : {}, { default: _d, __esModule: true });
66
+ }
67
+ });
68
+
69
+ // kwirth-globals:@mui/icons-material
70
+ var require_icons_material = __commonJS({
71
+ "kwirth-globals:@mui/icons-material"(exports, module) {
72
+ var _m = window.__kwirth__.MUI.icons;
73
+ var _d = _m != null && "default" in Object(_m) ? _m.default : _m;
74
+ if (typeof _d !== "function" && _d != null && typeof _d.default !== "undefined") _d = _d.default;
75
+ module.exports = Object.assign({}, typeof _m === "object" && _m !== null ? _m : {}, { default: _d, __esModule: true });
76
+ }
77
+ });
78
+
79
+ // src/front/ProviderDebugChannel.ts
80
+ var import_kwirth_common = __toESM(require_kwirth_common(), 1);
81
+ var import_kwirth_common_front = __toESM(require_kwirth_common_front(), 1);
82
+
83
+ // src/front/ProviderDebugConfig.ts
84
+ var ProviderDebugConfig = class {
85
+ constructor() {
86
+ this.maxEvents = 200;
87
+ }
88
+ };
89
+ var ProviderDebugInstanceConfig = class {
90
+ constructor() {
91
+ this.providerId = "";
92
+ this.subscriptionData = "";
93
+ }
94
+ };
95
+
96
+ // src/front/ProviderDebugData.ts
97
+ var ProviderDebugData = class {
98
+ constructor() {
99
+ this.events = [];
100
+ this.providers = [];
101
+ this.signals = [];
102
+ this.paused = false;
103
+ this.started = false;
104
+ }
105
+ };
106
+
107
+ // src/front/ProviderDebugSetup.tsx
108
+ var import_react = __toESM(require_react(), 1);
109
+ var import_material = __toESM(require_material(), 1);
110
+ var import_icons_material = __toESM(require_icons_material(), 1);
111
+ var ProviderDebugIcon = /* @__PURE__ */ import_react.default.createElement(import_icons_material.DataObjectOutlined, null);
112
+ var ProviderDebugSetup = (props) => {
113
+ const instanceConfig = props.setupConfig?.channelInstanceConfig || new ProviderDebugInstanceConfig();
114
+ const config = props.setupConfig?.channelConfig || new ProviderDebugConfig();
115
+ const data = props.channelObject.data;
116
+ const [providerId, setProviderId] = (0, import_react.useState)(instanceConfig.providerId);
117
+ const [subscriptionData, setSubscriptionData] = (0, import_react.useState)(instanceConfig.subscriptionData);
118
+ const [maxEvents, setMaxEvents] = (0, import_react.useState)(config.maxEvents);
119
+ const [catalogue, setCatalogue] = (0, import_react.useState)([]);
120
+ const [catalogueLoaded, setCatalogueLoaded] = (0, import_react.useState)(false);
121
+ const [mode, setMode] = (0, import_react.useState)("form" /* FORM */);
122
+ const defaultRef = (0, import_react.useRef)(null);
123
+ (0, import_react.useEffect)(() => {
124
+ const url = props.channelObject.clusterUrl;
125
+ if (!url) return;
126
+ const token = props.channelObject.accessString;
127
+ fetch(`${url}/core/providers`, token ? { headers: { Authorization: `Bearer ${token}` } } : void 0).then((r) => r.json()).then((list) => {
128
+ setCatalogue(Array.isArray(list) ? list.filter((p) => p && p.id) : []);
129
+ setCatalogueLoaded(true);
130
+ }).catch(() => {
131
+ });
132
+ }, []);
133
+ const options = () => {
134
+ const source = catalogueLoaded ? catalogue.map((e) => ({
135
+ id: e.id,
136
+ state: e.running ? "running" /* RUNNING */ : "notRunning" /* NOT_RUNNING */,
137
+ help: e.subscriptionHelp
138
+ })) : (data?.providers ?? []).map((p) => ({ id: p.id, state: "unknown" /* UNKNOWN */, help: p.help }));
139
+ if (providerId && !source.some((o) => o.id === providerId)) source.push({ id: providerId, state: "unknown" /* UNKNOWN */ });
140
+ return source.sort((a, b) => a.id.localeCompare(b.id));
141
+ };
142
+ const help = options().find((o) => o.id === providerId)?.help;
143
+ const fields = help?.fields ?? [];
144
+ const invalidJson = () => {
145
+ if (!subscriptionData || subscriptionData.trim() === "") return false;
146
+ try {
147
+ JSON.parse(subscriptionData);
148
+ return false;
149
+ } catch {
150
+ return true;
151
+ }
152
+ };
153
+ const payload = () => {
154
+ if (!subscriptionData || subscriptionData.trim() === "") return {};
155
+ try {
156
+ const parsed = JSON.parse(subscriptionData);
157
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
158
+ } catch {
159
+ return {};
160
+ }
161
+ };
162
+ const setField = (name, value) => {
163
+ const obj = payload();
164
+ if (value === void 0 || value === "" || Array.isArray(value) && value.length === 0) delete obj[name];
165
+ else obj[name] = value;
166
+ setSubscriptionData(Object.keys(obj).length === 0 ? "" : JSON.stringify(obj, null, 2));
167
+ };
168
+ const useExample = () => {
169
+ if (help) setSubscriptionData(JSON.stringify(help.example, null, 2));
170
+ };
171
+ const renderField = (field) => {
172
+ const current = payload()[field.name];
173
+ const label = field.required ? `${field.name} *` : field.name;
174
+ switch (field.type) {
175
+ case "boolean":
176
+ return /* @__PURE__ */ import_react.default.createElement(
177
+ import_material.FormControlLabel,
178
+ {
179
+ key: field.name,
180
+ control: /* @__PURE__ */ import_react.default.createElement(import_material.Checkbox, { checked: current === true, onChange: (e) => setField(field.name, e.target.checked ? true : void 0) }),
181
+ label: /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "column" }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2" }, label), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, field.description))
182
+ }
183
+ );
184
+ case "number":
185
+ return /* @__PURE__ */ import_react.default.createElement(import_material.TextField, { key: field.name, value: current ?? "", onChange: (e) => setField(field.name, e.target.value === "" ? void 0 : Number(e.target.value)), type: "number", variant: "standard", label, helperText: field.description, fullWidth: true });
186
+ case "string[]":
187
+ return /* @__PURE__ */ import_react.default.createElement(import_material.TextField, { key: field.name, value: Array.isArray(current) ? current.join(", ") : "", onChange: (e) => setField(field.name, e.target.value.split(",").map((s) => s.trim()).filter(Boolean)), variant: "standard", label, helperText: `${field.description} \u2014 comma separated`, fullWidth: true });
188
+ default:
189
+ return /* @__PURE__ */ import_react.default.createElement(import_material.TextField, { key: field.name, value: typeof current === "string" ? current : "", onChange: (e) => setField(field.name, e.target.value), variant: "standard", label, helperText: field.description, fullWidth: true });
190
+ }
191
+ };
192
+ const renderHelp = () => {
193
+ if (!providerId) return /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, "Pick a provider to see how to subscribe to it.");
194
+ if (!help) {
195
+ return /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, `'${providerId}' does not publish subscription help (getSubscriptionHelp is optional). Check its README for the payload it expects.`);
196
+ }
197
+ return /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary", sx: { whiteSpace: "pre-wrap" } }, help.usage);
198
+ };
199
+ const ok = () => {
200
+ config.maxEvents = maxEvents;
201
+ instanceConfig.providerId = providerId;
202
+ instanceConfig.subscriptionData = subscriptionData;
203
+ props.onChannelSetupClosed(props.channel, {
204
+ channelId: props.channel.channelId,
205
+ channelConfig: config,
206
+ channelInstanceConfig: instanceConfig
207
+ }, true, defaultRef.current?.checked || false);
208
+ };
209
+ const cancel = () => {
210
+ props.onChannelSetupClosed(props.channel, {
211
+ channelId: props.channel.channelId,
212
+ channelConfig: void 0,
213
+ channelInstanceConfig: void 0
214
+ }, false, false);
215
+ };
216
+ return /* @__PURE__ */ import_react.default.createElement(import_material.Dialog, { open: true, maxWidth: false, sx: { "& .MuiDialog-paper": { width: "46vw", maxWidth: "46vw", height: "76vh", maxHeight: "76vh" } } }, /* @__PURE__ */ import_react.default.createElement(import_material.DialogTitle, null, "Configure Provider Debug channel"), /* @__PURE__ */ import_react.default.createElement(import_material.DialogContent, null, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "column", spacing: 2, sx: { m: 1 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "column", spacing: 0.5 }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, "Provider"), /* @__PURE__ */ import_react.default.createElement(import_material.Select, { value: providerId, onChange: (e) => setProviderId(e.target.value), displayEmpty: true, size: "small", variant: "standard", inputProps: { "aria-label": "Provider" } }, /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "" }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2", color: "text.secondary" }, "(none \u2014 just list the running providers)")), options().map((o) => /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { key: o.id, value: o.id }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1, alignItems: "center" }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2" }, o.id), o.state === "notRunning" /* NOT_RUNNING */ && /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "not running", size: "small", variant: "outlined", color: "warning", sx: { fontSize: "0.65rem", height: 18 } }))))), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, catalogueLoaded ? "A provider only runs when some channel requires it. Only the ones marked as running can be subscribed to." : "Could not read the provider catalogue from the core \u2014 showing only what this channel already knows.")), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { borderLeft: 3, borderColor: "divider", pl: 1.5 } }, renderHelp()), /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "column", spacing: 1 }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", alignItems: "center", justifyContent: "space-between" }, /* @__PURE__ */ import_react.default.createElement(import_material.Tabs, { value: fields.length > 0 ? mode : "json" /* JSON */, onChange: (_e, v) => setMode(v), sx: { minHeight: 32, "& .MuiTab-root": { minHeight: 32, py: 0 } } }, /* @__PURE__ */ import_react.default.createElement(import_material.Tooltip, { title: fields.length > 0 ? "" : "This provider does not describe its payload field by field" }, /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement(import_material.Tab, { label: "Form", value: "form" /* FORM */, disabled: fields.length === 0 }))), /* @__PURE__ */ import_react.default.createElement(import_material.Tab, { label: "JSON", value: "json" /* JSON */ })), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", onClick: useExample, disabled: !help }, "USE EXAMPLE")), fields.length > 0 && mode === "form" /* FORM */ ? /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "column", spacing: 1.5 }, fields.map((f) => renderField(f))) : /* @__PURE__ */ import_react.default.createElement(import_material.TextField, { value: subscriptionData, onChange: (e) => setSubscriptionData(e.target.value), variant: "standard", label: "Subscription payload (JSON)", placeholder: "{}", multiline: true, minRows: 4, maxRows: 4, error: invalidJson(), helperText: invalidJson() ? "Not valid JSON" : "Empty means {} \u2014 note most providers deliver nothing without a payload", fullWidth: true })), /* @__PURE__ */ import_react.default.createElement(import_material.TextField, { value: maxEvents, onChange: (e) => setMaxEvents(+e.target.value), type: "number", variant: "standard", label: "Max events", fullWidth: true }))), /* @__PURE__ */ import_react.default.createElement(import_material.DialogActions, null, /* @__PURE__ */ import_react.default.createElement(import_material.FormControlLabel, { control: /* @__PURE__ */ import_react.default.createElement(import_material.Checkbox, { slotProps: { input: { ref: defaultRef } } }), label: "Set as default", sx: { width: "100%", ml: "8px" } }), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { variant: "outlined", onClick: ok, disabled: invalidJson() }, "OK"), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { variant: "outlined", onClick: cancel }, "CANCEL")));
217
+ };
218
+
219
+ // src/front/ProviderDebugTabContent.tsx
220
+ var import_react3 = __toESM(require_react(), 1);
221
+ var import_material3 = __toESM(require_material(), 1);
222
+ var import_icons_material2 = __toESM(require_icons_material(), 1);
223
+
224
+ // src/front/JsonBlock.tsx
225
+ var import_react2 = __toESM(require_react(), 1);
226
+ var import_material2 = __toESM(require_material(), 1);
227
+ var TOKEN = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g;
228
+ var JsonBlock = (props) => {
229
+ const theme = (0, import_material2.useTheme)();
230
+ const colorOf = (token) => {
231
+ if (token.startsWith('"')) return token.trimEnd().endsWith(":") ? theme.palette.primary.main : theme.palette.success.main;
232
+ if (token === "true" || token === "false" || token === "null") return theme.palette.secondary.main;
233
+ return theme.palette.warning.main;
234
+ };
235
+ const render = () => {
236
+ let text;
237
+ try {
238
+ text = JSON.stringify(props.value, null, 2) ?? String(props.value);
239
+ } catch {
240
+ return [/* @__PURE__ */ import_react2.default.createElement("span", { key: "err" }, "<unserializable event>")];
241
+ }
242
+ const nodes = [];
243
+ let last = 0;
244
+ let match;
245
+ TOKEN.lastIndex = 0;
246
+ while ((match = TOKEN.exec(text)) !== null) {
247
+ if (match.index > last) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `p${last}` }, text.slice(last, match.index)));
248
+ nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `t${match.index}`, style: { color: colorOf(match[0]) } }, match[0]));
249
+ last = match.index + match[0].length;
250
+ }
251
+ if (last < text.length) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `p${last}` }, text.slice(last)));
252
+ return nodes;
253
+ };
254
+ return /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { component: "pre", sx: { m: 0, fontFamily: "monospace", fontSize: "0.75rem", whiteSpace: "pre-wrap", wordBreak: "break-all", overflowX: "auto" } }, render());
255
+ };
256
+
257
+ // src/front/ProviderDebugTabContent.tsx
258
+ var ProviderDebugTabContent = (props) => {
259
+ const data = props.channelObject.data;
260
+ const config = props.channelObject.config;
261
+ const instanceConfig = props.channelObject.instanceConfig;
262
+ const boxRef = (0, import_react3.useRef)(null);
263
+ const [boxTop, setBoxTop] = (0, import_react3.useState)(0);
264
+ const [, forceRender] = (0, import_react3.useState)(0);
265
+ const [copied, setCopied] = (0, import_react3.useState)(null);
266
+ (0, import_react3.useEffect)(() => {
267
+ if (boxRef.current) setBoxTop(boxRef.current.getBoundingClientRect().top);
268
+ });
269
+ const clear = () => {
270
+ data.events = [];
271
+ data.signals = [];
272
+ forceRender((n) => n + 1);
273
+ };
274
+ const writeClipboard = async (text) => {
275
+ try {
276
+ await navigator.clipboard.writeText(text);
277
+ return true;
278
+ } catch {
279
+ try {
280
+ const area = document.createElement("textarea");
281
+ area.value = text;
282
+ area.style.position = "fixed";
283
+ area.style.opacity = "0";
284
+ document.body.appendChild(area);
285
+ area.select();
286
+ const ok = document.execCommand("copy");
287
+ document.body.removeChild(area);
288
+ return ok;
289
+ } catch {
290
+ return false;
291
+ }
292
+ }
293
+ };
294
+ const copy = (event) => {
295
+ writeClipboard(JSON.stringify(event.event, null, 2)).then((ok) => {
296
+ if (!ok) return;
297
+ setCopied(event);
298
+ setTimeout(() => setCopied((current) => current === event ? null : current), 1500);
299
+ });
300
+ };
301
+ const summaryOf = (event) => {
302
+ if (event === null) return "null";
303
+ if (Array.isArray(event)) return `array \xB7 ${event.length} items`;
304
+ if (typeof event === "object") {
305
+ const keys = Object.keys(event);
306
+ return keys.length === 0 ? "{}" : keys.join(", ");
307
+ }
308
+ return String(event);
309
+ };
310
+ const formatProviders = () => {
311
+ if (data.providers.length === 0) return /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "text.secondary" }, "No providers running.");
312
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1, flexWrap: "wrap", useFlexGap: true }, data.providers.map((p) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: p.id, label: p.id, size: "small", variant: p.id === instanceConfig.providerId ? "filled" : "outlined" })));
313
+ };
314
+ const formatEvent = (event, index) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Accordion, { key: index, disableGutters: true, slotProps: { transition: { unmountOnExit: true } } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.AccordionSummary, { expandIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ExpandMore, null) }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1.5, alignItems: "center", sx: { minWidth: 0, width: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, new Date(event.ts).toISOString()), /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { label: event.providerId, size: "small", variant: "outlined", sx: { fontSize: "0.65rem", height: 18 } }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", noWrap: true }, summaryOf(event.event)), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: copied === event ? "Copied" : "Copy event JSON" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Copy event JSON", sx: { ml: "auto" }, onClick: (e) => {
315
+ e.stopPropagation();
316
+ copy(event);
317
+ } }, copied === event ? /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Check, { fontSize: "small", color: "success" }) : /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.AccordionDetails, null, /* @__PURE__ */ import_react3.default.createElement(JsonBlock, { value: event.event })));
318
+ if (!data.started) {
319
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary" }, "Provider Debug not started. Start the channel (tab settings \u2699 \u2192 Start) to subscribe to a provider and watch its raw events."));
320
+ }
321
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Card, { sx: { flex: 1, width: "98%", alignSelf: "center", m: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.CardHeader, { title: /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", alignItems: "center", sx: { width: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Provider:"), " ", instanceConfig.providerId || "(none)"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Events:"), " ", data.events.length, " / ", config.maxEvents), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Info, { fontSize: "small", sx: { mb: 0.25 } }), /* @__PURE__ */ import_react3.default.createElement("b", null, "\xA0Status:"), " ", data.paused ? "paused" : data.started ? "started" : "stopped"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: "Clear captured events" }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { marginLeft: "auto" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Clear captured events", onClick: clear, disabled: data.events.length === 0 && data.signals.length === 0 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.DeleteSweep, { fontSize: "small" }))))) }), /* @__PURE__ */ import_react3.default.createElement(import_material3.CardContent, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "column", spacing: 1, sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "Running providers"), formatProviders(), data.signals.map((s, index) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { key: index, variant: "caption", color: "text.secondary" }, "*** ", s, " ***"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { ref: boxRef, sx: { display: "flex", flexDirection: "column", overflowY: "auto", overflowX: "hidden", width: "100%", flexGrow: 1, height: `calc(100vh - ${boxTop}px - 35px)` } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { flex: 1, overflowY: "auto", ml: 1, mr: 1 } }, data.events.map((e, index) => formatEvent(e, index))))));
322
+ };
323
+
324
+ // src/front/ProviderDebugChannel.ts
325
+ var ProviderDebugChannel = class {
326
+ constructor() {
327
+ this.setupVisible = false;
328
+ this.SetupDialog = ProviderDebugSetup;
329
+ this.TabContent = ProviderDebugTabContent;
330
+ this.channelId = "provider-debug";
331
+ this.requirements = {
332
+ accessString: true,
333
+ clusterUrl: true,
334
+ clusterInfo: false,
335
+ exit: false,
336
+ frontChannels: false,
337
+ metrics: false,
338
+ notifier: true,
339
+ notifications: true,
340
+ setup: true,
341
+ settings: false,
342
+ palette: false,
343
+ userSettings: false,
344
+ webSocket: false,
345
+ backChannels: false
346
+ };
347
+ }
348
+ getScope() {
349
+ return import_kwirth_common.EInstanceConfigScope.NONE;
350
+ }
351
+ getChannelIcon() {
352
+ return ProviderDebugIcon;
353
+ }
354
+ getSetupVisibility() {
355
+ return this.setupVisible;
356
+ }
357
+ setSetupVisibility(visibility) {
358
+ this.setupVisible = visibility;
359
+ }
360
+ processChannelMessage(channelObject, wsEvent) {
361
+ const msg = JSON.parse(wsEvent.data);
362
+ const data = channelObject.data;
363
+ const config = channelObject.config;
364
+ switch (msg.type) {
365
+ case import_kwirth_common.EInstanceMessageType.DATA:
366
+ if (msg.payloadType === "providers" /* PROVIDERS */) {
367
+ data.providers = msg.providers ?? [];
368
+ } else {
369
+ if (msg.event) {
370
+ data.events.push(msg.event);
371
+ while (data.events.length > config.maxEvents) data.events.shift();
372
+ }
373
+ }
374
+ return { action: import_kwirth_common_front.EChannelRefreshAction.REFRESH };
375
+ case import_kwirth_common.EInstanceMessageType.SIGNAL: {
376
+ const signalMessage = JSON.parse(wsEvent.data);
377
+ if (signalMessage.flow === import_kwirth_common.EInstanceMessageFlow.RESPONSE && signalMessage.action === import_kwirth_common.EInstanceMessageAction.START) {
378
+ channelObject.instanceId = signalMessage.instance;
379
+ }
380
+ if (signalMessage.text) data.signals.push(signalMessage.text);
381
+ return { action: import_kwirth_common_front.EChannelRefreshAction.REFRESH };
382
+ }
383
+ default:
384
+ return { action: import_kwirth_common_front.EChannelRefreshAction.NONE };
385
+ }
386
+ }
387
+ async initChannel(channelObject) {
388
+ channelObject.instanceConfig = new ProviderDebugInstanceConfig();
389
+ channelObject.config = new ProviderDebugConfig();
390
+ channelObject.data = new ProviderDebugData();
391
+ return false;
392
+ }
393
+ startChannel(channelObject) {
394
+ const data = channelObject.data;
395
+ data.events = [];
396
+ data.signals = [];
397
+ data.paused = false;
398
+ data.started = true;
399
+ return true;
400
+ }
401
+ pauseChannel(channelObject) {
402
+ const data = channelObject.data;
403
+ data.paused = true;
404
+ return true;
405
+ }
406
+ continueChannel(channelObject) {
407
+ const data = channelObject.data;
408
+ data.paused = false;
409
+ return true;
410
+ }
411
+ stopChannel(channelObject) {
412
+ const data = channelObject.data;
413
+ data.paused = false;
414
+ data.started = false;
415
+ return true;
416
+ }
417
+ socketDisconnected(_channelObject) {
418
+ return false;
419
+ }
420
+ socketReconnect(_channelObject) {
421
+ return false;
422
+ }
423
+ };
424
+
425
+ // src/front/index.ts
426
+ window.__kwirth_plugins__ = window.__kwirth_plugins__ || {};
427
+ window.__kwirth_plugins__["provider-debug"] = ProviderDebugChannel;
428
+ })();
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "type": "commonjs",
3
+ "extensionType": "plugin",
4
+ "id": "provider-debug",
5
+ "name": "@kwirthmagnify/kwirth-plugin-provider-debug",
6
+ "displayName": "Provider Debug",
7
+ "version": "0.1.0",
8
+ "description": "Provider debugging channel for Kwirth - subscribes to a running provider and streams its raw events",
9
+ "icon": "DataObjectOutlined",
10
+ "requiresRestart": false,
11
+ "requiresExtension": []
12
+ }