pumaguard 21.post8__py3-none-any.whl → 21.post11__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.
- pumaguard/camera_heartbeat.py +310 -0
- pumaguard/presets.py +8 -0
- pumaguard/pumaguard-ui/main.dart.js +2 -2
- pumaguard/web_routes/dhcp.py +49 -0
- pumaguard/web_ui.py +20 -0
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/METADATA +1 -1
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/RECORD +11 -10
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/WHEEL +0 -0
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/entry_points.txt +0 -0
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/licenses/LICENSE +0 -0
- {pumaguard-21.post8.dist-info → pumaguard-21.post11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Camera heartbeat monitoring for PumaGuard.
|
|
2
|
+
|
|
3
|
+
This module provides background monitoring of camera availability using
|
|
4
|
+
ICMP ping and TCP connection checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import (
|
|
8
|
+
annotations,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import socket
|
|
13
|
+
import subprocess
|
|
14
|
+
import threading
|
|
15
|
+
from datetime import (
|
|
16
|
+
datetime,
|
|
17
|
+
timezone,
|
|
18
|
+
)
|
|
19
|
+
from typing import (
|
|
20
|
+
TYPE_CHECKING,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from pumaguard.web_ui import (
|
|
25
|
+
WebUI,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CameraHeartbeat:
|
|
32
|
+
"""
|
|
33
|
+
Background service to monitor camera availability via ICMP ping
|
|
34
|
+
and TCP checks.
|
|
35
|
+
|
|
36
|
+
The heartbeat monitor runs in a separate thread and periodically
|
|
37
|
+
checks if cameras are reachable. It updates the camera status and
|
|
38
|
+
last_seen timestamp based on the results.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
webui: "WebUI",
|
|
45
|
+
interval: int = 60,
|
|
46
|
+
enabled: bool = True,
|
|
47
|
+
check_method: str = "tcp",
|
|
48
|
+
tcp_port: int = 80,
|
|
49
|
+
tcp_timeout: int = 3,
|
|
50
|
+
icmp_timeout: int = 2,
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize the camera heartbeat monitor.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
webui: WebUI instance containing camera information
|
|
57
|
+
interval: Check interval in seconds (default: 60)
|
|
58
|
+
enabled: Enable heartbeat monitoring (default: True)
|
|
59
|
+
check_method: Health check method - "icmp", "tcp", or
|
|
60
|
+
"both" (default: "tcp")
|
|
61
|
+
tcp_port: TCP port to check (default: 80 for HTTP)
|
|
62
|
+
tcp_timeout: TCP connection timeout in seconds (default: 3)
|
|
63
|
+
icmp_timeout: ICMP ping timeout in seconds (default: 2)
|
|
64
|
+
"""
|
|
65
|
+
self.webui = webui
|
|
66
|
+
self.interval = interval
|
|
67
|
+
self.enabled = enabled
|
|
68
|
+
self.check_method = check_method.lower()
|
|
69
|
+
self.tcp_port = tcp_port
|
|
70
|
+
self.tcp_timeout = tcp_timeout
|
|
71
|
+
self.icmp_timeout = icmp_timeout
|
|
72
|
+
|
|
73
|
+
self._running = False
|
|
74
|
+
self._thread: threading.Thread | None = None
|
|
75
|
+
self._stop_event = threading.Event()
|
|
76
|
+
|
|
77
|
+
# Validate check method
|
|
78
|
+
if self.check_method not in ["icmp", "tcp", "both"]:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"Invalid check_method '%s', defaulting to 'tcp'",
|
|
81
|
+
self.check_method,
|
|
82
|
+
)
|
|
83
|
+
self.check_method = "tcp"
|
|
84
|
+
|
|
85
|
+
def _check_icmp(self, ip_address: str) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Check camera availability using ICMP ping.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
ip_address: IP address to ping
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if ping successful, False otherwise
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
# Use ping command with 1 packet and timeout
|
|
97
|
+
# -c 1: send 1 packet
|
|
98
|
+
# -W timeout: wait timeout seconds for response
|
|
99
|
+
# -q: quiet output (only summary)
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
["ping", "-c", "1", "-W", str(self.icmp_timeout), ip_address],
|
|
102
|
+
capture_output=True,
|
|
103
|
+
timeout=self.icmp_timeout + 1,
|
|
104
|
+
check=False,
|
|
105
|
+
)
|
|
106
|
+
return result.returncode == 0
|
|
107
|
+
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
|
|
108
|
+
logger.debug("ICMP ping failed for %s: %s", ip_address, str(e))
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def _check_tcp(self, ip_address: str, port: int) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Check camera availability using TCP connection test.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
ip_address: IP address to connect to
|
|
117
|
+
port: TCP port to connect to
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if connection successful, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
124
|
+
sock.settimeout(self.tcp_timeout)
|
|
125
|
+
result = sock.connect_ex((ip_address, port))
|
|
126
|
+
sock.close()
|
|
127
|
+
return result == 0
|
|
128
|
+
except (socket.error, OSError) as e:
|
|
129
|
+
logger.debug(
|
|
130
|
+
"TCP connection failed for %s:%d: %s",
|
|
131
|
+
ip_address,
|
|
132
|
+
port,
|
|
133
|
+
str(e),
|
|
134
|
+
)
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def check_camera(self, ip_address: str) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Check if a camera is reachable using the configured method.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
ip_address: IP address of the camera
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if camera is reachable, False otherwise
|
|
146
|
+
"""
|
|
147
|
+
if self.check_method == "icmp":
|
|
148
|
+
return self._check_icmp(ip_address)
|
|
149
|
+
if self.check_method == "tcp":
|
|
150
|
+
return self._check_tcp(ip_address, self.tcp_port)
|
|
151
|
+
if self.check_method == "both":
|
|
152
|
+
# Try ICMP first (faster), fall back to TCP
|
|
153
|
+
if self._check_icmp(ip_address):
|
|
154
|
+
return True
|
|
155
|
+
return self._check_tcp(ip_address, self.tcp_port)
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def _update_camera_status(
|
|
159
|
+
self, mac_address: str, is_reachable: bool
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Update camera status and last_seen timestamp.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
mac_address: MAC address of the camera
|
|
166
|
+
is_reachable: Whether the camera is currently reachable
|
|
167
|
+
"""
|
|
168
|
+
if mac_address not in self.webui.cameras:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
camera = self.webui.cameras[mac_address]
|
|
172
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
173
|
+
|
|
174
|
+
if is_reachable:
|
|
175
|
+
# Camera is reachable - update status to connected
|
|
176
|
+
if camera["status"] != "connected":
|
|
177
|
+
logger.info(
|
|
178
|
+
"Camera '%s' is now reachable at %s",
|
|
179
|
+
camera["hostname"],
|
|
180
|
+
camera["ip_address"],
|
|
181
|
+
)
|
|
182
|
+
camera["status"] = "connected"
|
|
183
|
+
camera["last_seen"] = timestamp
|
|
184
|
+
else:
|
|
185
|
+
# Camera is not reachable - update status to disconnected
|
|
186
|
+
if camera["status"] == "connected":
|
|
187
|
+
logger.warning(
|
|
188
|
+
"Camera '%s' is no longer reachable at %s",
|
|
189
|
+
camera["hostname"],
|
|
190
|
+
camera["ip_address"],
|
|
191
|
+
)
|
|
192
|
+
camera["status"] = "disconnected"
|
|
193
|
+
# Don't update last_seen on failure - keep the last successful time
|
|
194
|
+
|
|
195
|
+
# Persist changes to settings
|
|
196
|
+
self._save_camera_list()
|
|
197
|
+
|
|
198
|
+
def _save_camera_list(self) -> None:
|
|
199
|
+
"""Save the camera list to settings file."""
|
|
200
|
+
try:
|
|
201
|
+
camera_list = []
|
|
202
|
+
for _, cam_info in self.webui.cameras.items():
|
|
203
|
+
camera_list.append(
|
|
204
|
+
{
|
|
205
|
+
"hostname": cam_info["hostname"],
|
|
206
|
+
"ip_address": cam_info["ip_address"],
|
|
207
|
+
"mac_address": cam_info["mac_address"],
|
|
208
|
+
"last_seen": cam_info["last_seen"],
|
|
209
|
+
"status": cam_info["status"],
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
self.webui.presets.cameras = camera_list
|
|
213
|
+
self.webui.presets.save()
|
|
214
|
+
except Exception as e: # pylint: disable=broad-except
|
|
215
|
+
logger.error("Failed to save camera list: %s", str(e))
|
|
216
|
+
|
|
217
|
+
def _monitor_loop(self) -> None:
|
|
218
|
+
"""Main monitoring loop that runs in a background thread."""
|
|
219
|
+
logger.info(
|
|
220
|
+
"Camera heartbeat monitor started "
|
|
221
|
+
"(method=%s, interval=%ds, port=%d)",
|
|
222
|
+
self.check_method,
|
|
223
|
+
self.interval,
|
|
224
|
+
self.tcp_port,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
while not self._stop_event.is_set():
|
|
228
|
+
try:
|
|
229
|
+
# Check each camera
|
|
230
|
+
for mac_address, camera in list(self.webui.cameras.items()):
|
|
231
|
+
if self._stop_event.is_set():
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
ip_address = camera["ip_address"]
|
|
235
|
+
if not ip_address:
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
logger.debug(
|
|
239
|
+
"Checking camera '%s' at %s",
|
|
240
|
+
camera["hostname"],
|
|
241
|
+
ip_address,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
is_reachable = self.check_camera(ip_address)
|
|
245
|
+
self._update_camera_status(mac_address, is_reachable)
|
|
246
|
+
|
|
247
|
+
except Exception as e: # pylint: disable=broad-except
|
|
248
|
+
logger.error("Error in heartbeat monitor loop: %s", str(e))
|
|
249
|
+
|
|
250
|
+
# Wait for the next check interval or stop event
|
|
251
|
+
self._stop_event.wait(self.interval)
|
|
252
|
+
|
|
253
|
+
logger.info("Camera heartbeat monitor stopped")
|
|
254
|
+
|
|
255
|
+
def start(self) -> None:
|
|
256
|
+
"""Start the heartbeat monitoring thread."""
|
|
257
|
+
if not self.enabled:
|
|
258
|
+
logger.info("Camera heartbeat monitoring is disabled")
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
if self._running:
|
|
262
|
+
logger.warning("Heartbeat monitor is already running")
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
self._running = True
|
|
266
|
+
self._stop_event.clear()
|
|
267
|
+
self._thread = threading.Thread(
|
|
268
|
+
target=self._monitor_loop, daemon=True, name="CameraHeartbeat"
|
|
269
|
+
)
|
|
270
|
+
self._thread.start()
|
|
271
|
+
logger.info("Camera heartbeat monitoring started")
|
|
272
|
+
|
|
273
|
+
def stop(self) -> None:
|
|
274
|
+
"""Stop the heartbeat monitoring thread."""
|
|
275
|
+
if not self._running:
|
|
276
|
+
logger.warning("Heartbeat monitor is not running")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
self._running = False
|
|
280
|
+
self._stop_event.set()
|
|
281
|
+
|
|
282
|
+
if self._thread and self._thread.is_alive():
|
|
283
|
+
self._thread.join(timeout=5)
|
|
284
|
+
if self._thread.is_alive():
|
|
285
|
+
logger.warning("Heartbeat monitor thread did not stop cleanly")
|
|
286
|
+
else:
|
|
287
|
+
logger.info("Camera heartbeat monitoring stopped")
|
|
288
|
+
|
|
289
|
+
def check_now(self) -> dict[str, bool]:
|
|
290
|
+
"""
|
|
291
|
+
Immediately check all cameras and return results.
|
|
292
|
+
|
|
293
|
+
This can be called manually to force a check outside the
|
|
294
|
+
regular interval.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Dictionary mapping MAC addresses to reachability status
|
|
298
|
+
"""
|
|
299
|
+
results = {}
|
|
300
|
+
for mac_address, camera in self.webui.cameras.items():
|
|
301
|
+
ip_address = camera["ip_address"]
|
|
302
|
+
if not ip_address:
|
|
303
|
+
results[mac_address] = False
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
is_reachable = self.check_camera(ip_address)
|
|
307
|
+
self._update_camera_status(mac_address, is_reachable)
|
|
308
|
+
results[mac_address] = is_reachable
|
|
309
|
+
|
|
310
|
+
return results
|
pumaguard/presets.py
CHANGED
|
@@ -151,6 +151,14 @@ class Preset:
|
|
|
151
151
|
self.no_lion_directories: list[str] = []
|
|
152
152
|
self.validation_no_lion_directories: list[str] = []
|
|
153
153
|
self.with_augmentation = False
|
|
154
|
+
|
|
155
|
+
# Camera heartbeat monitoring settings
|
|
156
|
+
self.camera_heartbeat_enabled = True
|
|
157
|
+
self.camera_heartbeat_interval = 60 # Check interval in seconds
|
|
158
|
+
self.camera_heartbeat_method = "tcp" # "icmp", "tcp", or "both"
|
|
159
|
+
self.camera_heartbeat_tcp_port = 80 # TCP port to check
|
|
160
|
+
self.camera_heartbeat_tcp_timeout = 3 # TCP timeout in seconds
|
|
161
|
+
self.camera_heartbeat_icmp_timeout = 2 # ICMP timeout in seconds
|
|
154
162
|
if version.parse(tf.__version__) < version.parse("2.17"):
|
|
155
163
|
self.tf_compat = "2.15"
|
|
156
164
|
else:
|
|
@@ -88927,7 +88927,7 @@ A.MQ.prototype={
|
|
|
88927
88927
|
K(a){var s,r=null,q=A.awv(B.ae,r,r,B.nE)
|
|
88928
88928
|
q=A.qz(B.mC,r,new A.md(r,r,r,r,2,r,new A.cM(A.hG(12),B.u)),q,"Roboto",!0)
|
|
88929
88929
|
s=A.awv(B.al,r,r,B.nE)
|
|
88930
|
-
return new A.Ay(new A.vb(new A.abK(),r,r,r,r,t.Gj),r,r,new A.zI(B.Hh,"PumaGuard v21-
|
|
88930
|
+
return new A.Ay(new A.vb(new A.abK(),r,r,r,r,t.Gj),r,r,new A.zI(B.Hh,"PumaGuard v21-11-gd633cbe",q,A.qz(B.mC,r,new A.md(r,r,r,r,2,r,new A.cM(A.hG(12),B.u)),s,"Roboto",!0),B.AN,!1,r),r,t.Bp)}}
|
|
88931
88931
|
A.abK.prototype={
|
|
88932
88932
|
$1(a){return new A.m6()},
|
|
88933
88933
|
$S:541}
|
|
@@ -89248,7 +89248,7 @@ q=A.aC("System Information",h,h,h,A.p(q).ok.r,h,h)
|
|
|
89248
89248
|
s=i.d
|
|
89249
89249
|
s=s==null?h:s.b
|
|
89250
89250
|
s=i.tF("Backend Version",s==null?"Unknown":s)
|
|
89251
|
-
n=i.tF("UI Version","v21-
|
|
89251
|
+
n=i.tF("UI Version","v21-11-gd633cbe")
|
|
89252
89252
|
l=i.d
|
|
89253
89253
|
k=l==null
|
|
89254
89254
|
j=k?h:l.d
|
pumaguard/web_routes/dhcp.py
CHANGED
|
@@ -337,3 +337,52 @@ def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
|
|
|
337
337
|
),
|
|
338
338
|
200,
|
|
339
339
|
)
|
|
340
|
+
|
|
341
|
+
@app.route("/api/dhcp/cameras/heartbeat", methods=["POST"])
|
|
342
|
+
def check_heartbeat():
|
|
343
|
+
"""
|
|
344
|
+
Manually trigger a heartbeat check for all cameras.
|
|
345
|
+
|
|
346
|
+
This immediately checks all cameras and returns their
|
|
347
|
+
reachability status.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
JSON with camera reachability results
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
results = webui.heartbeat.check_now()
|
|
354
|
+
|
|
355
|
+
# Convert results to human-readable format
|
|
356
|
+
camera_status = {}
|
|
357
|
+
for mac_address, is_reachable in results.items():
|
|
358
|
+
camera = webui.cameras.get(mac_address)
|
|
359
|
+
if camera:
|
|
360
|
+
camera_status[mac_address] = {
|
|
361
|
+
"hostname": camera["hostname"],
|
|
362
|
+
"ip_address": camera["ip_address"],
|
|
363
|
+
"reachable": is_reachable,
|
|
364
|
+
"status": camera["status"],
|
|
365
|
+
"last_seen": camera["last_seen"],
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
jsonify(
|
|
370
|
+
{
|
|
371
|
+
"status": "success",
|
|
372
|
+
"message": "Heartbeat check completed",
|
|
373
|
+
"cameras": camera_status,
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
200,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
except Exception as e: # pylint: disable=broad-except
|
|
380
|
+
logger.error("Error performing heartbeat check: %s", str(e))
|
|
381
|
+
return (
|
|
382
|
+
jsonify(
|
|
383
|
+
{
|
|
384
|
+
"error": "Failed to perform heartbeat check",
|
|
385
|
+
}
|
|
386
|
+
),
|
|
387
|
+
500,
|
|
388
|
+
)
|
pumaguard/web_ui.py
CHANGED
|
@@ -33,6 +33,9 @@ from zeroconf import (
|
|
|
33
33
|
Zeroconf,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
from pumaguard.camera_heartbeat import (
|
|
37
|
+
CameraHeartbeat,
|
|
38
|
+
)
|
|
36
39
|
from pumaguard.presets import (
|
|
37
40
|
Preset,
|
|
38
41
|
)
|
|
@@ -161,6 +164,17 @@ class WebUI:
|
|
|
161
164
|
# Format: {mac_address: CameraInfo}
|
|
162
165
|
self.cameras: dict[str, CameraInfo] = {}
|
|
163
166
|
|
|
167
|
+
# Camera heartbeat monitoring
|
|
168
|
+
self.heartbeat: CameraHeartbeat = CameraHeartbeat(
|
|
169
|
+
webui=self,
|
|
170
|
+
interval=presets.camera_heartbeat_interval,
|
|
171
|
+
enabled=presets.camera_heartbeat_enabled,
|
|
172
|
+
check_method=presets.camera_heartbeat_method,
|
|
173
|
+
tcp_port=presets.camera_heartbeat_tcp_port,
|
|
174
|
+
tcp_timeout=presets.camera_heartbeat_tcp_timeout,
|
|
175
|
+
icmp_timeout=presets.camera_heartbeat_icmp_timeout,
|
|
176
|
+
)
|
|
177
|
+
|
|
164
178
|
# Load cameras from persisted settings
|
|
165
179
|
for camera in presets.cameras:
|
|
166
180
|
mac = camera.get("mac_address")
|
|
@@ -454,6 +468,9 @@ class WebUI:
|
|
|
454
468
|
# Start mDNS service
|
|
455
469
|
self._start_mdns()
|
|
456
470
|
|
|
471
|
+
# Start camera heartbeat monitoring
|
|
472
|
+
self.heartbeat.start()
|
|
473
|
+
|
|
457
474
|
if self.debug:
|
|
458
475
|
self._run_server()
|
|
459
476
|
else:
|
|
@@ -473,6 +490,9 @@ class WebUI:
|
|
|
473
490
|
|
|
474
491
|
self._running = False
|
|
475
492
|
|
|
493
|
+
# Stop camera heartbeat monitoring
|
|
494
|
+
self.heartbeat.stop()
|
|
495
|
+
|
|
476
496
|
# Stop mDNS service
|
|
477
497
|
self._stop_mdns()
|
|
478
498
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
pumaguard/__init__.py,sha256=QUsYaH1hG9RKygFZfaS5tpe9pgN_1Kf7EMroQCVlQfE,421
|
|
2
|
+
pumaguard/camera_heartbeat.py,sha256=vfdprxBnVDF-_iA9MUaI2tVh4AbeqP1j09xKqT2N25Y,10291
|
|
2
3
|
pumaguard/classify.py,sha256=QnHfnIlkzSxHGy0dgqT6IeTbRBboRT3c3B_YC3YpqfI,1057
|
|
3
4
|
pumaguard/lock_manager.py,sha256=sYLl8Z7miOr_9R7ehoSFlKf8tToHNNUT6aH9Mlw2M9g,1608
|
|
4
5
|
pumaguard/main.py,sha256=1Wazv1wjwb46yNlqgWt88HQwKSxGmY24X5OsUv8gYyE,7029
|
|
5
6
|
pumaguard/model-registry.yaml,sha256=V-pTaqJrk_jJo568okBDaVhs51qTHttSqKe6PqdX6Bc,10318
|
|
6
7
|
pumaguard/model_cli.py,sha256=nzDv0lXSvRKpLxs579tiHInJPPV-AFO4jzeLk5t2GaA,1394
|
|
7
8
|
pumaguard/model_downloader.py,sha256=zJQgCMOF2AfhB30VsfOMYtgRxcxVxkZBAdtG8KznPyY,12895
|
|
8
|
-
pumaguard/presets.py,sha256=
|
|
9
|
+
pumaguard/presets.py,sha256=RziRfHRcRAA4ykNm9m2dfk7ko3xdpoS6natlAlzLJH4,28164
|
|
9
10
|
pumaguard/server.py,sha256=zmzSXabt6K26u8kwBPdm1gI6aMAwJo3gCaSaX5Sh0vk,14705
|
|
10
11
|
pumaguard/sound.py,sha256=-ceyO9xjfuVSal-CvaM_o2l45gYWUUV3bJN-Eni2XQQ,4732
|
|
11
12
|
pumaguard/stats.py,sha256=ZwocfnFCQ-ky7me-YTTrEoJqsIHOWAgSzeoJHItsIU4,927
|
|
12
13
|
pumaguard/utils.py,sha256=w1EgOLSZGyjq_b49hvVZhBESy-lVP0yRtNHe-sXBoIU,19735
|
|
13
14
|
pumaguard/verify.py,sha256=vfw3PRzDt1uuH5FKV9F5vb1PH7KQ6AEgVNhJ6jck_hQ,5513
|
|
14
|
-
pumaguard/web_ui.py,sha256=
|
|
15
|
+
pumaguard/web_ui.py,sha256=N51XEKBuCJfBWUsm4EqvzpS1EG5ey6Z2kBlKnCitRqQ,17332
|
|
15
16
|
pumaguard/completions/pumaguard-classify-completions.sh,sha256=5QySg-2Jdinj15qpUYa5UzHbTgYzi2gmPVYYyyXny4c,1353
|
|
16
17
|
pumaguard/completions/pumaguard-completions.sh,sha256=bRx3Q3_gM__3w0PyfQSCVdxylhhr3QlzaLCav24dfNc,1196
|
|
17
18
|
pumaguard/completions/pumaguard-server-completions.sh,sha256=33c6GjbTImBOHn0SSNUOJoxqJ2mMHuDv3P3GQJGGHhA,1161
|
|
@@ -22,7 +23,7 @@ pumaguard/pumaguard-ui/flutter.js,sha256=7V1ZIKmGiouT15CpquQWWmKWJyjUq77FoU9gDXP
|
|
|
22
23
|
pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=Nyt0e7m8U4hq4CmcHhK1ij4hNgUqANM1o4YieKUx4rA,9692
|
|
23
24
|
pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
25
|
pumaguard/pumaguard-ui/index.html,sha256=901-ZY0WysVAZWPwj2xGatoezwm9TX9IV_jpMrlsaXg,1205
|
|
25
|
-
pumaguard/pumaguard-ui/main.dart.js,sha256=
|
|
26
|
+
pumaguard/pumaguard-ui/main.dart.js,sha256=BLfrML4mgJKj_QKVDnjSAyBZO6sEEhn3dcWsy09Upsg,2729035
|
|
26
27
|
pumaguard/pumaguard-ui/manifest.json,sha256=Hhnw_eLUivdrOlL7O9KGBsGXCKKt3lix17Fh3GB0g-s,920
|
|
27
28
|
pumaguard/pumaguard-ui/version.json,sha256=uXZ6musTJUZaO0N2bEbr3cy9rpx2aesAS2YFMcu2WF8,94
|
|
28
29
|
pumaguard/pumaguard-ui/assets/AssetManifest.bin,sha256=Qzp1G9iPlHSW-PnHyszTxZO31_NjmTlvSBWY_REPH_8,562
|
|
@@ -58,14 +59,14 @@ pumaguard/pumaguard-ui/icons/Icon-maskable-192.png,sha256=0shC4iqfTsnZlrIzc6kFyI
|
|
|
58
59
|
pumaguard/pumaguard-ui/icons/Icon-maskable-512.png,sha256=au4Gzcq2sq73Sxc0xHePRCHS2hALD_nlKyG1UkAgKSk,20998
|
|
59
60
|
pumaguard/web_routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
61
|
pumaguard/web_routes/artifacts.py,sha256=IpnMLdbgAYkwU3TuYJE-JHGnC_x5_XNCrc-1M_n2YKk,3879
|
|
61
|
-
pumaguard/web_routes/dhcp.py,sha256=
|
|
62
|
+
pumaguard/web_routes/dhcp.py,sha256=YoF-987Ro1DqVBu1szloIkW-XQx81-WMKgqk83dNoRc,12607
|
|
62
63
|
pumaguard/web_routes/diagnostics.py,sha256=EIIbjuixJyGXdnVQf8RQ6xQxJar0UHZO8dF-9zQLY9g,3294
|
|
63
64
|
pumaguard/web_routes/directories.py,sha256=yy5TghCEyB4reRGAcVHIEfr2vlHnuiDChIXl9ZFquRM,2410
|
|
64
65
|
pumaguard/web_routes/folders.py,sha256=Z63ap6dRi6NWye70HYurpCnsSXmFgzTbTsFKYdZ1Bjk,6305
|
|
65
66
|
pumaguard/web_routes/photos.py,sha256=Tac_CbaZSeZzOfaJ73vlp3iyZbvfD7ei1YM3tsb0nTY,5106
|
|
66
67
|
pumaguard/web_routes/settings.py,sha256=GA7MERNRRnR2lfrG-aSRx8bJO5OTqVzyQTYSqPYP8wc,11754
|
|
67
68
|
pumaguard/web_routes/sync.py,sha256=Zvv6VARGE5xP29C5gWH3ul81PISRxoF8n472DITItE0,6378
|
|
68
|
-
pumaguard-21.
|
|
69
|
+
pumaguard-21.post11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
69
70
|
pumaguard-sounds/cougar_call.mp3,sha256=jdPzi7Qneect3ez2G6XAeHWtetU5vSOSB6pceuB26Wc,129048
|
|
70
71
|
pumaguard-sounds/cougarsounds.wav,sha256=hwVmmQ75dkOP3qd07YAvVOSm1neYtxLSzxw3Ulvs2cM,96346
|
|
71
72
|
pumaguard-sounds/dark-engine-logo-141942.mp3,sha256=Vw-qyLTMPJZvsgQcZtH0DpGcP1dd7nJq-9BnHuNPGug,372819
|
|
@@ -83,8 +84,8 @@ pumaguard-sounds/mixkit-vintage-telephone-ringtone-1356.wav,sha256=zWWY2uFF0-l7P
|
|
|
83
84
|
pumaguard-sounds/pumaguard-warning.mp3,sha256=wcCfHsulPo5P5s8MjpQAG2NYHQDsRpjqoMig1-o_MDI,232249
|
|
84
85
|
pumaguard-sounds/short-round-110940.mp3,sha256=vdskGD94SeH1UJyJyR0Ek_7xGXPIZfnPdoBvxGnUt98,450816
|
|
85
86
|
pumaguard-ui/ios/Flutter/ephemeral/flutter_lldb_helper.py,sha256=Bc_jl3_e5ZPvrSBJpPYtN05VxpztyKq-7lVms3rLg4Q,1276
|
|
86
|
-
pumaguard-21.
|
|
87
|
-
pumaguard-21.
|
|
88
|
-
pumaguard-21.
|
|
89
|
-
pumaguard-21.
|
|
90
|
-
pumaguard-21.
|
|
87
|
+
pumaguard-21.post11.dist-info/METADATA,sha256=37g3nhnn1ysuljUOt9NPy1x09zxi_Vz9oTLcSIWyQTc,8617
|
|
88
|
+
pumaguard-21.post11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
89
|
+
pumaguard-21.post11.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
|
|
90
|
+
pumaguard-21.post11.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
|
|
91
|
+
pumaguard-21.post11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|