woninet 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. woninet/__init__.py +2 -0
  2. woninet/__main__.py +4 -0
  3. woninet/core/__init__.py +2 -0
  4. woninet/core/alerts.py +251 -0
  5. woninet/core/collectors.py +303 -0
  6. woninet/core/engine.py +353 -0
  7. woninet/core/graph.py +63 -0
  8. woninet/core/manual_ip_enumerator.py +33 -0
  9. woninet/core/models.py +124 -0
  10. woninet/core/storage.py +163 -0
  11. woninet/core/subnet_enumerator.py +32 -0
  12. woninet/database/__init__.py +2 -0
  13. woninet/database/base.py +9 -0
  14. woninet/database/engine.py +44 -0
  15. woninet/database/repositories/__init__.py +2 -0
  16. woninet/database/repositories/alert_event_repository.py +69 -0
  17. woninet/database/repositories/alert_state_repository.py +81 -0
  18. woninet/database/repositories/device_repository.py +105 -0
  19. woninet/database/repositories/metric_repository.py +75 -0
  20. woninet/database/tables.py +89 -0
  21. woninet/logging.yaml +49 -0
  22. woninet/main.py +225 -0
  23. woninet/server/__init__.py +2 -0
  24. woninet/server/app.py +101 -0
  25. woninet/server/dependencies.py +25 -0
  26. woninet/server/routes/__init__.py +2 -0
  27. woninet/server/routes/api/__init__.py +0 -0
  28. woninet/server/routes/api/devices.py +43 -0
  29. woninet/server/routes/api/stats.py +34 -0
  30. woninet/server/routes/page/__init__.py +0 -0
  31. woninet/server/routes/page/devices.py +28 -0
  32. woninet/server/static/css/404.css +164 -0
  33. woninet/server/static/css/style.css +368 -0
  34. woninet/server/static/js/app.js +242 -0
  35. woninet/server/static/js/device_info.js +95 -0
  36. woninet/server/static/swagger-ui/favicon-32x32.png +0 -0
  37. woninet/server/static/swagger-ui/swagger-ui-bundle.js +2 -0
  38. woninet/server/static/swagger-ui/swagger-ui.css +3 -0
  39. woninet/server/templates/404.html +76 -0
  40. woninet/server/templates/dashboard.html +118 -0
  41. woninet/server/templates/device_info.html +93 -0
  42. woninet/utilities/__init__.py +2 -0
  43. woninet/utilities/arguments.py +94 -0
  44. woninet/utilities/cli_parser.py +90 -0
  45. woninet/utilities/detect_ip_range.py +21 -0
  46. woninet/utilities/ip_validator.py +39 -0
  47. woninet/utilities/logger.py +97 -0
  48. woninet-2.0.1.dist-info/METADATA +243 -0
  49. woninet-2.0.1.dist-info/RECORD +53 -0
  50. woninet-2.0.1.dist-info/WHEEL +5 -0
  51. woninet-2.0.1.dist-info/entry_points.txt +2 -0
  52. woninet-2.0.1.dist-info/licenses/LICENSE +21 -0
  53. woninet-2.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,368 @@
1
+ :root {
2
+ --bg:#08111f;
3
+ --panel:#101b31;
4
+ --panel-light:#172541;
5
+ --text:#e8f0ff;
6
+ --muted:#95a3bd;
7
+
8
+ --good:#22c55e;
9
+ --warn:#f59e0b;
10
+ --bad:#ef4444;
11
+
12
+ --accent:#6ea8fe;
13
+
14
+ --border:rgba(255,255,255,.08);
15
+ }
16
+
17
+ *{
18
+ box-sizing:border-box;
19
+ }
20
+
21
+ body{
22
+ margin:0;
23
+ font-family:Inter,Segoe UI,sans-serif;
24
+ min-height: 100vh;
25
+ background:
26
+ radial-gradient(circle at top left,
27
+ rgba(110,168,254,.12),transparent 25%),
28
+ linear-gradient(180deg,#08111f,#050b15);
29
+ color:var(--text);
30
+ }
31
+
32
+ .dashboard{
33
+ max-width:1400px;
34
+ margin:auto;
35
+ padding:30px;
36
+ }
37
+
38
+ .topbar{
39
+ display:flex;
40
+ justify-content:space-between;
41
+ align-items:center;
42
+ margin-bottom:30px;
43
+ }
44
+
45
+ .eyebrow{
46
+ color:var(--accent);
47
+ letter-spacing:2px;
48
+ font-size:.8rem;
49
+ }
50
+
51
+ .subtitle{
52
+ color:var(--muted);
53
+ }
54
+
55
+ .api-status{
56
+ display:flex;
57
+ align-items:center;
58
+ gap:10px;
59
+ }
60
+
61
+ .status-dot{
62
+ width:10px;
63
+ height:10px;
64
+ border-radius:50%;
65
+ background:var(--good);
66
+ }
67
+
68
+ .overview-grid{
69
+ display:grid;
70
+ grid-template-columns:repeat(4,1fr);
71
+ gap:15px;
72
+ margin-bottom:20px;
73
+ }
74
+
75
+ .kpi-card{
76
+ background:var(--panel);
77
+ border:1px solid var(--border);
78
+ border-radius:16px;
79
+ padding:20px;
80
+ }
81
+
82
+ .kpi-card span{
83
+ color:var(--muted);
84
+ }
85
+
86
+ .kpi-card h2{
87
+ margin-top:8px;
88
+ }
89
+
90
+ .health-panel{
91
+ background:var(--panel);
92
+ border:1px solid var(--border);
93
+ border-radius:16px;
94
+ padding:20px;
95
+ margin-bottom:20px;
96
+ display:flex;
97
+ justify-content:space-between;
98
+ }
99
+
100
+ .health-label{
101
+ color:var(--muted);
102
+ }
103
+
104
+ .toolbar{
105
+ display:flex;
106
+ gap:12px;
107
+ margin-bottom:20px;
108
+ justify-content: space-between;
109
+ }
110
+
111
+ .toolbar input,
112
+ .toolbar select{
113
+ background:var(--panel);
114
+ color:var(--text);
115
+ border:1px solid var(--border);
116
+ border-radius:12px;
117
+ padding:12px;
118
+ }
119
+
120
+ .toolbar input{
121
+ flex:1;
122
+ }
123
+
124
+ .main-layout{
125
+ display:grid;
126
+ grid-template-columns:2fr 1fr;
127
+ gap:20px;
128
+ }
129
+
130
+ .panel,
131
+ .events-panel{
132
+ background:var(--panel);
133
+ border-radius:16px;
134
+ border:1px solid var(--border);
135
+ padding:20px;
136
+ }
137
+
138
+ .device-grid{
139
+ display:grid;
140
+ gap:15px;
141
+ }
142
+
143
+ .device-card{
144
+ background:var(--panel-light);
145
+ border-radius:14px;
146
+ padding:18px;
147
+ transition:.2s;
148
+ }
149
+
150
+ .device-card:hover{
151
+ transform:translateY(-2px);
152
+ }
153
+
154
+ .device-online{
155
+ border-left:4px solid var(--good);
156
+ }
157
+
158
+ .device-degraded{
159
+ border-left:4px solid var(--warn);
160
+ }
161
+
162
+ .device-offline{
163
+ border-left:4px solid var(--bad);
164
+ }
165
+
166
+ .device-header{
167
+ display:flex;
168
+ justify-content:space-between;
169
+ align-items:center;
170
+ margin-bottom:15px;
171
+ }
172
+
173
+ .device-ip{
174
+ font-size:1.1rem;
175
+ font-weight:700;
176
+ }
177
+
178
+ .device-status{
179
+ font-size:.8rem;
180
+ font-weight:700;
181
+ }
182
+
183
+ .metrics{
184
+ display:grid;
185
+ grid-template-columns:repeat(2,1fr);
186
+ gap:10px;
187
+ }
188
+
189
+ .metric{
190
+ background:rgba(255,255,255,.03);
191
+ border-radius:10px;
192
+ padding:10px;
193
+ }
194
+
195
+ .metric-label{
196
+ color:var(--muted);
197
+ font-size:.8rem;
198
+ }
199
+
200
+ .metric-value{
201
+ margin-top:4px;
202
+ }
203
+
204
+ .device-link{
205
+ color: var(--accent);
206
+ }
207
+
208
+ .device-link:hover, .device-link:active{
209
+ color: var(--text);
210
+ }
211
+
212
+ .event {
213
+ background: #1e1e1e;
214
+ color: #fff;
215
+ padding: 12px;
216
+ margin-bottom: 10px;
217
+ border-left: 4px solid #ff9800;
218
+ border-radius: 6px;
219
+ font-size: 14px;
220
+ }
221
+
222
+ .event-trigger {
223
+ border-left-color: #ff9800;
224
+ }
225
+
226
+ .event-trigger strong {
227
+ color: #ff9800;
228
+ }
229
+
230
+ .event-recover {
231
+ border-left-color: #22c55e;
232
+ }
233
+
234
+ .event-recover strong {
235
+ color: #22c55e;
236
+ }
237
+
238
+ .loader{
239
+ width:40px;
240
+ height:40px;
241
+ border:4px solid rgba(255,255,255,.15);
242
+ border-top:4px solid var(--accent);
243
+ border-radius:50%;
244
+ animation:spin 1s linear infinite;
245
+ margin:auto;
246
+ }
247
+
248
+ /* device_info style */
249
+ .status-online {
250
+ background-color: var(--good);
251
+ }
252
+
253
+ .status-offline {
254
+ background-color: var(--bad);
255
+ }
256
+
257
+ .status-degraded {
258
+ background-color: var(--warn);
259
+ }
260
+
261
+ .back-link {
262
+ color: var(--accent);
263
+ text-decoration: none;
264
+ }
265
+
266
+ .back-link:hover {
267
+ text-decoration: underline;
268
+ }
269
+
270
+ .no-alerts {
271
+ color: var(--muted);
272
+ text-align: center;
273
+ padding: 20px;
274
+ }
275
+
276
+ .device-card-online {
277
+ border-left-color: var(--good);
278
+ }
279
+
280
+ .device-card-degraded {
281
+ border-left-color: var(--warn);
282
+ }
283
+
284
+ .device-card-offline {
285
+ border-left-color: var(--bad);
286
+ }
287
+
288
+ /* responsive settings */
289
+ @keyframes spin{
290
+ to{
291
+ transform:rotate(360deg);
292
+ }
293
+ }
294
+
295
+ @media(max-width:1000px){
296
+
297
+ .main-layout{
298
+ grid-template-columns:1fr;
299
+ }
300
+
301
+ .overview-grid{
302
+ grid-template-columns:repeat(2,1fr);
303
+ }
304
+ }
305
+
306
+ /* Table view for devices CSS code */
307
+ .table-wrapper{
308
+ overflow:auto;
309
+ border-radius:14px;
310
+ }
311
+
312
+ .device-table{
313
+ width:100%;
314
+ border-collapse:collapse;
315
+ }
316
+
317
+ .device-table thead{
318
+ background:#131f34;
319
+ position:sticky;
320
+ top:0;
321
+ }
322
+
323
+ .device-table th{
324
+ text-align:left;
325
+ padding:16px;
326
+ color:var(--muted);
327
+ font-size:.85rem;
328
+ font-weight:600;
329
+ border-bottom:1px solid var(--border);
330
+ }
331
+
332
+ .device-table td{
333
+ padding:15px 16px;
334
+ border-bottom:1px solid rgba(255,255,255,.05);
335
+ }
336
+
337
+ .device-table tbody tr{
338
+ transition:.15s;
339
+ }
340
+
341
+ .device-table tbody tr:hover{
342
+ background:#172540;
343
+ }
344
+ .status-badge{
345
+ display:inline-flex;
346
+ align-items:center;
347
+ justify-content:center;
348
+ padding:4px 12px;
349
+ border-radius:999px;
350
+ font-size:.75rem;
351
+ font-weight:700;
352
+ text-transform:uppercase;
353
+ }
354
+
355
+ .status-badge.online{
356
+ background:rgba(34,197,94,.15);
357
+ color:#22c55e;
358
+ }
359
+
360
+ .status-badge.degraded{
361
+ background:rgba(245,158,11,.15);
362
+ color:#f59e0b;
363
+ }
364
+
365
+ .status-badge.offline{
366
+ background:rgba(239,68,68,.15);
367
+ color:#ef4444;
368
+ }
@@ -0,0 +1,242 @@
1
+ let found_devices = new Map();
2
+ let events = [];
3
+
4
+ function addEvent(message) {
5
+
6
+ const timestamp = new Date().toLocaleTimeString();
7
+
8
+ events.unshift(`${timestamp} - ${message}`);
9
+
10
+ if(events.length > 15)
11
+ events.pop();
12
+
13
+ document.getElementById("events").innerHTML =
14
+ events.map(e => `<div class="event">${e}</div>`).join("");
15
+ }
16
+
17
+ function getDeviceState(device) {
18
+
19
+ const latency = device.latency ?? 0;
20
+ const loss = device.packet_loss ?? 0;
21
+
22
+ if(latency === 0)
23
+ return "offline";
24
+
25
+ if(latency > 100 || loss > 2)
26
+ return "degraded";
27
+
28
+ return "online";
29
+ }
30
+
31
+ function createDeviceRow(device){
32
+
33
+ const state = getDeviceState(device);
34
+
35
+ const latency =
36
+ device.latency !== 0
37
+ ? `${device.latency} ms`
38
+ : "--";
39
+
40
+ return `
41
+ <tr>
42
+
43
+ <td>
44
+
45
+ <span class="status-badge ${state}">
46
+ ${state}
47
+ </span>
48
+
49
+ </td>
50
+
51
+ <td>
52
+
53
+ <strong>${device.ip}</strong>
54
+
55
+ </td>
56
+
57
+ <td>${latency}</td>
58
+
59
+ <td>${(device.packet_loss ?? 0).toFixed(1)}%</td>
60
+
61
+ <td>${device.mac}</td>
62
+
63
+ <td>${device.last_seen}</td>
64
+
65
+ <td>
66
+
67
+ <a class="device-link"
68
+ href="/devices/${device.ip}">
69
+ <i class="fa fa-ellipsis-v"></i>
70
+ </a>
71
+
72
+ </td>
73
+
74
+ </tr>
75
+ `;
76
+ }
77
+
78
+ function updateOverview(devices){
79
+
80
+ const total = devices.length;
81
+
82
+ const online =
83
+ devices.filter(d => (d.latency ?? 0) > 0).length;
84
+
85
+ const latencyDevices =
86
+ devices.filter(d => (d.latency ?? 0) > 0);
87
+
88
+ const avgLatency =
89
+ latencyDevices.length
90
+ ? Math.round(
91
+ latencyDevices.reduce(
92
+ (a,b)=>a+b.latency,0
93
+ ) / latencyDevices.length
94
+ )
95
+ : 0;
96
+
97
+ const validDevices = devices.filter(device => device.latency !== 0);
98
+ const avgLoss = validDevices.length
99
+ ? (
100
+ validDevices.reduce(
101
+ (sum, device) => sum + (device.packet_loss ?? 0), 0) / validDevices.length
102
+ ).toFixed(1)
103
+ : 0;
104
+
105
+ document.getElementById(
106
+ "total_devices"
107
+ ).textContent = total;
108
+
109
+ document.getElementById(
110
+ "online_devices"
111
+ ).textContent = online;
112
+
113
+ document.getElementById(
114
+ "avg_latency"
115
+ ).textContent = `${avgLatency} ms`;
116
+
117
+ document.getElementById(
118
+ "avg_packet_loss"
119
+ ).textContent = `${avgLoss}%`;
120
+ }
121
+
122
+ function renderDevices(data){
123
+
124
+ const devices = data.devices || [];
125
+
126
+ updateOverview(devices);
127
+
128
+ const search =
129
+ document.getElementById("search")
130
+ .value
131
+ .toLowerCase();
132
+
133
+ const filter =
134
+ document.getElementById("filter_status")
135
+ .value;
136
+
137
+ const filtered =
138
+ devices.filter(device => {
139
+
140
+ const state =
141
+ getDeviceState(device);
142
+
143
+ const matchesSearch =
144
+ device.ip.toLowerCase().includes(search) ||
145
+ (device.mac || "")
146
+ .toLowerCase()
147
+ .includes(search);
148
+
149
+ const matchesFilter =
150
+ filter === "all" ||
151
+ state === filter;
152
+
153
+ return matchesSearch &&
154
+ matchesFilter;
155
+ });
156
+
157
+ document.getElementById(
158
+ "devices"
159
+ ).innerHTML =
160
+ filtered.map(createDeviceRow).join("");
161
+ }
162
+
163
+ async function loadDevices(){
164
+
165
+ const statusDot =
166
+ document.getElementById("status_dot");
167
+ const statusText = document.getElementById("status_text");
168
+
169
+ try{
170
+
171
+ const response =
172
+ await fetch("/api/devices");
173
+
174
+ const data =
175
+ await response.json();
176
+
177
+ renderDevices(data);
178
+
179
+ statusDot.style.background =
180
+ "#22c55e";
181
+ statusText.textContent = "Connected";
182
+
183
+ }catch(error){
184
+
185
+ statusDot.style.background =
186
+ "#ef4444";
187
+ statusText.textContent = "Disconnected";
188
+
189
+ console.error(error);
190
+ }
191
+ }
192
+
193
+ document
194
+ .getElementById("search")
195
+ ?.addEventListener(
196
+ "input",
197
+ loadDevices
198
+ );
199
+
200
+ document
201
+ .getElementById("filter_status")
202
+ ?.addEventListener(
203
+ "change",
204
+ loadDevices
205
+ );
206
+
207
+ async function loadAlertEvents() {
208
+ try {
209
+ const response = await fetch("/api/stats");
210
+ const data = await response.json();
211
+
212
+ const alertEvents = data.recent_alert_events || [];
213
+
214
+ const alert_events = alertEvents.map(event => {
215
+ const time = new Date(event.timestamp)
216
+ .toLocaleString();
217
+
218
+ const eventClass = event.event_type === "trigger" ? "event-trigger" : "event-recover";
219
+ const metricUnit = event.metric === "latency" ? "ms" : "%";
220
+
221
+ return `
222
+ <div class="event ${eventClass}">
223
+ <strong>${event.event_type.toUpperCase()}</strong><br>
224
+ ${event.device_ip} | ${event.metric}: ${event.value.toFixed(2)} ${metricUnit}
225
+ <small>(${time})</small>
226
+ </div>
227
+ `;
228
+ }).slice(0, 15);
229
+
230
+ document.getElementById("events").innerHTML =
231
+ alert_events.join("");
232
+
233
+ } catch (error) {
234
+ console.error("Error loading alert events:", error);
235
+ }
236
+ }
237
+
238
+ loadDevices();
239
+ loadAlertEvents();
240
+
241
+ setInterval(loadDevices, 5000);
242
+ setInterval(loadAlertEvents, 5000);
@@ -0,0 +1,95 @@
1
+ const deviceIP = document.getElementById("device-ip");
2
+ const latency = document.getElementById("device-latency");
3
+ const deviceMAC = document.getElementById("device-mac");
4
+ const packetLoss = document.getElementById("device-packet-loss");
5
+ const status = document.getElementById("device-status");
6
+ const lastSeen = document.getElementById("device-last-seen");
7
+ const alertSatteLatency = document.getElementById("alert-state-latency");
8
+ const alertStatePacketLoss = document.getElementById("alert-state-packet-loss");
9
+ const alertsContainer = document.getElementById("events");
10
+ const deviceCard = document.getElementById("device-card");
11
+ const lineGraph = document.getElementById("device-recent-events-line-graph");
12
+
13
+
14
+ function loadDeviceCardStyle(device_latency) {
15
+ // Remove everything
16
+ deviceCard.classList.remove("device-offline");
17
+ deviceCard.classList.remove("device-degraded");
18
+ deviceCard.classList.remove("device-online");
19
+ // reassign style
20
+ if (device_latency === 0) {
21
+ deviceCard.classList.add("device-offline");
22
+ } else if (device_latency >= 100) {
23
+ deviceCard.classList.add("device-degraded");
24
+ } else {
25
+ deviceCard.classList.add("device-online");
26
+ }
27
+ }
28
+
29
+
30
+ function renderAlertEvents(deviceAlertEvents) {
31
+ alertsContainer.textContent = "";
32
+
33
+ if (deviceAlertEvents.length > 0) {
34
+ deviceAlertEvents.forEach(event => {
35
+ const eventDiv = document.createElement("div");
36
+ eventDiv.classList.add("event");
37
+ eventDiv.classList.add(
38
+ event.event_type === "recover" ? "event-recover" : "event-trigger"
39
+ );
40
+
41
+ const strong = document.createElement("strong");
42
+ strong.textContent = event.event_type.toUpperCase();
43
+
44
+ const br = document.createElement("br");
45
+
46
+ const text = document.createTextNode(`${event.metric}: ${Number(event.value).toFixed(2)} `);
47
+
48
+ const small = document.createElement("small");
49
+ small.textContent = `(${event.timestamp})`;
50
+
51
+ eventDiv.appendChild(strong);
52
+ eventDiv.appendChild(br);
53
+ eventDiv.appendChild(text);
54
+ eventDiv.appendChild(small);
55
+
56
+ alertsContainer.appendChild(eventDiv);
57
+ });
58
+ } else {
59
+ const p = document.createElement("p");
60
+ p.className = "no-alerts";
61
+ p.textContent = "No alerts";
62
+ alertsContainer.appendChild(p);
63
+ }
64
+ }
65
+
66
+
67
+ async function loadDeviceInfo() {
68
+ const ip = window.location.pathname.split("/").pop();
69
+ deviceIP.textContent = ip;
70
+
71
+ const response = await fetch(`/api/devices/${ip}`);
72
+ const data = await response.json();
73
+ const device = data.device || "";
74
+ const deviceAlertState = data.device_alert_state || "";
75
+ const deviceAlertEvents = data.device_recent_alert_events || [];
76
+
77
+ device.latency === 0 ? latency.textContent = "OFFLINE" : latency.textContent = `${device.latency} ms`;
78
+ deviceMAC.textContent = device.mac;
79
+ packetLoss.textContent = `${device.packet_loss} %`;
80
+ device.latency === 0 ? status.textContent = "OFFLINE" : status.textContent = "ONLINE";
81
+ lastSeen.textContent = device.last_seen;
82
+
83
+ alertSatteLatency.textContent = deviceAlertState.latency;
84
+ alertStatePacketLoss.textContent = deviceAlertState.packet_loss;
85
+
86
+ // Send current date as query to prevent cache from not loading the new graph image
87
+ lineGraph.src = `/charts/${ip}.png?v=${Date.now()}`
88
+
89
+ loadDeviceCardStyle(device.latency);
90
+
91
+ renderAlertEvents(deviceAlertEvents);
92
+ }
93
+
94
+ loadDeviceInfo();
95
+ setInterval(loadDeviceInfo, 5000);