Skip to content

Commit 78c0bd4

Browse files
committed
update traceroute and ping modules can now execute against CIDR targets
1 parent b028544 commit 78c0bd4

10 files changed

Lines changed: 340 additions & 256 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Please see https://github.com/skhell/net-commander/releases for the latest release notes. The notes below have been kept for historical purposes.
33
All notable changes to the "net-commander" extension will be documented in this file.
44

5+
## 2026-02-19 - [0.1.3]
6+
### Added
7+
- traceroute and ping modules run and auto-export CSV for convenience.
8+
- traceroute and ping modules stop action button.
9+
10+
### Updated
11+
- traceroute and ping modules execute against CIDR targets.
12+
513
## 2026-02-18 - [0.1.2]
614
### Updated
715
- traceroute module update to reflect ping module capability of executing single and multitarget trace

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Aimed to be a powerful all-in-one toolkit for Network Engineers, DevOps Engineer
2323

2424
| Category | Highlights |
2525
| ------------- | ------------------------------------------------------------------------------------------------------------- |
26-
| **Troubleshoot** |Live Ping panel & multiple-shot ping<br>• Visual Traceroute with mapped SVG path<br>• Assisted Root-Cause Analysis Report creation for data-driven resolution approach with no more guessing |
26+
| **Troubleshoot** |Single or CIDR ping and traceroute<br>• Assisted Root-Cause Analysis Report creation for data-driven resolution approach with no more guessing |
2727
| **Wireless** | • Wi-Fi Analyzer ideal for quick site survey<br>• One-click packet capture for deep inspection with Wireshark |
2828
| **Lookup** | • IANA port registry search<br>• Public IP & ASN info (ipinfo.io)<br>• PeeringDB integration |
2929
| **Calculate** | • RFC-compliant CIDR calculator + *what-if* assisted subnet simulator |
@@ -108,13 +108,12 @@ Query PeeringDB for ASN and facility data directly in VS Code. Enter an ASN or
108108

109109

110110
## Ping and traceroute supercharged
111-
![Ping Panel](https://raw.githubusercontent.com/skhell/net-commander/refs/heads/main/media/img/readme/ping.png)
111+
![Ping Panel](https://raw.githubusercontent.com/skhell/net-commander/refs/heads/main/media/img/readme/ping-trace.jpg)
112112
- **Panel Mode:** Continuous ping monitoring in VS Code’s sidebar, with real‑time latency charts and packet‑loss stats.
113113
- **Single‑Shot:** Quick terminal ping for instant reachability tests.
114114

115-
Run a ping or traceroute against single or multiple targets.
116-
Both modes support detailed CSV export plus for Ping command you get a custom packet size/count via settings.
117-
115+
Run a ping or traceroute against single or multiple targets including CIDR blocks.
116+
Both modes support auto-export of detailed CSV export plus Ping command allow you to edit a custom packet size/count via settings.
118117

119118
## Network configuration colorizer
120119
![Config Colorizer](https://raw.githubusercontent.com/skhell/net-commander/refs/heads/main/media/img/readme/net-colorizer.gif)

media/img/readme/ping-trace.jpg

75.2 KB
Loading

media/img/readme/ping.png

-114 KB
Binary file not shown.

media/module-ping/main.js

Lines changed: 127 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Author: skhell *
44
* Description: Net Commander is the extension for Visual Studio Code *
55
* dedicated to Network Engineers, DevOps Engineers and *
6-
* Solution Architects streamlining everyday workflows and *
6+
* Solution Architects streamlining everyday workflows and *
77
* accelerating data-driven root-cause analysis. *
88
* *
99
* Github: https://github.com/skhell/net-commander *
@@ -19,156 +19,174 @@
1919

2020
// media/module-ping/main.js
2121

22-
// suppress ResizeObserver loop errors
2322
window.addEventListener('error', event => {
24-
if (
25-
event.message &&
26-
event.message.includes('ResizeObserver loop completed with undelivered notifications')
27-
) {
23+
if (event.message && event.message.includes('ResizeObserver loop completed with undelivered notifications')) {
2824
event.preventDefault();
2925
return false;
3026
}
3127
});
3228

3329
document.addEventListener('DOMContentLoaded', () => {
34-
const vscode = acquireVsCodeApi();
35-
const targetsInput = document.getElementById('pingtargets');
36-
const countInput = document.getElementById('pingcount');
37-
const sizeInput = document.getElementById('pingsize');
38-
const pingBtn = document.getElementById('pingBtn');
39-
const clearBtn = document.getElementById('clearBtn');
40-
const exportBtn = document.getElementById('exportBtn');
41-
const resultsDiv = document.getElementById('results');
30+
const vscode = acquireVsCodeApi();
31+
const targetsInput = document.getElementById('pingtargets');
32+
const countInput = document.getElementById('pingcount');
33+
const sizeInput = document.getElementById('pingsize');
34+
const pingBtn = document.getElementById('pingBtn');
35+
const stopBtn = document.getElementById('stopBtn');
36+
const clearBtn = document.getElementById('clearBtn');
37+
const resultsDiv = document.getElementById('results');
38+
4239
let resultsByTarget = {};
43-
44-
if (!targetsInput || !countInput || !sizeInput || !pingBtn || !clearBtn || !exportBtn || !resultsDiv) {
40+
let totalTargets = 0;
41+
let completedTargets = 0;
42+
let inputLabel = '';
43+
let running = false;
44+
45+
if (!targetsInput || !countInput || !sizeInput || !pingBtn || !stopBtn || !clearBtn || !resultsDiv) {
4546
console.error('Missing DOM elements in Ping UI');
4647
return;
4748
}
48-
49+
4950
pingBtn.addEventListener('click', () => {
50-
const targets = targetsInput.value.split(/,|\n/)
51-
.map(t => t.trim()).filter(t => t);
52-
resultsByTarget = {};
53-
renderClear();
51+
const targets = targetsInput.value.split(/[,\n]/).map(t => t.trim()).filter(t => t);
52+
if (targets.length === 0) return;
53+
inputLabel = targetsInput.value.trim();
54+
resultsByTarget = {};
55+
totalTargets = 0;
56+
completedTargets = 0;
57+
running = true;
58+
setRunning(true);
59+
resultsDiv.innerHTML = '<div style="padding:12px;">Preparing targets\u2026</div>';
5460
vscode.postMessage({
5561
command: 'ping',
56-
data: {
57-
targets,
58-
count: parseInt(countInput.value, 10) || 4,
59-
size: parseInt(sizeInput.value, 10) || 56
60-
}
62+
data: { targets, count: parseInt(countInput.value, 10) || 4, size: parseInt(sizeInput.value, 10) || 56 }
6163
});
6264
});
63-
65+
66+
stopBtn.addEventListener('click', () => {
67+
running = false;
68+
setRunning(false);
69+
resultsDiv.innerHTML = '<div style="padding:12px;">Stopped.</div>';
70+
vscode.postMessage({ command: 'stop' });
71+
});
72+
6473
clearBtn.addEventListener('click', () => {
65-
resultsByTarget = {};
66-
renderClear();
74+
running = false;
75+
resultsByTarget = {};
76+
totalTargets = 0;
77+
completedTargets = 0;
78+
inputLabel = '';
79+
setRunning(false);
80+
resultsDiv.innerHTML = '';
6781
vscode.postMessage({ command: 'clear' });
6882
});
69-
70-
exportBtn.addEventListener('click', () => {
71-
// I export one file per target
72-
Object.values(resultsByTarget).forEach(group => {
73-
let csv = '';
74-
group.replies.forEach(r => {
75-
csv += [
76-
r.seq || '',
77-
r.bytes, r.ttl, r.time,
78-
r.target, r.localIP, r.macAddress, r.timestamp
79-
].join(',') + '\n';
80-
});
81-
const s = group.summary;
82-
if (s) {
83-
csv += `Summary:,Sent=${s.transmitted},Rec=${s.received},Loss=${s.loss},Time=${s.totalTime},RTT=${s.rtt
84-
? `${s.rtt.min}/${s.rtt.avg}/${s.rtt.max}/${s.rtt.mdev}`
85-
: 'N/A'}\n`;
86-
}
87-
vscode.postMessage({ command: 'exportCSV', data: { csv, targets: [group.target] } });
88-
});
89-
});
90-
83+
84+
function setRunning(on) {
85+
pingBtn.style.display = on ? 'none' : '';
86+
stopBtn.style.display = on ? '' : 'none';
87+
}
88+
9189
window.addEventListener('message', ({ data }) => {
9290
switch (data.command) {
91+
case 'pingTotal':
92+
totalTargets = data.total;
93+
completedTargets = 0;
94+
renderProgress(0, totalTargets);
95+
break;
9396
case 'pingResult':
9497
handlePingResult(data);
9598
break;
9699
case 'clearResults':
97-
renderClear();
100+
resultsByTarget = {};
101+
totalTargets = 0;
102+
completedTargets = 0;
103+
resultsDiv.innerHTML = '';
98104
break;
99105
case 'toggleStop':
100106
break;
101107
}
102108
});
103-
109+
104110
function handlePingResult({ data }) {
111+
if (!running) return;
105112
const { target, type } = data;
106113
if (!resultsByTarget[target]) {
107114
resultsByTarget[target] = { target, replies: [], summary: null };
108115
}
109116
if (type === 'reply') {
110-
resultsByTarget[target].replies.push(data.row);
117+
const entry = resultsByTarget[target];
118+
entry.replies.push(data.row);
119+
120+
// Compute cumulative running stats up to this reply
121+
const replies = entry.replies;
122+
const received = replies.length;
123+
const sent = parseInt(data.row.seq, 10) || received;
124+
const lossStr = sent > 0 ? ((sent - received) / sent * 100).toFixed(1) + '%' : '0%';
125+
const rtts = replies.map(r => parseFloat(r.time)).filter(v => !isNaN(v));
126+
data.row._running = {
127+
sent,
128+
received,
129+
loss: lossStr,
130+
rttMin: rtts.length ? Math.min(...rtts).toFixed(3) + ' ms' : '',
131+
rttAvg: rtts.length ? (rtts.reduce((a, b) => a + b, 0) / rtts.length).toFixed(3) + ' ms' : '',
132+
rttMax: rtts.length ? Math.max(...rtts).toFixed(3) + ' ms' : ''
133+
};
111134
} else if (type === 'summary') {
112135
resultsByTarget[target].summary = data.summary;
136+
completedTargets++;
137+
renderProgress(completedTargets, totalTargets);
138+
if (totalTargets > 0 && completedTargets >= totalTargets) {
139+
running = false;
140+
setRunning(false);
141+
autoExportAll();
142+
}
113143
}
114-
renderAll();
115144
}
116-
117-
function renderClear() {
118-
resultsDiv.innerHTML = '';
145+
146+
function renderProgress(done, total) {
147+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
148+
resultsDiv.innerHTML = `<div style="padding:12px;">
149+
<div>Pinging <strong>${total}</strong> host${total !== 1 ? 's' : ''}&hellip; <strong>${done} / ${total}</strong> completed (${pct}%)</div>
150+
<div style="background:var(--vscode-editorWidget-border,#454545);height:6px;border-radius:3px;margin-top:10px;overflow:hidden;">
151+
<div style="background:var(--vscode-progressBar-background,#0e70c0);height:100%;width:${pct}%;transition:width 0.15s;"></div>
152+
</div>
153+
</div>`;
119154
}
120-
121-
function renderAll() {
122-
resultsDiv.innerHTML = '';
123-
for (const key in resultsByTarget) {
124-
const group = resultsByTarget[key];
125-
const title = document.createElement('h3');
126-
title.textContent = `Ping: ${group.target}`;
127-
resultsDiv.appendChild(title);
128-
129-
const table = document.createElement('vscode-table');
130-
table.zebra = true;
131-
table['bordered-rows'] = true;
132-
133-
const header = document.createElement('vscode-table-header');
134-
header.slot = 'header';
135-
['Seq','Bytes','TTL','Time','Target','Source','Source Mac','Timestamp']
136-
.forEach(col => {
137-
const th = document.createElement('vscode-table-header-cell');
138-
th.textContent = col;
139-
header.appendChild(th);
155+
156+
function autoExportAll() {
157+
let csv = 'Target,Seq,Bytes,TTL,Time,Sent,Received,Loss,RTT Min,RTT Avg,RTT Max,Source,Source Mac,Timestamp\n';
158+
Object.values(resultsByTarget).forEach(group => {
159+
const s = group.summary;
160+
if (group.replies.length > 0) {
161+
group.replies.forEach((r) => {
162+
const rs = r._running || {};
163+
csv += [
164+
group.target,
165+
r.seq || '',
166+
r.bytes, r.ttl, r.time,
167+
rs.sent !== undefined ? rs.sent : '',
168+
rs.received !== undefined ? rs.received : '',
169+
rs.loss || '',
170+
rs.rttMin || '',
171+
rs.rttAvg || '',
172+
rs.rttMax || '',
173+
r.localIP, r.macAddress, r.timestamp
174+
].join(',') + '\n';
140175
});
141-
table.appendChild(header);
142-
143-
const body = document.createElement('vscode-table-body');
144-
body.slot = 'body';
145-
group.replies.forEach(row => {
146-
const tr = document.createElement('vscode-table-row');
147-
[row.seq||'',row.bytes,row.ttl,row.time,row.target,row.localIP,row.macAddress,row.timestamp]
148-
.forEach(val => {
149-
const td = document.createElement('vscode-table-cell');
150-
td.textContent = val;
151-
tr.appendChild(td);
152-
});
153-
body.appendChild(tr);
154-
});
155-
156-
if (group.summary) {
157-
const tr = document.createElement('vscode-table-row');
158-
const td = document.createElement('vscode-table-cell');
159-
td.colspan = 8;
160-
td.style.fontWeight = 'bold';
161-
const s = group.summary;
162-
td.textContent = `Sent ${s.transmitted}, Rec ${s.received}, Loss ${s.loss}, Time ${s.totalTime}, RTT ${s.rtt
163-
? `${s.rtt.min}/${s.rtt.avg}/${s.rtt.max}/${s.rtt.mdev}`
164-
: 'N/A'}`;
165-
tr.appendChild(td);
166-
body.appendChild(tr);
176+
} else {
177+
// unreachable host — one row with summary stats so it appears in the CSV
178+
csv += [
179+
group.target, '', '', '', '',
180+
s ? s.transmitted : '',
181+
s ? s.received : '',
182+
s ? s.loss : '',
183+
'', '', '', '', '', ''
184+
].join(',') + '\n';
167185
}
168-
169-
table.appendChild(body);
170-
resultsDiv.appendChild(table);
171-
}
186+
});
187+
vscode.postMessage({ command: 'exportCSV', data: { csv, targets: [inputLabel] } });
188+
resultsDiv.innerHTML = `<div style="padding:12px;">
189+
<strong>${totalTargets} host${totalTargets !== 1 ? 's' : ''}</strong> completed. CSV exported automatically.
190+
</div>`;
172191
}
173192
});
174-

0 commit comments

Comments
 (0)