pumaguard 20.post244__py3-none-any.whl → 20.post247__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/pumaguard-ui/.last_build_id +1 -1
- pumaguard/pumaguard-ui/flutter_bootstrap.js +1 -1
- pumaguard/pumaguard-ui/main.dart.js +2 -2
- pumaguard/web_routes/dhcp.py +178 -0
- pumaguard/web_ui.py +19 -0
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/METADATA +1 -1
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/RECORD +11 -10
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/WHEEL +0 -0
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/entry_points.txt +0 -0
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/licenses/LICENSE +0 -0
- {pumaguard-20.post244.dist-info → pumaguard-20.post247.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
01c252faf73780a4cebf82557c853a5d
|
|
@@ -34,6 +34,6 @@ addEventListener("message", eventListener);
|
|
|
34
34
|
if (!window._flutter) {
|
|
35
35
|
window._flutter = {};
|
|
36
36
|
}
|
|
37
|
-
_flutter.buildConfig = {"engineRevision":"
|
|
37
|
+
_flutter.buildConfig = {"engineRevision":"1527ae0ec577a4ef50e65f6fefcfc1326707d9bf","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}],"useLocalCanvasKit":true};
|
|
38
38
|
|
|
39
39
|
_flutter.loader.load();
|
|
@@ -88888,7 +88888,7 @@ A.MN.prototype={
|
|
|
88888
88888
|
K(a){var s,r=null,q=A.awp(B.ae,r,r,B.nE)
|
|
88889
88889
|
q=A.qy(B.mB,r,new A.mb(r,r,r,r,2,r,new A.cM(A.hF(12),B.u)),q,"Roboto",!0)
|
|
88890
88890
|
s=A.awp(B.al,r,r,B.nE)
|
|
88891
|
-
return new A.Av(new A.va(new A.abI(),r,r,r,r,t.Gj),r,r,new A.zF(B.Hi,"PumaGuard v20-
|
|
88891
|
+
return new A.Av(new A.va(new A.abI(),r,r,r,r,t.Gj),r,r,new A.zF(B.Hi,"PumaGuard v20-247-gf025667",q,A.qy(B.mB,r,new A.mb(r,r,r,r,2,r,new A.cM(A.hF(12),B.u)),s,"Roboto",!0),B.AQ,!1,r),r,t.Bp)}}
|
|
88892
88892
|
A.abI.prototype={
|
|
88893
88893
|
$1(a){return new A.m5()},
|
|
88894
88894
|
$S:541}
|
|
@@ -89168,7 +89168,7 @@ q=A.aL("System Information",h,h,h,A.r(q).ok.r,h,h)
|
|
|
89168
89168
|
s=i.d
|
|
89169
89169
|
s=s==null?h:s.b
|
|
89170
89170
|
s=i.tE("Backend Version",s==null?"Unknown":s)
|
|
89171
|
-
n=i.tE("UI Version","v20-
|
|
89171
|
+
n=i.tE("UI Version","v20-247-gf025667")
|
|
89172
89172
|
l=i.d
|
|
89173
89173
|
k=l==null
|
|
89174
89174
|
j=k?h:l.d
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""DHCP event routes for camera detection notifications."""
|
|
2
|
+
|
|
3
|
+
from __future__ import (
|
|
4
|
+
annotations,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from flask import (
|
|
13
|
+
jsonify,
|
|
14
|
+
request,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from flask import (
|
|
19
|
+
Flask,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from pumaguard.web_ui import (
|
|
23
|
+
WebUI,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
|
|
30
|
+
"""Register DHCP event endpoints for camera detection."""
|
|
31
|
+
|
|
32
|
+
@app.route("/api/dhcp/event", methods=["POST"])
|
|
33
|
+
def dhcp_event():
|
|
34
|
+
"""
|
|
35
|
+
Receive DHCP event notifications from dnsmasq script.
|
|
36
|
+
|
|
37
|
+
Expected JSON payload:
|
|
38
|
+
{
|
|
39
|
+
"action": "add|old|del",
|
|
40
|
+
"mac_address": "xx:xx:xx:xx:xx:xx",
|
|
41
|
+
"ip_address": "192.168.52.xxx",
|
|
42
|
+
"hostname": "device-hostname",
|
|
43
|
+
"timestamp": "ISO8601 timestamp"
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
data = request.get_json()
|
|
48
|
+
|
|
49
|
+
if not data:
|
|
50
|
+
return jsonify({"error": "No JSON data provided"}), 400
|
|
51
|
+
|
|
52
|
+
action = data.get("action")
|
|
53
|
+
mac_address = data.get("mac_address")
|
|
54
|
+
ip_address = data.get("ip_address")
|
|
55
|
+
hostname = data.get("hostname")
|
|
56
|
+
timestamp = data.get("timestamp")
|
|
57
|
+
|
|
58
|
+
# Log the DHCP event (MAC address redacted)
|
|
59
|
+
logger.info(
|
|
60
|
+
"DHCP event received: action=%s, hostname=%s, "
|
|
61
|
+
"mac=%s, ip=%s, timestamp=%s",
|
|
62
|
+
action,
|
|
63
|
+
hostname,
|
|
64
|
+
"***",
|
|
65
|
+
ip_address,
|
|
66
|
+
timestamp,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Store camera information in webui.cameras dictionary
|
|
70
|
+
if action in ["add", "old"]:
|
|
71
|
+
# Camera connected or renewed lease
|
|
72
|
+
logger.info(
|
|
73
|
+
"Camera '%s' connected at IP %s", hostname, ip_address
|
|
74
|
+
)
|
|
75
|
+
# Store camera info indexed by MAC address
|
|
76
|
+
webui.cameras[mac_address] = {
|
|
77
|
+
"hostname": hostname,
|
|
78
|
+
"ip_address": ip_address,
|
|
79
|
+
"mac_address": mac_address,
|
|
80
|
+
"last_seen": timestamp,
|
|
81
|
+
"status": "connected",
|
|
82
|
+
}
|
|
83
|
+
elif action == "del":
|
|
84
|
+
# Camera disconnected
|
|
85
|
+
logger.info("Camera '%s' disconnected", hostname)
|
|
86
|
+
# Update camera status to disconnected (keep history)
|
|
87
|
+
if mac_address in webui.cameras:
|
|
88
|
+
webui.cameras[mac_address]["status"] = "disconnected"
|
|
89
|
+
webui.cameras[mac_address]["last_seen"] = timestamp
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
jsonify(
|
|
93
|
+
{
|
|
94
|
+
"status": "success",
|
|
95
|
+
"message": "DHCP event processed",
|
|
96
|
+
"data": {
|
|
97
|
+
"action": action,
|
|
98
|
+
"hostname": hostname,
|
|
99
|
+
"ip_address": ip_address,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
200,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
except Exception as e: # pylint: disable=broad-except
|
|
107
|
+
logger.error("Error processing DHCP event: %s", str(e))
|
|
108
|
+
return (
|
|
109
|
+
jsonify(
|
|
110
|
+
{
|
|
111
|
+
"error": "Failed to process DHCP event",
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
500,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@app.route("/api/dhcp/cameras", methods=["GET"])
|
|
118
|
+
def get_cameras():
|
|
119
|
+
"""
|
|
120
|
+
Get list of known cameras.
|
|
121
|
+
|
|
122
|
+
Returns all detected cameras with their connection status.
|
|
123
|
+
"""
|
|
124
|
+
cameras_list = list(webui.cameras.values())
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
jsonify(
|
|
128
|
+
{
|
|
129
|
+
"cameras": cameras_list,
|
|
130
|
+
"count": len(cameras_list),
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
200,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@app.route("/api/dhcp/cameras/<mac_address>", methods=["GET"])
|
|
137
|
+
def get_camera(mac_address: str):
|
|
138
|
+
"""
|
|
139
|
+
Get specific camera by MAC address.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
mac_address: MAC address of the camera (e.g., aa:bb:cc:dd:ee:ff)
|
|
143
|
+
"""
|
|
144
|
+
# Normalize MAC address format (lowercase, colons)
|
|
145
|
+
mac_address = mac_address.lower()
|
|
146
|
+
|
|
147
|
+
if mac_address in webui.cameras:
|
|
148
|
+
return jsonify(webui.cameras[mac_address]), 200
|
|
149
|
+
return (
|
|
150
|
+
jsonify(
|
|
151
|
+
{
|
|
152
|
+
"error": "Camera not found",
|
|
153
|
+
"mac_address": mac_address,
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
404,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@app.route("/api/dhcp/cameras", methods=["DELETE"])
|
|
160
|
+
def clear_cameras():
|
|
161
|
+
"""
|
|
162
|
+
Clear all camera records.
|
|
163
|
+
|
|
164
|
+
This removes all stored camera information from memory.
|
|
165
|
+
"""
|
|
166
|
+
count = len(webui.cameras)
|
|
167
|
+
webui.cameras.clear()
|
|
168
|
+
logger.info("Cleared %d camera records", count)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
jsonify(
|
|
172
|
+
{
|
|
173
|
+
"status": "success",
|
|
174
|
+
"message": f"Cleared {count} camera record(s)",
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
200,
|
|
178
|
+
)
|
pumaguard/web_ui.py
CHANGED
|
@@ -39,6 +39,9 @@ from pumaguard.presets import (
|
|
|
39
39
|
from pumaguard.web_routes.artifacts import (
|
|
40
40
|
register_artifacts_routes,
|
|
41
41
|
)
|
|
42
|
+
from pumaguard.web_routes.dhcp import (
|
|
43
|
+
register_dhcp_routes,
|
|
44
|
+
)
|
|
42
45
|
from pumaguard.web_routes.diagnostics import (
|
|
43
46
|
register_diagnostics_routes,
|
|
44
47
|
)
|
|
@@ -66,6 +69,16 @@ if TYPE_CHECKING:
|
|
|
66
69
|
logger = logging.getLogger(__name__)
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
class CameraInfo(TypedDict):
|
|
73
|
+
"""Type definition for camera information stored in webui.cameras."""
|
|
74
|
+
|
|
75
|
+
hostname: str
|
|
76
|
+
ip_address: str
|
|
77
|
+
mac_address: str
|
|
78
|
+
last_seen: str
|
|
79
|
+
status: str
|
|
80
|
+
|
|
81
|
+
|
|
69
82
|
class PhotoDict(TypedDict):
|
|
70
83
|
"""Type definition for photo metadata dictionary."""
|
|
71
84
|
|
|
@@ -144,6 +157,10 @@ class WebUI:
|
|
|
144
157
|
self.image_directories: list[str] = []
|
|
145
158
|
self.classification_directories: list[str] = []
|
|
146
159
|
|
|
160
|
+
# Camera tracking - stores detected cameras by MAC address
|
|
161
|
+
# Format: {mac_address: CameraInfo}
|
|
162
|
+
self.cameras: dict[str, CameraInfo] = {}
|
|
163
|
+
|
|
147
164
|
# mDNS/Zeroconf support
|
|
148
165
|
self.zeroconf: Zeroconf | None = None
|
|
149
166
|
self.service_info: ServiceInfo | None = None
|
|
@@ -241,6 +258,8 @@ class WebUI:
|
|
|
241
258
|
|
|
242
259
|
register_diagnostics_routes(self.app, self)
|
|
243
260
|
|
|
261
|
+
register_dhcp_routes(self.app, self)
|
|
262
|
+
|
|
244
263
|
@self.app.route("/<path:path>")
|
|
245
264
|
def serve_static(path: str):
|
|
246
265
|
"""
|
|
@@ -11,18 +11,18 @@ pumaguard/sound.py,sha256=fUp0FWmuj99ZMDacZAayYZOurFfvp68aFxqj0VvbqGw,4723
|
|
|
11
11
|
pumaguard/stats.py,sha256=ZwocfnFCQ-ky7me-YTTrEoJqsIHOWAgSzeoJHItsIU4,927
|
|
12
12
|
pumaguard/utils.py,sha256=w1EgOLSZGyjq_b49hvVZhBESy-lVP0yRtNHe-sXBoIU,19735
|
|
13
13
|
pumaguard/verify.py,sha256=vfw3PRzDt1uuH5FKV9F5vb1PH7KQ6AEgVNhJ6jck_hQ,5513
|
|
14
|
-
pumaguard/web_ui.py,sha256=
|
|
14
|
+
pumaguard/web_ui.py,sha256=_DiEELlJwuL4DjlUoxGFnHecRmIc21J6zvQwEcXpFdA,16140
|
|
15
15
|
pumaguard/completions/pumaguard-classify-completions.sh,sha256=5QySg-2Jdinj15qpUYa5UzHbTgYzi2gmPVYYyyXny4c,1353
|
|
16
16
|
pumaguard/completions/pumaguard-completions.sh,sha256=bRx3Q3_gM__3w0PyfQSCVdxylhhr3QlzaLCav24dfNc,1196
|
|
17
17
|
pumaguard/completions/pumaguard-server-completions.sh,sha256=33c6GjbTImBOHn0SSNUOJoxqJ2mMHuDv3P3GQJGGHhA,1161
|
|
18
18
|
pumaguard/completions/pumaguard-train-completions.sh,sha256=lI8LG-QrncvhUqCeKtfrSU1MSRBn52KnDsiJJQm37W4,1184
|
|
19
|
-
pumaguard/pumaguard-ui/.last_build_id,sha256=
|
|
19
|
+
pumaguard/pumaguard-ui/.last_build_id,sha256=MqeXJXz-uOBq3M0XNvGxIcX4cKB19A_LfS3HU_820DM,32
|
|
20
20
|
pumaguard/pumaguard-ui/favicon.png,sha256=erJSX0uGtl0-THA1ihfloar29Df5nLzARtrXPVm7kBU,917
|
|
21
21
|
pumaguard/pumaguard-ui/flutter.js,sha256=7V1ZIKmGiouT15CpquQWWmKWJyjUq77FoU9gDXPFO9M,9412
|
|
22
|
-
pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=
|
|
22
|
+
pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=Nyt0e7m8U4hq4CmcHhK1ij4hNgUqANM1o4YieKUx4rA,9692
|
|
23
23
|
pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
pumaguard/pumaguard-ui/index.html,sha256=901-ZY0WysVAZWPwj2xGatoezwm9TX9IV_jpMrlsaXg,1205
|
|
25
|
-
pumaguard/pumaguard-ui/main.dart.js,sha256=
|
|
25
|
+
pumaguard/pumaguard-ui/main.dart.js,sha256=uoSU-v_5ZgHKWW3pWQoEZg2hQXQoTKymXVCSXmJ61hQ,2727567
|
|
26
26
|
pumaguard/pumaguard-ui/manifest.json,sha256=Hhnw_eLUivdrOlL7O9KGBsGXCKKt3lix17Fh3GB0g-s,920
|
|
27
27
|
pumaguard/pumaguard-ui/version.json,sha256=uXZ6musTJUZaO0N2bEbr3cy9rpx2aesAS2YFMcu2WF8,94
|
|
28
28
|
pumaguard/pumaguard-ui/assets/AssetManifest.bin,sha256=Qzp1G9iPlHSW-PnHyszTxZO31_NjmTlvSBWY_REPH_8,562
|
|
@@ -58,13 +58,14 @@ pumaguard/pumaguard-ui/icons/Icon-maskable-192.png,sha256=0shC4iqfTsnZlrIzc6kFyI
|
|
|
58
58
|
pumaguard/pumaguard-ui/icons/Icon-maskable-512.png,sha256=au4Gzcq2sq73Sxc0xHePRCHS2hALD_nlKyG1UkAgKSk,20998
|
|
59
59
|
pumaguard/web_routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
pumaguard/web_routes/artifacts.py,sha256=IpnMLdbgAYkwU3TuYJE-JHGnC_x5_XNCrc-1M_n2YKk,3879
|
|
61
|
+
pumaguard/web_routes/dhcp.py,sha256=wgRwN-pMix5FxJ-UWG38Hn5FiZpMvUxW0rpff_hYjUA,5176
|
|
61
62
|
pumaguard/web_routes/diagnostics.py,sha256=EIIbjuixJyGXdnVQf8RQ6xQxJar0UHZO8dF-9zQLY9g,3294
|
|
62
63
|
pumaguard/web_routes/directories.py,sha256=yy5TghCEyB4reRGAcVHIEfr2vlHnuiDChIXl9ZFquRM,2410
|
|
63
64
|
pumaguard/web_routes/folders.py,sha256=Z63ap6dRi6NWye70HYurpCnsSXmFgzTbTsFKYdZ1Bjk,6305
|
|
64
65
|
pumaguard/web_routes/photos.py,sha256=Tac_CbaZSeZzOfaJ73vlp3iyZbvfD7ei1YM3tsb0nTY,5106
|
|
65
66
|
pumaguard/web_routes/settings.py,sha256=kjSqoX6E38UJ_J_YZRQr7QmACR7bDwFcUarqnowjHP4,11655
|
|
66
67
|
pumaguard/web_routes/sync.py,sha256=Zvv6VARGE5xP29C5gWH3ul81PISRxoF8n472DITItE0,6378
|
|
67
|
-
pumaguard-20.
|
|
68
|
+
pumaguard-20.post247.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
68
69
|
pumaguard-sounds/cougar_call.mp3,sha256=jdPzi7Qneect3ez2G6XAeHWtetU5vSOSB6pceuB26Wc,129048
|
|
69
70
|
pumaguard-sounds/cougarsounds.wav,sha256=hwVmmQ75dkOP3qd07YAvVOSm1neYtxLSzxw3Ulvs2cM,96346
|
|
70
71
|
pumaguard-sounds/dark-engine-logo-141942.mp3,sha256=Vw-qyLTMPJZvsgQcZtH0DpGcP1dd7nJq-9BnHuNPGug,372819
|
|
@@ -82,8 +83,8 @@ pumaguard-sounds/mixkit-vintage-telephone-ringtone-1356.wav,sha256=zWWY2uFF0-l7P
|
|
|
82
83
|
pumaguard-sounds/pumaguard-warning.mp3,sha256=wcCfHsulPo5P5s8MjpQAG2NYHQDsRpjqoMig1-o_MDI,232249
|
|
83
84
|
pumaguard-sounds/short-round-110940.mp3,sha256=vdskGD94SeH1UJyJyR0Ek_7xGXPIZfnPdoBvxGnUt98,450816
|
|
84
85
|
pumaguard-ui/ios/Flutter/ephemeral/flutter_lldb_helper.py,sha256=Bc_jl3_e5ZPvrSBJpPYtN05VxpztyKq-7lVms3rLg4Q,1276
|
|
85
|
-
pumaguard-20.
|
|
86
|
-
pumaguard-20.
|
|
87
|
-
pumaguard-20.
|
|
88
|
-
pumaguard-20.
|
|
89
|
-
pumaguard-20.
|
|
86
|
+
pumaguard-20.post247.dist-info/METADATA,sha256=c7Jd4SrpH9_78yrulTC-8HsivfMx5as13QBC-0uqWeE,8618
|
|
87
|
+
pumaguard-20.post247.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
88
|
+
pumaguard-20.post247.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
|
|
89
|
+
pumaguard-20.post247.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
|
|
90
|
+
pumaguard-20.post247.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|