@openrfid/shell 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,889 @@
1
+ // src/views/DashboardView.tsx
2
+ import React, { useState, useEffect } from "react";
3
+ var LIMIT_PRESETS = [1e3, 5e3, 1e4, 5e4, 1e5];
4
+ var card = {
5
+ backgroundColor: "#1E293B",
6
+ padding: "20px",
7
+ borderRadius: "10px",
8
+ border: "1px solid #334155"
9
+ };
10
+ var DashboardView = ({ simulator, storage }) => {
11
+ const [readers, setReaders] = useState(simulator.getAllReaders());
12
+ const [tags, setTags] = useState(simulator.getAllTags());
13
+ const [memUsage, setMemUsage] = useState(storage?.getMemoryUsage());
14
+ const [customLimit, setCustomLimit] = useState("");
15
+ const [autoTrim, setAutoTrim] = useState(true);
16
+ const [showWarning, setShowWarning] = useState(true);
17
+ const [limitInput, setLimitInput] = useState(storage?.getMemoryUsage().limit ?? 1e4);
18
+ useEffect(() => {
19
+ const interval = setInterval(() => {
20
+ setReaders(simulator.getAllReaders());
21
+ setTags(simulator.getAllTags());
22
+ if (storage) setMemUsage(storage.getMemoryUsage());
23
+ }, 2e3);
24
+ return () => clearInterval(interval);
25
+ }, [simulator, storage]);
26
+ const onlineCount = readers.filter((r) => r.status === "ONLINE").length;
27
+ const offlineCount = readers.length - onlineCount;
28
+ const applyLimit = (limit) => {
29
+ if (!storage) return;
30
+ storage.setMemoryLimit(limit);
31
+ setLimitInput(limit);
32
+ setMemUsage(storage.getMemoryUsage());
33
+ };
34
+ const handleApplyCustom = () => {
35
+ const v = parseInt(customLimit, 10);
36
+ if (!isNaN(v) && v >= 100) applyLimit(v);
37
+ };
38
+ const handleClearBuffer = () => {
39
+ storage?.clearAllEvents();
40
+ setMemUsage(storage?.getMemoryUsage());
41
+ };
42
+ const mem = memUsage;
43
+ const memPercent = mem?.percent ?? 0;
44
+ const memBarColor = memPercent >= 80 ? "#F59E0B" : memPercent >= 95 ? "#EF4444" : "#10B981";
45
+ return /* @__PURE__ */ React.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: "12px", flexWrap: "wrap", marginBottom: "20px", padding: "12px 16px", backgroundColor: "#0F172A", borderRadius: "8px", border: "1px solid #334155", fontSize: "13px" } }, /* @__PURE__ */ React.createElement("span", { style: { color: "#10B981", fontWeight: "bold" } }, "\u25CF ", onlineCount, " Online"), /* @__PURE__ */ React.createElement("span", { style: { color: "#64748B" } }, "|"), /* @__PURE__ */ React.createElement("span", { style: { color: "#64748B" } }, offlineCount, " Offline"), /* @__PURE__ */ React.createElement("span", { style: { color: "#64748B" } }, "|"), /* @__PURE__ */ React.createElement("span", { style: { color: "#00E5EE" } }, "\u{1F3F7} ", tags.length.toLocaleString(), " Tags"), mem && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "#64748B" } }, "|"), /* @__PURE__ */ React.createElement("span", { style: { color: mem.isWarning ? "#F59E0B" : "#94A3B8" } }, mem.isWarning ? "\u26A0\uFE0F " : "", "Buffer: ", mem.current.toLocaleString(), "/", mem.limit.toLocaleString(), " (", mem.percent, "%)"))), /* @__PURE__ */ React.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", marginBottom: "20px" } }, "Dashboard Overview"), /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "16px" } }, /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: "13px", color: "#94A3B8" } }, "Readers Online"), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "28px", fontWeight: "bold", color: "#10B981", marginTop: "4px" } }, onlineCount)), /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: "13px", color: "#94A3B8" } }, "Readers Offline"), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "28px", fontWeight: "bold", color: "#64748B", marginTop: "4px" } }, offlineCount)), /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: "13px", color: "#94A3B8" } }, "Active Simulated Tags"), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "28px", fontWeight: "bold", color: "#00E5EE", marginTop: "4px" } }, tags.length.toLocaleString())), /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: "13px", color: "#94A3B8" } }, "Event Buffer"), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "28px", fontWeight: "bold", color: mem?.isWarning ? "#F59E0B" : "#FFFFFF", marginTop: "4px" } }, mem ? `${mem.current.toLocaleString()}` : "\u2014"), mem && /* @__PURE__ */ React.createElement("div", { style: { fontSize: "11px", color: "#64748B", marginTop: "2px" } }, "of ", mem.limit.toLocaleString(), " limit"))), /* @__PURE__ */ React.createElement("div", { style: { ...card, marginTop: "20px" } }, /* @__PURE__ */ React.createElement("h3", { style: { margin: "0 0 12px", fontSize: "16px" } }, "Quick Controls"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: "12px" } }, /* @__PURE__ */ React.createElement(
46
+ "button",
47
+ {
48
+ onClick: () => simulator.startAll(),
49
+ style: { backgroundColor: "#10B981", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
50
+ },
51
+ "\u25B6 Start All Readers"
52
+ ), /* @__PURE__ */ React.createElement(
53
+ "button",
54
+ {
55
+ onClick: () => simulator.stopAll(),
56
+ style: { backgroundColor: "#EF4444", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
57
+ },
58
+ "\u25A0 Stop All Readers"
59
+ ))), storage && /* @__PURE__ */ React.createElement("div", { style: { ...card, marginTop: "20px" } }, /* @__PURE__ */ React.createElement("h3", { style: { margin: "0 0 16px", fontSize: "16px" } }, "\u2699\uFE0F Storage & Memory Settings"), /* @__PURE__ */ React.createElement("div", { style: { marginBottom: "14px", fontSize: "14px", color: "#94A3B8" } }, "In-Memory Event Buffer Limit"), mem && /* @__PURE__ */ React.createElement("div", { style: { marginBottom: "16px" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", marginBottom: "4px" } }, /* @__PURE__ */ React.createElement("span", { style: { color: mem.isWarning ? "#F59E0B" : "#94A3B8" } }, mem.isWarning ? "\u26A0\uFE0F " : "", mem.current.toLocaleString(), " events in buffer"), /* @__PURE__ */ React.createElement("span", { style: { color: "#64748B" } }, mem.percent, "% of ", mem.limit.toLocaleString())), /* @__PURE__ */ React.createElement("div", { style: { backgroundColor: "#0F172A", borderRadius: "6px", height: "10px", overflow: "hidden" } }, /* @__PURE__ */ React.createElement(
60
+ "div",
61
+ {
62
+ style: {
63
+ width: `${Math.min(100, mem.percent)}%`,
64
+ height: "100%",
65
+ backgroundColor: memBarColor,
66
+ borderRadius: "6px",
67
+ transition: "width 0.5s ease"
68
+ }
69
+ }
70
+ ))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap", marginBottom: "14px" } }, LIMIT_PRESETS.map((preset) => /* @__PURE__ */ React.createElement(
71
+ "button",
72
+ {
73
+ key: preset,
74
+ onClick: () => applyLimit(preset),
75
+ style: {
76
+ padding: "5px 12px",
77
+ borderRadius: "6px",
78
+ border: `1px solid ${limitInput === preset ? "#00E5EE" : "#334155"}`,
79
+ backgroundColor: limitInput === preset ? "#0E3A3F" : "#0F172A",
80
+ color: limitInput === preset ? "#00E5EE" : "#94A3B8",
81
+ cursor: "pointer",
82
+ fontSize: "13px",
83
+ fontWeight: limitInput === preset ? "bold" : "normal"
84
+ }
85
+ },
86
+ preset.toLocaleString()
87
+ ))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: "8px", marginBottom: "16px", alignItems: "center" } }, /* @__PURE__ */ React.createElement(
88
+ "input",
89
+ {
90
+ type: "number",
91
+ placeholder: "Custom limit (100\u2013500,000)",
92
+ value: customLimit,
93
+ onChange: (e) => setCustomLimit(e.target.value),
94
+ style: { backgroundColor: "#0F172A", border: "1px solid #334155", color: "#F8FAFC", padding: "6px 10px", borderRadius: "6px", fontSize: "13px", width: "240px" }
95
+ }
96
+ ), /* @__PURE__ */ React.createElement(
97
+ "button",
98
+ {
99
+ onClick: handleApplyCustom,
100
+ style: { backgroundColor: "#00E5EE", color: "#0F172A", border: "none", padding: "6px 14px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold", fontSize: "13px" }
101
+ },
102
+ "Apply Limit"
103
+ )), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "10px", marginBottom: "16px", fontSize: "14px" } }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
104
+ "input",
105
+ {
106
+ type: "checkbox",
107
+ checked: autoTrim,
108
+ onChange: (e) => setAutoTrim(e.target.checked),
109
+ style: { accentColor: "#00E5EE", width: "16px", height: "16px" }
110
+ }
111
+ ), /* @__PURE__ */ React.createElement("span", { style: { color: "#CBD5E1" } }, "Auto-trim oldest events when limit is reached")), /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
112
+ "input",
113
+ {
114
+ type: "checkbox",
115
+ checked: showWarning,
116
+ onChange: (e) => setShowWarning(e.target.checked),
117
+ style: { accentColor: "#00E5EE", width: "16px", height: "16px" }
118
+ }
119
+ ), /* @__PURE__ */ React.createElement("span", { style: { color: "#CBD5E1" } }, "Show \u26A0\uFE0F warning when buffer is \u226580% full"))), /* @__PURE__ */ React.createElement(
120
+ "button",
121
+ {
122
+ onClick: handleClearBuffer,
123
+ style: { backgroundColor: "#334155", color: "#F8FAFC", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold", fontSize: "13px" }
124
+ },
125
+ "\u{1F5D1} Clear Memory Buffer Now"
126
+ )));
127
+ };
128
+
129
+ // src/views/ReadersView.tsx
130
+ import React2, { useState as useState2 } from "react";
131
+ import { VirtualReader } from "@openrfid/readers";
132
+ import { ReaderStatusBadge, AntennaPowerSlider } from "@openrfid/ui";
133
+ var ReadersView = ({ simulator, refresh }) => {
134
+ const [, setTick] = useState2(0);
135
+ const readers = simulator.getAllReaders();
136
+ const [showAddForm, setShowAddForm] = useState2(false);
137
+ const [readerName, setReaderName] = useState2("");
138
+ const [vendor, setVendor] = useState2("RFID Softwares");
139
+ const [readerType, setReaderType] = useState2("4-Port Reader");
140
+ const [readMode, setReadMode] = useState2("continuous");
141
+ const [readIntervalValue, setReadIntervalValue] = useState2(1);
142
+ const [readIntervalUnit, setReadIntervalUnit] = useState2("seconds");
143
+ const [readRate, setReadRate] = useState2(0);
144
+ const [epcFilterPrefix, setEpcFilterPrefix] = useState2("");
145
+ const [epcFilterStart, setEpcFilterStart] = useState2("");
146
+ const [epcFilterEnd, setEpcFilterEnd] = useState2("");
147
+ const [expandedReaderId, setExpandedReaderId] = useState2(null);
148
+ const triggerUpdate = () => {
149
+ setTick((t) => t + 1);
150
+ refresh();
151
+ };
152
+ const getAntennasCount = (type) => {
153
+ if (type === "Desktop Reader") return 1;
154
+ if (type === "Integrated Reader") return 2;
155
+ if (type === "4-Port Reader") return 4;
156
+ if (type === "8-Port Reader") return 8;
157
+ if (type === "16-Port Reader") return 16;
158
+ return 4;
159
+ };
160
+ const handleAddReader = () => {
161
+ const name = readerName.trim() || `${vendor} ${readerType}`;
162
+ const antennasCount = getAntennasCount(readerType);
163
+ const reader = new VirtualReader({
164
+ name,
165
+ vendor,
166
+ model: readerType,
167
+ antennasCount,
168
+ readRate,
169
+ readMode,
170
+ readIntervalValue,
171
+ readIntervalUnit,
172
+ epcFilterStart,
173
+ epcFilterEnd,
174
+ epcFilterPrefix
175
+ });
176
+ simulator.addReader(reader);
177
+ setReaderName("");
178
+ setEpcFilterPrefix("");
179
+ setEpcFilterStart("");
180
+ setEpcFilterEnd("");
181
+ setReadRate(0);
182
+ setReadMode("continuous");
183
+ setReadIntervalValue(1);
184
+ setReadIntervalUnit("seconds");
185
+ setShowAddForm(false);
186
+ triggerUpdate();
187
+ };
188
+ const handleReaderSettingsChange = (reader, key, val) => {
189
+ reader[key] = val;
190
+ if (simulator.storage) {
191
+ simulator.storage.saveReader(reader.toJSON());
192
+ }
193
+ if (reader.status === "ONLINE") {
194
+ simulator.stopReader(reader.id);
195
+ simulator.startReader(reader.id);
196
+ }
197
+ triggerUpdate();
198
+ };
199
+ const handleToggleAntenna = (reader, index) => {
200
+ const antenna = reader.getAntenna(index);
201
+ if (antenna) {
202
+ antenna.enabled = !antenna.enabled;
203
+ if (simulator.storage) {
204
+ simulator.storage.saveAntenna({
205
+ id: antenna.id,
206
+ readerId: antenna.readerId,
207
+ index: antenna.index,
208
+ gain: antenna.gain,
209
+ power: antenna.power,
210
+ frequency: antenna.frequency,
211
+ rssiOffset: antenna.rssiOffset,
212
+ readZone: antenna.readZone,
213
+ enabled: antenna.enabled
214
+ });
215
+ }
216
+ triggerUpdate();
217
+ }
218
+ };
219
+ const handleAntennaPowerChange = (reader, index, power) => {
220
+ const antenna = reader.getAntenna(index);
221
+ if (antenna) {
222
+ antenna.power = power;
223
+ if (simulator.storage) {
224
+ simulator.storage.saveAntenna({
225
+ id: antenna.id,
226
+ readerId: antenna.readerId,
227
+ index: antenna.index,
228
+ gain: antenna.gain,
229
+ power: antenna.power,
230
+ frequency: antenna.frequency,
231
+ rssiOffset: antenna.rssiOffset,
232
+ readZone: antenna.readZone,
233
+ enabled: antenna.enabled
234
+ });
235
+ }
236
+ triggerUpdate();
237
+ }
238
+ };
239
+ const handleAntennaGainChange = (reader, index, gain) => {
240
+ const antenna = reader.getAntenna(index);
241
+ if (antenna) {
242
+ antenna.gain = gain;
243
+ if (simulator.storage) {
244
+ simulator.storage.saveAntenna({
245
+ id: antenna.id,
246
+ readerId: antenna.readerId,
247
+ index: antenna.index,
248
+ gain: antenna.gain,
249
+ power: antenna.power,
250
+ frequency: antenna.frequency,
251
+ rssiOffset: antenna.rssiOffset,
252
+ readZone: antenna.readZone,
253
+ enabled: antenna.enabled
254
+ });
255
+ }
256
+ triggerUpdate();
257
+ }
258
+ };
259
+ const handleAntennaRssiChange = (reader, index, rssiOffset) => {
260
+ const antenna = reader.getAntenna(index);
261
+ if (antenna) {
262
+ antenna.rssiOffset = rssiOffset;
263
+ if (simulator.storage) {
264
+ simulator.storage.saveAntenna({
265
+ id: antenna.id,
266
+ readerId: antenna.readerId,
267
+ index: antenna.index,
268
+ gain: antenna.gain,
269
+ power: antenna.power,
270
+ frequency: antenna.frequency,
271
+ rssiOffset: antenna.rssiOffset,
272
+ readZone: antenna.readZone,
273
+ enabled: antenna.enabled
274
+ });
275
+ }
276
+ triggerUpdate();
277
+ }
278
+ };
279
+ const handleAntennaZoneChange = (reader, index, zone) => {
280
+ const antenna = reader.getAntenna(index);
281
+ if (antenna) {
282
+ antenna.readZone = zone;
283
+ if (simulator.storage) {
284
+ simulator.storage.saveAntenna({
285
+ id: antenna.id,
286
+ readerId: antenna.readerId,
287
+ index: antenna.index,
288
+ gain: antenna.gain,
289
+ power: antenna.power,
290
+ frequency: antenna.frequency,
291
+ rssiOffset: antenna.rssiOffset,
292
+ readZone: antenna.readZone,
293
+ enabled: antenna.enabled
294
+ });
295
+ }
296
+ triggerUpdate();
297
+ }
298
+ };
299
+ return /* @__PURE__ */ React2.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "20px" } }, /* @__PURE__ */ React2.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", margin: 0 } }, "Virtual Readers (", readers.length, ")"), /* @__PURE__ */ React2.createElement(
300
+ "button",
301
+ {
302
+ onClick: () => setShowAddForm(!showAddForm),
303
+ style: { backgroundColor: "#00E5EE", color: "#0F172A", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
304
+ },
305
+ showAddForm ? "Close Form" : "\u2795 Add Reader"
306
+ )), showAddForm && /* @__PURE__ */ React2.createElement("div", { style: { backgroundColor: "#1E293B", padding: "20px", borderRadius: "8px", border: "1px solid #00E5EE", marginBottom: "20px", display: "flex", flexDirection: "column", gap: "16px" } }, /* @__PURE__ */ React2.createElement("h3", { style: { margin: 0, fontSize: "16px", color: "#00E5EE" } }, "Configure New RFID Reader"), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "16px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Reader Name"), /* @__PURE__ */ React2.createElement(
307
+ "input",
308
+ {
309
+ type: "text",
310
+ placeholder: "e.g. Warehouse Gate A...",
311
+ value: readerName,
312
+ onChange: (e) => setReaderName(e.target.value),
313
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
314
+ }
315
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Vendor"), /* @__PURE__ */ React2.createElement(
316
+ "select",
317
+ {
318
+ value: vendor,
319
+ onChange: (e) => setVendor(e.target.value),
320
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
321
+ },
322
+ /* @__PURE__ */ React2.createElement("option", { value: "RFID Softwares" }, "RFID Softwares"),
323
+ /* @__PURE__ */ React2.createElement("option", { value: "Impinj" }, "Impinj"),
324
+ /* @__PURE__ */ React2.createElement("option", { value: "Zebra" }, "Zebra"),
325
+ /* @__PURE__ */ React2.createElement("option", { value: "ID Tech" }, "ID Tech"),
326
+ /* @__PURE__ */ React2.createElement("option", { value: "Identium" }, "Identium"),
327
+ /* @__PURE__ */ React2.createElement("option", { value: "Realtime" }, "Realtime"),
328
+ /* @__PURE__ */ React2.createElement("option", { value: "Generic" }, "Generic")
329
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Type & Ports"), /* @__PURE__ */ React2.createElement(
330
+ "select",
331
+ {
332
+ value: readerType,
333
+ onChange: (e) => setReaderType(e.target.value),
334
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
335
+ },
336
+ /* @__PURE__ */ React2.createElement("option", { value: "Desktop Reader" }, "Desktop Reader (1 Port)"),
337
+ /* @__PURE__ */ React2.createElement("option", { value: "Integrated Reader" }, "Integrated Reader (2 Ports)"),
338
+ /* @__PURE__ */ React2.createElement("option", { value: "4-Port Reader" }, "4-Port Reader (4 Ports)"),
339
+ /* @__PURE__ */ React2.createElement("option", { value: "8-Port Reader" }, "8-Port Reader (8 Ports)"),
340
+ /* @__PURE__ */ React2.createElement("option", { value: "16-Port Reader" }, "16-Port Reader (16 Ports)")
341
+ ))), /* @__PURE__ */ React2.createElement("div", { style: { borderTop: "1px dashed #334155", paddingTop: "16px" } }, /* @__PURE__ */ React2.createElement("h4", { style: { margin: "0 0 12px 0", fontSize: "14px", color: "#00E5EE" } }, "\u2699\uFE0F Default Simulation Settings"), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "16px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Read Mode"), /* @__PURE__ */ React2.createElement(
342
+ "select",
343
+ {
344
+ value: readMode,
345
+ onChange: (e) => setReadMode(e.target.value),
346
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
347
+ },
348
+ /* @__PURE__ */ React2.createElement("option", { value: "continuous" }, "Continuous Read"),
349
+ /* @__PURE__ */ React2.createElement("option", { value: "periodic" }, "Periodic Read")
350
+ )), readMode === "periodic" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px", flex: 1 } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Interval"), /* @__PURE__ */ React2.createElement(
351
+ "input",
352
+ {
353
+ type: "number",
354
+ min: 1,
355
+ value: readIntervalValue,
356
+ onChange: (e) => setReadIntervalValue(parseInt(e.target.value, 10)),
357
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF", width: "100%" }
358
+ }
359
+ )), /* @__PURE__ */ React2.createElement(
360
+ "select",
361
+ {
362
+ value: readIntervalUnit,
363
+ onChange: (e) => setReadIntervalUnit(e.target.value),
364
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF", height: "37px" }
365
+ },
366
+ /* @__PURE__ */ React2.createElement("option", { value: "seconds" }, "Seconds"),
367
+ /* @__PURE__ */ React2.createElement("option", { value: "minutes" }, "Minutes"),
368
+ /* @__PURE__ */ React2.createElement("option", { value: "hours" }, "Hours")
369
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Max Read Rate Limit"), /* @__PURE__ */ React2.createElement(
370
+ "select",
371
+ {
372
+ value: readRate,
373
+ onChange: (e) => setReadRate(parseInt(e.target.value, 10)),
374
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
375
+ },
376
+ /* @__PURE__ */ React2.createElement("option", { value: 0 }, "Unlimited"),
377
+ /* @__PURE__ */ React2.createElement("option", { value: 1 }, "1 tag/sec"),
378
+ /* @__PURE__ */ React2.createElement("option", { value: 10 }, "10 tags/sec"),
379
+ /* @__PURE__ */ React2.createElement("option", { value: 100 }, "100 tags/sec"),
380
+ /* @__PURE__ */ React2.createElement("option", { value: 1e3 }, "1000 tags/sec")
381
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Filter Prefix"), /* @__PURE__ */ React2.createElement(
382
+ "input",
383
+ {
384
+ type: "text",
385
+ placeholder: "e.g. E200",
386
+ value: epcFilterPrefix,
387
+ onChange: (e) => setEpcFilterPrefix(e.target.value),
388
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
389
+ }
390
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Range Start"), /* @__PURE__ */ React2.createElement(
391
+ "input",
392
+ {
393
+ type: "text",
394
+ placeholder: "e.g. E20000000001",
395
+ value: epcFilterStart,
396
+ onChange: (e) => setEpcFilterStart(e.target.value),
397
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
398
+ }
399
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Range End"), /* @__PURE__ */ React2.createElement(
400
+ "input",
401
+ {
402
+ type: "text",
403
+ placeholder: "e.g. E20000000010",
404
+ value: epcFilterEnd,
405
+ onChange: (e) => setEpcFilterEnd(e.target.value),
406
+ style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#0F172A", color: "#FFF" }
407
+ }
408
+ )))), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "10px", marginTop: "10px" } }, /* @__PURE__ */ React2.createElement(
409
+ "button",
410
+ {
411
+ onClick: handleAddReader,
412
+ style: { backgroundColor: "#00E5EE", color: "#0F172A", border: "none", padding: "8px 24px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
413
+ },
414
+ "Confirm and Add"
415
+ ), /* @__PURE__ */ React2.createElement(
416
+ "button",
417
+ {
418
+ onClick: () => setShowAddForm(false),
419
+ style: { backgroundColor: "#334155", color: "#FFF", border: "none", padding: "8px 24px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
420
+ },
421
+ "Cancel"
422
+ ))), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr", gap: "16px" } }, readers.map((r) => {
423
+ const isExpanded = expandedReaderId === r.id;
424
+ return /* @__PURE__ */ React2.createElement("div", { key: r.id, style: { backgroundColor: "#1E293B", padding: "20px", borderRadius: "8px", border: "1px solid #334155" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("span", { style: { fontWeight: "bold", fontSize: "18px", marginRight: "12px" } }, r.name), /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "12px", color: "#64748B", fontFamily: "monospace" } }, "ID: ", r.id)), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", gap: "12px" } }, /* @__PURE__ */ React2.createElement(ReaderStatusBadge, { status: r.status }), /* @__PURE__ */ React2.createElement(
425
+ "button",
426
+ {
427
+ onClick: () => setExpandedReaderId(isExpanded ? null : r.id),
428
+ style: { backgroundColor: "#334155", color: "#FFF", border: "none", padding: "6px 12px", borderRadius: "6px", cursor: "pointer", fontSize: "13px" }
429
+ },
430
+ isExpanded ? "Hide Config" : "Configure Reader"
431
+ ))), /* @__PURE__ */ React2.createElement("div", { style: { fontSize: "13px", color: "#94A3B8", marginTop: "12px", display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "16px" } }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("strong", null, "Vendor:"), " ", r.vendor, " (", r.model, ")"), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("strong", null, "Address:"), " ", r.ip, ":", r.port), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("strong", null, "Protocol:"), " ", r.protocol), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("strong", null, "Antenna Ports:"), " ", r.antennas.size, " ports")), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "8px", marginTop: "16px" } }, r.status === "ONLINE" ? /* @__PURE__ */ React2.createElement(
432
+ "button",
433
+ {
434
+ onClick: () => {
435
+ simulator.stopReader(r.id);
436
+ triggerUpdate();
437
+ },
438
+ style: { backgroundColor: "#EF4444", color: "#FFF", border: "none", padding: "6px 14px", borderRadius: "6px", cursor: "pointer", fontSize: "13px", fontWeight: "bold" }
439
+ },
440
+ "\u23F9 Stop Simulation"
441
+ ) : /* @__PURE__ */ React2.createElement(
442
+ "button",
443
+ {
444
+ onClick: () => {
445
+ simulator.startReader(r.id);
446
+ triggerUpdate();
447
+ },
448
+ style: { backgroundColor: "#10B981", color: "#FFF", border: "none", padding: "6px 14px", borderRadius: "6px", cursor: "pointer", fontSize: "13px", fontWeight: "bold" }
449
+ },
450
+ "\u25B6 Start Simulation"
451
+ ), /* @__PURE__ */ React2.createElement(
452
+ "button",
453
+ {
454
+ onClick: () => {
455
+ simulator.removeReader(r.id);
456
+ triggerUpdate();
457
+ },
458
+ style: { backgroundColor: "#334155", color: "#FFF", border: "none", padding: "6px 14px", borderRadius: "6px", cursor: "pointer", fontSize: "13px", fontWeight: "bold" }
459
+ },
460
+ "\u{1F5D1} Delete Reader"
461
+ )), isExpanded && /* @__PURE__ */ React2.createElement("div", { style: { marginTop: "20px", borderTop: "1px solid #334155", paddingTop: "20px" } }, /* @__PURE__ */ React2.createElement("div", { style: { marginBottom: "24px" } }, /* @__PURE__ */ React2.createElement("h4", { style: { margin: "0 0 16px", fontSize: "15px", color: "#00E5EE" } }, "\u2699\uFE0F Simulation & Filtering Settings"), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: "16px", backgroundColor: "#0F172A", padding: "16px", borderRadius: "8px", border: "1px solid #1E293B" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Read Mode"), /* @__PURE__ */ React2.createElement(
462
+ "select",
463
+ {
464
+ value: r.readMode,
465
+ onChange: (e) => handleReaderSettingsChange(r, "readMode", e.target.value),
466
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155" }
467
+ },
468
+ /* @__PURE__ */ React2.createElement("option", { value: "continuous" }, "Continuous Read"),
469
+ /* @__PURE__ */ React2.createElement("option", { value: "periodic" }, "Periodic Read")
470
+ )), r.readMode === "periodic" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px", flex: 1 } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Interval"), /* @__PURE__ */ React2.createElement(
471
+ "input",
472
+ {
473
+ type: "number",
474
+ min: 1,
475
+ value: r.readIntervalValue ?? 1,
476
+ onChange: (e) => handleReaderSettingsChange(r, "readIntervalValue", parseInt(e.target.value, 10)),
477
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155", width: "100%" }
478
+ }
479
+ )), /* @__PURE__ */ React2.createElement(
480
+ "select",
481
+ {
482
+ value: r.readIntervalUnit,
483
+ onChange: (e) => handleReaderSettingsChange(r, "readIntervalUnit", e.target.value),
484
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155", height: "33px" }
485
+ },
486
+ /* @__PURE__ */ React2.createElement("option", { value: "seconds" }, "Seconds"),
487
+ /* @__PURE__ */ React2.createElement("option", { value: "minutes" }, "Minutes"),
488
+ /* @__PURE__ */ React2.createElement("option", { value: "hours" }, "Hours")
489
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Read Rate Limit"), /* @__PURE__ */ React2.createElement(
490
+ "select",
491
+ {
492
+ value: r.readRate ?? 0,
493
+ onChange: (e) => handleReaderSettingsChange(r, "readRate", parseInt(e.target.value, 10)),
494
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155" }
495
+ },
496
+ /* @__PURE__ */ React2.createElement("option", { value: 0 }, "Unlimited"),
497
+ /* @__PURE__ */ React2.createElement("option", { value: 1 }, "1 tag/sec"),
498
+ /* @__PURE__ */ React2.createElement("option", { value: 10 }, "10 tags/sec"),
499
+ /* @__PURE__ */ React2.createElement("option", { value: 100 }, "100 tags/sec"),
500
+ /* @__PURE__ */ React2.createElement("option", { value: 1e3 }, "1000 tags/sec")
501
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Filter Prefix"), /* @__PURE__ */ React2.createElement(
502
+ "input",
503
+ {
504
+ type: "text",
505
+ placeholder: "e.g. E200",
506
+ value: r.epcFilterPrefix ?? "",
507
+ onChange: (e) => handleReaderSettingsChange(r, "epcFilterPrefix", e.target.value),
508
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155" }
509
+ }
510
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Range Start"), /* @__PURE__ */ React2.createElement(
511
+ "input",
512
+ {
513
+ type: "text",
514
+ placeholder: "e.g. E20000000001",
515
+ value: r.epcFilterStart ?? "",
516
+ onChange: (e) => handleReaderSettingsChange(r, "epcFilterStart", e.target.value),
517
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155" }
518
+ }
519
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "EPC Range End"), /* @__PURE__ */ React2.createElement(
520
+ "input",
521
+ {
522
+ type: "text",
523
+ placeholder: "e.g. E20000000010",
524
+ value: r.epcFilterEnd ?? "",
525
+ onChange: (e) => handleReaderSettingsChange(r, "epcFilterEnd", e.target.value),
526
+ style: { padding: "6px", borderRadius: "6px", backgroundColor: "#1E293B", color: "#FFF", border: "1px solid #334155" }
527
+ }
528
+ )))), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("h4", { style: { margin: "0 0 16px", fontSize: "15px", color: "#00E5EE" } }, "\u{1F4E1} Antenna Configuration"), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: "16px" } }, Array.from(r.antennas.values()).map((ant) => /* @__PURE__ */ React2.createElement("div", { key: ant.id, style: { backgroundColor: "#0F172A", padding: "16px", borderRadius: "8px", border: ant.enabled ? "1px solid #00E5EE" : "1px solid #1E293B", display: "flex", flexDirection: "column", gap: "12px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontWeight: "bold", fontSize: "14px", color: ant.enabled ? "#FFF" : "#64748B" } }, "Port ", ant.index), /* @__PURE__ */ React2.createElement("label", { style: { display: "flex", alignItems: "center", gap: "6px", cursor: "pointer", fontSize: "12px" } }, /* @__PURE__ */ React2.createElement(
529
+ "input",
530
+ {
531
+ type: "checkbox",
532
+ checked: ant.enabled,
533
+ onChange: () => handleToggleAntenna(r, ant.index),
534
+ style: { accentColor: "#00E5EE" }
535
+ }
536
+ ), /* @__PURE__ */ React2.createElement("span", { style: { color: ant.enabled ? "#00E5EE" : "#64748B" } }, ant.enabled ? "Enabled" : "Disabled"))), ant.enabled ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(
537
+ AntennaPowerSlider,
538
+ {
539
+ power: ant.power,
540
+ onChange: (val) => handleAntennaPowerChange(r, ant.index, val),
541
+ min: 0,
542
+ max: 33
543
+ }
544
+ ), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", color: "#D1D5DB" } }, /* @__PURE__ */ React2.createElement("span", null, "Antenna Gain"), /* @__PURE__ */ React2.createElement("span", { style: { fontWeight: "bold", color: "#00E5EE" } }, ant.gain.toFixed(1), " dBi")), /* @__PURE__ */ React2.createElement(
545
+ "input",
546
+ {
547
+ type: "range",
548
+ min: 0,
549
+ max: 15,
550
+ step: 0.5,
551
+ value: ant.gain,
552
+ onChange: (e) => handleAntennaGainChange(r, ant.index, parseFloat(e.target.value)),
553
+ style: { accentColor: "#00E5EE", cursor: "pointer" }
554
+ }
555
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", color: "#D1D5DB" } }, /* @__PURE__ */ React2.createElement("span", null, "RSSI Offset"), /* @__PURE__ */ React2.createElement("span", { style: { fontWeight: "bold", color: "#00E5EE" } }, ant.rssiOffset >= 0 ? "+" : "", ant.rssiOffset.toFixed(1), " dB")), /* @__PURE__ */ React2.createElement(
556
+ "input",
557
+ {
558
+ type: "range",
559
+ min: -20,
560
+ max: 20,
561
+ step: 0.5,
562
+ value: ant.rssiOffset,
563
+ onChange: (e) => handleAntennaRssiChange(r, ant.index, parseFloat(e.target.value)),
564
+ style: { accentColor: "#00E5EE", cursor: "pointer" }
565
+ }
566
+ )), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, /* @__PURE__ */ React2.createElement("span", { style: { fontSize: "13px", color: "#D1D5DB" } }, "Coverage Read Zone"), /* @__PURE__ */ React2.createElement(
567
+ "input",
568
+ {
569
+ type: "text",
570
+ placeholder: "Zone identifier...",
571
+ value: ant.readZone,
572
+ onChange: (e) => handleAntennaZoneChange(r, ant.index, e.target.value),
573
+ style: { padding: "6px 10px", fontSize: "13px", borderRadius: "6px", border: "1px solid #334155", backgroundColor: "#1E293B", color: "#FFF" }
574
+ }
575
+ ))) : /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "140px", color: "#64748B", fontSize: "13px", border: "1px dashed #334155", borderRadius: "6px" } }, "Antenna port disabled")))))));
576
+ })));
577
+ };
578
+
579
+ // src/views/TagsView.tsx
580
+ import React3, { useState as useState3 } from "react";
581
+ import { TagGenerator, TagSerializer } from "@openrfid/tags";
582
+ var TagsView = ({ simulator, refresh }) => {
583
+ const tags = simulator.getAllTags();
584
+ const [batchCount, setBatchCount] = useState3(10);
585
+ const [genType, setGenType] = useState3("sequential");
586
+ const handleGenerate = () => {
587
+ const batch = TagGenerator.generateBatch({ count: batchCount, type: genType });
588
+ simulator.addTagBatch(batch);
589
+ refresh();
590
+ };
591
+ const handleExportCSV = () => {
592
+ const csv = TagSerializer.toCSV(tags);
593
+ const blob = new Blob([csv], { type: "text/csv" });
594
+ const url = URL.createObjectURL(blob);
595
+ const a = document.createElement("a");
596
+ a.href = url;
597
+ a.download = "tags_export.csv";
598
+ a.click();
599
+ URL.revokeObjectURL(url);
600
+ };
601
+ return /* @__PURE__ */ React3.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "20px" } }, /* @__PURE__ */ React3.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", margin: 0 } }, "Simulated RFID Tags (", tags.length, ")"), /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", gap: "8px" } }, /* @__PURE__ */ React3.createElement(
602
+ "select",
603
+ {
604
+ value: genType,
605
+ onChange: (e) => setGenType(e.target.value),
606
+ style: { padding: "8px", borderRadius: "6px", backgroundColor: "#0F172A", color: "#FFF", border: "1px solid #334155" }
607
+ },
608
+ /* @__PURE__ */ React3.createElement("option", { value: "sequential" }, "Sequential EPC"),
609
+ /* @__PURE__ */ React3.createElement("option", { value: "random" }, "Random Hex"),
610
+ /* @__PURE__ */ React3.createElement("option", { value: "sgtin96" }, "SGTIN-96 GS1")
611
+ ), /* @__PURE__ */ React3.createElement(
612
+ "input",
613
+ {
614
+ type: "number",
615
+ value: batchCount,
616
+ onChange: (e) => setBatchCount(parseInt(e.target.value, 10)),
617
+ style: { width: "60px", padding: "8px", borderRadius: "6px", backgroundColor: "#0F172A", color: "#FFF", border: "1px solid #334155" }
618
+ }
619
+ ), /* @__PURE__ */ React3.createElement(
620
+ "button",
621
+ {
622
+ onClick: handleGenerate,
623
+ style: { backgroundColor: "#00E5EE", color: "#0F172A", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
624
+ },
625
+ "Generate Tags"
626
+ ), /* @__PURE__ */ React3.createElement(
627
+ "button",
628
+ {
629
+ onClick: handleExportCSV,
630
+ style: { backgroundColor: "#334155", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
631
+ },
632
+ "Export CSV"
633
+ ))), /* @__PURE__ */ React3.createElement("div", { style: { backgroundColor: "#1E293B", borderRadius: "8px", border: "1px solid #334155", overflow: "hidden" } }, /* @__PURE__ */ React3.createElement("table", { style: { width: "100%", borderCollapse: "collapse", textAlign: "left", fontSize: "13px" } }, /* @__PURE__ */ React3.createElement("thead", null, /* @__PURE__ */ React3.createElement("tr", { style: { backgroundColor: "#0F172A", borderBottom: "1px solid #334155", color: "#94A3B8" } }, /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "EPC Header"), /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "TID"), /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "Protocol"), /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "Read Count"), /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "Current RSSI"), /* @__PURE__ */ React3.createElement("th", { style: { padding: "12px 16px" } }, "Action"))), /* @__PURE__ */ React3.createElement("tbody", null, tags.slice(0, 100).map((t) => /* @__PURE__ */ React3.createElement("tr", { key: t.id, style: { borderBottom: "1px solid #334155" } }, /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px", fontFamily: "monospace", fontWeight: "bold", color: "#00E5EE" } }, t.epc), /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px", fontFamily: "monospace", color: "#94A3B8" } }, t.tid), /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px" } }, t.protocol), /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px" } }, t.readCount), /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px", color: "#10B981" } }, t.currentRssi, " dBm"), /* @__PURE__ */ React3.createElement("td", { style: { padding: "12px 16px" } }, /* @__PURE__ */ React3.createElement(
634
+ "button",
635
+ {
636
+ onClick: () => {
637
+ simulator.removeTag(t.epc);
638
+ refresh();
639
+ },
640
+ style: { backgroundColor: "#EF4444", color: "#FFF", border: "none", padding: "4px 8px", borderRadius: "4px", cursor: "pointer" }
641
+ },
642
+ "Remove"
643
+ ))))))));
644
+ };
645
+
646
+ // src/views/EventMonitorView.tsx
647
+ import React4, { useState as useState4, useEffect as useEffect2 } from "react";
648
+ import { TagSignalMeter } from "@openrfid/ui";
649
+ var EventMonitorView = ({ eventBus, storage, onViewHistory }) => {
650
+ const [events, setEvents] = useState4([]);
651
+ const [paused, setPaused] = useState4(false);
652
+ const [totalStored, setTotalStored] = useState4(0);
653
+ useEffect2(() => {
654
+ if (!storage) return;
655
+ const loadCount = async () => {
656
+ const stats = await storage.getEventStats();
657
+ setTotalStored(stats.totalEvents);
658
+ };
659
+ loadCount();
660
+ const interval = setInterval(loadCount, 2e3);
661
+ return () => clearInterval(interval);
662
+ }, [storage]);
663
+ useEffect2(() => {
664
+ const unsub = eventBus.on("TagDetected", (payload) => {
665
+ if (paused) return;
666
+ const item = {
667
+ id: `ev_${Date.now()}_${Math.random()}`,
668
+ timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString(),
669
+ readerId: payload.readerId,
670
+ antennaId: payload.antennaId,
671
+ epc: payload.epc,
672
+ rssi: payload.rssi,
673
+ protocol: payload.protocol || "GEN2"
674
+ };
675
+ setEvents((prev) => [item, ...prev.slice(0, 199)]);
676
+ });
677
+ return () => unsub();
678
+ }, [eventBus, paused]);
679
+ return /* @__PURE__ */ React4.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React4.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "20px", flexWrap: "wrap", gap: "12px" } }, /* @__PURE__ */ React4.createElement("div", null, /* @__PURE__ */ React4.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", margin: 0 } }, "Real-Time Event Stream (", events.length, ")"), storage && /* @__PURE__ */ React4.createElement("div", { style: { fontSize: "13px", color: "#94A3B8", marginTop: "4px" } }, "Total persisted events: ", /* @__PURE__ */ React4.createElement("strong", { style: { color: "#00E5EE" } }, totalStored.toLocaleString()), " in database")), /* @__PURE__ */ React4.createElement("div", { style: { display: "flex", gap: "8px" } }, onViewHistory && /* @__PURE__ */ React4.createElement(
680
+ "button",
681
+ {
682
+ onClick: onViewHistory,
683
+ style: { backgroundColor: "#7C3AED", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
684
+ },
685
+ "\u{1F4CA} View Storage History"
686
+ ), /* @__PURE__ */ React4.createElement(
687
+ "button",
688
+ {
689
+ onClick: () => setPaused(!paused),
690
+ style: { backgroundColor: paused ? "#10B981" : "#F59E0B", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
691
+ },
692
+ paused ? "Resume Stream" : "Pause Stream"
693
+ ), /* @__PURE__ */ React4.createElement(
694
+ "button",
695
+ {
696
+ onClick: () => setEvents([]),
697
+ style: { backgroundColor: "#334155", color: "#FFF", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold" }
698
+ },
699
+ "Clear Stream"
700
+ ))), /* @__PURE__ */ React4.createElement("div", { style: { backgroundColor: "#1E293B", borderRadius: "8px", border: "1px solid #334155", overflow: "hidden" } }, /* @__PURE__ */ React4.createElement("table", { style: { width: "100%", borderCollapse: "collapse", textAlign: "left", fontSize: "13px" } }, /* @__PURE__ */ React4.createElement("thead", null, /* @__PURE__ */ React4.createElement("tr", { style: { backgroundColor: "#0F172A", borderBottom: "1px solid #334155", color: "#94A3B8" } }, /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Time"), /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Reader"), /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Antenna Port"), /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Tag EPC"), /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Signal (RSSI)"), /* @__PURE__ */ React4.createElement("th", { style: { padding: "12px 16px" } }, "Protocol"))), /* @__PURE__ */ React4.createElement("tbody", null, events.length === 0 ? /* @__PURE__ */ React4.createElement("tr", null, /* @__PURE__ */ React4.createElement("td", { colSpan: 6, style: { padding: "30px", textAlign: "center", color: "#64748B" } }, "No real-time events. Start simulated readers to see tag detections live.")) : events.map((e) => /* @__PURE__ */ React4.createElement("tr", { key: e.id, style: { borderBottom: "1px solid #334155" } }, /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px", color: "#94A3B8" } }, e.timestamp), /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px", fontWeight: "bold" } }, e.readerId), /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px" } }, "Port #", e.antennaId), /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px", fontFamily: "monospace", fontWeight: "bold", color: "#00E5EE" } }, e.epc), /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px" } }, /* @__PURE__ */ React4.createElement(TagSignalMeter, { rssi: e.rssi })), /* @__PURE__ */ React4.createElement("td", { style: { padding: "12px 16px" } }, e.protocol)))))));
701
+ };
702
+
703
+ // src/views/EventHistoryView.tsx
704
+ import React5, { useState as useState5, useEffect as useEffect3, useCallback } from "react";
705
+ var PAGE_SIZE = 1e3;
706
+ var card2 = {
707
+ backgroundColor: "#1E293B",
708
+ borderRadius: "10px",
709
+ border: "1px solid #334155",
710
+ padding: "20px"
711
+ };
712
+ var btn = (color) => ({
713
+ backgroundColor: color,
714
+ color: "#fff",
715
+ border: "none",
716
+ padding: "6px 14px",
717
+ borderRadius: "6px",
718
+ cursor: "pointer",
719
+ fontWeight: "bold",
720
+ fontSize: "13px"
721
+ });
722
+ var EventHistoryView = ({ storage }) => {
723
+ const [stats, setStats] = useState5(null);
724
+ const [expandedDate, setExpandedDate] = useState5(null);
725
+ const [events, setEvents] = useState5([]);
726
+ const [page, setPage] = useState5(0);
727
+ const [totalCount, setTotalCount] = useState5(0);
728
+ const [filterEpc, setFilterEpc] = useState5("");
729
+ const [filterReader, setFilterReader] = useState5("");
730
+ const [filterProtocol, setFilterProtocol] = useState5("");
731
+ const [confirmClear, setConfirmClear] = useState5(null);
732
+ const [loading, setLoading] = useState5(false);
733
+ const loadStats = useCallback(async () => {
734
+ const s = await storage.getEventStats();
735
+ setStats(s);
736
+ }, [storage]);
737
+ useEffect3(() => {
738
+ loadStats();
739
+ const interval = setInterval(loadStats, 5e3);
740
+ return () => clearInterval(interval);
741
+ }, [loadStats]);
742
+ const loadDayEvents = useCallback(async (date, pageNum = 0) => {
743
+ setLoading(true);
744
+ const [evts, count] = await Promise.all([
745
+ storage.getEvents({
746
+ date,
747
+ epc: filterEpc || void 0,
748
+ readerId: filterReader || void 0,
749
+ protocol: filterProtocol || void 0,
750
+ limit: PAGE_SIZE,
751
+ offset: pageNum * PAGE_SIZE
752
+ }),
753
+ storage.getEventCount({
754
+ date,
755
+ epc: filterEpc || void 0,
756
+ readerId: filterReader || void 0,
757
+ protocol: filterProtocol || void 0
758
+ })
759
+ ]);
760
+ setEvents(evts);
761
+ setTotalCount(count);
762
+ setPage(pageNum);
763
+ setLoading(false);
764
+ }, [storage, filterEpc, filterReader, filterProtocol]);
765
+ const handleExpandDate = (date) => {
766
+ if (expandedDate === date) {
767
+ setExpandedDate(null);
768
+ setEvents([]);
769
+ } else {
770
+ setExpandedDate(date);
771
+ loadDayEvents(date, 0);
772
+ }
773
+ };
774
+ const handleDeleteDay = async (date) => {
775
+ if (!window.confirm(`Delete ALL events for ${date}? This cannot be undone.`)) return;
776
+ await storage.deleteEventsByDate(date);
777
+ if (expandedDate === date) {
778
+ setExpandedDate(null);
779
+ setEvents([]);
780
+ }
781
+ await loadStats();
782
+ };
783
+ const handleExportCSV = async (date) => {
784
+ const evts = await storage.getEvents({ date, limit: 1e5 });
785
+ const csv = [
786
+ "id,date,timestamp,readerId,antennaId,epc,rssi,protocol",
787
+ ...evts.map((e) => `${e.id},${e.date},${e.timestamp},${e.readerId},${e.antennaId},${e.epc},${e.rssi},${e.protocol}`)
788
+ ].join("\n");
789
+ const blob = new Blob([csv], { type: "text/csv" });
790
+ const url = URL.createObjectURL(blob);
791
+ const a = document.createElement("a");
792
+ a.href = url;
793
+ a.download = `openrfid-events-${date}.csv`;
794
+ a.click();
795
+ URL.revokeObjectURL(url);
796
+ };
797
+ const handleClearEvents = async () => {
798
+ await storage.clearAllEvents();
799
+ setConfirmClear(null);
800
+ setExpandedDate(null);
801
+ setEvents([]);
802
+ await loadStats();
803
+ };
804
+ const handleClearAll = async () => {
805
+ await storage.clearAllData();
806
+ setConfirmClear(null);
807
+ setExpandedDate(null);
808
+ setEvents([]);
809
+ await loadStats();
810
+ };
811
+ const totalPages = Math.ceil(totalCount / PAGE_SIZE);
812
+ return /* @__PURE__ */ React5.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React5.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", marginBottom: "20px" } }, "\u{1F4CA} Event History & Storage Manager"), stats && /* @__PURE__ */ React5.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: "12px", marginBottom: "20px" } }, [
813
+ { label: "Total Events", value: stats.totalEvents.toLocaleString(), color: "#00E5EE" },
814
+ { label: "Days with Data", value: String(stats.totalDays), color: "#10B981" },
815
+ { label: "Oldest Day", value: stats.oldestEventDate ?? "\u2014", color: "#94A3B8" },
816
+ { label: "Newest Day", value: stats.newestEventDate ?? "\u2014", color: "#94A3B8" }
817
+ ].map((stat) => /* @__PURE__ */ React5.createElement("div", { key: stat.label, style: card2 }, /* @__PURE__ */ React5.createElement("div", { style: { fontSize: "12px", color: "#94A3B8" } }, stat.label), /* @__PURE__ */ React5.createElement("div", { style: { fontSize: "22px", fontWeight: "bold", color: stat.color, marginTop: "4px" } }, stat.value)))), /* @__PURE__ */ React5.createElement("div", { style: { ...card2, display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" } }, /* @__PURE__ */ React5.createElement("span", { style: { fontWeight: "bold", fontSize: "15px" } }, "Day-by-Day Event Log"), /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", gap: "10px" } }, /* @__PURE__ */ React5.createElement("button", { style: btn("#EF4444"), onClick: () => setConfirmClear("events") }, "\u{1F5D1} Clear All Events"), /* @__PURE__ */ React5.createElement("button", { style: btn("#7C3AED"), onClick: () => setConfirmClear("all") }, "\u2622\uFE0F Clear All Data"))), confirmClear && /* @__PURE__ */ React5.createElement("div", { style: { ...card2, border: "1px solid #EF4444", marginBottom: "16px", backgroundColor: "#1F0A0A" } }, /* @__PURE__ */ React5.createElement("p", { style: { color: "#FCA5A5", marginBottom: "12px" } }, confirmClear === "events" ? "\u26A0\uFE0F This will permanently delete ALL event history. Readers and tags are kept." : "\u2622\uFE0F This will permanently delete ALL readers, tags, and event history. Cannot be undone."), /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", gap: "10px" } }, /* @__PURE__ */ React5.createElement("button", { style: btn("#EF4444"), onClick: confirmClear === "events" ? handleClearEvents : handleClearAll }, "Confirm Delete"), /* @__PURE__ */ React5.createElement("button", { style: { ...btn("#334155") }, onClick: () => setConfirmClear(null) }, "Cancel"))), stats?.eventsByDate.length === 0 && /* @__PURE__ */ React5.createElement("div", { style: { ...card2, textAlign: "center", color: "#64748B", padding: "40px" } }, "No event history yet. Start a reader and let it run to generate events."), stats?.eventsByDate.map(({ date, count }) => /* @__PURE__ */ React5.createElement("div", { key: date, style: { ...card2, marginBottom: "10px" } }, /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", alignItems: "center", gap: "12px" } }, /* @__PURE__ */ React5.createElement("span", { style: { fontSize: "18px", cursor: "pointer" }, onClick: () => handleExpandDate(date) }, expandedDate === date ? "\u25BC" : "\u25B6"), /* @__PURE__ */ React5.createElement("div", null, /* @__PURE__ */ React5.createElement("span", { style: { fontWeight: "bold", fontSize: "15px", cursor: "pointer" }, onClick: () => handleExpandDate(date) }, date), /* @__PURE__ */ React5.createElement("span", { style: { fontSize: "13px", color: "#94A3B8", marginLeft: "10px" } }, count.toLocaleString(), " events"))), /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", gap: "8px" } }, /* @__PURE__ */ React5.createElement("button", { style: btn("#0EA5E9"), onClick: () => handleExpandDate(date) }, expandedDate === date ? "Collapse" : "View"), /* @__PURE__ */ React5.createElement("button", { style: btn("#10B981"), onClick: () => handleExportCSV(date) }, "Export CSV"), /* @__PURE__ */ React5.createElement("button", { style: btn("#EF4444"), onClick: () => handleDeleteDay(date) }, "Delete"))), expandedDate === date && /* @__PURE__ */ React5.createElement("div", { style: { marginTop: "16px" } }, /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", gap: "10px", marginBottom: "12px", flexWrap: "wrap" } }, /* @__PURE__ */ React5.createElement(
818
+ "input",
819
+ {
820
+ placeholder: "Filter by EPC...",
821
+ value: filterEpc,
822
+ onChange: (e) => setFilterEpc(e.target.value),
823
+ style: { backgroundColor: "#0F172A", border: "1px solid #334155", color: "#F8FAFC", padding: "6px 10px", borderRadius: "6px", fontSize: "13px", width: "180px" }
824
+ }
825
+ ), /* @__PURE__ */ React5.createElement(
826
+ "input",
827
+ {
828
+ placeholder: "Filter by Reader...",
829
+ value: filterReader,
830
+ onChange: (e) => setFilterReader(e.target.value),
831
+ style: { backgroundColor: "#0F172A", border: "1px solid #334155", color: "#F8FAFC", padding: "6px 10px", borderRadius: "6px", fontSize: "13px", width: "180px" }
832
+ }
833
+ ), /* @__PURE__ */ React5.createElement(
834
+ "select",
835
+ {
836
+ value: filterProtocol,
837
+ onChange: (e) => setFilterProtocol(e.target.value),
838
+ style: { backgroundColor: "#0F172A", border: "1px solid #334155", color: "#F8FAFC", padding: "6px 10px", borderRadius: "6px", fontSize: "13px" }
839
+ },
840
+ /* @__PURE__ */ React5.createElement("option", { value: "" }, "All Protocols"),
841
+ /* @__PURE__ */ React5.createElement("option", { value: "GEN2" }, "GEN2"),
842
+ /* @__PURE__ */ React5.createElement("option", { value: "ISO15693" }, "ISO15693"),
843
+ /* @__PURE__ */ React5.createElement("option", { value: "ISO14443A" }, "ISO14443A")
844
+ ), /* @__PURE__ */ React5.createElement("button", { style: btn("#00E5EE"), onClick: () => loadDayEvents(date, 0) }, "Search")), /* @__PURE__ */ React5.createElement("div", { style: { overflowX: "auto" } }, /* @__PURE__ */ React5.createElement("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: "13px" } }, /* @__PURE__ */ React5.createElement("thead", null, /* @__PURE__ */ React5.createElement("tr", { style: { backgroundColor: "#0F172A", color: "#94A3B8" } }, ["Time", "Reader", "Ant", "EPC", "RSSI", "Protocol"].map((h) => /* @__PURE__ */ React5.createElement("th", { key: h, style: { padding: "8px 12px", textAlign: "left", borderBottom: "1px solid #334155" } }, h)))), /* @__PURE__ */ React5.createElement("tbody", null, loading ? /* @__PURE__ */ React5.createElement("tr", null, /* @__PURE__ */ React5.createElement("td", { colSpan: 6, style: { padding: "20px", textAlign: "center", color: "#64748B" } }, "Loading...")) : events.length === 0 ? /* @__PURE__ */ React5.createElement("tr", null, /* @__PURE__ */ React5.createElement("td", { colSpan: 6, style: { padding: "20px", textAlign: "center", color: "#64748B" } }, "No events match the current filters.")) : events.map((e) => /* @__PURE__ */ React5.createElement("tr", { key: e.id, style: { borderBottom: "1px solid #1E293B" } }, /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px", color: "#94A3B8" } }, e.timestamp.split("T")[1]?.split(".")[0] ?? e.timestamp), /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px" } }, e.readerId), /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px", color: "#94A3B8" } }, e.antennaId), /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px", fontFamily: "monospace", color: "#00E5EE" } }, e.epc), /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px", color: e.rssi > -60 ? "#10B981" : "#F59E0B" } }, e.rssi, " dBm"), /* @__PURE__ */ React5.createElement("td", { style: { padding: "7px 12px", color: "#94A3B8" } }, e.protocol)))))), totalPages > 1 && /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", alignItems: "center", gap: "12px", marginTop: "12px", justifyContent: "center" } }, /* @__PURE__ */ React5.createElement(
845
+ "button",
846
+ {
847
+ style: btn(page > 0 ? "#334155" : "#1E293B"),
848
+ disabled: page === 0,
849
+ onClick: () => loadDayEvents(date, page - 1)
850
+ },
851
+ "\u2190 Prev"
852
+ ), /* @__PURE__ */ React5.createElement("span", { style: { color: "#94A3B8", fontSize: "13px" } }, "Page ", page + 1, " of ", totalPages, " (", totalCount.toLocaleString(), " events)"), /* @__PURE__ */ React5.createElement(
853
+ "button",
854
+ {
855
+ style: btn(page < totalPages - 1 ? "#334155" : "#1E293B"),
856
+ disabled: page >= totalPages - 1,
857
+ onClick: () => loadDayEvents(date, page + 1)
858
+ },
859
+ "Next \u2192"
860
+ ))))));
861
+ };
862
+
863
+ // src/views/ProtocolsPluginsView.tsx
864
+ import React6 from "react";
865
+ var ProtocolsPluginsView = ({ pluginManager }) => {
866
+ const plugins = pluginManager.getAllPlugins();
867
+ return /* @__PURE__ */ React6.createElement("div", { style: { padding: "24px" } }, /* @__PURE__ */ React6.createElement("h2", { style: { fontSize: "20px", fontWeight: "bold", marginBottom: "20px" } }, "Protocol Servers & Installed Plugins"), /* @__PURE__ */ React6.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))", gap: "16px" } }, plugins.map((p) => /* @__PURE__ */ React6.createElement("div", { key: p.metadata.name, style: { backgroundColor: "#1E293B", padding: "20px", borderRadius: "10px", border: "1px solid #334155" } }, /* @__PURE__ */ React6.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, /* @__PURE__ */ React6.createElement("h3", { style: { margin: 0, fontSize: "16px", color: "#00E5EE" } }, p.metadata.name), /* @__PURE__ */ React6.createElement(
868
+ "span",
869
+ {
870
+ style: {
871
+ backgroundColor: p.state === "STARTED" ? "#10B981" : "#64748B",
872
+ color: "#FFF",
873
+ fontSize: "11px",
874
+ padding: "2px 8px",
875
+ borderRadius: "10px",
876
+ fontWeight: "bold"
877
+ }
878
+ },
879
+ p.state
880
+ )), /* @__PURE__ */ React6.createElement("div", { style: { fontSize: "12px", color: "#94A3B8", marginTop: "8px" } }, "v", p.metadata.version), /* @__PURE__ */ React6.createElement("p", { style: { fontSize: "13px", color: "#CBD5E1", marginTop: "10px" } }, p.metadata.description)))));
881
+ };
882
+ export {
883
+ DashboardView,
884
+ EventHistoryView,
885
+ EventMonitorView,
886
+ ProtocolsPluginsView,
887
+ ReadersView,
888
+ TagsView
889
+ };