netwatchpy 0.1.0__tar.gz
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.
- netwatchpy-0.1.0/PKG-INFO +58 -0
- netwatchpy-0.1.0/README.md +46 -0
- netwatchpy-0.1.0/pyproject.toml +27 -0
- netwatchpy-0.1.0/setup.cfg +4 -0
- netwatchpy-0.1.0/src/netwatch/__init__.py +0 -0
- netwatchpy-0.1.0/src/netwatch/tui.py +381 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/PKG-INFO +58 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/SOURCES.txt +10 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/dependency_links.txt +1 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/entry_points.txt +2 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/requires.txt +3 -0
- netwatchpy-0.1.0/src/netwatchpy.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netwatchpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A TUI network usage monitor.
|
|
5
|
+
Author-email: Pranav Kishan <ty.pranavkishan7905@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: textual
|
|
10
|
+
Requires-Dist: psutil
|
|
11
|
+
Requires-Dist: desktop-notifier
|
|
12
|
+
|
|
13
|
+
Netwatch TUI (netwatchpy)
|
|
14
|
+
|
|
15
|
+
A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
|
|
16
|
+
|
|
17
|
+
Installation
|
|
18
|
+
|
|
19
|
+
pip install netwatchpy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Usage
|
|
23
|
+
|
|
24
|
+
Once installed, the netwatch command will be available in your terminal.
|
|
25
|
+
|
|
26
|
+
Run the monitor (monitors all interfaces):
|
|
27
|
+
|
|
28
|
+
netwatch
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
See all available options:
|
|
32
|
+
|
|
33
|
+
netwatch --help
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
|
|
38
|
+
Set a 10GB data limit:
|
|
39
|
+
|
|
40
|
+
netwatch -l "10GB"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Monitor a specific interface and log to a file:
|
|
44
|
+
|
|
45
|
+
netwatch -i "Wi-Fi" --log "my_usage.csv"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Features
|
|
49
|
+
|
|
50
|
+
Real-time dashboard for Upload/Download speeds and totals.
|
|
51
|
+
|
|
52
|
+
Data limit progress bar and desktop notifications for 80% and 100% usage.
|
|
53
|
+
|
|
54
|
+
Interactive log of all activity.
|
|
55
|
+
|
|
56
|
+
Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
|
|
57
|
+
|
|
58
|
+
Ability to log all traffic to a CSV file (--log).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Netwatch TUI (netwatchpy)
|
|
2
|
+
|
|
3
|
+
A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
|
|
4
|
+
|
|
5
|
+
Installation
|
|
6
|
+
|
|
7
|
+
pip install netwatchpy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Usage
|
|
11
|
+
|
|
12
|
+
Once installed, the netwatch command will be available in your terminal.
|
|
13
|
+
|
|
14
|
+
Run the monitor (monitors all interfaces):
|
|
15
|
+
|
|
16
|
+
netwatch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
See all available options:
|
|
20
|
+
|
|
21
|
+
netwatch --help
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
|
|
26
|
+
Set a 10GB data limit:
|
|
27
|
+
|
|
28
|
+
netwatch -l "10GB"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Monitor a specific interface and log to a file:
|
|
32
|
+
|
|
33
|
+
netwatch -i "Wi-Fi" --log "my_usage.csv"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Features
|
|
37
|
+
|
|
38
|
+
Real-time dashboard for Upload/Download speeds and totals.
|
|
39
|
+
|
|
40
|
+
Data limit progress bar and desktop notifications for 80% and 100% usage.
|
|
41
|
+
|
|
42
|
+
Interactive log of all activity.
|
|
43
|
+
|
|
44
|
+
Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
|
|
45
|
+
|
|
46
|
+
Ability to log all traffic to a CSV file (--log).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "netwatchpy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Pranav Kishan", email = "ty.pranavkishan7905@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "A TUI network usage monitor."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"textual",
|
|
18
|
+
"psutil",
|
|
19
|
+
"desktop-notifier"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
netwatch = "netwatch.tui:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
packages = ["netwatch"]
|
|
File without changes
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
import time
|
|
3
|
+
import argparse
|
|
4
|
+
import threading
|
|
5
|
+
import csv
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from textwrap import dedent
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.containers import Container, VerticalScroll, Horizontal
|
|
10
|
+
from textual.widgets import Header, Footer, DataTable, ProgressBar, Static
|
|
11
|
+
from textual.reactive import var
|
|
12
|
+
from desktop_notifier import DesktopNotifier
|
|
13
|
+
|
|
14
|
+
def get_size(byte_val):
|
|
15
|
+
"""Converts bytes to a human-readable format (KB, MB, GB)."""
|
|
16
|
+
power = 1024
|
|
17
|
+
n = 0
|
|
18
|
+
power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
|
|
19
|
+
while byte_val >= power and n < len(power_labels) - 1:
|
|
20
|
+
byte_val /= power
|
|
21
|
+
n += 1
|
|
22
|
+
return f"{byte_val:.2f} {power_labels[n]}B"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_limit(size_str):
|
|
26
|
+
"""Parses a size string (e.g., '10GB', '500MB') into bytes."""
|
|
27
|
+
if not size_str:
|
|
28
|
+
return None
|
|
29
|
+
size_str = size_str.upper().strip()
|
|
30
|
+
if size_str.endswith('GB'):
|
|
31
|
+
return int(float(size_str[:-2]) * 1024**3)
|
|
32
|
+
elif size_str.endswith('MB'):
|
|
33
|
+
return int(float(size_str[:-2]) * 1024**2)
|
|
34
|
+
elif size_str.endswith('KB'):
|
|
35
|
+
return int(float(size_str[:-2]) * 1024)
|
|
36
|
+
else:
|
|
37
|
+
try:
|
|
38
|
+
return int(float(size_str))
|
|
39
|
+
except ValueError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NetworkMonitorThread(threading.Thread):
|
|
45
|
+
"""A separate thread that monitors network stats."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, app_callback, interface='all', log_file=None):
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.daemon = True
|
|
50
|
+
self.app_callback = app_callback
|
|
51
|
+
self.interface = interface
|
|
52
|
+
self.stop_event = threading.Event()
|
|
53
|
+
self.log_file = log_file
|
|
54
|
+
|
|
55
|
+
if log_file:
|
|
56
|
+
try:
|
|
57
|
+
with open(log_file, "w", newline="", encoding="utf-8") as f:
|
|
58
|
+
writer = csv.writer(f)
|
|
59
|
+
writer.writerow([
|
|
60
|
+
"Timestamp", "Upload Speed (B/s)", "Download Speed (B/s)",
|
|
61
|
+
"Total Upload", "Total Download", "Total Usage"
|
|
62
|
+
])
|
|
63
|
+
except Exception as e:
|
|
64
|
+
self.app_callback({"error": f"Failed to create log file: {e}"})
|
|
65
|
+
self.log_file = None
|
|
66
|
+
|
|
67
|
+
def stop(self):
|
|
68
|
+
self.stop_event.set()
|
|
69
|
+
|
|
70
|
+
def run(self):
|
|
71
|
+
total_upload = 0
|
|
72
|
+
total_download = 0
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
last_stats = psutil.net_io_counters(pernic=True)
|
|
76
|
+
if not last_stats:
|
|
77
|
+
self.app_callback({"error": "No network interfaces found."})
|
|
78
|
+
return
|
|
79
|
+
if self.interface != 'all' and self.interface not in last_stats:
|
|
80
|
+
self.app_callback({"error": f"Interface '{self.interface}' not found."})
|
|
81
|
+
return
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.app_callback({"error": f"Error getting stats: {e}"})
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
while not self.stop_event.is_set():
|
|
87
|
+
try:
|
|
88
|
+
time.sleep(1)
|
|
89
|
+
current_stats = psutil.net_io_counters(pernic=True)
|
|
90
|
+
if not current_stats:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
upload_delta, download_delta = 0, 0
|
|
94
|
+
if self.interface == 'all':
|
|
95
|
+
for iface in current_stats:
|
|
96
|
+
if iface in last_stats:
|
|
97
|
+
upload_delta += current_stats[iface].bytes_sent - last_stats[iface].bytes_sent
|
|
98
|
+
download_delta += current_stats[iface].bytes_recv - last_stats[iface].bytes_recv
|
|
99
|
+
else:
|
|
100
|
+
if self.interface in current_stats and self.interface in last_stats:
|
|
101
|
+
upload_delta = current_stats[self.interface].bytes_sent - last_stats[self.interface].bytes_sent
|
|
102
|
+
download_delta = current_stats[self.interface].bytes_recv - last_stats[self.interface].bytes_recv
|
|
103
|
+
|
|
104
|
+
last_stats = current_stats
|
|
105
|
+
upload_delta = max(upload_delta, 0)
|
|
106
|
+
download_delta = max(download_delta, 0)
|
|
107
|
+
total_upload += upload_delta
|
|
108
|
+
total_download += download_delta
|
|
109
|
+
|
|
110
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
111
|
+
total_usage = total_upload + total_download
|
|
112
|
+
data_packet = {
|
|
113
|
+
"upload_speed": upload_delta,
|
|
114
|
+
"download_speed": download_delta,
|
|
115
|
+
"total_upload": total_upload,
|
|
116
|
+
"total_download": total_download,
|
|
117
|
+
"total_usage": total_usage,
|
|
118
|
+
"timestamp": timestamp
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if self.log_file:
|
|
122
|
+
with open(self.log_file, "a", newline="", encoding="utf-8") as f:
|
|
123
|
+
writer = csv.writer(f)
|
|
124
|
+
writer.writerow([
|
|
125
|
+
timestamp,
|
|
126
|
+
upload_delta,
|
|
127
|
+
download_delta,
|
|
128
|
+
get_size(total_upload),
|
|
129
|
+
get_size(total_download),
|
|
130
|
+
get_size(total_usage)
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
self.app_callback(data_packet)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.app_callback({"error": f"Error in loop: {e}"})
|
|
136
|
+
time.sleep(3)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class NetMonitorTUI(App):
|
|
141
|
+
"""A Textual TUI for the network monitor."""
|
|
142
|
+
|
|
143
|
+
TITLE = "Network Usage Monitor"
|
|
144
|
+
SUB_TITLE = "Press Ctrl+P for commands, Ctrl+Q to quit"
|
|
145
|
+
|
|
146
|
+
BINDINGS = [
|
|
147
|
+
("ctrl+p", "command_palette", "Command Palette"),
|
|
148
|
+
("ctrl+d", "toggle_dark", "Toggle Dark Mode"),
|
|
149
|
+
("r", "reset_counters", "Reset Counters"),
|
|
150
|
+
("ctrl+q", "quit", "Quit"),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
CSS = dedent("""
|
|
154
|
+
Screen {
|
|
155
|
+
background: #f8f8f8;
|
|
156
|
+
color: black;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.-dark-mode Screen {
|
|
160
|
+
background: #101010;
|
|
161
|
+
color: #f0f0f0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#main_container {
|
|
165
|
+
layout: vertical;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#summary_cards {
|
|
169
|
+
layout: horizontal;
|
|
170
|
+
height: auto;
|
|
171
|
+
padding: 1 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.summary_card {
|
|
175
|
+
width: 1fr;
|
|
176
|
+
min-height: 5;
|
|
177
|
+
border: solid black;
|
|
178
|
+
padding: 1;
|
|
179
|
+
margin: 0 1;
|
|
180
|
+
background: #e8e8e8;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.-dark-mode .summary_card {
|
|
184
|
+
border: solid #888;
|
|
185
|
+
background: #222;
|
|
186
|
+
color: #e0e0e0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#limit_container {
|
|
190
|
+
height: auto;
|
|
191
|
+
padding: 0 1 1 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#stats_table {
|
|
195
|
+
height: 1fr;
|
|
196
|
+
margin: 0 1;
|
|
197
|
+
border: solid black;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.-dark-mode #stats_table {
|
|
201
|
+
border: solid #666;
|
|
202
|
+
color: #e0e0e0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ProgressBar > .progress-bar--bar {
|
|
206
|
+
background: #007acc;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.-dark-mode ProgressBar > .progress-bar--bar {
|
|
210
|
+
background: #55aaff;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#footer {
|
|
214
|
+
color: white;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#header {
|
|
218
|
+
color: white;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#error_box {
|
|
222
|
+
height: auto;
|
|
223
|
+
padding: 1 2;
|
|
224
|
+
color: red;
|
|
225
|
+
display: none;
|
|
226
|
+
}
|
|
227
|
+
""")
|
|
228
|
+
|
|
229
|
+
total_usage = var(0)
|
|
230
|
+
total_upload = var(0)
|
|
231
|
+
total_download = var(0)
|
|
232
|
+
upload_speed = var(0)
|
|
233
|
+
download_speed = var(0)
|
|
234
|
+
dark = var(False)
|
|
235
|
+
|
|
236
|
+
def __init__(self, interface='all', limit_str=None, log_file=None):
|
|
237
|
+
super().__init__()
|
|
238
|
+
self.interface = interface
|
|
239
|
+
self.limit_bytes = parse_limit(limit_str)
|
|
240
|
+
self.limit_str = limit_str or "No Limit"
|
|
241
|
+
self.monitor_thread = None
|
|
242
|
+
self.alert_80_sent = False
|
|
243
|
+
self.alert_100_sent = False
|
|
244
|
+
self.notifier = DesktopNotifier(app_name="Netwatch")
|
|
245
|
+
self.log_file = log_file
|
|
246
|
+
|
|
247
|
+
def compose(self) -> ComposeResult:
|
|
248
|
+
yield Header()
|
|
249
|
+
with VerticalScroll(id="main_container"):
|
|
250
|
+
with Horizontal(id="summary_cards"):
|
|
251
|
+
yield Static("Total Download\n[b]0.00 B[/b]", id="total-dl-card", classes="summary_card")
|
|
252
|
+
yield Static("Total Upload\n[b]0.00 B[/b]", id="total-ul-card", classes="summary_card")
|
|
253
|
+
yield Static("Total Usage\n[b]0.00 B[/b]", id="total-usage-card", classes="summary_card")
|
|
254
|
+
|
|
255
|
+
with Container(id="limit_container"):
|
|
256
|
+
if self.limit_bytes:
|
|
257
|
+
yield Static(f"Usage Limit: {get_size(self.limit_bytes)}")
|
|
258
|
+
yield ProgressBar(id="limit_bar", total=self.limit_bytes, show_eta=False)
|
|
259
|
+
else:
|
|
260
|
+
yield Static("Usage Limit: Not Set")
|
|
261
|
+
|
|
262
|
+
yield Static(id="error_box")
|
|
263
|
+
yield DataTable(id="stats_table")
|
|
264
|
+
|
|
265
|
+
yield Footer()
|
|
266
|
+
|
|
267
|
+
def on_mount(self) -> None:
|
|
268
|
+
table = self.query_one(DataTable)
|
|
269
|
+
table.add_column("Time", key="time")
|
|
270
|
+
table.add_column("Up Speed", key="up_spd")
|
|
271
|
+
table.add_column("Down Speed", key="dl_spd")
|
|
272
|
+
table.add_column("Total Up", key="total_up")
|
|
273
|
+
table.add_column("Total Down", key="total_dl")
|
|
274
|
+
table.add_column("Total Usage", key="total")
|
|
275
|
+
|
|
276
|
+
self.monitor_thread = NetworkMonitorThread(
|
|
277
|
+
app_callback=self.on_data_update,
|
|
278
|
+
interface=self.interface,
|
|
279
|
+
log_file=self.log_file
|
|
280
|
+
)
|
|
281
|
+
self.monitor_thread.start()
|
|
282
|
+
|
|
283
|
+
def on_exit(self) -> None:
|
|
284
|
+
if self.monitor_thread:
|
|
285
|
+
self.monitor_thread.stop()
|
|
286
|
+
|
|
287
|
+
def on_data_update(self, data: dict) -> None:
|
|
288
|
+
self.call_from_thread(self._process_data_packet, data)
|
|
289
|
+
|
|
290
|
+
def _process_data_packet(self, data: dict) -> None:
|
|
291
|
+
if "error" in data:
|
|
292
|
+
error_box = self.query_one("#error_box")
|
|
293
|
+
error_box.update(f"ERROR: {data['error']}")
|
|
294
|
+
error_box.styles.display = "block"
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
self.upload_speed = data["upload_speed"]
|
|
298
|
+
self.download_speed = data["download_speed"]
|
|
299
|
+
self.total_upload = data["total_upload"]
|
|
300
|
+
self.total_download = data["total_download"]
|
|
301
|
+
self.total_usage = data["total_usage"]
|
|
302
|
+
|
|
303
|
+
table = self.query_one(DataTable)
|
|
304
|
+
table.add_row(
|
|
305
|
+
data["timestamp"].split(" ")[1],
|
|
306
|
+
f"{get_size(self.upload_speed)}/s",
|
|
307
|
+
f"{get_size(self.download_speed)}/s",
|
|
308
|
+
get_size(self.total_upload),
|
|
309
|
+
get_size(self.total_download),
|
|
310
|
+
get_size(self.total_usage)
|
|
311
|
+
)
|
|
312
|
+
table.scroll_end(animate=False)
|
|
313
|
+
|
|
314
|
+
if table.row_count > 50:
|
|
315
|
+
first_key = next(iter(table.rows.keys()))
|
|
316
|
+
table.remove_row(first_key)
|
|
317
|
+
|
|
318
|
+
def action_toggle_dark(self):
|
|
319
|
+
"""Toggle dark/light mode properly."""
|
|
320
|
+
self.dark = not self.dark
|
|
321
|
+
self.set_class(self.dark, "-dark-mode")
|
|
322
|
+
self.sub_title = "🌙 Dark Mode ON" if self.dark else "☀️ Light Mode ON"
|
|
323
|
+
|
|
324
|
+
def action_reset_counters(self):
|
|
325
|
+
"""Reset counters."""
|
|
326
|
+
self.total_upload = self.total_download = self.total_usage = 0
|
|
327
|
+
self.alert_80_sent = self.alert_100_sent = False
|
|
328
|
+
self.sub_title = "Counters Reset!"
|
|
329
|
+
if self.limit_bytes:
|
|
330
|
+
bar = self.query_one(ProgressBar)
|
|
331
|
+
bar.styles.color = None
|
|
332
|
+
|
|
333
|
+
def watch_total_download(self, new_val: int) -> None:
|
|
334
|
+
self.query_one("#total-dl-card").update(f"Total Download\n[b]{get_size(new_val)}[/b]")
|
|
335
|
+
|
|
336
|
+
def watch_total_upload(self, new_val: int) -> None:
|
|
337
|
+
self.query_one("#total-ul-card").update(f"Total Upload\n[b]{get_size(new_val)}[/b]")
|
|
338
|
+
|
|
339
|
+
async def watch_total_usage(self, new_total_usage: int) -> None:
|
|
340
|
+
self.query_one("#total-usage-card").update(f"Total Usage\n[b]{get_size(new_total_usage)}[/b]")
|
|
341
|
+
if self.limit_bytes:
|
|
342
|
+
bar = self.query_one(ProgressBar)
|
|
343
|
+
bar.progress = new_total_usage
|
|
344
|
+
if new_total_usage >= 0.8 * self.limit_bytes and not self.alert_80_sent:
|
|
345
|
+
self.alert_80_sent = True
|
|
346
|
+
bar.styles.color = "yellow"
|
|
347
|
+
self.sub_title = "⚠️ 80% of limit reached!"
|
|
348
|
+
try:
|
|
349
|
+
await self.notifier.send(
|
|
350
|
+
title="Netwatch: 80% Usage Warning",
|
|
351
|
+
message=f"You have used {get_size(new_total_usage)} of your {get_size(self.limit_bytes)} limit."
|
|
352
|
+
)
|
|
353
|
+
except Exception as e:
|
|
354
|
+
print(f"[Notification Error] {e}")
|
|
355
|
+
if new_total_usage >= self.limit_bytes and not self.alert_100_sent:
|
|
356
|
+
self.alert_100_sent = True
|
|
357
|
+
bar.styles.color = "red"
|
|
358
|
+
self.sub_title = "🚨 Data limit exceeded!"
|
|
359
|
+
try:
|
|
360
|
+
await self.notifier.send(
|
|
361
|
+
title="Netwatch: Data Limit Exceeded!",
|
|
362
|
+
message=f"You have exceeded your {get_size(self.limit_bytes)} data limit."
|
|
363
|
+
)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
print(f"[Notification Error] {e}")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def main():
|
|
370
|
+
parser = argparse.ArgumentParser(description="Network Usage Monitor TUI")
|
|
371
|
+
parser.add_argument('-i', '--interface', type=str, default='all', help="Network interface to monitor (e.g., 'Wi-Fi'). Default is 'all'.")
|
|
372
|
+
parser.add_argument('-l', '--limit', type=str, help="Set data usage cap (e.g., '10GB', '500MB').")
|
|
373
|
+
parser.add_argument('--log', type=str, help="Optional CSV file to log network usage data.")
|
|
374
|
+
args = parser.parse_args()
|
|
375
|
+
|
|
376
|
+
app = NetMonitorTUI(interface=args.interface, limit_str=args.limit, log_file=args.log)
|
|
377
|
+
app.run()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netwatchpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A TUI network usage monitor.
|
|
5
|
+
Author-email: Pranav Kishan <ty.pranavkishan7905@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: textual
|
|
10
|
+
Requires-Dist: psutil
|
|
11
|
+
Requires-Dist: desktop-notifier
|
|
12
|
+
|
|
13
|
+
Netwatch TUI (netwatchpy)
|
|
14
|
+
|
|
15
|
+
A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
|
|
16
|
+
|
|
17
|
+
Installation
|
|
18
|
+
|
|
19
|
+
pip install netwatchpy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Usage
|
|
23
|
+
|
|
24
|
+
Once installed, the netwatch command will be available in your terminal.
|
|
25
|
+
|
|
26
|
+
Run the monitor (monitors all interfaces):
|
|
27
|
+
|
|
28
|
+
netwatch
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
See all available options:
|
|
32
|
+
|
|
33
|
+
netwatch --help
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
|
|
38
|
+
Set a 10GB data limit:
|
|
39
|
+
|
|
40
|
+
netwatch -l "10GB"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Monitor a specific interface and log to a file:
|
|
44
|
+
|
|
45
|
+
netwatch -i "Wi-Fi" --log "my_usage.csv"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Features
|
|
49
|
+
|
|
50
|
+
Real-time dashboard for Upload/Download speeds and totals.
|
|
51
|
+
|
|
52
|
+
Data limit progress bar and desktop notifications for 80% and 100% usage.
|
|
53
|
+
|
|
54
|
+
Interactive log of all activity.
|
|
55
|
+
|
|
56
|
+
Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
|
|
57
|
+
|
|
58
|
+
Ability to log all traffic to a CSV file (--log).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/netwatch/__init__.py
|
|
4
|
+
src/netwatch/tui.py
|
|
5
|
+
src/netwatchpy.egg-info/PKG-INFO
|
|
6
|
+
src/netwatchpy.egg-info/SOURCES.txt
|
|
7
|
+
src/netwatchpy.egg-info/dependency_links.txt
|
|
8
|
+
src/netwatchpy.egg-info/entry_points.txt
|
|
9
|
+
src/netwatchpy.egg-info/requires.txt
|
|
10
|
+
src/netwatchpy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
netwatch
|