pumaguard 21.post29__py3-none-any.whl → 21.post83__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/presets.py +1 -0
- pumaguard/pumaguard-ui/.last_build_id +1 -1
- pumaguard/pumaguard-ui/assets/NOTICES +621 -71
- pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf +0 -0
- pumaguard/pumaguard-ui/flutter_bootstrap.js +1 -1
- pumaguard/pumaguard-ui/main.dart.js +28869 -28787
- pumaguard/web_routes/dhcp.py +311 -54
- pumaguard/web_routes/diagnostics.py +6 -0
- pumaguard/web_routes/settings.py +13 -0
- pumaguard/web_ui.py +29 -0
- {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/METADATA +1 -1
- pumaguard-21.post83.dist-info/RECORD +254 -0
- pumaguard-ui/.gitignore +48 -0
- pumaguard-ui/.metadata +45 -0
- pumaguard-ui/API_REFERENCE.md +717 -0
- pumaguard-ui/LICENSE +201 -0
- pumaguard-ui/Makefile +36 -0
- pumaguard-ui/README.md +371 -0
- pumaguard-ui/UI_DEVELOPMENT_CONTEXT.md +427 -0
- pumaguard-ui/analysis_options.yaml +28 -0
- pumaguard-ui/android/.gitignore +14 -0
- pumaguard-ui/android/app/build.gradle.kts +44 -0
- pumaguard-ui/android/app/src/debug/AndroidManifest.xml +7 -0
- pumaguard-ui/android/app/src/main/AndroidManifest.xml +45 -0
- pumaguard-ui/android/app/src/main/kotlin/com/example/pumaguard_ui/MainActivity.kt +5 -0
- pumaguard-ui/android/app/src/main/res/drawable/launch_background.xml +12 -0
- pumaguard-ui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- pumaguard-ui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/values/styles.xml +18 -0
- pumaguard-ui/android/app/src/main/res/values-night/styles.xml +18 -0
- pumaguard-ui/android/app/src/profile/AndroidManifest.xml +7 -0
- pumaguard-ui/android/build.gradle.kts +24 -0
- pumaguard-ui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- pumaguard-ui/android/gradle.properties +2 -0
- pumaguard-ui/android/settings.gradle.kts +26 -0
- pumaguard-ui/fonts/README.md +38 -0
- pumaguard-ui/fonts/Roboto-Bold.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Light.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Medium.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Regular.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Bold.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Medium.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Regular.ttf +0 -0
- pumaguard-ui/fonts/download_fonts.sh +76 -0
- pumaguard-ui/ios/.gitignore +34 -0
- pumaguard-ui/ios/Flutter/AppFrameworkInfo.plist +26 -0
- pumaguard-ui/ios/Flutter/Debug.xcconfig +1 -0
- pumaguard-ui/ios/Flutter/Release.xcconfig +1 -0
- pumaguard-ui/ios/Runner/AppDelegate.swift +13 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- pumaguard-ui/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
- pumaguard-ui/ios/Runner/Base.lproj/Main.storyboard +26 -0
- pumaguard-ui/ios/Runner/Info.plist +49 -0
- pumaguard-ui/ios/Runner/Runner-Bridging-Header.h +1 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.pbxproj +616 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- pumaguard-ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- pumaguard-ui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- pumaguard-ui/ios/RunnerTests/RunnerTests.swift +12 -0
- pumaguard-ui/lib/main.dart +56 -0
- pumaguard-ui/lib/models/camera.dart +45 -0
- pumaguard-ui/lib/models/plug.dart +45 -0
- pumaguard-ui/lib/models/settings.dart +112 -0
- pumaguard-ui/lib/models/status.dart +58 -0
- pumaguard-ui/lib/screens/directories_screen.dart +319 -0
- pumaguard-ui/lib/screens/home_screen.dart +545 -0
- pumaguard-ui/lib/screens/image_browser_screen.dart +1248 -0
- pumaguard-ui/lib/screens/server_discovery_screen.dart +390 -0
- pumaguard-ui/lib/screens/settings_screen.dart +1162 -0
- pumaguard-ui/lib/screens/wifi_settings_screen.dart +671 -0
- pumaguard-ui/lib/services/api_service.dart +717 -0
- pumaguard-ui/lib/services/camera_events_service.dart +195 -0
- pumaguard-ui/lib/services/mdns_service.dart +4 -0
- pumaguard-ui/lib/services/mdns_service_impl.dart +282 -0
- pumaguard-ui/lib/services/mdns_service_io.dart +1 -0
- pumaguard-ui/lib/services/mdns_service_web.dart +106 -0
- pumaguard-ui/lib/utils/download_helper.dart +2 -0
- pumaguard-ui/lib/utils/download_helper_stub.dart +6 -0
- pumaguard-ui/lib/utils/download_helper_web.dart +14 -0
- pumaguard-ui/lib/utils/platform_url.dart +10 -0
- pumaguard-ui/lib/utils/platform_url_stub.dart +11 -0
- pumaguard-ui/lib/utils/platform_url_web.dart +16 -0
- pumaguard-ui/linux/.gitignore +1 -0
- pumaguard-ui/linux/CMakeLists.txt +128 -0
- pumaguard-ui/linux/flutter/CMakeLists.txt +88 -0
- pumaguard-ui/linux/flutter/generated_plugin_registrant.cc +15 -0
- pumaguard-ui/linux/flutter/generated_plugin_registrant.h +15 -0
- pumaguard-ui/linux/flutter/generated_plugins.cmake +24 -0
- pumaguard-ui/linux/runner/CMakeLists.txt +26 -0
- pumaguard-ui/linux/runner/main.cc +6 -0
- pumaguard-ui/linux/runner/my_application.cc +148 -0
- pumaguard-ui/linux/runner/my_application.h +21 -0
- pumaguard-ui/macos/.gitignore +7 -0
- pumaguard-ui/macos/Flutter/Flutter-Debug.xcconfig +1 -0
- pumaguard-ui/macos/Flutter/Flutter-Release.xcconfig +1 -0
- pumaguard-ui/macos/Flutter/GeneratedPluginRegistrant.swift +16 -0
- pumaguard-ui/macos/Runner/AppDelegate.swift +13 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- pumaguard-ui/macos/Runner/Base.lproj/MainMenu.xib +343 -0
- pumaguard-ui/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- pumaguard-ui/macos/Runner/Configs/Debug.xcconfig +2 -0
- pumaguard-ui/macos/Runner/Configs/Release.xcconfig +2 -0
- pumaguard-ui/macos/Runner/Configs/Warnings.xcconfig +13 -0
- pumaguard-ui/macos/Runner/DebugProfile.entitlements +12 -0
- pumaguard-ui/macos/Runner/Info.plist +32 -0
- pumaguard-ui/macos/Runner/MainFlutterWindow.swift +15 -0
- pumaguard-ui/macos/Runner/Release.entitlements +8 -0
- pumaguard-ui/macos/Runner.xcodeproj/project.pbxproj +705 -0
- pumaguard-ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- pumaguard-ui/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/macos/RunnerTests/RunnerTests.swift +12 -0
- pumaguard-ui/pubspec.lock +882 -0
- pumaguard-ui/pubspec.yaml +125 -0
- pumaguard-ui/test/models/camera_test.dart +515 -0
- pumaguard-ui/test/models/plug_test.dart +499 -0
- pumaguard-ui/test/models/settings_test.dart +903 -0
- pumaguard-ui/test/models/status_test.dart +707 -0
- pumaguard-ui/test/screens/image_browser_grouping_test.dart +555 -0
- pumaguard-ui/test/services/api_service_cameras_test.dart +580 -0
- pumaguard-ui/test/services/api_service_image_browser_test.dart +512 -0
- pumaguard-ui/test/widget_test.dart.skip +38 -0
- pumaguard-ui/web/favicon.png +0 -0
- pumaguard-ui/web/icons/Icon-192.png +0 -0
- pumaguard-ui/web/icons/Icon-512.png +0 -0
- pumaguard-ui/web/icons/Icon-maskable-192.png +0 -0
- pumaguard-ui/web/icons/Icon-maskable-512.png +0 -0
- pumaguard-ui/web/index.html +38 -0
- pumaguard-ui/web/manifest.json +35 -0
- pumaguard-ui/windows/.gitignore +17 -0
- pumaguard-ui/windows/CMakeLists.txt +108 -0
- pumaguard-ui/windows/flutter/CMakeLists.txt +109 -0
- pumaguard-ui/windows/flutter/generated_plugin_registrant.cc +14 -0
- pumaguard-ui/windows/flutter/generated_plugin_registrant.h +15 -0
- pumaguard-ui/windows/flutter/generated_plugins.cmake +24 -0
- pumaguard-ui/windows/runner/CMakeLists.txt +40 -0
- pumaguard-ui/windows/runner/Runner.rc +121 -0
- pumaguard-ui/windows/runner/flutter_window.cpp +71 -0
- pumaguard-ui/windows/runner/flutter_window.h +33 -0
- pumaguard-ui/windows/runner/main.cpp +43 -0
- pumaguard-ui/windows/runner/resource.h +16 -0
- pumaguard-ui/windows/runner/resources/app_icon.ico +0 -0
- pumaguard-ui/windows/runner/runner.exe.manifest +14 -0
- pumaguard-ui/windows/runner/utils.cpp +65 -0
- pumaguard-ui/windows/runner/utils.h +19 -0
- pumaguard-ui/windows/runner/win32_window.cpp +288 -0
- pumaguard-ui/windows/runner/win32_window.h +102 -0
- pumaguard-21.post29.dist-info/RECORD +0 -83
- {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/WHEEL +0 -0
- {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/entry_points.txt +0 -0
- {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/licenses/LICENSE +0 -0
- {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/top_level.txt +0 -0
pumaguard/web_routes/dhcp.py
CHANGED
|
@@ -114,65 +114,32 @@ def register_dhcp_routes(
|
|
|
114
114
|
timestamp,
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"mac_address": mac_address,
|
|
128
|
-
"last_seen": timestamp,
|
|
129
|
-
"status": "connected",
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
# Notify SSE clients
|
|
133
|
-
notify_camera_change(
|
|
134
|
-
"camera_connected", dict(webui.cameras[mac_address])
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
# Update settings with camera list
|
|
138
|
-
# Convert cameras dict to list for settings persistence
|
|
139
|
-
camera_list = []
|
|
140
|
-
for _, cam_info in webui.cameras.items():
|
|
141
|
-
camera_list.append(
|
|
142
|
-
{
|
|
143
|
-
"hostname": cam_info["hostname"],
|
|
144
|
-
"ip_address": cam_info["ip_address"],
|
|
145
|
-
"mac_address": cam_info["mac_address"],
|
|
146
|
-
"last_seen": cam_info["last_seen"],
|
|
147
|
-
"status": cam_info["status"],
|
|
148
|
-
}
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
webui.presets.cameras = camera_list
|
|
152
|
-
|
|
153
|
-
# Persist to settings file
|
|
154
|
-
try:
|
|
155
|
-
webui.presets.save()
|
|
156
|
-
logger.info("Camera list saved to settings")
|
|
157
|
-
except Exception as e: # pylint: disable=broad-except
|
|
158
|
-
logger.error(
|
|
159
|
-
"Failed to save camera list to settings: %s", str(e)
|
|
117
|
+
# Determine device type based on hostname pattern
|
|
118
|
+
is_camera = hostname and hostname.startswith("Microseven")
|
|
119
|
+
is_plug = hostname and hostname.lower().startswith("shellyplug")
|
|
120
|
+
|
|
121
|
+
if is_camera:
|
|
122
|
+
# Handle camera events
|
|
123
|
+
if action in ["add", "old"]:
|
|
124
|
+
# Camera connected or renewed lease
|
|
125
|
+
logger.info(
|
|
126
|
+
"Camera '%s' connected at IP %s", hostname, ip_address
|
|
160
127
|
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
128
|
+
# Store camera info indexed by MAC address
|
|
129
|
+
webui.cameras[mac_address] = {
|
|
130
|
+
"hostname": hostname,
|
|
131
|
+
"ip_address": ip_address,
|
|
132
|
+
"mac_address": mac_address,
|
|
133
|
+
"last_seen": timestamp,
|
|
134
|
+
"status": "connected",
|
|
135
|
+
}
|
|
169
136
|
|
|
170
137
|
# Notify SSE clients
|
|
171
138
|
notify_camera_change(
|
|
172
|
-
"
|
|
139
|
+
"camera_connected", dict(webui.cameras[mac_address])
|
|
173
140
|
)
|
|
174
141
|
|
|
175
|
-
# Update settings with
|
|
142
|
+
# Update settings with camera list
|
|
176
143
|
camera_list = []
|
|
177
144
|
for _, cam_info in webui.cameras.items():
|
|
178
145
|
camera_list.append(
|
|
@@ -190,13 +157,135 @@ def register_dhcp_routes(
|
|
|
190
157
|
# Persist to settings file
|
|
191
158
|
try:
|
|
192
159
|
webui.presets.save()
|
|
193
|
-
logger.info("Camera list
|
|
160
|
+
logger.info("Camera list saved to settings")
|
|
194
161
|
except Exception as e: # pylint: disable=broad-except
|
|
195
162
|
logger.error(
|
|
196
163
|
"Failed to save camera list to settings: %s",
|
|
197
164
|
str(e),
|
|
198
165
|
)
|
|
199
166
|
|
|
167
|
+
elif action == "del":
|
|
168
|
+
# Camera disconnected
|
|
169
|
+
logger.info("Camera '%s' disconnected", hostname)
|
|
170
|
+
# Update camera status to disconnected (keep history)
|
|
171
|
+
if mac_address in webui.cameras:
|
|
172
|
+
webui.cameras[mac_address]["status"] = "disconnected"
|
|
173
|
+
webui.cameras[mac_address]["last_seen"] = timestamp
|
|
174
|
+
|
|
175
|
+
# Notify SSE clients
|
|
176
|
+
notify_camera_change(
|
|
177
|
+
"camera_disconnected",
|
|
178
|
+
dict(webui.cameras[mac_address]),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Update settings with updated camera list
|
|
182
|
+
camera_list = []
|
|
183
|
+
for _, cam_info in webui.cameras.items():
|
|
184
|
+
camera_list.append(
|
|
185
|
+
{
|
|
186
|
+
"hostname": cam_info["hostname"],
|
|
187
|
+
"ip_address": cam_info["ip_address"],
|
|
188
|
+
"mac_address": cam_info["mac_address"],
|
|
189
|
+
"last_seen": cam_info["last_seen"],
|
|
190
|
+
"status": cam_info["status"],
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
webui.presets.cameras = camera_list
|
|
195
|
+
|
|
196
|
+
# Persist to settings file
|
|
197
|
+
try:
|
|
198
|
+
webui.presets.save()
|
|
199
|
+
logger.info("Camera list updated in settings")
|
|
200
|
+
except Exception as e: # pylint: disable=broad-except
|
|
201
|
+
logger.error(
|
|
202
|
+
"Failed to save camera list to settings: %s",
|
|
203
|
+
str(e),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
elif is_plug:
|
|
207
|
+
# Handle plug events
|
|
208
|
+
if action in ["add", "old"]:
|
|
209
|
+
# Plug connected or renewed lease
|
|
210
|
+
logger.info(
|
|
211
|
+
"Plug '%s' connected at IP %s", hostname, ip_address
|
|
212
|
+
)
|
|
213
|
+
# Store plug info indexed by MAC address
|
|
214
|
+
webui.plugs[mac_address] = {
|
|
215
|
+
"hostname": hostname,
|
|
216
|
+
"ip_address": ip_address,
|
|
217
|
+
"mac_address": mac_address,
|
|
218
|
+
"last_seen": timestamp,
|
|
219
|
+
"status": "connected",
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Notify SSE clients
|
|
223
|
+
notify_camera_change(
|
|
224
|
+
"plug_connected", dict(webui.plugs[mac_address])
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Update settings with plug list
|
|
228
|
+
plug_list = []
|
|
229
|
+
for _, plug_info in webui.plugs.items():
|
|
230
|
+
plug_list.append(
|
|
231
|
+
{
|
|
232
|
+
"hostname": plug_info["hostname"],
|
|
233
|
+
"ip_address": plug_info["ip_address"],
|
|
234
|
+
"mac_address": plug_info["mac_address"],
|
|
235
|
+
"last_seen": plug_info["last_seen"],
|
|
236
|
+
"status": plug_info["status"],
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
webui.presets.plugs = plug_list
|
|
241
|
+
|
|
242
|
+
# Persist to settings file
|
|
243
|
+
try:
|
|
244
|
+
webui.presets.save()
|
|
245
|
+
logger.info("Plug list saved to settings")
|
|
246
|
+
except Exception as e: # pylint: disable=broad-except
|
|
247
|
+
logger.error(
|
|
248
|
+
"Failed to save plug list to settings: %s", str(e)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
elif action == "del":
|
|
252
|
+
# Plug disconnected
|
|
253
|
+
logger.info("Plug '%s' disconnected", hostname)
|
|
254
|
+
# Update plug status to disconnected (keep history)
|
|
255
|
+
if mac_address in webui.plugs:
|
|
256
|
+
webui.plugs[mac_address]["status"] = "disconnected"
|
|
257
|
+
webui.plugs[mac_address]["last_seen"] = timestamp
|
|
258
|
+
|
|
259
|
+
# Notify SSE clients
|
|
260
|
+
notify_camera_change(
|
|
261
|
+
"plug_disconnected", dict(webui.plugs[mac_address])
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Update settings with updated plug list
|
|
265
|
+
plug_list = []
|
|
266
|
+
for _, plug_info in webui.plugs.items():
|
|
267
|
+
plug_list.append(
|
|
268
|
+
{
|
|
269
|
+
"hostname": plug_info["hostname"],
|
|
270
|
+
"ip_address": plug_info["ip_address"],
|
|
271
|
+
"mac_address": plug_info["mac_address"],
|
|
272
|
+
"last_seen": plug_info["last_seen"],
|
|
273
|
+
"status": plug_info["status"],
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
webui.presets.plugs = plug_list
|
|
278
|
+
|
|
279
|
+
# Persist to settings file
|
|
280
|
+
try:
|
|
281
|
+
webui.presets.save()
|
|
282
|
+
logger.info("Plug list updated in settings")
|
|
283
|
+
except Exception as e: # pylint: disable=broad-except
|
|
284
|
+
logger.error(
|
|
285
|
+
"Failed to save plug list to settings: %s",
|
|
286
|
+
str(e),
|
|
287
|
+
)
|
|
288
|
+
|
|
200
289
|
return (
|
|
201
290
|
jsonify(
|
|
202
291
|
{
|
|
@@ -206,6 +295,11 @@ def register_dhcp_routes(
|
|
|
206
295
|
"action": action,
|
|
207
296
|
"hostname": hostname,
|
|
208
297
|
"ip_address": ip_address,
|
|
298
|
+
"device_type": (
|
|
299
|
+
"camera"
|
|
300
|
+
if is_camera
|
|
301
|
+
else "plug" if is_plug else "unknown"
|
|
302
|
+
),
|
|
209
303
|
},
|
|
210
304
|
}
|
|
211
305
|
),
|
|
@@ -446,6 +540,169 @@ def register_dhcp_routes(
|
|
|
446
540
|
500,
|
|
447
541
|
)
|
|
448
542
|
|
|
543
|
+
@app.route("/api/dhcp/plugs", methods=["GET"])
|
|
544
|
+
def get_plugs():
|
|
545
|
+
"""
|
|
546
|
+
Get list of known plugs.
|
|
547
|
+
|
|
548
|
+
Returns all detected plugs with their connection status.
|
|
549
|
+
"""
|
|
550
|
+
plugs_list = list(webui.plugs.values())
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
jsonify(
|
|
554
|
+
{
|
|
555
|
+
"plugs": plugs_list,
|
|
556
|
+
"count": len(plugs_list),
|
|
557
|
+
}
|
|
558
|
+
),
|
|
559
|
+
200,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
@app.route("/api/dhcp/plugs/<mac_address>", methods=["GET"])
|
|
563
|
+
def get_plug(mac_address: str):
|
|
564
|
+
"""
|
|
565
|
+
Get information about a specific plug.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
mac_address: MAC address of the plug
|
|
569
|
+
"""
|
|
570
|
+
plug = webui.plugs.get(mac_address)
|
|
571
|
+
if not plug:
|
|
572
|
+
return jsonify({"error": "Plug not found"}), 404
|
|
573
|
+
|
|
574
|
+
return jsonify({"plug": plug}), 200
|
|
575
|
+
|
|
576
|
+
@app.route("/api/dhcp/plugs", methods=["POST"])
|
|
577
|
+
def add_plug():
|
|
578
|
+
"""
|
|
579
|
+
Manually add a plug (for testing purposes).
|
|
580
|
+
|
|
581
|
+
Expected JSON payload:
|
|
582
|
+
{
|
|
583
|
+
"hostname": "plug-name",
|
|
584
|
+
"ip_address": "192.168.52.100",
|
|
585
|
+
"mac_address": "aa:bb:cc:dd:ee:ff",
|
|
586
|
+
"status": "connected" // optional, defaults to "connected"
|
|
587
|
+
}
|
|
588
|
+
"""
|
|
589
|
+
try:
|
|
590
|
+
data = request.get_json()
|
|
591
|
+
|
|
592
|
+
if not data:
|
|
593
|
+
return jsonify({"error": "No JSON data provided"}), 400
|
|
594
|
+
|
|
595
|
+
hostname = data.get("hostname")
|
|
596
|
+
ip_address = data.get("ip_address")
|
|
597
|
+
mac_address = data.get("mac_address")
|
|
598
|
+
status = data.get("status", "connected")
|
|
599
|
+
|
|
600
|
+
# Validate required fields
|
|
601
|
+
if not hostname or not ip_address or not mac_address:
|
|
602
|
+
return (
|
|
603
|
+
jsonify(
|
|
604
|
+
{
|
|
605
|
+
"error": "Missing required fields: hostname, "
|
|
606
|
+
"ip_address, mac_address"
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
400,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# Generate timestamp
|
|
613
|
+
timestamp = datetime.now(timezone.utc).strftime(
|
|
614
|
+
"%Y-%m-%dT%H:%M:%SZ"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Add plug to webui.plugs
|
|
618
|
+
webui.plugs[mac_address] = {
|
|
619
|
+
"hostname": hostname,
|
|
620
|
+
"ip_address": ip_address,
|
|
621
|
+
"mac_address": mac_address,
|
|
622
|
+
"last_seen": timestamp,
|
|
623
|
+
"status": status,
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
# Update settings with plug list
|
|
627
|
+
plug_list = []
|
|
628
|
+
for _, plug_info in webui.plugs.items():
|
|
629
|
+
plug_list.append(
|
|
630
|
+
{
|
|
631
|
+
"hostname": plug_info["hostname"],
|
|
632
|
+
"ip_address": plug_info["ip_address"],
|
|
633
|
+
"mac_address": plug_info["mac_address"],
|
|
634
|
+
"last_seen": plug_info["last_seen"],
|
|
635
|
+
"status": plug_info["status"],
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
webui.presets.plugs = plug_list
|
|
640
|
+
|
|
641
|
+
# Persist to settings file
|
|
642
|
+
try:
|
|
643
|
+
webui.presets.save()
|
|
644
|
+
logger.info(
|
|
645
|
+
"Manually added plug '%s' at %s", hostname, ip_address
|
|
646
|
+
)
|
|
647
|
+
except Exception as e: # pylint: disable=broad-except
|
|
648
|
+
logger.error(
|
|
649
|
+
"Failed to save plug list to settings: %s", str(e)
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
# Notify SSE clients
|
|
653
|
+
notify_camera_change("plug_added", dict(webui.plugs[mac_address]))
|
|
654
|
+
|
|
655
|
+
return (
|
|
656
|
+
jsonify(
|
|
657
|
+
{
|
|
658
|
+
"status": "success",
|
|
659
|
+
"message": "Plug added successfully",
|
|
660
|
+
"plug": webui.plugs[mac_address],
|
|
661
|
+
}
|
|
662
|
+
),
|
|
663
|
+
201,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
except Exception as e: # pylint: disable=broad-except
|
|
667
|
+
logger.error("Error adding plug: %s", str(e))
|
|
668
|
+
return (
|
|
669
|
+
jsonify(
|
|
670
|
+
{
|
|
671
|
+
"error": "Failed to add plug",
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
500,
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
@app.route("/api/dhcp/plugs", methods=["DELETE"])
|
|
678
|
+
def clear_plugs():
|
|
679
|
+
"""
|
|
680
|
+
Clear all plug records.
|
|
681
|
+
|
|
682
|
+
This removes all stored plug information from memory.
|
|
683
|
+
"""
|
|
684
|
+
count = len(webui.plugs)
|
|
685
|
+
webui.plugs.clear()
|
|
686
|
+
logger.info("Cleared %d plug records", count)
|
|
687
|
+
|
|
688
|
+
# Update settings
|
|
689
|
+
webui.presets.plugs = []
|
|
690
|
+
try:
|
|
691
|
+
webui.presets.save()
|
|
692
|
+
logger.info("Plug list cleared from settings")
|
|
693
|
+
except Exception as e: # pylint: disable=broad-except
|
|
694
|
+
logger.error("Failed to save plug list to settings: %s", str(e))
|
|
695
|
+
|
|
696
|
+
return (
|
|
697
|
+
jsonify(
|
|
698
|
+
{
|
|
699
|
+
"status": "success",
|
|
700
|
+
"message": f"Cleared {count} plug record(s)",
|
|
701
|
+
}
|
|
702
|
+
),
|
|
703
|
+
200,
|
|
704
|
+
)
|
|
705
|
+
|
|
449
706
|
@app.route("/api/dhcp/cameras/events", methods=["GET"])
|
|
450
707
|
def camera_events():
|
|
451
708
|
"""
|
|
@@ -4,6 +4,7 @@ from __future__ import (
|
|
|
4
4
|
annotations,
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
+
import time
|
|
7
8
|
from typing import (
|
|
8
9
|
TYPE_CHECKING,
|
|
9
10
|
)
|
|
@@ -35,6 +36,10 @@ def register_diagnostics_routes(app: "Flask", webui: "WebUI") -> None:
|
|
|
35
36
|
def get_status():
|
|
36
37
|
origin = request.headers.get("Origin", "No Origin header")
|
|
37
38
|
host = request.headers.get("Host", "No Host header")
|
|
39
|
+
|
|
40
|
+
# Calculate uptime in seconds
|
|
41
|
+
uptime_seconds = int(time.time() - webui.start_time)
|
|
42
|
+
|
|
38
43
|
return jsonify(
|
|
39
44
|
{
|
|
40
45
|
"status": "running",
|
|
@@ -42,6 +47,7 @@ def register_diagnostics_routes(app: "Flask", webui: "WebUI") -> None:
|
|
|
42
47
|
"directories_count": len(webui.image_directories),
|
|
43
48
|
"host": webui._get_local_ip(), # pylint: disable=protected-access
|
|
44
49
|
"port": webui.port,
|
|
50
|
+
"uptime_seconds": uptime_seconds,
|
|
45
51
|
"request_origin": origin,
|
|
46
52
|
"request_host": host,
|
|
47
53
|
}
|
pumaguard/web_routes/settings.py
CHANGED
|
@@ -66,6 +66,19 @@ def register_settings_routes(app: "Flask", webui: "WebUI") -> None:
|
|
|
66
66
|
}
|
|
67
67
|
)
|
|
68
68
|
settings_dict["cameras"] = camera_list
|
|
69
|
+
# Add plugs from webui.plugs (runtime state)
|
|
70
|
+
plug_list = []
|
|
71
|
+
for _, plug_info in webui.plugs.items():
|
|
72
|
+
plug_list.append(
|
|
73
|
+
{
|
|
74
|
+
"hostname": plug_info["hostname"],
|
|
75
|
+
"ip_address": plug_info["ip_address"],
|
|
76
|
+
"mac_address": plug_info["mac_address"],
|
|
77
|
+
"last_seen": plug_info["last_seen"],
|
|
78
|
+
"status": plug_info["status"],
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
settings_dict["plugs"] = plug_list
|
|
69
82
|
return jsonify(settings_dict)
|
|
70
83
|
|
|
71
84
|
@app.route("/api/settings", methods=["PUT"])
|
pumaguard/web_ui.py
CHANGED
|
@@ -82,6 +82,16 @@ class CameraInfo(TypedDict):
|
|
|
82
82
|
status: str
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
class PlugInfo(TypedDict):
|
|
86
|
+
"""Type definition for plug information stored in webui.plugs."""
|
|
87
|
+
|
|
88
|
+
hostname: str
|
|
89
|
+
ip_address: str
|
|
90
|
+
mac_address: str
|
|
91
|
+
last_seen: str
|
|
92
|
+
status: str
|
|
93
|
+
|
|
94
|
+
|
|
85
95
|
class PhotoDict(TypedDict):
|
|
86
96
|
"""Type definition for photo metadata dictionary."""
|
|
87
97
|
|
|
@@ -160,10 +170,17 @@ class WebUI:
|
|
|
160
170
|
self.image_directories: list[str] = []
|
|
161
171
|
self.classification_directories: list[str] = []
|
|
162
172
|
|
|
173
|
+
# Track server start time for uptime calculation
|
|
174
|
+
self.start_time: float = time.time()
|
|
175
|
+
|
|
163
176
|
# Camera tracking - stores detected cameras by MAC address
|
|
164
177
|
# Format: {mac_address: CameraInfo}
|
|
165
178
|
self.cameras: dict[str, CameraInfo] = {}
|
|
166
179
|
|
|
180
|
+
# Plug tracking - stores detected plugs by MAC address
|
|
181
|
+
# Format: {mac_address: PlugInfo}
|
|
182
|
+
self.plugs: dict[str, PlugInfo] = {}
|
|
183
|
+
|
|
167
184
|
# Camera heartbeat monitoring (callback set after routes registered)
|
|
168
185
|
self.heartbeat: CameraHeartbeat = CameraHeartbeat(
|
|
169
186
|
webui=self,
|
|
@@ -189,6 +206,18 @@ class WebUI:
|
|
|
189
206
|
status=camera.get("status", "disconnected"),
|
|
190
207
|
)
|
|
191
208
|
|
|
209
|
+
# Load plugs from persisted settings
|
|
210
|
+
for plug in presets.plugs:
|
|
211
|
+
mac = plug.get("mac_address")
|
|
212
|
+
if mac:
|
|
213
|
+
self.plugs[mac] = PlugInfo(
|
|
214
|
+
hostname=plug.get("hostname", ""),
|
|
215
|
+
ip_address=plug.get("ip_address", ""),
|
|
216
|
+
mac_address=mac,
|
|
217
|
+
last_seen=plug.get("last_seen", ""),
|
|
218
|
+
status=plug.get("status", "disconnected"),
|
|
219
|
+
)
|
|
220
|
+
|
|
192
221
|
# mDNS/Zeroconf support
|
|
193
222
|
self.zeroconf: Zeroconf | None = None
|
|
194
223
|
self.service_info: ServiceInfo | None = None
|