packetsnitch 1.5.599
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/.eslintrc.json +28 -0
- package/.webpack/x64/main/index.js +2 -0
- package/.webpack/x64/main/index.js.map +1 -0
- package/.webpack/x64/renderer/assets/css/rubikglitch.woff2 +0 -0
- package/.webpack/x64/renderer/assets/css/style.css +1916 -0
- package/.webpack/x64/renderer/assets/images/loading.gif +0 -0
- package/.webpack/x64/renderer/assets/images/logo.webp +0 -0
- package/.webpack/x64/renderer/assets/images/packet-snitch-tag.webp +0 -0
- package/.webpack/x64/renderer/main_window/index.html +3 -0
- package/.webpack/x64/renderer/main_window/index.js +3 -0
- package/.webpack/x64/renderer/main_window/index.js.LICENSE.txt +36 -0
- package/.webpack/x64/renderer/main_window/index.js.map +1 -0
- package/.webpack/x64/renderer/main_window/preload.js +2 -0
- package/.webpack/x64/renderer/main_window/preload.js.map +1 -0
- package/backend/common/GeoLite2-City.mmdb +0 -0
- package/backend/common/mac-vendors-export.csv +56923 -0
- package/backend/common/service-names-port-numbers.csv +15368 -0
- package/backend/requirements.txt +14 -0
- package/backend/snitch.py +3611 -0
- package/forge.config.js +80 -0
- package/package.json +102 -0
- package/ps-icon.ico +0 -0
- package/snitch.spec +44 -0
- package/src/assets/css/rubikglitch.woff2 +0 -0
- package/src/assets/css/style.css +1916 -0
- package/src/assets/images/loading.gif +0 -0
- package/src/assets/images/logo.webp +0 -0
- package/src/assets/images/packet-snitch-tag.webp +0 -0
- package/src/back-comm.js +70 -0
- package/src/decoders.js +579 -0
- package/src/filter.js +461 -0
- package/src/front.js +10 -0
- package/src/index.html +1036 -0
- package/src/logging.js +150 -0
- package/src/main.js +571 -0
- package/src/preload.js +73 -0
- package/src/renderer.js +30 -0
- package/src/ui/common-frontend.js +13 -0
- package/src/ui/context-menu.js +88 -0
- package/src/ui/decoders.js +1 -0
- package/src/ui/main-frontend.js +4957 -0
- package/src/ui/panels/crypt-panel.js +565 -0
- package/src/ui/panels/data-panel.js +151 -0
- package/src/ui/panels/data-tools-panel.js +939 -0
- package/src/ui/panels/install-screen.js +59 -0
- package/src/ui/panels/keystore-panel.js +1248 -0
- package/src/ui/panels/list-panel.js +403 -0
- package/src/ui/panels/stats-panel.js +351 -0
- package/src/ui/panels/summary-panel.js +63 -0
- package/webpack.main.config.js +11 -0
- package/webpack.plugins.js +13 -0
- package/webpack.preload.config.js +7 -0
- package/webpack.renderer.config.js +30 -0
- package/webpack.rules.js +35 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
function createListPanel({
|
|
2
|
+
constants,
|
|
3
|
+
getJsonCapture,
|
|
4
|
+
getCapturedPackets,
|
|
5
|
+
getBookmarkList,
|
|
6
|
+
setActiveMainTab,
|
|
7
|
+
statusUpdate,
|
|
8
|
+
writeLogEntry,
|
|
9
|
+
hostFilterEl,
|
|
10
|
+
filterInputEl,
|
|
11
|
+
syncFilterHighlight,
|
|
12
|
+
runFilterQuery,
|
|
13
|
+
getFilteredPackets,
|
|
14
|
+
setPacketsForHost,
|
|
15
|
+
setIndex,
|
|
16
|
+
setCurrentIp,
|
|
17
|
+
setCurrentPacketKey,
|
|
18
|
+
syncBookmarkDropdown,
|
|
19
|
+
setActivePacketCursor,
|
|
20
|
+
showAllData,
|
|
21
|
+
infoPanel,
|
|
22
|
+
popHexGrid,
|
|
23
|
+
populateDataTypes,
|
|
24
|
+
}) {
|
|
25
|
+
const { MAIN_TAB_LIST } = constants;
|
|
26
|
+
|
|
27
|
+
function buildStreamFilterQuery(transport, srcIp, dstIp, srcPort, dstPort) {
|
|
28
|
+
if (!srcIp || !dstIp) return null;
|
|
29
|
+
const tp = (transport || "").toLowerCase();
|
|
30
|
+
const hasPorts =
|
|
31
|
+
(srcPort !== "" && srcPort !== undefined && srcPort !== null) &&
|
|
32
|
+
(dstPort !== "" && dstPort !== undefined && dstPort !== null);
|
|
33
|
+
if (hasPorts && (tp === "tcp" || tp === "udp")) {
|
|
34
|
+
return (
|
|
35
|
+
`(ip.src.addr: ${srcIp} && ip.dst.addr: ${dstIp} && ${tp}.src.port: ${srcPort} && ${tp}.dst.port: ${dstPort})` +
|
|
36
|
+
` || ` +
|
|
37
|
+
`(ip.src.addr: ${dstIp} && ip.dst.addr: ${srcIp} && ${tp}.src.port: ${dstPort} && ${tp}.dst.port: ${srcPort})`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return `(ip.src.addr: ${srcIp} && ip.dst.addr: ${dstIp}) || (ip.src.addr: ${dstIp} && ip.dst.addr: ${srcIp})`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function showPacketList() {
|
|
44
|
+
setActiveMainTab(MAIN_TAB_LIST);
|
|
45
|
+
if (getJsonCapture() === "") {
|
|
46
|
+
statusUpdate("Status: No JSON file loaded, please upload a file first");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
statusUpdate("Status: Displaying packet list");
|
|
50
|
+
writeLogEntry("User opened packet list view");
|
|
51
|
+
|
|
52
|
+
document.getElementById("packetInfoPane").style.display = "none";
|
|
53
|
+
document.getElementById("packetPayloadPane").style.display = "none";
|
|
54
|
+
document.getElementById("prev-btn").style.display = "none";
|
|
55
|
+
document.getElementById("next-btn").style.display = "none";
|
|
56
|
+
document.getElementById("summary_box").style.display = "none";
|
|
57
|
+
document.getElementById("stats_box").style.display = "none";
|
|
58
|
+
document.getElementById("data_tools_box").style.display = "none";
|
|
59
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
60
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
61
|
+
document.getElementById("notes_box").style.display = "none";
|
|
62
|
+
document.getElementById("rightside").style.display = "none";
|
|
63
|
+
const listBox = document.getElementById("list_box");
|
|
64
|
+
listBox.style.display = "flex";
|
|
65
|
+
|
|
66
|
+
const content = document.getElementById("list_content");
|
|
67
|
+
const searchEl = document.getElementById("list-search");
|
|
68
|
+
const groupByStreamEl = document.getElementById("list-group-streams");
|
|
69
|
+
const columnDefinitions = [
|
|
70
|
+
{ label: "#", key: "idx" },
|
|
71
|
+
{ label: "★", key: "isBookmarked" },
|
|
72
|
+
{ label: "Stream", key: "streamOrder" },
|
|
73
|
+
{ label: "Host", key: "host" },
|
|
74
|
+
{ label: "Src IP", key: "srcIp" },
|
|
75
|
+
{ label: "Dst IP", key: "dstIp" },
|
|
76
|
+
{ label: "Src Port", key: "srcPort" },
|
|
77
|
+
{ label: "Dst Port", key: "dstPort" },
|
|
78
|
+
{ label: "Transport", key: "transport" },
|
|
79
|
+
{ label: "App Protocol", key: "appProto" },
|
|
80
|
+
];
|
|
81
|
+
const sortState = { key: "idx", direction: "asc" };
|
|
82
|
+
|
|
83
|
+
function buildTable(filterText) {
|
|
84
|
+
content.replaceChildren();
|
|
85
|
+
const capturedPackets = getCapturedPackets();
|
|
86
|
+
if (!capturedPackets || !capturedPackets["Host"]) {
|
|
87
|
+
content.textContent = "No packet data available.";
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hosts = Object.keys(capturedPackets["Host"]).sort();
|
|
92
|
+
const lc = filterText ? filterText.toLowerCase() : "";
|
|
93
|
+
|
|
94
|
+
const rows = [];
|
|
95
|
+
|
|
96
|
+
const getStreamKey = (packetInfo) => {
|
|
97
|
+
const transportName = packetInfo?.["Protocol"] || "Unknown";
|
|
98
|
+
const transportData = packetInfo?.[transportName] || {};
|
|
99
|
+
const sourceIp = packetInfo?.["IP"]?.["Source IP"] ?? "";
|
|
100
|
+
const destinationIp = packetInfo?.["IP"]?.["Destination IP"] ?? "";
|
|
101
|
+
const sourcePort = transportData?.["Source port"] ?? "";
|
|
102
|
+
const destinationPort = transportData?.["Destination port"] ?? "";
|
|
103
|
+
|
|
104
|
+
const endpointA = `${sourceIp}:${sourcePort}`;
|
|
105
|
+
const endpointB = `${destinationIp}:${destinationPort}`;
|
|
106
|
+
const [firstEndpoint, secondEndpoint] = [endpointA, endpointB].sort();
|
|
107
|
+
return `${transportName}|${firstEndpoint}|${secondEndpoint}`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
for (const host of hosts) {
|
|
111
|
+
const packets = capturedPackets["Host"][host];
|
|
112
|
+
if (!Array.isArray(packets)) continue;
|
|
113
|
+
|
|
114
|
+
packets.forEach((pkt, pktIdx) => {
|
|
115
|
+
const pi = pkt?.["Packet Info"];
|
|
116
|
+
const ei = pkt?.["Extra Info"];
|
|
117
|
+
if (!pi) return;
|
|
118
|
+
|
|
119
|
+
const idx = pi["Index"] ?? pktIdx + 1;
|
|
120
|
+
const srcIp = pi?.["IP"]?.["Source IP"] ?? "";
|
|
121
|
+
const dstIp = pi?.["IP"]?.["Destination IP"] ?? "";
|
|
122
|
+
const transport = pi["Protocol"] || "TCP";
|
|
123
|
+
const tpData = pi[transport] || null;
|
|
124
|
+
const srcPort = tpData?.["Source port"] ?? "";
|
|
125
|
+
const dstPort = tpData?.["Destination port"] ?? "";
|
|
126
|
+
const netData = ei?.["Traits"]?.["Network Data"];
|
|
127
|
+
const appProto =
|
|
128
|
+
netData?.["Port Protocol"] ?? netData?.["Port Protcol"] ?? "";
|
|
129
|
+
const packetKey = srcIp + ":" + pi["Index"];
|
|
130
|
+
const isBookmarked = getBookmarkList().includes(packetKey);
|
|
131
|
+
const streamKey = getStreamKey(pi);
|
|
132
|
+
|
|
133
|
+
if (lc) {
|
|
134
|
+
const rowText = [
|
|
135
|
+
host,
|
|
136
|
+
srcIp,
|
|
137
|
+
dstIp,
|
|
138
|
+
String(srcPort),
|
|
139
|
+
String(dstPort),
|
|
140
|
+
transport,
|
|
141
|
+
appProto,
|
|
142
|
+
]
|
|
143
|
+
.join(" ")
|
|
144
|
+
.toLowerCase();
|
|
145
|
+
if (!rowText.includes(lc)) return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
rows.push({
|
|
149
|
+
idx,
|
|
150
|
+
host,
|
|
151
|
+
srcIp,
|
|
152
|
+
dstIp,
|
|
153
|
+
srcPort,
|
|
154
|
+
dstPort,
|
|
155
|
+
transport,
|
|
156
|
+
appProto,
|
|
157
|
+
pktIdx,
|
|
158
|
+
pi,
|
|
159
|
+
streamKey,
|
|
160
|
+
isBookmarked,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const streamOrderMap = new Map();
|
|
166
|
+
let nextStreamOrder = 1;
|
|
167
|
+
rows.forEach((row) => {
|
|
168
|
+
if (!streamOrderMap.has(row.streamKey)) {
|
|
169
|
+
streamOrderMap.set(row.streamKey, nextStreamOrder++);
|
|
170
|
+
}
|
|
171
|
+
row.streamOrder = streamOrderMap.get(row.streamKey);
|
|
172
|
+
row.streamLabel = `S${row.streamOrder}`;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const activeGroupByStream =
|
|
176
|
+
document.getElementById("list-group-streams")?.checked;
|
|
177
|
+
const sortDirection = sortState.direction === "asc" ? 1 : -1;
|
|
178
|
+
const compareText = (left, right) =>
|
|
179
|
+
String(left ?? "").localeCompare(String(right ?? ""));
|
|
180
|
+
const comparePortValue = (left, right) => {
|
|
181
|
+
const leftNum = Number(left);
|
|
182
|
+
const rightNum = Number(right);
|
|
183
|
+
const leftIsNumber = Number.isFinite(leftNum);
|
|
184
|
+
const rightIsNumber = Number.isFinite(rightNum);
|
|
185
|
+
if (leftIsNumber && rightIsNumber) return leftNum - rightNum;
|
|
186
|
+
return compareText(left, right);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const compareByColumn = (left, right, columnKey) => {
|
|
190
|
+
switch (columnKey) {
|
|
191
|
+
case "idx":
|
|
192
|
+
case "streamOrder":
|
|
193
|
+
return Number(left[columnKey]) - Number(right[columnKey]);
|
|
194
|
+
case "isBookmarked":
|
|
195
|
+
return Number(left.isBookmarked) - Number(right.isBookmarked);
|
|
196
|
+
case "srcPort":
|
|
197
|
+
case "dstPort":
|
|
198
|
+
return comparePortValue(left[columnKey], right[columnKey]);
|
|
199
|
+
default:
|
|
200
|
+
return compareText(left[columnKey], right[columnKey]);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
rows.sort((left, right) => {
|
|
205
|
+
if (activeGroupByStream && sortState.key !== "streamOrder") {
|
|
206
|
+
const streamDiff = left.streamOrder - right.streamOrder;
|
|
207
|
+
if (streamDiff !== 0) return streamDiff;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const sortedDiff = compareByColumn(left, right, sortState.key);
|
|
211
|
+
if (sortedDiff !== 0) return sortedDiff * sortDirection;
|
|
212
|
+
return Number(left.idx) - Number(right.idx);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const table = document.createElement("table");
|
|
216
|
+
table.className = "packet-list-table";
|
|
217
|
+
|
|
218
|
+
const thead = document.createElement("thead");
|
|
219
|
+
const headerRow = document.createElement("tr");
|
|
220
|
+
columnDefinitions.forEach((column) => {
|
|
221
|
+
const th = document.createElement("th");
|
|
222
|
+
const isActiveSort = sortState.key === column.key;
|
|
223
|
+
const sortArrow = isActiveSort
|
|
224
|
+
? sortState.direction === "asc"
|
|
225
|
+
? " ▲"
|
|
226
|
+
: " ▼"
|
|
227
|
+
: "";
|
|
228
|
+
th.textContent = column.label + sortArrow;
|
|
229
|
+
th.classList.add("packet-list-sortable-header");
|
|
230
|
+
th.tabIndex = 0;
|
|
231
|
+
th.title = `Sort by ${column.label}`;
|
|
232
|
+
th.setAttribute(
|
|
233
|
+
"aria-sort",
|
|
234
|
+
isActiveSort
|
|
235
|
+
? sortState.direction === "asc"
|
|
236
|
+
? "ascending"
|
|
237
|
+
: "descending"
|
|
238
|
+
: "none",
|
|
239
|
+
);
|
|
240
|
+
const sortByColumn = () => {
|
|
241
|
+
if (sortState.key === column.key) {
|
|
242
|
+
sortState.direction = sortState.direction === "asc" ? "desc" : "asc";
|
|
243
|
+
} else {
|
|
244
|
+
sortState.key = column.key;
|
|
245
|
+
sortState.direction = "asc";
|
|
246
|
+
}
|
|
247
|
+
buildTable(document.getElementById("list-search")?.value || "");
|
|
248
|
+
};
|
|
249
|
+
th.addEventListener("click", sortByColumn);
|
|
250
|
+
th.addEventListener("keydown", (event) => {
|
|
251
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
sortByColumn();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
headerRow.appendChild(th);
|
|
257
|
+
});
|
|
258
|
+
thead.appendChild(headerRow);
|
|
259
|
+
table.appendChild(thead);
|
|
260
|
+
|
|
261
|
+
const tbody = document.createElement("tbody");
|
|
262
|
+
|
|
263
|
+
if (rows.length === 0) {
|
|
264
|
+
const tr = document.createElement("tr");
|
|
265
|
+
const td = document.createElement("td");
|
|
266
|
+
td.colSpan = columnDefinitions.length;
|
|
267
|
+
td.textContent = filterText
|
|
268
|
+
? "No packets match the filter."
|
|
269
|
+
: "No packets available.";
|
|
270
|
+
td.style.textAlign = "center";
|
|
271
|
+
td.style.padding = "12px";
|
|
272
|
+
tr.appendChild(td);
|
|
273
|
+
tbody.appendChild(tr);
|
|
274
|
+
} else {
|
|
275
|
+
let previousStreamLabel = "";
|
|
276
|
+
rows.forEach((row) => {
|
|
277
|
+
const tr = document.createElement("tr");
|
|
278
|
+
tr.dataset.host = row.host;
|
|
279
|
+
tr.dataset.pktIdx = row.pktIdx;
|
|
280
|
+
tr.dataset.stream = row.streamLabel;
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
activeGroupByStream &&
|
|
284
|
+
previousStreamLabel !== "" &&
|
|
285
|
+
previousStreamLabel !== row.streamLabel
|
|
286
|
+
) {
|
|
287
|
+
tr.classList.add("packet-list-stream-break");
|
|
288
|
+
}
|
|
289
|
+
previousStreamLabel = row.streamLabel;
|
|
290
|
+
|
|
291
|
+
[
|
|
292
|
+
row.idx,
|
|
293
|
+
row.isBookmarked ? "★" : "",
|
|
294
|
+
row.streamLabel,
|
|
295
|
+
row.host,
|
|
296
|
+
row.srcIp,
|
|
297
|
+
row.dstIp,
|
|
298
|
+
row.srcPort,
|
|
299
|
+
row.dstPort,
|
|
300
|
+
row.transport,
|
|
301
|
+
row.appProto,
|
|
302
|
+
].forEach((val) => {
|
|
303
|
+
const td = document.createElement("td");
|
|
304
|
+
td.textContent = val ?? "";
|
|
305
|
+
tr.appendChild(td);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
tr.addEventListener("mouseenter", () => {
|
|
309
|
+
tr.classList.add("packet-list-hovered");
|
|
310
|
+
});
|
|
311
|
+
tr.addEventListener("mouseleave", () => {
|
|
312
|
+
tr.classList.remove("packet-list-hovered");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
tr.addEventListener("click", () => {
|
|
316
|
+
tbody
|
|
317
|
+
.querySelectorAll(".packet-list-selected")
|
|
318
|
+
.forEach((r) => r.classList.remove("packet-list-selected"));
|
|
319
|
+
tr.classList.add("packet-list-selected");
|
|
320
|
+
|
|
321
|
+
hostFilterEl.value = row.host;
|
|
322
|
+
document.getElementById("target_hosts").value = row.host;
|
|
323
|
+
setCurrentIp(row.srcIp);
|
|
324
|
+
setCurrentPacketKey(row.srcIp + ":" + row.pi["Index"]);
|
|
325
|
+
syncBookmarkDropdown(row.srcIp + ":" + row.pi["Index"]);
|
|
326
|
+
writeLogEntry(
|
|
327
|
+
`Packet list row selected host=${row.host} index=${row.pi["Index"]}`,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const streamFilter = buildStreamFilterQuery(
|
|
331
|
+
row.transport, row.srcIp, row.dstIp, row.srcPort, row.dstPort,
|
|
332
|
+
);
|
|
333
|
+
if (streamFilter) {
|
|
334
|
+
filterInputEl.value = streamFilter;
|
|
335
|
+
syncFilterHighlight();
|
|
336
|
+
runFilterQuery(streamFilter);
|
|
337
|
+
setPacketsForHost(getFilteredPackets());
|
|
338
|
+
} else {
|
|
339
|
+
const capturedPackets = getCapturedPackets();
|
|
340
|
+
const hostPackets = capturedPackets["Host"][row.host];
|
|
341
|
+
setPacketsForHost(hostPackets);
|
|
342
|
+
setIndex(row.pktIdx);
|
|
343
|
+
setActivePacketCursor(row.pktIdx);
|
|
344
|
+
document.getElementById("list_box").style.display = "none";
|
|
345
|
+
document.getElementById("data_tools_box").style.display = "none";
|
|
346
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
347
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
348
|
+
document.getElementById("notes_box").style.display = "none";
|
|
349
|
+
document.getElementById("packetInfoPane").style.display = "block";
|
|
350
|
+
document.getElementById("packetPayloadPane").style.display = "block";
|
|
351
|
+
const rightsideDataEl = document.getElementById("rightside-data");
|
|
352
|
+
const rightsideNotesEl = document.getElementById("rightside-notes");
|
|
353
|
+
if (rightsideDataEl) rightsideDataEl.hidden = false;
|
|
354
|
+
if (rightsideNotesEl) rightsideNotesEl.hidden = true;
|
|
355
|
+
showAllData();
|
|
356
|
+
infoPanel(hostPackets);
|
|
357
|
+
const hexPayload =
|
|
358
|
+
hostPackets[row.pktIdx]?.["Packet Info"]?.["Raw data"]?.[
|
|
359
|
+
"Payload"
|
|
360
|
+
]?.["Hex Encoded"];
|
|
361
|
+
if (hexPayload) popHexGrid(hexPayload);
|
|
362
|
+
populateDataTypes(hostPackets);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
statusUpdate(
|
|
366
|
+
"Status: Displaying packet " +
|
|
367
|
+
row.pi["Index"] +
|
|
368
|
+
" for host " +
|
|
369
|
+
row.host,
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
tbody.appendChild(tr);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
table.appendChild(tbody);
|
|
378
|
+
content.appendChild(table);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
buildTable(searchEl.value);
|
|
382
|
+
|
|
383
|
+
const newSearch = searchEl.cloneNode(true);
|
|
384
|
+
searchEl.parentNode.replaceChild(newSearch, searchEl);
|
|
385
|
+
newSearch.addEventListener("input", () => buildTable(newSearch.value));
|
|
386
|
+
if (groupByStreamEl) {
|
|
387
|
+
const newGroupByStream = groupByStreamEl.cloneNode(true);
|
|
388
|
+
groupByStreamEl.parentNode.replaceChild(newGroupByStream, groupByStreamEl);
|
|
389
|
+
newGroupByStream.addEventListener("change", () =>
|
|
390
|
+
buildTable(newSearch.value),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
showPacketList,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
module.exports = {
|
|
401
|
+
id: "list",
|
|
402
|
+
createListPanel,
|
|
403
|
+
};
|