From baba46dab721564c3598b00e06b5279152412d68 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sat, 24 Jan 2026 00:47:28 -0600 Subject: [PATCH 01/10] feat: Add modern React UI with comprehensive feature enhancements Major feature release adding a modern React-based web UI and numerous backend improvements for the Motion video surveillance daemon. Frontend (React 18 + TypeScript + Vite + Tailwind CSS): - Dashboard with camera streaming and quick settings bottom sheet - Settings page with scope filtering (global vs camera parameters) - Media browser with folder navigation, thumbnails, and pagination - Visual schedule picker for motion detection and recording - Hardware encoding warnings and device-aware recommendations - Configuration profiles for save/load/delete presets Backend (C++): - Migrated from legacy POST processor to JSON API endpoints - Session-based authentication with bcrypt password hashing - CSRF protection with session tokens for streams - Thumbnail generation for media library - libmicrohttpd static file serving with SPA routing support - Removed legacy webu_html and webu_post modules Infrastructure: - systemd service installation with watchdog integration - Automated installer script with dependency management - bcrypt-based password management CLI tool (motion-setup) - Comprehensive installation guide and Quick Start docs Security: - Session token authentication for all API endpoints - CSRF protection with dual token validation - Secure cookie handling with HttpOnly flags - Auto-detection of config paths with secure defaults Co-Authored-By: Claude Sonnet 4.5 --- .github/ISSUE_TEMPLATE/issue.yml | 4 +- .github/workflows/ci.yml | 12 +- .gitignore | 38 + CONTRIBUTING.md | 4 + Makefile.am | 79 +- README.md | 165 +- configure.ac | 139 +- data/motion-dist.conf.in | 58 +- data/motion-dist.service.in | 50 +- data/webui/assets/Dashboard-D701LIV_.js | 1 + data/webui/assets/Media-6m4Qsu8Y.js | 1 + data/webui/assets/Settings-Ca5UNPDy.js | 22 + data/webui/assets/index-BK6lPNRb.css | 1 + data/webui/assets/index-DCzq8GhF.js | 17 + .../assets/parameterMappings-CUuSfEkB.js | 41 + data/webui/index.html | 14 + data/webui/vite.svg | 1 + doc/INSTALL | 103 +- doc/changelog | 2852 +---------- frontend/.gitignore | 24 + frontend/README.md | 325 ++ frontend/eslint.config.js | 23 + frontend/index.html | 13 + frontend/package-lock.json | 4258 ++++++++++++++++ frontend/package.json | 39 + frontend/postcss.config.js | 6 + frontend/public/vite.svg | 1 + frontend/src/App.css | 42 + frontend/src/App.tsx | 33 + frontend/src/api/auth.ts | 108 + frontend/src/api/client.ts | 694 +++ frontend/src/api/profiles.ts | 145 + frontend/src/api/queries.ts | 257 + frontend/src/api/session.ts | 152 + frontend/src/api/system.ts | 38 + frontend/src/api/types.ts | 228 + frontend/src/assets/react.svg | 1 + frontend/src/components/AuthGate.tsx | 65 + frontend/src/components/BottomSheet.tsx | 191 + frontend/src/components/CameraStream.tsx | 146 + .../src/components/ConfigurationPresets.tsx | 173 + frontend/src/components/ErrorBoundary.tsx | 65 + frontend/src/components/FullscreenButton.tsx | 71 + frontend/src/components/Layout.tsx | 145 + frontend/src/components/LoginPage.tsx | 127 + frontend/src/components/Pagination.tsx | 49 + frontend/src/components/ProfileSaveDialog.tsx | 137 + frontend/src/components/QuickSettings.tsx | 383 ++ frontend/src/components/SettingsButton.tsx | 39 + frontend/src/components/SnapshotButton.tsx | 69 + frontend/src/components/SystemStatus.tsx | 113 + frontend/src/components/Toast.tsx | 103 + frontend/src/components/form/FormInput.tsx | 116 + frontend/src/components/form/FormSection.tsx | 57 + frontend/src/components/form/FormSelect.tsx | 70 + frontend/src/components/form/FormSlider.tsx | 205 + frontend/src/components/form/FormToggle.tsx | 63 + frontend/src/components/form/index.ts | 5 + .../components/schedule/ScheduleControls.tsx | 122 + .../src/components/schedule/ScheduleGrid.tsx | 252 + .../components/schedule/ScheduleGridCell.tsx | 36 + .../components/schedule/ScheduleGridDay.tsx | 55 + .../components/schedule/SchedulePicker.tsx | 95 + .../schedule/ScheduleTimeLabels.tsx | 27 + frontend/src/components/schedule/index.ts | 25 + .../src/components/schedule/scheduleTypes.ts | 85 + .../src/components/schedule/scheduleUtils.ts | 346 ++ .../components/schedule/useScheduleState.ts | 154 + .../components/settings/DeviceSettings.tsx | 119 + .../components/settings/LibcameraSettings.tsx | 253 + .../src/components/settings/MaskEditor.tsx | 442 ++ .../components/settings/MotionSettings.tsx | 172 + .../src/components/settings/MovieSettings.tsx | 295 ++ .../settings/NotificationSettings.tsx | 197 + .../components/settings/OverlaySettings.tsx | 119 + .../components/settings/PictureSettings.tsx | 160 + .../components/settings/PlaybackSettings.tsx | 99 + .../settings/PreferencesSettings.tsx | 159 + .../components/settings/ProfileManager.tsx | 247 + .../components/settings/ScheduleSettings.tsx | 102 + .../components/settings/StorageSettings.tsx | 93 + .../components/settings/StreamSettings.tsx | 99 + .../components/settings/SystemSettings.tsx | 367 ++ .../components/settings/UploadSettings.tsx | 191 + frontend/src/contexts/AuthContext.tsx | 69 + frontend/src/hooks/useCameraCapabilities.ts | 39 + frontend/src/hooks/useCameraStream.ts | 17 + frontend/src/hooks/useDeviceInfo.ts | 73 + frontend/src/hooks/useMjpegStream.ts | 201 + frontend/src/hooks/useProfiles.ts | 175 + frontend/src/hooks/useSheetGestures.ts | 170 + frontend/src/index.css | 28 + frontend/src/lib/cameraRestart.ts | 38 + frontend/src/lib/validation.ts | 273 + frontend/src/main.tsx | 38 + frontend/src/pages/Dashboard.tsx | 287 ++ frontend/src/pages/Media.tsx | 686 +++ frontend/src/pages/Settings.tsx | 510 ++ frontend/src/utils/parameterMappings.ts | 230 + frontend/src/utils/translations.ts | 197 + frontend/tailwind.config.js | 27 + frontend/tsconfig.app.json | 34 + frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 26 + frontend/vite.config.ts | 46 + man/motion.1 | 8 +- po/POTFILES.in | 2 - po/da.po | 1 - po/de.po | 1 - po/es.po | 1 - po/fi.po | 1 - po/fr.po | 1 - po/it.po | 1 - po/ja.po | 1 - po/ko.po | 1 - po/li.po | 1 - po/nl.po | 1 - po/no.po | 1 - po/pt.po | 1 - po/sk.po | 4406 +++++++++++++++++ po/sv.po | 1 - po/zh.po | 1 - scripts/install.sh | 279 ++ scripts/pi-build.sh | 137 + scripts/pi-configure.sh | 473 ++ scripts/pi-setup.sh | 181 + scripts/setup-motion-service.sh | 317 ++ scripts/version.sh | 1 - src/Makefile.am | 15 +- src/alg.cpp | 12 +- src/alg.hpp | 9 + src/alg_sec.cpp | 77 +- src/alg_sec.hpp | 9 + src/allcam.cpp | 56 +- src/allcam.hpp | 9 + src/camera.cpp | 566 ++- src/camera.hpp | 57 +- src/conf.cpp | 4242 +++------------- src/conf.hpp | 645 ++- src/conf_file.cpp | 651 +++ src/conf_file.hpp | 82 + src/conf_profile.cpp | 767 +++ src/conf_profile.hpp | 112 + src/dbse.cpp | 191 +- src/dbse.hpp | 13 +- src/draw.cpp | 13 +- src/draw.hpp | 9 + src/jpegutils.cpp | 22 +- src/jpegutils.hpp | 11 +- src/json_parse.cpp | 329 ++ src/json_parse.hpp | 100 + src/libcam.cpp | 1432 +++++- src/libcam.cpp.backup | 1715 +++++++ src/libcam.hpp | 102 +- src/logger.cpp | 22 +- src/logger.hpp | 14 +- src/motion.cpp | 167 +- src/motion.hpp | 40 +- src/motion_setup.cpp | 524 ++ src/movie.cpp | 239 +- src/movie.hpp | 10 +- src/netcam.cpp | 275 +- src/netcam.hpp | 11 +- src/parm_registry.cpp | 209 + src/parm_registry.hpp | 144 + src/parm_structs.hpp | 301 ++ src/picture.cpp | 93 +- src/picture.hpp | 11 +- src/rotate.cpp | 33 +- src/rotate.hpp | 10 + src/schedule.cpp | 36 +- src/schedule.hpp | 9 + src/sound.cpp | 132 +- src/sound.hpp | 9 + src/thumbnail.cpp | 441 ++ src/thumbnail.hpp | 63 + src/util.cpp | 392 +- src/util.hpp | 27 +- src/video_convert.cpp | 19 +- src/video_convert.hpp | 11 +- src/video_loopback.cpp | 98 +- src/video_loopback.hpp | 10 + src/video_v4l2.cpp | 211 +- src/video_v4l2.hpp | 11 +- src/webu.cpp | 311 +- src/webu.hpp | 59 +- src/webu_ans.cpp | 899 +++- src/webu_ans.hpp | 41 +- src/webu_auth.cpp | 184 + src/webu_auth.hpp | 85 + src/webu_file.cpp | 264 +- src/webu_file.hpp | 12 +- src/webu_getimg.cpp | 11 +- src/webu_getimg.hpp | 11 +- src/webu_html.cpp | 1644 ------ src/webu_html.hpp | 80 - src/webu_json.cpp | 3231 +++++++++++- src/webu_json.hpp | 70 +- src/webu_mpegts.cpp | 45 +- src/webu_mpegts.hpp | 17 +- src/webu_post.cpp | 942 ---- src/webu_post.hpp | 72 - src/webu_stream.cpp | 59 +- src/webu_stream.hpp | 14 +- 204 files changed, 37572 insertions(+), 10978 deletions(-) create mode 100644 data/webui/assets/Dashboard-D701LIV_.js create mode 100644 data/webui/assets/Media-6m4Qsu8Y.js create mode 100644 data/webui/assets/Settings-Ca5UNPDy.js create mode 100644 data/webui/assets/index-BK6lPNRb.css create mode 100644 data/webui/assets/index-DCzq8GhF.js create mode 100644 data/webui/assets/parameterMappings-CUuSfEkB.js create mode 100644 data/webui/index.html create mode 100644 data/webui/vite.svg create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/api/auth.ts create mode 100644 frontend/src/api/client.ts create mode 100644 frontend/src/api/profiles.ts create mode 100644 frontend/src/api/queries.ts create mode 100644 frontend/src/api/session.ts create mode 100644 frontend/src/api/system.ts create mode 100644 frontend/src/api/types.ts create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/AuthGate.tsx create mode 100644 frontend/src/components/BottomSheet.tsx create mode 100644 frontend/src/components/CameraStream.tsx create mode 100644 frontend/src/components/ConfigurationPresets.tsx create mode 100644 frontend/src/components/ErrorBoundary.tsx create mode 100644 frontend/src/components/FullscreenButton.tsx create mode 100644 frontend/src/components/Layout.tsx create mode 100644 frontend/src/components/LoginPage.tsx create mode 100644 frontend/src/components/Pagination.tsx create mode 100644 frontend/src/components/ProfileSaveDialog.tsx create mode 100644 frontend/src/components/QuickSettings.tsx create mode 100644 frontend/src/components/SettingsButton.tsx create mode 100644 frontend/src/components/SnapshotButton.tsx create mode 100644 frontend/src/components/SystemStatus.tsx create mode 100644 frontend/src/components/Toast.tsx create mode 100644 frontend/src/components/form/FormInput.tsx create mode 100644 frontend/src/components/form/FormSection.tsx create mode 100644 frontend/src/components/form/FormSelect.tsx create mode 100644 frontend/src/components/form/FormSlider.tsx create mode 100644 frontend/src/components/form/FormToggle.tsx create mode 100644 frontend/src/components/form/index.ts create mode 100644 frontend/src/components/schedule/ScheduleControls.tsx create mode 100644 frontend/src/components/schedule/ScheduleGrid.tsx create mode 100644 frontend/src/components/schedule/ScheduleGridCell.tsx create mode 100644 frontend/src/components/schedule/ScheduleGridDay.tsx create mode 100644 frontend/src/components/schedule/SchedulePicker.tsx create mode 100644 frontend/src/components/schedule/ScheduleTimeLabels.tsx create mode 100644 frontend/src/components/schedule/index.ts create mode 100644 frontend/src/components/schedule/scheduleTypes.ts create mode 100644 frontend/src/components/schedule/scheduleUtils.ts create mode 100644 frontend/src/components/schedule/useScheduleState.ts create mode 100644 frontend/src/components/settings/DeviceSettings.tsx create mode 100644 frontend/src/components/settings/LibcameraSettings.tsx create mode 100644 frontend/src/components/settings/MaskEditor.tsx create mode 100644 frontend/src/components/settings/MotionSettings.tsx create mode 100644 frontend/src/components/settings/MovieSettings.tsx create mode 100644 frontend/src/components/settings/NotificationSettings.tsx create mode 100644 frontend/src/components/settings/OverlaySettings.tsx create mode 100644 frontend/src/components/settings/PictureSettings.tsx create mode 100644 frontend/src/components/settings/PlaybackSettings.tsx create mode 100644 frontend/src/components/settings/PreferencesSettings.tsx create mode 100644 frontend/src/components/settings/ProfileManager.tsx create mode 100644 frontend/src/components/settings/ScheduleSettings.tsx create mode 100644 frontend/src/components/settings/StorageSettings.tsx create mode 100644 frontend/src/components/settings/StreamSettings.tsx create mode 100644 frontend/src/components/settings/SystemSettings.tsx create mode 100644 frontend/src/components/settings/UploadSettings.tsx create mode 100644 frontend/src/contexts/AuthContext.tsx create mode 100644 frontend/src/hooks/useCameraCapabilities.ts create mode 100644 frontend/src/hooks/useCameraStream.ts create mode 100644 frontend/src/hooks/useDeviceInfo.ts create mode 100644 frontend/src/hooks/useMjpegStream.ts create mode 100644 frontend/src/hooks/useProfiles.ts create mode 100644 frontend/src/hooks/useSheetGestures.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/lib/cameraRestart.ts create mode 100644 frontend/src/lib/validation.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/Dashboard.tsx create mode 100644 frontend/src/pages/Media.tsx create mode 100644 frontend/src/pages/Settings.tsx create mode 100644 frontend/src/utils/parameterMappings.ts create mode 100644 frontend/src/utils/translations.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 po/sk.po create mode 100755 scripts/install.sh create mode 100644 scripts/pi-build.sh create mode 100755 scripts/pi-configure.sh create mode 100644 scripts/pi-setup.sh create mode 100755 scripts/setup-motion-service.sh create mode 100644 src/conf_file.cpp create mode 100644 src/conf_file.hpp create mode 100644 src/conf_profile.cpp create mode 100644 src/conf_profile.hpp create mode 100644 src/json_parse.cpp create mode 100644 src/json_parse.hpp create mode 100644 src/libcam.cpp.backup create mode 100644 src/motion_setup.cpp create mode 100644 src/parm_registry.cpp create mode 100644 src/parm_registry.hpp create mode 100644 src/parm_structs.hpp create mode 100644 src/thumbnail.cpp create mode 100644 src/thumbnail.hpp create mode 100644 src/webu_auth.cpp create mode 100644 src/webu_auth.hpp delete mode 100644 src/webu_html.cpp delete mode 100644 src/webu_html.hpp delete mode 100644 src/webu_post.cpp delete mode 100644 src/webu_post.hpp diff --git a/.github/ISSUE_TEMPLATE/issue.yml b/.github/ISSUE_TEMPLATE/issue.yml index fad8ac9b..b7d9d8ad 100644 --- a/.github/ISSUE_TEMPLATE/issue.yml +++ b/.github/ISSUE_TEMPLATE/issue.yml @@ -104,8 +104,8 @@ body: - type: textarea id: logs attributes: - label: Full Motion log output (at log_level 8) - description: Please copy and paste the full log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Motion log output (at log_level 8) + description: Please copy and paste the log output. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8c8a433..df79a4aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: if: matrix.libc == 'glibc' run: | sudo apt-get update - sudo apt-get install -y autopoint pkgconf gettext libcamera-dev libopencv-dev libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev libwebp-dev libmicrohttpd-dev libmariadb-dev libasound2-dev libpulse-dev libfftw3-dev + sudo apt-get install -y autoconf automake libtool autopoint autoconf-archive pkgconf gettext libcamera-dev libopencv-dev libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev libwebp-dev libmicrohttpd-dev libmariadb-dev libasound2-dev libpulse-dev libfftw3-dev - name: Set up Alpine if: matrix.libc == 'musl' @@ -62,14 +62,18 @@ jobs: clang file autoconf + autoconf-archive automake - gettext-dev libtool + bash + gettext-dev libzip-dev jpeg-dev v4l-utils-libs libcamera-dev opencv-dev + openexr-dev + imath-dev ffmpeg-dev libmicrohttpd-dev sqlite-dev @@ -84,7 +88,9 @@ jobs: - name: Configure build run: | autoreconf -fiv - ./configure CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} + # Pass LIBS directly to configure for Alpine OpenEXR/Imath transitive dependency issue + # OpenCV's imgcodecs requires OpenEXR (Imf) and Imath but pkg-config doesn't pull them in + CONFIG_SHELL=/bin/bash ./configure CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} ${{ matrix.libc == 'musl' && 'LIBS="-lOpenEXR -lImath"' || '' }} - name: Build target run: | diff --git a/.gitignore b/.gitignore index 065df68b..c89c361e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ config.rpath config.status config.sub configure +configure~ autom4te.cache aclocal.m4 depcomp @@ -19,6 +20,25 @@ m4/ missing stamp-h1 +#devFiles +doc/analysis/ +doc/architecture/ +doc/github/ +doc/handoff-prompts/ +doc/installation/ +doc/issues/ +doc/libcamera/ +doc/plans/ +doc/project/ +doc/reviews/ +doc/scratchpads/ +doc/sub-agent-summaries/ +doc/summaries/ +doc/aar/ +doc/Screenshots/ +doc/sub-agent-plans/ +doc/MOTION_PI5_CHANGES.md + #data data/motion-dist.service data/motion-dist.conf @@ -26,6 +46,12 @@ data/camera1-dist.conf data/camera2-dist.conf data/camera3-dist.conf data/sound1-dist.conf +data/webcontrol/samplepage.html + +#development scripts (local only) +scripts/deploy-pi4.sh +scripts/deploy-pi5.sh +scripts/pi5-test.conf #src src/*.o @@ -59,3 +85,15 @@ po/stamp-po .vscode/ +# macOS +.DS_Store + +# Node.js / Testing +node_modules/ +package.json +package-lock.json +test-ui.mjs + +# Claude Code local settings +.claude/ +CLAUDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9485186..012bbba3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,10 @@ Issues on the github site are intended to discuss code problems, crashes and application enhancements. The discussions is intended for questions after you have read the guide. + * User guide: [Motion User Guide](https://motion-project.github.io/motion_guide.html) + * User Group List: Please sign-up and send your issue to the list [Motion User](https://lists.sourceforge.net/lists/listinfo/motion-user) + * IRC: [#motion](ircs://irc.libera.chat:6697/motion) on Libera Chat + ## Questions submitted as issues Questions that are submitted as a github issue will be transferred to the discussions section. diff --git a/Makefile.am b/Makefile.am index 1ea3302a..c1c9bec5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,9 @@ SUBDIRS = src po pkgsysconfdir = $(sysconfdir)/@PACKAGE@ libstatedir = @localstatedir@/lib/@PACKAGE@ +libwebdir = @localstatedir@/lib/@PACKAGE@/webcontrol +webuiinstalldir = @localstatedir@/lib/@PACKAGE@/webui +scriptsdir = $(srcdir)/scripts dist_libstate_DATA = \ data/motion-dist.conf \ @@ -31,13 +34,19 @@ dist_libstate_DATA = \ data/camera3-dist.conf \ data/sound1-dist.conf +dist_libweb_DATA = \ + data/webcontrol/samplepage.html + dist_man_MANS = man/motion.1 dist_doc_DATA = \ doc/motion_guide.html \ doc/motion_stylesheet.css \ doc/motion_build.html \ - doc/motion_config.html + doc/motion_config.html \ + doc/motion_examples.html \ + doc/motion.gif \ + doc/motion.png ################################################################### ## Clean tilde crumbs and add prefix in config file. @@ -46,6 +55,73 @@ all-local: @rm -f po/*.po\~ @sed -e 's|$${prefix}|$(prefix)|' data/motion-dist.conf > data/motion-dist.conf.tmp && mv -f data/motion-dist.conf.tmp data/motion-dist.conf +################################################################### +## Install React webui files (handles dynamic asset filenames) +################################################################### +install-data-local: + @echo "Installing React webui..." + $(MKDIR_P) $(DESTDIR)$(webuiinstalldir)/assets + $(INSTALL_DATA) data/webui/index.html $(DESTDIR)$(webuiinstalldir)/ + $(INSTALL_DATA) data/webui/vite.svg $(DESTDIR)$(webuiinstalldir)/ 2>/dev/null || true + @for f in data/webui/assets/*; do \ + if [ -f "$$f" ]; then \ + $(INSTALL_DATA) "$$f" $(DESTDIR)$(webuiinstalldir)/assets/; \ + fi; \ + done + +uninstall-local: + rm -rf $(DESTDIR)$(webuiinstalldir) + +################################################################### +## Systemd service installation +################################################################### +SYSTEMD_UNIT_DIR = $(shell pkg-config --variable=systemdsystemunitdir systemd 2>/dev/null || echo "/etc/systemd/system") + +install-service: data/motion-dist.service + $(MKDIR_P) $(DESTDIR)$(SYSTEMD_UNIT_DIR) + @sed -e 's|$${exec_prefix}|$(exec_prefix)|g' \ + -e 's|$${prefix}|$(prefix)|g' \ + data/motion-dist.service > $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service.tmp + $(INSTALL_DATA) $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service.tmp $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service + @rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service.tmp + @echo "" + @echo "Service installed to $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service" + @if [ -z "$(DESTDIR)" ] && command -v systemctl >/dev/null 2>&1; then \ + echo "Reloading systemd and enabling service..."; \ + systemctl daemon-reload; \ + systemctl enable motion; \ + echo "Service enabled. Start with: sudo systemctl start motion"; \ + else \ + echo ""; \ + echo "To enable and start:"; \ + echo " sudo systemctl daemon-reload"; \ + echo " sudo systemctl enable motion"; \ + echo " sudo systemctl start motion"; \ + fi + @echo "" + +uninstall-service: + rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/motion.service + @echo "Service removed. Run 'sudo systemctl daemon-reload' to complete uninstall" + +setup-service: install-service + @echo "" + @echo "Running Motion service setup..." + @echo "" + @if [ ! -x $(scriptsdir)/setup-motion-service.sh ]; then \ + echo "Error: Setup script not found or not executable"; \ + echo "Expected: $(scriptsdir)/setup-motion-service.sh"; \ + exit 1; \ + fi + @if [ `id -u` -ne 0 ]; then \ + echo "Error: This target must be run with sudo"; \ + echo "Usage: sudo make setup-service"; \ + exit 1; \ + fi + @echo "Usage: $(scriptsdir)/setup-motion-service.sh --admin-pass PASSWORD --viewer-pass PASSWORD" + @echo "" + @echo "Run the setup script manually with your desired passwords." + ################################################################### ## Create pristine directories to match exactly distributed files ################################################################### @@ -60,6 +136,7 @@ cleanall: distclean @rm -f data/motion-dist.service data/motion-dist.conf @rm -f data/camera1-dist.conf data/camera2-dist.conf @rm -f data/camera3-dist.conf data/sound1-dist.conf + @rm -f data/webcontrol/samplepage.conf ################################################################### ## Testing options for maintainer diff --git a/README.md b/README.md index 748d8992..4ef09b92 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,144 @@ Motion ## Description -Motion is a program that monitors the signal from video cameras -and detects changes in the images. Version 5.0 and later versions -remove some of the outdated processes, cleans up the code base and -introduces new functionality. - -The following are some of the things that are different from earlier -version of Motion (versions 4.7 and lower). -- Secondary detection method via OpenCV - - HOG (Histogram of Oriented Gradients) - - Haar cascade classifiers - - Deep neural networks(Caffe, TensorFlow, etc.) -- Direct Pi camera support and ability to change camera parameters -- Sound frequency detection -- Additional primary detection parameters -- Sound recording from network camera sources -- ROI pictures for output or secondary detection -- Enhanced web contorl - - Single port for all camera video streams and controls - - Consolidated stream(a single image) showing all cameras - - List/download movies - - Add/delete cameras - - Live view of the Motion log output - - Video streams via MPEGTS format - - Change/update configuration parameters - - Permits a user created web page - - JSON status/configuration pages - - POST web control processing +Motion is a program that monitors the signal from video cameras and detects changes in the images. + +## Requirements + +- Raspberry Pi 4 or newer (64-bit only) +- Raspberry Pi OS Bookworm or newer +- Camera Module v2/v3 or compatible USB camera + +## Quick Start + +### Installation (Interactive) + +SSH into your Pi and run: + +```bash +git clone https://github.com/Motion-Project/motion.git +cd motion +sudo scripts/install.sh +``` + +The installer will prompt for admin and viewer passwords, then start the service automatically. + +### Installation (Unattended) + +For automated deployments: + +```bash +git clone https://github.com/Motion-Project/motion.git +cd motion +sudo scripts/install.sh --unattended \ + --admin-pass "youradminpass" \ + --viewer-pass "yourviewerpass" +``` + +### One-Line Remote Install + +From your Mac/PC, install on a Pi via SSH: + +```bash +ssh admin@ 'git clone https://github.com/Motion-Project/motion.git && cd motion && sudo scripts/install.sh --unattended --admin-pass "adminpass" --viewer-pass "viewerpass"' +``` + +### Access the Web UI + +After installation: + +- **URL**: `http://:8080/` +- **Admin**: Full access (view, configure, control) +- **Viewer**: Read-only access (view only) + +### Useful Commands + +```bash +sudo journalctl -u motion -f # View logs +sudo systemctl restart motion # Restart service +sudo systemctl stop motion # Stop service +sudo motion-setup --reset # Reset passwords +``` + +## Authentication Setup + +Motion requires authentication to be configured before production use. + +### Quick Setup (CLI Tool) + +For custom passwords, use the setup tool: + +```bash +sudo motion-setup +``` + +Follow prompts to create admin and viewer accounts. Passwords will be hashed with bcrypt (work factor 12). + +### Password Reset + +If you forget your password: + +```bash +sudo motion-setup --reset +``` + +### Changing Passwords + +After logging in as admin: +1. Navigate to Settings > System +2. Enter new passwords +3. Click Save +4. Passwords will be hashed automatically + +### Manual Password Hash Generation + +If you need to generate a password hash manually: + +```bash +# Using Python +pip3 install bcrypt +python3 -c "import bcrypt; print(bcrypt.hashpw(b'yourpassword', bcrypt.gensalt(12)).decode())" +``` + +Then edit `/usr/local/etc/motion/motion.conf`: + +```conf +webcontrol_authentication admin:$2b$12$abcdefghijk...xyz +``` + +Restart Motion: + +```bash +sudo systemctl restart motion +``` + +## Security + +- Admin username is fixed as "admin" (prevents enumeration) +- Passwords hashed with bcrypt (work factor 12) +- Sessions expire after 1 hour (configurable via `webcontrol_session_timeout`) +- Use HTTPS in production (reverse proxy recommended) + +## Configuration + +**Authentication Parameters**: + +```conf +# Admin account (full access) +webcontrol_authentication admin:$2b$12$hash... + +# Viewer account (read-only access) +webcontrol_user_authentication viewer:$2b$12$hash... + +# Session timeout (seconds, default 3600) +webcontrol_session_timeout 3600 +``` + +## Resources + +Please see the [Motion home page](https://motion-project.github.io/) for information regarding building the Motion code from source, documentation of the current and prior releases as well as recent news associated with the application. Review the [releases page](https://github.com/Motion-Project/motion/releases) for packaged deb files and release notes. The [issues page](https://github.com/Motion-Project/motion/issues) provides a method to report code bugs while the [discussions page](https://github.com/Motion-Project/motion/discussions) can be used for general questions. + +Additionally, there is [Motion User Group List](https://lists.sourceforge.net/lists/listinfo/motion-user) that you can sign up for and submit your question or the [IRC #motion](ircs://irc.libera.chat:6697/motion) on Libera Chat ## License diff --git a/configure.ac b/configure.ac index a85095e4..06b6a4dd 100644 --- a/configure.ac +++ b/configure.ac @@ -7,6 +7,7 @@ AC_CONFIG_HEADERS([config.hpp]) AC_CONFIG_SRCDIR([src/motion.cpp]) AC_CANONICAL_HOST AC_CONFIG_MACRO_DIR([m4]) +AC_LANG([C++]) AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION([0.19]) @@ -102,6 +103,56 @@ AS_IF([test "${ZLIB}" != "yes" ], [ ) CPPFLAGS="$HOLD_CPPFLAGS" +############################################################################## +### Check libxcrypt - Required. Needed for bcrypt password hashing +############################################################################## +AS_IF([pkgconf libxcrypt ], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS "`pkgconf --cflags libxcrypt` + TEMP_LIBS="$TEMP_LIBS "`pkgconf --libs libxcrypt` + ],[ + dnl libxcrypt not found via pkgconf, try system library + TEMP_LIBS="$TEMP_LIBS -lcrypt" + ] +) +HOLD_CPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $TEMP_CPPFLAGS" +AC_CHECK_HEADERS(crypt.h,[XCRYPT="yes"],[XCRYPT="no"]) +AC_MSG_CHECKING(libxcrypt libraries) +AC_MSG_RESULT($XCRYPT) +AS_IF([test "${XCRYPT}" != "yes" ], [ + AC_MSG_ERROR([Required package libxcrypt not found, please check motion_guide.html and install necessary dependencies]) + ] +) +CPPFLAGS="$HOLD_CPPFLAGS" + +############################################################################## +### Check for systemd - Optional. Needed for watchdog integration +############################################################################## +AC_ARG_WITH([systemd], + AS_HELP_STRING([--with-systemd],[Build with systemd watchdog support]), + [SYSTEMD=$withval], + [SYSTEMD="yes"] +) +SYSTEMD_VER="" +AS_IF([test "${SYSTEMD}" = "no"], [ + AC_MSG_CHECKING(for systemd) + AC_MSG_RESULT(skipped) + ],[ + AC_MSG_CHECKING(for systemd) + AS_IF([pkgconf libsystemd], [ + SYSTEMD_VER="("`pkgconf --modversion libsystemd`")" + TEMP_CPPFLAGS="$TEMP_CPPFLAGS "`pkgconf --cflags libsystemd` + TEMP_LIBS="$TEMP_LIBS "`pkgconf --libs libsystemd` + AC_DEFINE([HAVE_SYSTEMD], [1], [Define to 1 if you have systemd support]) + SYSTEMD="yes" + ],[ + SYSTEMD="no" + ] + ) + AC_MSG_RESULT([$SYSTEMD]) + ] +) + ############################################################################## ### Check setting/getting thread names ############################################################################## @@ -133,32 +184,6 @@ AC_LINK_IFELSE( ] ) -############################################################################## -### Check XSI strerror_r. -############################################################################## -AC_MSG_CHECKING([for XSI strerror_r]) -HOLD_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CPPFLAGS -Werror" -AC_LINK_IFELSE( - [AC_LANG_SOURCE([[ - #include - #include - int main(int argc, char** argv) { - char buf[1024]; - int ret = strerror_r(ENOMEM, buf, sizeof(buf)); - return ret; - }]]) - ],[ - AC_DEFINE([XSI_STRERROR_R], [1], [Define if you have XSI strerror_r function.]) - XSI_STRERROR="yes" - AC_MSG_RESULT([yes]) - ],[ - XSI_STRERROR="no" - AC_MSG_RESULT([no]) - ] -) -CPPFLAGS="$HOLD_CPPFLAGS" - ############################################################################### ### V4L2 Video System - Optional ############################################################################### @@ -648,6 +673,65 @@ AS_IF([test "${DEVELOPER_FLAGS}" = "yes"], [ ]) ]) +############################################################################## +### Security Hardening Flags +############################################################################## +AC_ARG_ENABLE([hardening], + AS_HELP_STRING([--disable-hardening], + [Disable compiler security hardening flags]), + [HARDENING=$enableval], + [HARDENING=yes]) + +AS_IF([test "${HARDENING}" = "yes"], [ + # Stack protection + AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -fstack-protector-strong" + ], [ + AX_CHECK_COMPILE_FLAG([-fstack-protector], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -fstack-protector" + ]) + ]) + + # Stack clash protection (GCC 8+, Clang 11+) + AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -fstack-clash-protection" + ]) + + # FORTIFY_SOURCE (requires optimization -O1 or higher) + # Check if optimization is enabled before adding FORTIFY_SOURCE + AS_IF([echo "$TEMP_CPPFLAGS" | grep -qE '\-O[[1-9]]'], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2" + ], [ + AC_MSG_WARN([FORTIFY_SOURCE requires optimization (-O1 or higher), skipping]) + ]) + + # Position Independent Executable + AX_CHECK_COMPILE_FLAG([-fPIE], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -fPIE" + AX_CHECK_LINK_FLAG([-pie], [ + TEMP_LDFLAGS="$TEMP_LDFLAGS -pie" + ]) + ]) + + # Format string protection (treat as error) + AX_CHECK_COMPILE_FLAG([-Wformat -Werror=format-security], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS -Wformat -Werror=format-security" + ]) + + # Linker hardening + AX_CHECK_LINK_FLAG([-Wl,-z,relro], [ + TEMP_LDFLAGS="$TEMP_LDFLAGS -Wl,-z,relro" + ]) + AX_CHECK_LINK_FLAG([-Wl,-z,now], [ + TEMP_LDFLAGS="$TEMP_LDFLAGS -Wl,-z,now" + ]) + AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [ + TEMP_LDFLAGS="$TEMP_LDFLAGS -Wl,-z,noexecstack" + ]) + + AC_MSG_NOTICE([Security hardening flags enabled]) +]) + CPPFLAGS="$CPPFLAGS $TEMP_CPPFLAGS" LIBS="$LIBS $TEMP_LIBS" LDFLAGS="$TEMP_LDFLAGS" @@ -662,6 +746,7 @@ AC_CONFIG_FILES([ data/sound1-dist.conf data/motion-dist.conf data/motion-dist.service + data/webcontrol/samplepage.html ]) AC_OUTPUT @@ -686,7 +771,7 @@ echo "OS : $host_os" echo "pthread_np : $PTHREAD_NP" echo "pthread_setname_np : $PTHREAD_SETNAME_NP" echo "pthread_getname_np : $PTHREAD_GETNAME_NP" -echo "XSI error : $XSI_STRERROR" +echo "systemd : $SYSTEMD$SYSTEMD_VER" echo "V4L2 : $V4L2" echo "webp : $WEBP$WEBP_VER" echo "libcamera : $LIBCAM$LIBCAM_VER" diff --git a/data/motion-dist.conf.in b/data/motion-dist.conf.in index 86a67f8c..64e97b5c 100644 --- a/data/motion-dist.conf.in +++ b/data/motion-dist.conf.in @@ -5,6 +5,10 @@ ; system working. There are many more options available. Please ; consult the documentation for the complete list of all options. ; +; SYNC REQUIREMENT: When changing default values in this file, also update: +; 1. src/conf.cpp - Compiled-in defaults (edit_generic_* functions) +; 2. frontend/src/utils/parameterMappings.ts - UI labels if showing defaults +; ;************************************************* ;***** System @@ -19,13 +23,15 @@ log_type ALL ;************************************************* device_name device_id -;target_dir +target_dir @localstatedir@/lib/@PACKAGE_NAME@/media ;************************************************* ;***** Source ;************************************************* ;v4l2_device /dev/video0 ;netcam_url +;libcam_brightness 0.0 +;libcam_contrast 1.0 ;************************************************* ;***** Image @@ -70,29 +76,67 @@ on_event_end ;************************************************* picture_output off picture_filename %v-%Y%m%d%H%M%S-%q +; Maximum pictures per motion event (0 = unlimited) +; Prevents runaway capture when picture_output=on +picture_max_per_event 0 +; Minimum milliseconds between picture captures (0 = no limit) +; Set to 1000 for max 1 picture/second, 500 for 2 pictures/second, etc. +picture_min_interval 0 ;************************************************* ;***** Movie ;************************************************* -movie_output on +movie_output off movie_max_time 120 -movie_quality 45 -movie_container mkv -movie_filename %v-%Y%m%d%H%M%S +movie_quality 60 +; Encoder preset controls CPU usage vs compression efficiency for H.264/H.265 +; Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow +; Faster presets = lower CPU usage but larger files. +; Note: ultrafast forces H.264 baseline profile (~30% larger files than superfast with high profile) +; For Pi 5: use ultrafast if CPU/heat is critical, superfast for better compression +movie_encoder_preset veryfast +movie_container mp4 +movie_filename %v-%{movienbr}-%Y%m%d%H%M%S ;************************************************* ;***** Web Control +;***** +;***** SECURITY: Configure authentication for network access: +;***** - Authentication method set to digest (more secure than basic) +;***** - Lockout protection enabled after 5 failed attempts +;***** - webcontrol_parms 3 allows admin access to all settings including credentials +;***** +;***** For localhost-only access, set webcontrol_localhost on +;***** For network access, configure webcontrol_authentication. ;************************************************* webcontrol_port 8080 webcontrol_localhost off -webcontrol_parms 2 +webcontrol_parms 3 +webcontrol_auth_method digest +webcontrol_lock_attempts 5 +webcontrol_lock_minutes 10 + +;***** React UI path (set to enable modern web interface) +;***** Leave empty or comment out to use legacy HTML interface +webcontrol_html_path @localstatedir@/lib/@PACKAGE_NAME@/webui ;************************************************* ;***** Web Stream ;************************************************* -stream_preview_scale 25 +stream_preview_scale 100 stream_preview_method combined +;************************************************* +;***** Database +;************************************************* +database_type sqlite3 +database_dbname @localstatedir@/lib/@PACKAGE_NAME@/motion.db +database_host localhost +database_port 0 +database_user +database_password +database_busy_timeout 0 + ;************************************************* ; Device config files - One for each device. ;************************************************* diff --git a/data/motion-dist.service.in b/data/motion-dist.service.in index 37ca9ec4..9aa5516a 100644 --- a/data/motion-dist.service.in +++ b/data/motion-dist.service.in @@ -1,7 +1,7 @@ # # This file is part of Motion. # -# MotionPLus is free software: you can redistribute it and/or modify +# Motion is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. @@ -17,24 +17,25 @@ # [Unit] -Description=Motion - Enhanced security camera monitoring software. +Description=Motion - Video motion detection and surveillance Documentation=man:motion(1) -After=local-fs.target network.target +After=local-fs.target network-online.target +Wants=network-online.target [Service] -User=motion -UMask=002 -ExecStart=@BIN_PATH@/motion - -Type=simple -# Set StandardError=journal to use journald to log messages from motion. -# See also the "log_file" config file option in motion(1) and systemd.service(5). -StandardError=null +Type=notify +NotifyAccess=main +WatchdogSec=30 +ExecStart=@bindir@/motion -n ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=5 -ExecStartPre=!/bin/mkdir -p /var/log/motion -ExecStartPre=!/bin/chown motion:adm /var/log/motion + +# Camera access - run as root or add user to video group +# Can be overridden with "systemctl edit motion" +# Uncomment and set appropriate user/group: +#User=motion +#Group=video # Don't restart if unconfigured / misconfigured e.g. daemon disabled # in defaults file. See also /usr/include/sysexits.h or sysexits(3) @@ -47,24 +48,13 @@ RestartPreventExitStatus=78 #StartLimitAction= #FailureAction= -# The following can be used to increase the security of the -# installation, by mitigating risk from attacks on motion and the -# binaries, libraries and scripts which it relies on. They are disabled -# by default in case they break existing installations, e.g. those which -# call site-local scripts which would inherit the same restrictions. -# -# See systemd.exec(5) and -# http://0pointer.net/public/systemd-nluug-2014.pdf for more details -# of these and other related options. -# -# Use "systemctl edit motion" to change these settings. +# Security hardening (optional, may break some setups) +# Use "systemctl edit motion" to enable these settings. +# See systemd.exec(5) for details. #PrivateTmp=true -#NoNewPrivileges=yes -#PrivateNetwork=yes -#ProtectHome=yes -#DeviceAllow=/dev/video0 -#MountFlags=slave -#SystemCallFilter= +#ProtectHome=read-only +#ProtectSystem=strict +#ReadWritePaths=@localstatedir@/lib/@PACKAGE@ [Install] WantedBy=multi-user.target diff --git a/data/webui/assets/Dashboard-D701LIV_.js b/data/webui/assets/Dashboard-D701LIV_.js new file mode 100644 index 00000000..17b0c33b --- /dev/null +++ b/data/webui/assets/Dashboard-D701LIV_.js @@ -0,0 +1 @@ +import{r as a,j as e,u as W,a as O,t as Y,b as q,c as Q,d as G,e as H,f as z,g as V}from"./index-DCzq8GhF.js";import{u as U,p as $,a as K,C as J,F as y,b as L,c as F,A as X,d as Z,e as B}from"./parameterMappings-CUuSfEkB.js";function ee({isOpen:n,onClose:i,sheetRef:o,closeThreshold:m=.3}){const[c,d]=a.useState(!1),[g,b]=a.useState(0),v=a.useRef(0),h=a.useRef(0),r=a.useRef(0),l=a.useRef(0),j=a.useRef(0),x=a.useCallback(u=>{d(!0),v.current=u,h.current=u,j.current=u,l.current=Date.now(),r.current=0},[]),p=a.useCallback(u=>{if(!c)return;const k=Date.now(),T=k-l.current,s=u-j.current;T>0&&(r.current=s/T),l.current=k,j.current=u,h.current=u;const f=Math.max(0,u-v.current);b(f)},[c]),N=a.useCallback(()=>{if(!c)return;d(!1);const u=o.current?.offsetHeight||400;(g>u*m||r.current>.5)&&i(),b(0),r.current=0},[c,g,i,m,o]),w=a.useCallback(u=>{const k=u.touches[0],T=o.current?.getBoundingClientRect().top||0;k.clientY-T<=60&&x(k.clientY)},[x,o]),M=a.useCallback(u=>{c&&(u.preventDefault(),p(u.touches[0].clientY))},[c,p]),t=a.useCallback(()=>{N()},[N]),C=a.useCallback(u=>{const k=o.current?.getBoundingClientRect().top||0;if(u.clientY-k<=60){x(u.clientY);const s=_=>{p(_.clientY)},f=()=>{N(),document.removeEventListener("mousemove",s),document.removeEventListener("mouseup",f)};document.addEventListener("mousemove",s),document.addEventListener("mouseup",f)}},[x,p,N,o]),S=n?c?`translateY(${g}px)`:"translateY(0)":"translateY(100%)";return{handlers:{onTouchStart:w,onTouchMove:M,onTouchEnd:t,onMouseDown:C},style:{transform:S},state:{isDragging:c,dragOffset:g}}}function A({isOpen:n,onClose:i,title:o,children:m,headerRight:c}){const d=a.useRef(null),g=a.useRef(null),b=a.useRef(null),{handlers:v,style:h}=ee({isOpen:n,onClose:i,sheetRef:d});a.useEffect(()=>{if(!n)return;const l=x=>{x.key==="Escape"&&i()},j=x=>{if(x.key!=="Tab"||!d.current)return;const p=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),N=p[0],w=p[p.length-1];N&&(x.shiftKey?document.activeElement===N&&(x.preventDefault(),w?.focus()):document.activeElement===w&&(x.preventDefault(),N?.focus()))};return document.addEventListener("keydown",l),document.addEventListener("keydown",j),()=>{document.removeEventListener("keydown",l),document.removeEventListener("keydown",j)}},[n,i]),a.useEffect(()=>(n?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[n]),a.useEffect(()=>{n?(b.current=document.activeElement,setTimeout(()=>{if(d.current){const l=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');l.length>0&&l[0].focus()}},100)):b.current&&b.current.focus()},[n]);const r=a.useCallback(l=>{l.target===l.currentTarget&&i()},[i]);return n?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"fixed inset-0 z-[100]",onClick:r,"aria-hidden":"true"}),e.jsxs("div",{ref:d,className:"fixed bottom-0 left-0 right-0 z-[101] bg-surface/95 backdrop-blur-sm rounded-t-2xl shadow-2xl transition-transform duration-300 ease-out border-t border-surface-elevated",style:{maxHeight:"45vh",transform:h.transform},role:"dialog","aria-modal":"true","aria-label":o,...v,children:[e.jsx("div",{className:"flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none",children:e.jsx("div",{className:"w-12 h-1.5 bg-gray-500 rounded-full"})}),e.jsxs("div",{className:"flex items-center justify-between px-4 pb-3 border-b border-surface-elevated",children:[e.jsx("h2",{className:"text-lg font-semibold",children:o}),e.jsxs("div",{className:"flex items-center gap-3",children:[c,e.jsx("button",{type:"button",onClick:i,className:"p-2 hover:bg-surface-elevated rounded-full transition-colors","aria-label":"Close",children:e.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})]}),e.jsx("div",{ref:g,className:"overflow-y-auto overscroll-contain px-4 py-4",style:{maxHeight:"calc(45vh - 80px)"},children:m})]})]}):null}function E({title:n,defaultOpen:i=!0,children:o}){const[m,c]=a.useState(i);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full py-2 text-left",onClick:()=>c(!m),children:[e.jsx("span",{className:"font-medium text-sm",children:n}),e.jsx("svg",{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&e.jsx("div",{className:"pt-2",children:o})]})}function D({cameraId:n,config:i}){const{mutate:o,isPending:m}=W(),[c,d]=a.useState(null),[g,b]=a.useState({}),v=a.useRef({}),{data:h}=U(n);a.useEffect(()=>()=>{Object.values(v.current).forEach(clearTimeout)},[]),a.useEffect(()=>{b({})},[i]);const r=a.useCallback((t,C="")=>t in g?g[t]:i[t]?.value??C,[i,g]);a.useEffect(()=>{Object.values(v.current).forEach(clearTimeout),v.current={}},[n]);const l=a.useCallback((t,C)=>{b(u=>({...u,[t]:C})),v.current[t]&&clearTimeout(v.current[t]);const S=n;v.current[t]=setTimeout(()=>{o({camId:S,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},300)},[n,o]),j=a.useCallback((t,C)=>{b(S=>({...S,[t]:C})),o({camId:n,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},[n,o]),x=Number(r("width",640)),p=Number(r("height",480)),N=Number(r("threshold",1500)),w=$(N,x,p),M=a.useCallback(t=>{const C=K(t,x,p);l("threshold",C)},[x,p,l]);return e.jsxs("div",{className:"space-y-2",children:[e.jsx(J,{cameraId:n,readOnly:!0}),e.jsxs(E,{title:"Stream",defaultOpen:!1,children:[e.jsx(y,{label:"Quality",value:Number(r("stream_quality",50)),onChange:t=>l("stream_quality",t),min:1,max:100,unit:"%",helpText:"JPEG compression quality"}),e.jsx(y,{label:"Max Framerate",value:Number(r("stream_maxrate",15)),onChange:t=>l("stream_maxrate",t),min:1,max:30,unit:" fps",helpText:"Maximum stream framerate"})]}),e.jsxs(E,{title:"Image",defaultOpen:!1,children:[e.jsx(y,{label:"Brightness",value:Number(r("libcam_brightness",0)),onChange:t=>l("libcam_brightness",t),min:-1,max:1,step:.1,helpText:"Brightness adjustment"}),e.jsx(y,{label:"Contrast",value:Number(r("libcam_contrast",1)),onChange:t=>l("libcam_contrast",t),min:0,max:32,step:.5,helpText:"Contrast adjustment"}),e.jsx(y,{label:"Gain (ISO)",value:Number(r("libcam_gain",1)),onChange:t=>l("libcam_gain",t),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)"}),e.jsx(L,{label:"Auto White Balance",value:!!r("libcam_awb_enable",!0),onChange:t=>j("libcam_awb_enable",t),helpText:"Enable automatic white balance"}),!!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"AWB Mode",value:String(r("libcam_awb_mode",0)),onChange:t=>j("libcam_awb_mode",Number(t)),options:X.map(t=>({value:String(t.value),label:t.label})),helpText:"White balance mode"}),h?.AwbLocked!==!1&&e.jsx(L,{label:"Lock AWB",value:!!r("libcam_awb_locked",!1),onChange:t=>j("libcam_awb_locked",t),helpText:"Lock white balance settings"})]}),!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[h?.ColourTemperature!==!1&&e.jsx(y,{label:"Color Temperature",value:Number(r("libcam_colour_temp",0)),onChange:t=>l("libcam_colour_temp",t),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)"}),e.jsx(y,{label:"Red Gain",value:Number(r("libcam_colour_gain_r",1)),onChange:t=>l("libcam_colour_gain_r",t),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)"}),e.jsx(y,{label:"Blue Gain",value:Number(r("libcam_colour_gain_b",1)),onChange:t=>l("libcam_colour_gain_b",t),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)"}),h?.ColourTemperature===!1&&e.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[e.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),h?.AfMode&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"Autofocus Mode",value:String(r("libcam_af_mode",0)),onChange:t=>j("libcam_af_mode",Number(t)),options:Z.map(t=>({value:String(t.value),label:t.label})),helpText:"Focus control mode"}),Number(r("libcam_af_mode",0))===0&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),!h?.AfMode&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),e.jsxs(E,{title:"Detection",defaultOpen:!1,children:[e.jsx(y,{label:"Threshold",value:w,onChange:M,min:0,max:20,step:.1,unit:"%",helpText:"Motion sensitivity (higher = less sensitive)"}),e.jsx(y,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:t=>l("noise_level",t),min:1,max:255,helpText:"Noise tolerance"}),e.jsx(L,{label:"Auto-tune Noise",value:!!r("noise_tune",!1),onChange:t=>j("noise_tune",t),helpText:"Automatically adjust noise level"})]}),m&&e.jsx("div",{className:"text-center text-sm text-gray-400 py-2",children:"Applying..."}),c&&!m&&e.jsx("div",{className:"text-center text-sm text-green-400 py-2",children:"Applied!"})]})}function R({cameraId:n}){const[i,o]=a.useState(!1),m=c=>{c.stopPropagation();const d=document.querySelector(`[data-camera-id="${n}"]`);d&&(i?document.exitFullscreen&&document.exitFullscreen():d.requestFullscreen&&d.requestFullscreen())};return a.useEffect(()=>{const c=()=>{o(!!document.fullscreenElement)};return document.addEventListener("fullscreenchange",c),()=>{document.removeEventListener("fullscreenchange",c)}},[]),e.jsx("button",{type:"button",onClick:m,className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Toggle fullscreen",title:"Toggle fullscreen",children:e.jsx("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:i?e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"}):e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"})})})}function I({cameraId:n,onClick:i}){return e.jsx("button",{type:"button",onClick:o=>{o.stopPropagation(),i(n)},className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Quick settings",title:"Quick settings",children:e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function P({cameraId:n}){const[i,o]=a.useState(!1),{addToast:m}=O(),c=async d=>{if(d.stopPropagation(),!i){o(!0);try{await Y(n),m("Snapshot captured","success")}catch(g){m(g instanceof Error?g.message:"Failed to capture snapshot","error")}finally{o(!1)}}};return e.jsx("button",{type:"button",onClick:c,disabled:i,className:"p-1.5 hover:bg-surface rounded-full transition-colors disabled:opacity-50","aria-label":"Take snapshot",title:"Take snapshot",children:i?e.jsxs("svg",{className:"w-5 h-5 text-gray-400 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}):e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 13a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function ae(){const{data:n,isLoading:i,error:o}=q(),{role:m,isAuthenticated:c,authRequired:d}=Q(),{data:g}=G({enabled:!d||c}),[b,v]=a.useState(!1),[h,r]=a.useState(null),[l,j]=a.useState({}),x=s=>g?.find(f=>f.id===s)?.fps??0,p=s=>l[s]??0,N=s=>f=>{j(_=>({..._,[s]:f}))},{data:w}=H({queryKey:["config"],queryFn:async()=>{const s=await z("/0/api/config");return s.csrf_token&&V(s.csrf_token),s},enabled:b,staleTime:3e4}),M=s=>{r(s),v(!0)},t=()=>{v(!1)},C=n?.find(s=>s.id===h),S=C?`Quick Settings - ${C.name}`:"Quick Settings",u=a.useMemo(()=>{if(!w||!h)return{};const s=w.configuration?.default||{},f=w.configuration?.[`cam${h}`]||{};return{...s,...f}},[w,h]);if(i)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsx("div",{className:"flex flex-col items-center gap-6",children:[1].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 animate-pulse w-full max-w-4xl",children:[e.jsx("div",{className:"h-6 bg-surface rounded w-1/3 mb-4"}),e.jsx("div",{className:"aspect-video bg-surface rounded"})]},s))})]});if(o)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-danger/10 border border-danger rounded-lg p-4 max-w-2xl mx-auto",children:[e.jsxs("p",{className:"text-danger",children:["Failed to load cameras: ",o instanceof Error?o.message:"Unknown error"]}),e.jsx("button",{className:"mt-2 text-sm text-primary hover:underline",onClick:()=>window.location.reload(),children:"Retry"})]})]});if(!n||n.length===0)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[e.jsx("svg",{className:"w-16 h-16 mx-auto text-gray-600 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"})}),e.jsx("p",{className:"text-gray-400 text-lg",children:"No cameras configured"}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:"Add cameras in Motion's configuration file"})]})]});const k=n.length;if(k===1){const s=n[0],f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("div",{className:"max-w-5xl mx-auto",children:e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}const T=()=>k===2?"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6":k<=4?"grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6":"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6";return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsxs("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:["Cameras (",k,")"]}),e.jsx("div",{className:T(),children:n.map(s=>{const f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium text-sm sm:text-base",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]},s.id)})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}export{ae as Dashboard}; diff --git a/data/webui/assets/Media-6m4Qsu8Y.js b/data/webui/assets/Media-6m4Qsu8Y.js new file mode 100644 index 00000000..69431ac7 --- /dev/null +++ b/data/webui/assets/Media-6m4Qsu8Y.js @@ -0,0 +1 @@ +import{j as e,a as ie,c as ne,i as re,r as n,q as R,b as oe,s as ce,v as de,w as ue,x as me,y as xe,z as pe,l as he}from"./index-DCzq8GhF.js";function O({offset:a,limit:c,total:r,onPageChange:f,context:l}){const v=Math.floor(a/c)+1,i=Math.ceil(r/c),b=r===0?0:a+1,t=Math.min(a+c,r),x=a>0,d=a+c1&&e.jsxs("div",{className:"flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>f(Math.max(0,a-c)),disabled:!x,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Previous page",children:"◀ Previous"}),e.jsxs("span",{className:"px-3 py-1 text-sm text-gray-400",children:["Page ",v," of ",i]}),e.jsx("button",{onClick:()=>f(a+c),disabled:!d,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Next page",children:"Next ▶"})]})]})}function k(a){const c=he();if(!c)return a;const r=a.includes("?")?"&":"?";return`${a}${r}token=${encodeURIComponent(c)}`}const g=100;function fe(a,c){if(!a||a.length!==8)return null;const r=parseInt(a.substring(0,4),10),f=parseInt(a.substring(4,6),10)-1,l=parseInt(a.substring(6,8),10);if(isNaN(r)||isNaN(f)||isNaN(l))return null;let v=0,i=0,b=0;if(c){const x=c.split(":");x.length>=2&&(v=parseInt(x[0],10)||0,i=parseInt(x[1],10)||0,b=parseInt(x[2],10)||0)}const t=new Date(r,f,l,v,i,b);return isNaN(t.getTime())?null:t}function be(){const{addToast:a}=ie(),{role:c}=ne(),r=re(),f=c==="admin",[l,v]=n.useState(1),[i,b]=n.useState("pictures"),[t,x]=n.useState("all"),[d,P]=n.useState(""),[Y,y]=n.useState(0),[o,w]=n.useState(null),[p,C]=n.useState(null),[j,A]=n.useState(null),N=Y*g;n.useEffect(()=>{y(0)},[l,i,d]),n.useEffect(()=>{t==="all"&&P("")},[t]),n.useEffect(()=>{t==="all"&&(r.invalidateQueries({queryKey:R.movies(l)}),r.invalidateQueries({queryKey:R.pictures(l)}))},[t,l,r]),n.useEffect(()=>{t==="folders"&&r.invalidateQueries({predicate:s=>Array.isArray(s.queryKey)&&s.queryKey[0]==="media-folders"&&s.queryKey[1]===l})},[t,d,l,r]);const{data:Z}=oe(),{data:B,isLoading:J}=ce(l,N,g,null,{enabled:t==="all"&&i==="pictures"}),{data:E,isLoading:X}=de(l,N,g,null,{enabled:t==="all"&&i==="movies"}),{data:u,isLoading:ee}=ue(l,d,N,g,{enabled:t==="folders"}),$=me(),L=xe(),F=pe(),T=t==="folders"?ee:i==="pictures"?J:X,q=t==="folders"?u?.files??[]:i==="pictures"?B?.pictures??[]:E?.movies??[],D=t==="folders"?u?.total_files??0:i==="pictures"?B?.total_count??0:E?.total_count??0,I=s=>s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:s<1024*1024*1024?`${(s/(1024*1024)).toFixed(1)} MB`:`${(s/(1024*1024*1024)).toFixed(1)} GB`,K=(s,m)=>{const h=fe(s,m);return h?h.toLocaleDateString()+" "+h.toLocaleTimeString():"Unknown date"},se=n.useCallback((s,m)=>{m.stopPropagation(),C(s)},[]),te=n.useCallback(async()=>{if(p)try{const s="type"in p?p.type:i;s==="picture"||s==="pictures"?await $.mutateAsync({camId:l,pictureId:p.id}):await L.mutateAsync({camId:l,movieId:p.id}),a(`${s==="picture"||s==="pictures"?"Picture":"Movie"} deleted`,"success"),C(null),o?.id===p.id&&w(null)}catch{a("Failed to delete file","error")}},[p,i,l,$,L,a,o]),V=n.useCallback(()=>{C(null)},[]),W=n.useCallback((s,m)=>{A({path:s,fileCount:m})},[]),ae=n.useCallback(async()=>{if(j)try{const s=await F.mutateAsync({camId:l,path:j.path});a(`Deleted ${s.deleted.movies} movies, ${s.deleted.pictures} pictures`,"success"),A(null)}catch{a("Failed to delete folder contents","error")}},[j,l,F,a]),U=n.useCallback(()=>{A(null)},[]),z=n.useCallback(s=>{P(s),y(0)},[]),le=n.useCallback(()=>{u?.parent!==null&&(P(u?.parent??""),y(0))},[u]),S=$.isPending||L.isPending,_=F.isPending,G=d?d.split("/"):[];return e.jsxs("div",{className:"p-6",children:[e.jsx("div",{className:"flex items-center justify-between mb-6",children:e.jsx("h2",{className:"text-3xl font-bold",children:"Media"})}),e.jsxs("div",{className:"flex flex-wrap gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"camera-select",className:"block text-sm font-medium mb-2",children:"Camera"}),e.jsx("select",{id:"camera-select",value:l,onChange:s=>v(parseInt(s.target.value)),className:"px-3 py-2 bg-surface border border-surface-elevated rounded-lg",children:Z?.map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"Type"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>b("pictures"),className:`px-4 py-2 rounded-lg transition-colors ${i==="pictures"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Pictures"}),e.jsx("button",{onClick:()=>b("movies"),className:`px-4 py-2 rounded-lg transition-colors ${i==="movies"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Movies"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"View"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>x("all"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="all"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"All"}),e.jsx("button",{onClick:()=>x("folders"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="folders"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Folders"})]})]})]}),t==="folders"&&e.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3",children:[e.jsx("svg",{className:"w-5 h-5 text-gray-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"})}),e.jsx("span",{className:"text-sm font-medium text-gray-300",children:"Browse Folders"})]}),e.jsxs("div",{className:"flex items-center gap-1 text-sm flex-wrap",children:[e.jsx("button",{onClick:()=>z(""),className:"hover:text-primary px-1",children:"Root"}),G.map((s,m)=>{const h=G.slice(0,m+1).join("/");return e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("button",{onClick:()=>z(h),className:"hover:text-primary px-1",children:s})]},m)})]}),u?.parent!==null&&e.jsxs("button",{onClick:le,className:"mt-2 flex items-center gap-2 text-sm text-gray-400 hover:text-white",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 19l-7-7 7-7"})}),"Up to parent folder"]})]}),t==="folders"&&u&&u.folders.length>0&&e.jsx("div",{className:"mb-6 grid gap-2 md:grid-cols-2 lg:grid-cols-4",children:u.folders.map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 hover:ring-2 hover:ring-primary cursor-pointer transition-all group",children:[e.jsx("button",{onClick:()=>z(s.path),className:"w-full text-left",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("svg",{className:"w-8 h-8 text-yellow-500 flex-shrink-0",fill:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{d:"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"font-medium truncate",children:s.name}),e.jsxs("p",{className:"text-xs text-gray-400",children:[s.file_count," files - ",I(s.total_size)]})]})]})}),f&&s.file_count>0&&e.jsx("button",{onClick:m=>{m.stopPropagation(),W(s.path,s.file_count)},className:"mt-2 w-full px-2 py-1 text-xs bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded opacity-0 group-hover:opacity-100 transition-opacity",title:`Delete all ${s.file_count} media files in this folder`,children:"Delete All Media"})]},s.path))}),t==="folders"&&f&&d&&u&&u.total_files>0&&e.jsx("div",{className:"mb-4 flex justify-end",children:e.jsxs("button",{onClick:()=>W(d,u.total_files),className:"px-3 py-1.5 text-sm bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded-lg flex items-center gap-2",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})}),"Delete All Media in This Folder (",u.total_files,")"]})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),T?e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:[1,2,3,4,5,6,7,8].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg animate-pulse",children:[e.jsx("div",{className:"aspect-video bg-surface rounded-t-lg"}),e.jsxs("div",{className:"p-3",children:[e.jsx("div",{className:"h-4 bg-surface rounded w-3/4 mb-2"}),e.jsx("div",{className:"h-3 bg-surface rounded w-1/2"})]})]},s))}):q.length===0?e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center",children:[e.jsx("p",{className:"text-gray-400",children:t==="folders"?"No media files in this folder":`No ${i} found`}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:t==="folders"?"Navigate to a folder with media files":i==="pictures"?"Motion detection snapshots will appear here":"Recorded videos will appear here"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:q.map(s=>{const m="type"in s?s.type:i==="pictures"?"picture":"movie",h=s.thumbnail||void 0;return e.jsxs("button",{className:"bg-surface-elevated rounded-lg overflow-hidden cursor-pointer hover:ring-2 hover:ring-primary focus:ring-2 focus:ring-primary focus:outline-none transition-all group relative text-left w-full",onClick:()=>w(s),"aria-label":`View ${s.filename}`,children:[e.jsx("button",{onClick:M=>se(s,M),className:"absolute top-2 right-2 z-10 p-1.5 bg-red-600/80 hover:bg-red-600 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity",title:"Delete","aria-label":`Delete ${s.filename}`,children:e.jsx("svg",{className:"w-4 h-4 text-white",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})})}),e.jsxs("div",{className:"aspect-video bg-surface flex items-center justify-center relative overflow-hidden",children:[m==="picture"?e.jsx("img",{src:k(s.path),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy"}):h?e.jsx("img",{src:k(h),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy",onError:M=>{M.currentTarget.style.display="none";const H=M.currentTarget.parentElement;if(H){const Q=H.querySelector(".fallback-icon");Q&&(Q.style.display="flex")}}}):null,e.jsx("div",{className:`fallback-icon text-gray-400 absolute inset-0 flex items-center justify-center ${h?"hidden":""}`,children:e.jsxs("svg",{className:"w-16 h-16",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]})})]}),e.jsxs("div",{className:"p-3",children:[e.jsx("p",{className:"text-sm font-medium truncate",children:s.filename}),e.jsxs("div",{className:"flex justify-between text-xs text-gray-400 mt-1",children:[e.jsx("span",{children:I(s.size)}),e.jsx("span",{children:s.date?K(s.date,s.time):""})]})]})]},`${t}-${d}-${s.id}`)})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4",onClick:()=>w(null),children:e.jsxs("div",{className:"max-w-6xl w-full bg-surface-elevated rounded-lg overflow-hidden",onClick:s=>s.stopPropagation(),children:[e.jsxs("div",{className:"p-4 border-b border-surface flex justify-between items-center",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:o.filename}),e.jsxs("p",{className:"text-sm text-gray-400",children:[I(o.size)," ",o.date&&`- ${K(o.date,o.time)}`]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("a",{href:k(o.path),download:o.filename,className:"px-3 py-1 bg-primary hover:bg-primary-hover rounded text-sm",onClick:s=>s.stopPropagation(),children:"Download"}),e.jsx("button",{onClick:s=>{s.stopPropagation(),C(o)},className:"px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm",children:"Delete"}),e.jsx("button",{onClick:()=>w(null),className:"px-3 py-1 bg-surface hover:bg-surface-elevated rounded text-sm",children:"Close"})]})]}),e.jsx("div",{className:"p-4",children:("type"in o?o.type:i)==="picture"?e.jsx("img",{src:k(o.path),alt:o.filename,className:"w-full h-auto max-h-[70vh] object-contain"}):e.jsx("video",{src:k(o.path),controls:!0,className:"w-full h-auto max-h-[70vh]",autoPlay:!0,children:"Your browser does not support video playback."})})]})}),p&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:V,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2",children:"Delete File?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsx("span",{className:"font-medium text-white",children:p.filename}),"? This action cannot be undone."]}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:V,disabled:S,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:te,disabled:S,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:S?"Deleting...":"Delete"})]})]})})}),j&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:U,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2 text-red-400",children:"Delete All Media Files?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsxs("span",{className:"font-medium text-white",children:["all ",j.fileCount," media files"]})," in folder ",e.jsx("span",{className:"font-mono text-primary",children:j.path||"root"}),"?"]}),e.jsx("p",{className:"text-sm text-yellow-500 mb-4",children:"This will delete movies, pictures, and their thumbnails. Subfolders will NOT be deleted. This action cannot be undone."}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:U,disabled:_,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:ae,disabled:_,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:_?"Deleting...":"Delete All"})]})]})})})]})}export{be as Media}; diff --git a/data/webui/assets/Settings-Ca5UNPDy.js b/data/webui/assets/Settings-Ca5UNPDy.js new file mode 100644 index 00000000..dde79aee --- /dev/null +++ b/data/webui/assets/Settings-Ca5UNPDy.js @@ -0,0 +1,22 @@ +import{r as x,j as s,h as Ce,a as Be,e as qe,i as Rt,k as rt,l as fn,m as gn,f as Ft,c as bn,u as xn,n as vn,o as _n,g as yn}from"./index-DCzq8GhF.js";import{c as A,b as B,R as st,f as ot,F as T,g as wn,h as jn,A as Sn,d as Nn,i as kn,j as Cn,m as at,k as it,l as Tn,p as Pn,L as zn,a as $n,n as Mn,o as On,q as In,r as te,s as Ie,E as An,t as En,M as ct,u as Zn,C as Dn,v as Rn,w as Fn}from"./parameterMappings-CUuSfEkB.js";function S({label:e,value:t,onChange:n,type:r="text",placeholder:o,disabled:a=!1,required:i=!1,helpText:c,error:l,min:u,max:d,step:h,originalValue:f,showVisibilityToggle:p}){const[v,b]=x.useState(!1),w=oe=>{n(oe.target.value)},g=!!l,k=f!==void 0&&String(t)!==String(f),V=r==="password",q=p??V,O=V&&v?"text":r;return s.jsxs("div",{className:"mb-4",children:[s.jsxs("label",{className:"block text-sm font-medium mb-1",children:[e,i&&s.jsx("span",{className:"text-red-500 ml-1",children:"*"}),k&&s.jsx("span",{className:"ml-2 text-xs text-yellow-400",children:"(modified)"})]}),s.jsxs("div",{className:"relative",children:[s.jsx("input",{type:O,value:t,onChange:w,placeholder:o,disabled:a,required:i,min:u,max:d,step:h,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${q?"pr-10":""} ${g?"border-red-500 focus:ring-red-500":k?"border-yellow-500/50 focus:ring-yellow-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":g,"aria-describedby":g?`${e}-error`:void 0}),q&&s.jsx("button",{type:"button",onClick:()=>b(!v),className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-200 transition-colors","aria-label":v?"Hide password":"Show password",children:v?s.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"})}):s.jsxs("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})]})})]}),g&&s.jsx("p",{id:`${e}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:l}),c&&!g&&s.jsx("p",{className:"mt-1 text-sm text-gray-400",children:c})]})}function E({title:e,description:t,children:n,collapsible:r=!1,defaultOpen:o=!0}){const[a,i]=x.useState(o),c=()=>{r&&i(!a)};return s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-6 mb-6",children:[s.jsxs("div",{className:`flex items-center justify-between ${r?"cursor-pointer":""}`,onClick:c,children:[s.jsxs("div",{children:[s.jsx("h3",{className:"text-lg font-semibold",children:e}),t&&s.jsx("p",{className:"text-sm text-gray-400 mt-1",children:t})]}),r&&s.jsx("svg",{className:`w-5 h-5 transition-transform ${a?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),a&&s.jsx("div",{className:"mt-4",children:n})]})}function m(e,t,n){function r(c,l){if(c._zod||Object.defineProperty(c,"_zod",{value:{def:l,constr:i,traits:new Set},enumerable:!1}),c._zod.traits.has(e))return;c._zod.traits.add(e),t(c,l);const u=i.prototype,d=Object.keys(u);for(let h=0;hn?.Parent&&c instanceof n.Parent?!0:c?._zod?.traits?.has(e)}),Object.defineProperty(i,"name",{value:e}),i}class le extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class Ut extends Error{constructor(t){super(`Encountered unidirectional transform during encode: ${t}`),this.name="ZodEncodeError"}}const Un={};function re(e){return Un}function Ue(e,t){return typeof t=="bigint"?t.toString():t}function Je(e){return e==null}function Ge(e){const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length;return e.slice(t,n)}function Ln(e,t){const n=(e.toString().split(".")[1]||"").length,r=t.toString();let o=(r.split(".")[1]||"").length;if(o===0&&/\d?e-\d?/.test(r)){const l=r.match(/\d?e-(\d?)/);l?.[1]&&(o=Number.parseInt(l[1]))}const a=n>o?n:o,i=Number.parseInt(e.toFixed(a).replace(".","")),c=Number.parseInt(t.toFixed(a).replace(".",""));return i%c/10**a}const lt=Symbol("evaluating");function P(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==lt)return r===void 0&&(r=lt,r=n()),r},set(o){Object.defineProperty(e,t,{value:o})},configurable:!0})}function Hn(...e){const t={};for(const n of e){const r=Object.getOwnPropertyDescriptors(n);Object.assign(t,r)}return Object.defineProperties({},t)}function Vn(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const Lt="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function ut(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Le(e){if(ut(e)===!1)return!1;const t=e.constructor;if(t===void 0||typeof t!="function")return!0;const n=t.prototype;return!(ut(n)===!1||Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")===!1)}function Ht(e){return Le(e)?{...e}:Array.isArray(e)?[...e]:e}function Ke(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Yn(e,t,n){const r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function _(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}const Wn={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ce(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var r;return(r=n).path??(r.path=[]),n.path.unshift(e),n})}function ve(e){return typeof e=="string"?e:e?.message}function se(e,t,n){const r={...e,path:e.path??[]};if(!e.message){const o=ve(e.inst?._zod.def?.error?.(e))??ve(t?.error?.(e))??ve(n.customError?.(e))??ve(n.localeError?.(e))??"Invalid input";r.message=o}return delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function Qe(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function pe(...e){const[t,n,r]=e;return typeof t=="string"?{message:t,code:"custom",input:n,inst:r}:{...t}}const Vt=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),e.message=JSON.stringify(t,Ue,2),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},Yt=m("$ZodError",Vt),Wt=m("$ZodError",Vt,{Parent:Error});function qn(e,t=n=>n.message){const n={},r=[];for(const o of e.issues)o.path.length>0?(n[o.path[0]]=n[o.path[0]]||[],n[o.path[0]].push(t(o))):r.push(t(o));return{formErrors:r,fieldErrors:n}}function Jn(e,t=n=>n.message){const n={_errors:[]},r=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(i=>r({issues:i}));else if(a.code==="invalid_key")r({issues:a.issues});else if(a.code==="invalid_element")r({issues:a.issues});else if(a.path.length===0)n._errors.push(t(a));else{let i=n,c=0;for(;c(t,n,r,o)=>{const a=r?Object.assign(r,{async:!1}):{async:!1},i=t._zod.run({value:n,issues:[]},a);if(i instanceof Promise)throw new le;if(i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>se(l,a,re())));throw Lt(c,o?.callee),c}return i.value},et=e=>async(t,n,r,o)=>{const a=r?Object.assign(r,{async:!0}):{async:!0};let i=t._zod.run({value:n,issues:[]},a);if(i instanceof Promise&&(i=await i),i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>se(l,a,re())));throw Lt(c,o?.callee),c}return i.value},Te=e=>(t,n,r)=>{const o=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise)throw new le;return a.issues.length?{success:!1,error:new(e??Yt)(a.issues.map(i=>se(i,o,re())))}:{success:!0,data:a.value}},Gn=Te(Wt),Pe=e=>async(t,n,r)=>{const o=r?Object.assign(r,{async:!0}):{async:!0};let a=t._zod.run({value:n,issues:[]},o);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(i=>se(i,o,re())))}:{success:!0,data:a.value}},Kn=Pe(Wt),Qn=e=>(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Xe(e)(t,n,o)},Xn=e=>(t,n,r)=>Xe(e)(t,n,r),er=e=>async(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return et(e)(t,n,o)},tr=e=>async(t,n,r)=>et(e)(t,n,r),nr=e=>(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Te(e)(t,n,o)},rr=e=>(t,n,r)=>Te(e)(t,n,r),sr=e=>async(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Pe(e)(t,n,o)},or=e=>async(t,n,r)=>Pe(e)(t,n,r),ar=/^[cC][^\s-]{8,}$/,ir=/^[0-9a-z]+$/,cr=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,lr=/^[0-9a-vA-V]{20}$/,ur=/^[A-Za-z0-9]{27}$/,dr=/^[a-zA-Z0-9_-]{21}$/,mr=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,hr=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,dt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,pr=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,fr="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function gr(){return new RegExp(fr,"u")}const br=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,xr=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,vr=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,_r=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,yr=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,Bt=/^[A-Za-z0-9_-]*$/,wr=/^\+(?:[0-9]){6,14}[0-9]$/,qt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",jr=new RegExp(`^${qt}$`);function Jt(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function Sr(e){return new RegExp(`^${Jt(e)}$`)}function Nr(e){const t=Jt({precision:e.precision}),n=["Z"];e.local&&n.push(""),e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const r=`${t}(?:${n.join("|")})`;return new RegExp(`^${qt}T(?:${r})$`)}const kr=e=>{const t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},Cr=/^-?\d+$/,Tr=/^-?\d+(?:\.\d+)?/,Pr=/^[^A-Z]*$/,zr=/^[^a-z]*$/,H=m("$ZodCheck",(e,t)=>{var n;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),Gt={number:"number",bigint:"bigint",object:"date"},Kt=m("$ZodCheckLessThan",(e,t)=>{H.init(e,t);const n=Gt[typeof t.value];e._zod.onattach.push(r=>{const o=r._zod.bag,a=(t.inclusive?o.maximum:o.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?r.value<=t.value:r.value{H.init(e,t);const n=Gt[typeof t.value];e._zod.onattach.push(r=>{const o=r._zod.bag,a=(t.inclusive?o.minimum:o.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>a&&(t.inclusive?o.minimum=t.value:o.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:"too_small",minimum:t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),$r=m("$ZodCheckMultipleOf",(e,t)=>{H.init(e,t),e._zod.onattach.push(n=>{var r;(r=n._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof n.value=="bigint"?n.value%t.value===BigInt(0):Ln(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),Mr=m("$ZodCheckNumberFormat",(e,t)=>{H.init(e,t),t.format=t.format||"float64";const n=t.format?.includes("int"),r=n?"int":"number",[o,a]=Wn[t.format];e._zod.onattach.push(i=>{const c=i._zod.bag;c.format=t.format,c.minimum=o,c.maximum=a,n&&(c.pattern=Cr)}),e._zod.check=i=>{const c=i.value;if(n){if(!Number.isInteger(c)){i.issues.push({expected:r,format:t.format,code:"invalid_type",continue:!1,input:c,inst:e});return}if(!Number.isSafeInteger(c)){c>0?i.issues.push({input:c,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort}):i.issues.push({input:c,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort});return}}ca&&i.issues.push({origin:"number",input:c,code:"too_big",maximum:a,inst:e})}}),Or=m("$ZodCheckMaxLength",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const o=r.value;if(o.length<=t.maximum)return;const i=Qe(o);r.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),Ir=m("$ZodCheckMinLength",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>o&&(r._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{const o=r.value;if(o.length>=t.minimum)return;const i=Qe(o);r.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),Ar=m("$ZodCheckLengthEquals",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag;o.minimum=t.length,o.maximum=t.length,o.length=t.length}),e._zod.check=r=>{const o=r.value,a=o.length;if(a===t.length)return;const i=Qe(o),c=a>t.length;r.issues.push({origin:i,...c?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),ze=m("$ZodCheckStringFormat",(e,t)=>{var n,r;H.init(e,t),e._zod.onattach.push(o=>{const a=o._zod.bag;a.format=t.format,t.pattern&&(a.patterns??(a.patterns=new Set),a.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=o=>{t.pattern.lastIndex=0,!t.pattern.test(o.value)&&o.issues.push({origin:"string",code:"invalid_format",format:t.format,input:o.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),Er=m("$ZodCheckRegex",(e,t)=>{ze.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:"string",code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),Zr=m("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=Pr),ze.init(e,t)}),Dr=m("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=zr),ze.init(e,t)}),Rr=m("$ZodCheckIncludes",(e,t)=>{H.init(e,t);const n=Ke(t.includes),r=new RegExp(typeof t.position=="number"?`^.{${t.position}}${n}`:n);t.pattern=r,e._zod.onattach.push(o=>{const a=o._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(r)}),e._zod.check=o=>{o.value.includes(t.includes,t.position)||o.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:o.value,inst:e,continue:!t.abort})}}),Fr=m("$ZodCheckStartsWith",(e,t)=>{H.init(e,t);const n=new RegExp(`^${Ke(t.prefix)}.*`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const o=r._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(n)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),Ur=m("$ZodCheckEndsWith",(e,t)=>{H.init(e,t);const n=new RegExp(`.*${Ke(t.suffix)}$`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const o=r._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(n)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),Lr=m("$ZodCheckOverwrite",(e,t)=>{H.init(e,t),e._zod.check=n=>{n.value=t.tx(n.value)}}),Hr={major:4,minor:2,patch:1},Z=m("$ZodType",(e,t)=>{var n;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=Hr;const r=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&r.unshift(e);for(const o of r)for(const a of o._zod.onattach)a(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{const o=(i,c,l)=>{let u=ce(i),d;for(const h of c){if(h._zod.def.when){if(!h._zod.def.when(i))continue}else if(u)continue;const f=i.issues.length,p=h._zod.check(i);if(p instanceof Promise&&l?.async===!1)throw new le;if(d||p instanceof Promise)d=(d??Promise.resolve()).then(async()=>{await p,i.issues.length!==f&&(u||(u=ce(i,f)))});else{if(i.issues.length===f)continue;u||(u=ce(i,f))}}return d?d.then(()=>i):i},a=(i,c,l)=>{if(ce(i))return i.aborted=!0,i;const u=o(c,r,l);if(u instanceof Promise){if(l.async===!1)throw new le;return u.then(d=>e._zod.parse(d,l))}return e._zod.parse(u,l)};e._zod.run=(i,c)=>{if(c.skipChecks)return e._zod.parse(i,c);if(c.direction==="backward"){const u=e._zod.parse({value:i.value,issues:[]},{...c,skipChecks:!0});return u instanceof Promise?u.then(d=>a(d,i,c)):a(u,i,c)}const l=e._zod.parse(i,c);if(l instanceof Promise){if(c.async===!1)throw new le;return l.then(u=>o(u,r,c))}return o(l,r,c)}}e["~standard"]={validate:o=>{try{const a=Gn(e,o);return a.success?{value:a.data}:{issues:a.error?.issues}}catch{return Kn(e,o).then(i=>i.success?{value:i.data}:{issues:i.error?.issues})}},vendor:"zod",version:1}}),tt=m("$ZodString",(e,t)=>{Z.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??kr(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value=="string"||n.issues.push({expected:"string",code:"invalid_type",input:n.value,inst:e}),n}}),z=m("$ZodStringFormat",(e,t)=>{ze.init(e,t),tt.init(e,t)}),Vr=m("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=hr),z.init(e,t)}),Yr=m("$ZodUUID",(e,t)=>{if(t.version){const r={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(r===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=dt(r))}else t.pattern??(t.pattern=dt());z.init(e,t)}),Wr=m("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=pr),z.init(e,t)}),Br=m("$ZodURL",(e,t)=>{z.init(e,t),e._zod.check=n=>{try{const r=n.value.trim(),o=new URL(r);t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(o.hostname)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:t.hostname.source,input:n.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(o.protocol.endsWith(":")?o.protocol.slice(0,-1):o.protocol)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:n.value,inst:e,continue:!t.abort})),t.normalize?n.value=o.href:n.value=r;return}catch{n.issues.push({code:"invalid_format",format:"url",input:n.value,inst:e,continue:!t.abort})}}}),qr=m("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=gr()),z.init(e,t)}),Jr=m("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=dr),z.init(e,t)}),Gr=m("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=ar),z.init(e,t)}),Kr=m("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=ir),z.init(e,t)}),Qr=m("$ZodULID",(e,t)=>{t.pattern??(t.pattern=cr),z.init(e,t)}),Xr=m("$ZodXID",(e,t)=>{t.pattern??(t.pattern=lr),z.init(e,t)}),es=m("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=ur),z.init(e,t)}),ts=m("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=Nr(t)),z.init(e,t)}),ns=m("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=jr),z.init(e,t)}),rs=m("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=Sr(t)),z.init(e,t)}),ss=m("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=mr),z.init(e,t)}),os=m("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=br),z.init(e,t),e._zod.bag.format="ipv4"}),as=m("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=xr),z.init(e,t),e._zod.bag.format="ipv6",e._zod.check=n=>{try{new URL(`http://[${n.value}]`)}catch{n.issues.push({code:"invalid_format",format:"ipv6",input:n.value,inst:e,continue:!t.abort})}}}),is=m("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=vr),z.init(e,t)}),cs=m("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=_r),z.init(e,t),e._zod.check=n=>{const r=n.value.split("/");try{if(r.length!==2)throw new Error;const[o,a]=r;if(!a)throw new Error;const i=Number(a);if(`${i}`!==a)throw new Error;if(i<0||i>128)throw new Error;new URL(`http://[${o}]`)}catch{n.issues.push({code:"invalid_format",format:"cidrv6",input:n.value,inst:e,continue:!t.abort})}}});function Xt(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const ls=m("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=yr),z.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=n=>{Xt(n.value)||n.issues.push({code:"invalid_format",format:"base64",input:n.value,inst:e,continue:!t.abort})}});function us(e){if(!Bt.test(e))return!1;const t=e.replace(/[-_]/g,r=>r==="-"?"+":"/"),n=t.padEnd(Math.ceil(t.length/4)*4,"=");return Xt(n)}const ds=m("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=Bt),z.init(e,t),e._zod.bag.contentEncoding="base64url",e._zod.check=n=>{us(n.value)||n.issues.push({code:"invalid_format",format:"base64url",input:n.value,inst:e,continue:!t.abort})}}),ms=m("$ZodE164",(e,t)=>{t.pattern??(t.pattern=wr),z.init(e,t)});function hs(e,t=null){try{const n=e.split(".");if(n.length!==3)return!1;const[r]=n;if(!r)return!1;const o=JSON.parse(atob(r));return!("typ"in o&&o?.typ!=="JWT"||!o.alg||t&&(!("alg"in o)||o.alg!==t))}catch{return!1}}const ps=m("$ZodJWT",(e,t)=>{z.init(e,t),e._zod.check=n=>{hs(n.value,t.alg)||n.issues.push({code:"invalid_format",format:"jwt",input:n.value,inst:e,continue:!t.abort})}}),en=m("$ZodNumber",(e,t)=>{Z.init(e,t),e._zod.pattern=e._zod.bag.pattern??Tr,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}const o=n.value;if(typeof o=="number"&&!Number.isNaN(o)&&Number.isFinite(o))return n;const a=typeof o=="number"?Number.isNaN(o)?"NaN":Number.isFinite(o)?void 0:"Infinity":void 0;return n.issues.push({expected:"number",code:"invalid_type",input:o,inst:e,...a?{received:a}:{}}),n}}),fs=m("$ZodNumberFormat",(e,t)=>{Mr.init(e,t),en.init(e,t)});function mt(e,t,n){e.issues.length&&t.issues.push(...Bn(n,e.issues)),t.value[n]=e.value}const gs=m("$ZodArray",(e,t)=>{Z.init(e,t),e._zod.parse=(n,r)=>{const o=n.value;if(!Array.isArray(o))return n.issues.push({expected:"array",code:"invalid_type",input:o,inst:e}),n;n.value=Array(o.length);const a=[];for(let i=0;imt(u,n,i))):mt(l,n,i)}return a.length?Promise.all(a).then(()=>n):n}});function ht(e,t,n,r){for(const a of e)if(a.issues.length===0)return t.value=a.value,t;const o=e.filter(a=>!ce(a));return o.length===1?(t.value=o[0].value,o[0]):(t.issues.push({code:"invalid_union",input:t.value,inst:n,errors:e.map(a=>a.issues.map(i=>se(i,r,re())))}),t)}const bs=m("$ZodUnion",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.options.some(o=>o._zod.optin==="optional")?"optional":void 0),P(e._zod,"optout",()=>t.options.some(o=>o._zod.optout==="optional")?"optional":void 0),P(e._zod,"values",()=>{if(t.options.every(o=>o._zod.values))return new Set(t.options.flatMap(o=>Array.from(o._zod.values)))}),P(e._zod,"pattern",()=>{if(t.options.every(o=>o._zod.pattern)){const o=t.options.map(a=>a._zod.pattern);return new RegExp(`^(${o.map(a=>Ge(a.source)).join("|")})$`)}});const n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(o,a)=>{if(n)return r(o,a);let i=!1;const c=[];for(const l of t.options){const u=l._zod.run({value:o.value,issues:[]},a);if(u instanceof Promise)c.push(u),i=!0;else{if(u.issues.length===0)return u;c.push(u)}}return i?Promise.all(c).then(l=>ht(l,o,e,a)):ht(c,o,e,a)}}),xs=m("$ZodIntersection",(e,t)=>{Z.init(e,t),e._zod.parse=(n,r)=>{const o=n.value,a=t.left._zod.run({value:o,issues:[]},r),i=t.right._zod.run({value:o,issues:[]},r);return a instanceof Promise||i instanceof Promise?Promise.all([a,i]).then(([l,u])=>pt(n,l,u)):pt(n,a,i)}});function He(e,t){if(e===t)return{valid:!0,data:e};if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Le(e)&&Le(t)){const n=Object.keys(t),r=Object.keys(e).filter(a=>n.indexOf(a)!==-1),o={...e,...t};for(const a of r){const i=He(e[a],t[a]);if(!i.valid)return{valid:!1,mergeErrorPath:[a,...i.mergeErrorPath]};o[a]=i.data}return{valid:!0,data:o}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};const n=[];for(let r=0;r{Z.init(e,t),e._zod.parse=(n,r)=>{if(r.direction==="backward")throw new Ut(e.constructor.name);const o=t.transform(n.value,n);if(r.async)return(o instanceof Promise?o:Promise.resolve(o)).then(i=>(n.value=i,n));if(o instanceof Promise)throw new le;return n.value=o,n}});function ft(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const _s=m("$ZodOptional",(e,t)=>{Z.init(e,t),e._zod.optin="optional",e._zod.optout="optional",P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),P(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)})?$`):void 0}),e._zod.parse=(n,r)=>{if(t.innerType._zod.optin==="optional"){const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>ft(a,n.value)):ft(o,n.value)}return n.value===void 0?n:t.innerType._zod.run(n,r)}}),ys=m("$ZodNullable",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)}|null)$`):void 0}),P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(n,r)=>n.value===null?n:t.innerType._zod.run(n,r)}),ws=m("$ZodDefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);if(n.value===void 0)return n.value=t.defaultValue,n;const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>gt(a,t)):gt(o,t)}});function gt(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}const js=m("$ZodPrefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>(r.direction==="backward"||n.value===void 0&&(n.value=t.defaultValue),t.innerType._zod.run(n,r))}),Ss=m("$ZodNonOptional",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>{const n=t.innerType._zod.values;return n?new Set([...n].filter(r=>r!==void 0)):void 0}),e._zod.parse=(n,r)=>{const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>bt(a,e)):bt(o,e)}});function bt(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}const Ns=m("$ZodCatch",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>(n.value=a.value,a.issues.length&&(n.value=t.catchValue({...n,error:{issues:a.issues.map(i=>se(i,r,re()))},input:n.value}),n.issues=[]),n)):(n.value=o.value,o.issues.length&&(n.value=t.catchValue({...n,error:{issues:o.issues.map(a=>se(a,r,re()))},input:n.value}),n.issues=[]),n)}}),ks=m("$ZodPipe",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>t.in._zod.values),P(e._zod,"optin",()=>t.in._zod.optin),P(e._zod,"optout",()=>t.out._zod.optout),P(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(n,r)=>{if(r.direction==="backward"){const a=t.out._zod.run(n,r);return a instanceof Promise?a.then(i=>_e(i,t.in,r)):_e(a,t.in,r)}const o=t.in._zod.run(n,r);return o instanceof Promise?o.then(a=>_e(a,t.out,r)):_e(o,t.out,r)}});function _e(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}const Cs=m("$ZodReadonly",(e,t)=>{Z.init(e,t),P(e._zod,"propValues",()=>t.innerType._zod.propValues),P(e._zod,"values",()=>t.innerType._zod.values),P(e._zod,"optin",()=>t.innerType?._zod?.optin),P(e._zod,"optout",()=>t.innerType?._zod?.optout),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(xt):xt(o)}});function xt(e){return e.value=Object.freeze(e.value),e}const Ts=m("$ZodCustom",(e,t)=>{H.init(e,t),Z.init(e,t),e._zod.parse=(n,r)=>n,e._zod.check=n=>{const r=n.value,o=t.fn(r);if(o instanceof Promise)return o.then(a=>vt(a,n,r,e));vt(o,n,r,e)}});function vt(e,t,n,r){if(!e){const o={code:"custom",input:n,inst:r,path:[...r._zod.def.path??[]],continue:!r._zod.def.abort};r._zod.def.params&&(o.params=r._zod.def.params),t.issues.push(pe(o))}}var _t;class Ps{constructor(){this._map=new WeakMap,this._idmap=new Map}add(t,...n){const r=n[0];if(this._map.set(t,r),r&&typeof r=="object"&&"id"in r){if(this._idmap.has(r.id))throw new Error(`ID ${r.id} already exists in the registry`);this._idmap.set(r.id,t)}return this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(t){const n=this._map.get(t);return n&&typeof n=="object"&&"id"in n&&this._idmap.delete(n.id),this._map.delete(t),this}get(t){const n=t._zod.parent;if(n){const r={...this.get(n)??{}};delete r.id;const o={...r,...this._map.get(t)};return Object.keys(o).length?o:void 0}return this._map.get(t)}has(t){return this._map.has(t)}}function zs(){return new Ps}(_t=globalThis).__zod_globalRegistry??(_t.__zod_globalRegistry=zs());const he=globalThis.__zod_globalRegistry;function $s(e,t){return new e({type:"string",..._(t)})}function Ms(e,t){return new e({type:"string",format:"email",check:"string_format",abort:!1,..._(t)})}function yt(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,..._(t)})}function Os(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,..._(t)})}function Is(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",..._(t)})}function As(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",..._(t)})}function Es(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",..._(t)})}function Zs(e,t){return new e({type:"string",format:"url",check:"string_format",abort:!1,..._(t)})}function Ds(e,t){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,..._(t)})}function Rs(e,t){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,..._(t)})}function Fs(e,t){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,..._(t)})}function Us(e,t){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,..._(t)})}function Ls(e,t){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,..._(t)})}function Hs(e,t){return new e({type:"string",format:"xid",check:"string_format",abort:!1,..._(t)})}function Vs(e,t){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,..._(t)})}function Ys(e,t){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,..._(t)})}function Ws(e,t){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,..._(t)})}function Bs(e,t){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,..._(t)})}function qs(e,t){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,..._(t)})}function Js(e,t){return new e({type:"string",format:"base64",check:"string_format",abort:!1,..._(t)})}function Gs(e,t){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,..._(t)})}function Ks(e,t){return new e({type:"string",format:"e164",check:"string_format",abort:!1,..._(t)})}function Qs(e,t){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,..._(t)})}function Xs(e,t){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,..._(t)})}function eo(e,t){return new e({type:"string",format:"date",check:"string_format",..._(t)})}function to(e,t){return new e({type:"string",format:"time",check:"string_format",precision:null,..._(t)})}function no(e,t){return new e({type:"string",format:"duration",check:"string_format",..._(t)})}function ro(e,t){return new e({type:"number",coerce:!0,checks:[],..._(t)})}function so(e,t){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",..._(t)})}function wt(e,t){return new Kt({check:"less_than",..._(t),value:e,inclusive:!1})}function Ae(e,t){return new Kt({check:"less_than",..._(t),value:e,inclusive:!0})}function jt(e,t){return new Qt({check:"greater_than",..._(t),value:e,inclusive:!1})}function Ee(e,t){return new Qt({check:"greater_than",..._(t),value:e,inclusive:!0})}function St(e,t){return new $r({check:"multiple_of",..._(t),value:e})}function tn(e,t){return new Or({check:"max_length",..._(t),maximum:e})}function Ne(e,t){return new Ir({check:"min_length",..._(t),minimum:e})}function nn(e,t){return new Ar({check:"length_equals",..._(t),length:e})}function oo(e,t){return new Er({check:"string_format",format:"regex",..._(t),pattern:e})}function ao(e){return new Zr({check:"string_format",format:"lowercase",..._(e)})}function io(e){return new Dr({check:"string_format",format:"uppercase",..._(e)})}function co(e,t){return new Rr({check:"string_format",format:"includes",..._(t),includes:e})}function lo(e,t){return new Fr({check:"string_format",format:"starts_with",..._(t),prefix:e})}function uo(e,t){return new Ur({check:"string_format",format:"ends_with",..._(t),suffix:e})}function ue(e){return new Lr({check:"overwrite",tx:e})}function mo(e){return ue(t=>t.normalize(e))}function ho(){return ue(e=>e.trim())}function po(){return ue(e=>e.toLowerCase())}function fo(){return ue(e=>e.toUpperCase())}function go(){return ue(e=>Vn(e))}function bo(e,t,n){return new e({type:"array",element:t,..._(n)})}function xo(e,t,n){return new e({type:"custom",check:"custom",fn:t,..._(n)})}function vo(e){const t=_o(n=>(n.addIssue=r=>{if(typeof r=="string")n.issues.push(pe(r,n.value,t._zod.def));else{const o=r;o.fatal&&(o.continue=!1),o.code??(o.code="custom"),o.input??(o.input=n.value),o.inst??(o.inst=t),o.continue??(o.continue=!t._zod.def.abort),n.issues.push(pe(o))}},e(n.value,n)));return t}function _o(e,t){const n=new H({check:"custom",..._(t)});return n._zod.check=e,n}function rn(e){let t=e?.target??"draft-2020-12";return t==="draft-4"&&(t="draft-04"),t==="draft-7"&&(t="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??he,target:t,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref",reused:e?.reused??"inline",external:e?.external??void 0}}function R(e,t,n={path:[],schemaPath:[]}){var r;const o=e._zod.def,a=t.seen.get(e);if(a)return a.count++,n.schemaPath.includes(e)&&(a.cycle=n.path),a.schema;const i={schema:{},count:1,cycle:void 0,path:n.path};t.seen.set(e,i);const c=e._zod.toJSONSchema?.();if(c)i.schema=c;else{const d={...n,schemaPath:[...n.schemaPath,e],path:n.path},h=e._zod.parent;if(h)i.ref=h,R(h,t,d),t.seen.get(h).isParent=!0;else if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,d);else{const f=i.schema,p=t.processors[o.type];if(!p)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${o.type}`);p(e,t,f,d)}}const l=t.metadataRegistry.get(e);return l&&Object.assign(i.schema,l),t.io==="input"&&D(e)&&(delete i.schema.examples,delete i.schema.default),t.io==="input"&&i.schema._prefault&&((r=i.schema).default??(r.default=i.schema._prefault)),delete i.schema._prefault,t.seen.get(e).schema}function sn(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const r=a=>{const i=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const d=e.external.registry.get(a[0])?.id,h=e.external.uri??(p=>p);if(d)return{ref:h(d)};const f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${h("__shared")}#/${i}/${f}`}}if(a[1]===n)return{ref:"#"};const l=`#/${i}/`,u=a[1].schema.id??`__schema${e.counter++}`;return{defId:u,ref:l+u}},o=a=>{if(a[1].schema.$ref)return;const i=a[1],{ref:c,defId:l}=r(a);i.def={...i.schema},l&&(i.defId=l);const u=i.schema;for(const d in u)delete u[d];u.$ref=c};if(e.cycles==="throw")for(const a of e.seen.entries()){const i=a[1];if(i.cycle)throw new Error(`Cycle detected: #/${i.cycle?.join("/")}/ + +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const a of e.seen.entries()){const i=a[1];if(t===a[0]){o(a);continue}if(e.external){const l=e.external.registry.get(a[0])?.id;if(t!==a[0]&&l){o(a);continue}}if(e.metadataRegistry.get(a[0])?.id){o(a);continue}if(i.cycle){o(a);continue}if(i.count>1&&e.reused==="ref"){o(a);continue}}}function on(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const r=i=>{const c=e.seen.get(i),l=c.def??c.schema,u={...l};if(c.ref===null)return;const d=c.ref;if(c.ref=null,d){r(d);const h=e.seen.get(d).schema;h.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(l.allOf=l.allOf??[],l.allOf.push(h)):(Object.assign(l,h),Object.assign(l,u))}c.isParent||e.override({zodSchema:i,jsonSchema:l,path:c.path??[]})};for(const i of[...e.seen.entries()].reverse())r(i[0]);const o={};if(e.target==="draft-2020-12"?o.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?o.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?o.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){const i=e.external.registry.get(t)?.id;if(!i)throw new Error("Schema is missing an `id` property");o.$id=e.external.uri(i)}Object.assign(o,n.def??n.schema);const a=e.external?.defs??{};for(const i of e.seen.entries()){const c=i[1];c.def&&c.defId&&(a[c.defId]=c.def)}e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?o.$defs=a:o.definitions=a);try{const i=JSON.parse(JSON.stringify(o));return Object.defineProperty(i,"~standard",{value:{...t["~standard"],jsonSchema:{input:ke(t,"input"),output:ke(t,"output")}},enumerable:!1,writable:!1}),i}catch{throw new Error("Error converting schema to JSON.")}}function D(e,t){const n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);const r=e._zod.def;if(r.type==="transform")return!0;if(r.type==="array")return D(r.element,n);if(r.type==="set")return D(r.valueType,n);if(r.type==="lazy")return D(r.getter(),n);if(r.type==="promise"||r.type==="optional"||r.type==="nonoptional"||r.type==="nullable"||r.type==="readonly"||r.type==="default"||r.type==="prefault")return D(r.innerType,n);if(r.type==="intersection")return D(r.left,n)||D(r.right,n);if(r.type==="record"||r.type==="map")return D(r.keyType,n)||D(r.valueType,n);if(r.type==="pipe")return D(r.in,n)||D(r.out,n);if(r.type==="object"){for(const o in r.shape)if(D(r.shape[o],n))return!0;return!1}if(r.type==="union"){for(const o of r.options)if(D(o,n))return!0;return!1}if(r.type==="tuple"){for(const o of r.items)if(D(o,n))return!0;return!!(r.rest&&D(r.rest,n))}return!1}const yo=(e,t={})=>n=>{const r=rn({...n,processors:t});return R(e,r),sn(r,e),on(r,e)},ke=(e,t)=>n=>{const{libraryOptions:r,target:o}=n??{},a=rn({...r??{},target:o,io:t,processors:{}});return R(e,a),sn(a,e),on(a,e)},wo={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},jo=(e,t,n,r)=>{const o=n;o.type="string";const{minimum:a,maximum:i,format:c,patterns:l,contentEncoding:u}=e._zod.bag;if(typeof a=="number"&&(o.minLength=a),typeof i=="number"&&(o.maxLength=i),c&&(o.format=wo[c]??c,o.format===""&&delete o.format),u&&(o.contentEncoding=u),l&&l.size>0){const d=[...l];d.length===1?o.pattern=d[0].source:d.length>1&&(o.allOf=[...d.map(h=>({...t.target==="draft-07"||t.target==="draft-04"||t.target==="openapi-3.0"?{type:"string"}:{},pattern:h.source}))])}},So=(e,t,n,r)=>{const o=n,{minimum:a,maximum:i,format:c,multipleOf:l,exclusiveMaximum:u,exclusiveMinimum:d}=e._zod.bag;typeof c=="string"&&c.includes("int")?o.type="integer":o.type="number",typeof d=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.minimum=d,o.exclusiveMinimum=!0):o.exclusiveMinimum=d),typeof a=="number"&&(o.minimum=a,typeof d=="number"&&t.target!=="draft-04"&&(d>=a?delete o.minimum:delete o.exclusiveMinimum)),typeof u=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.maximum=u,o.exclusiveMaximum=!0):o.exclusiveMaximum=u),typeof i=="number"&&(o.maximum=i,typeof u=="number"&&t.target!=="draft-04"&&(u<=i?delete o.maximum:delete o.exclusiveMaximum)),typeof l=="number"&&(o.multipleOf=l)},No=(e,t,n,r)=>{if(t.unrepresentable==="throw")throw new Error("Custom types cannot be represented in JSON Schema")},ko=(e,t,n,r)=>{if(t.unrepresentable==="throw")throw new Error("Transforms cannot be represented in JSON Schema")},Co=(e,t,n,r)=>{const o=n,a=e._zod.def,{minimum:i,maximum:c}=e._zod.bag;typeof i=="number"&&(o.minItems=i),typeof c=="number"&&(o.maxItems=c),o.type="array",o.items=R(a.element,t,{...r,path:[...r.path,"items"]})},To=(e,t,n,r)=>{const o=e._zod.def,a=o.inclusive===!1,i=o.options.map((c,l)=>R(c,t,{...r,path:[...r.path,a?"oneOf":"anyOf",l]}));a?n.oneOf=i:n.anyOf=i},Po=(e,t,n,r)=>{const o=e._zod.def,a=R(o.left,t,{...r,path:[...r.path,"allOf",0]}),i=R(o.right,t,{...r,path:[...r.path,"allOf",1]}),c=u=>"allOf"in u&&Object.keys(u).length===1,l=[...c(a)?a.allOf:[a],...c(i)?i.allOf:[i]];n.allOf=l},zo=(e,t,n,r)=>{const o=e._zod.def,a=R(o.innerType,t,r),i=t.seen.get(e);t.target==="openapi-3.0"?(i.ref=o.innerType,n.nullable=!0):n.anyOf=[a,{type:"null"}]},$o=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType},Mo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,n.default=JSON.parse(JSON.stringify(o.defaultValue))},Oo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,t.io==="input"&&(n._prefault=JSON.parse(JSON.stringify(o.defaultValue)))},Io=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType;let i;try{i=o.catchValue(void 0)}catch{throw new Error("Dynamic catch values are not supported in JSON Schema")}n.default=i},Ao=(e,t,n,r)=>{const o=e._zod.def,a=t.io==="input"?o.in._zod.def.type==="transform"?o.out:o.in:o.out;R(a,t,r);const i=t.seen.get(e);i.ref=a},Eo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,n.readOnly=!0},Zo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType},Do=m("ZodISODateTime",(e,t)=>{ts.init(e,t),$.init(e,t)});function Ro(e){return Xs(Do,e)}const Fo=m("ZodISODate",(e,t)=>{ns.init(e,t),$.init(e,t)});function Uo(e){return eo(Fo,e)}const Lo=m("ZodISOTime",(e,t)=>{rs.init(e,t),$.init(e,t)});function Ho(e){return to(Lo,e)}const Vo=m("ZodISODuration",(e,t)=>{ss.init(e,t),$.init(e,t)});function Yo(e){return no(Vo,e)}const Wo=(e,t)=>{Yt.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:n=>Jn(e,n)},flatten:{value:n=>qn(e,n)},addIssue:{value:n=>{e.issues.push(n),e.message=JSON.stringify(e.issues,Ue,2)}},addIssues:{value:n=>{e.issues.push(...n),e.message=JSON.stringify(e.issues,Ue,2)}},isEmpty:{get(){return e.issues.length===0}}})},J=m("ZodError",Wo,{Parent:Error}),Bo=Xe(J),qo=et(J),Jo=Te(J),Go=Pe(J),Ko=Qn(J),Qo=Xn(J),Xo=er(J),ea=tr(J),ta=nr(J),na=rr(J),ra=sr(J),sa=or(J),F=m("ZodType",(e,t)=>(Z.init(e,t),Object.assign(e["~standard"],{jsonSchema:{input:ke(e,"input"),output:ke(e,"output")}}),e.toJSONSchema=yo(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{value:t}),e.check=(...n)=>e.clone(Hn(t,{checks:[...t.checks??[],...n.map(r=>typeof r=="function"?{_zod:{check:r,def:{check:"custom"},onattach:[]}}:r)]})),e.clone=(n,r)=>Yn(e,n,r),e.brand=()=>e,e.register=((n,r)=>(n.add(e,r),e)),e.parse=(n,r)=>Bo(e,n,r,{callee:e.parse}),e.safeParse=(n,r)=>Jo(e,n,r),e.parseAsync=async(n,r)=>qo(e,n,r,{callee:e.parseAsync}),e.safeParseAsync=async(n,r)=>Go(e,n,r),e.spa=e.safeParseAsync,e.encode=(n,r)=>Ko(e,n,r),e.decode=(n,r)=>Qo(e,n,r),e.encodeAsync=async(n,r)=>Xo(e,n,r),e.decodeAsync=async(n,r)=>ea(e,n,r),e.safeEncode=(n,r)=>ta(e,n,r),e.safeDecode=(n,r)=>na(e,n,r),e.safeEncodeAsync=async(n,r)=>ra(e,n,r),e.safeDecodeAsync=async(n,r)=>sa(e,n,r),e.refine=(n,r)=>e.check(Wa(n,r)),e.superRefine=n=>e.check(Ba(n)),e.overwrite=n=>e.check(ue(n)),e.optional=()=>Ct(e),e.nullable=()=>Tt(e),e.nullish=()=>Ct(Tt(e)),e.nonoptional=n=>Ra(e,n),e.array=()=>Na(e),e.or=n=>Ca([e,n]),e.and=n=>Pa(e,n),e.transform=n=>Pt(e,$a(n)),e.default=n=>Aa(e,n),e.prefault=n=>Za(e,n),e.catch=n=>Ua(e,n),e.pipe=n=>Pt(e,n),e.readonly=()=>Va(e),e.describe=n=>{const r=e.clone();return he.add(r,{description:n}),r},Object.defineProperty(e,"description",{get(){return he.get(e)?.description},configurable:!0}),e.meta=(...n)=>{if(n.length===0)return he.get(e);const r=e.clone();return he.add(r,n[0]),r},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),an=m("_ZodString",(e,t)=>{tt.init(e,t),F.init(e,t),e._zod.processJSONSchema=(r,o,a)=>jo(e,r,o);const n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...r)=>e.check(oo(...r)),e.includes=(...r)=>e.check(co(...r)),e.startsWith=(...r)=>e.check(lo(...r)),e.endsWith=(...r)=>e.check(uo(...r)),e.min=(...r)=>e.check(Ne(...r)),e.max=(...r)=>e.check(tn(...r)),e.length=(...r)=>e.check(nn(...r)),e.nonempty=(...r)=>e.check(Ne(1,...r)),e.lowercase=r=>e.check(ao(r)),e.uppercase=r=>e.check(io(r)),e.trim=()=>e.check(ho()),e.normalize=(...r)=>e.check(mo(...r)),e.toLowerCase=()=>e.check(po()),e.toUpperCase=()=>e.check(fo()),e.slugify=()=>e.check(go())}),oa=m("ZodString",(e,t)=>{tt.init(e,t),an.init(e,t),e.email=n=>e.check(Ms(aa,n)),e.url=n=>e.check(Zs(ia,n)),e.jwt=n=>e.check(Qs(wa,n)),e.emoji=n=>e.check(Ds(ca,n)),e.guid=n=>e.check(yt(Nt,n)),e.uuid=n=>e.check(Os(ye,n)),e.uuidv4=n=>e.check(Is(ye,n)),e.uuidv6=n=>e.check(As(ye,n)),e.uuidv7=n=>e.check(Es(ye,n)),e.nanoid=n=>e.check(Rs(la,n)),e.guid=n=>e.check(yt(Nt,n)),e.cuid=n=>e.check(Fs(ua,n)),e.cuid2=n=>e.check(Us(da,n)),e.ulid=n=>e.check(Ls(ma,n)),e.base64=n=>e.check(Js(va,n)),e.base64url=n=>e.check(Gs(_a,n)),e.xid=n=>e.check(Hs(ha,n)),e.ksuid=n=>e.check(Vs(pa,n)),e.ipv4=n=>e.check(Ys(fa,n)),e.ipv6=n=>e.check(Ws(ga,n)),e.cidrv4=n=>e.check(Bs(ba,n)),e.cidrv6=n=>e.check(qs(xa,n)),e.e164=n=>e.check(Ks(ya,n)),e.datetime=n=>e.check(Ro(n)),e.date=n=>e.check(Uo(n)),e.time=n=>e.check(Ho(n)),e.duration=n=>e.check(Yo(n))});function ge(e){return $s(oa,e)}const $=m("ZodStringFormat",(e,t)=>{z.init(e,t),an.init(e,t)}),aa=m("ZodEmail",(e,t)=>{Wr.init(e,t),$.init(e,t)}),Nt=m("ZodGUID",(e,t)=>{Vr.init(e,t),$.init(e,t)}),ye=m("ZodUUID",(e,t)=>{Yr.init(e,t),$.init(e,t)}),ia=m("ZodURL",(e,t)=>{Br.init(e,t),$.init(e,t)}),ca=m("ZodEmoji",(e,t)=>{qr.init(e,t),$.init(e,t)}),la=m("ZodNanoID",(e,t)=>{Jr.init(e,t),$.init(e,t)}),ua=m("ZodCUID",(e,t)=>{Gr.init(e,t),$.init(e,t)}),da=m("ZodCUID2",(e,t)=>{Kr.init(e,t),$.init(e,t)}),ma=m("ZodULID",(e,t)=>{Qr.init(e,t),$.init(e,t)}),ha=m("ZodXID",(e,t)=>{Xr.init(e,t),$.init(e,t)}),pa=m("ZodKSUID",(e,t)=>{es.init(e,t),$.init(e,t)}),fa=m("ZodIPv4",(e,t)=>{os.init(e,t),$.init(e,t)}),ga=m("ZodIPv6",(e,t)=>{as.init(e,t),$.init(e,t)}),ba=m("ZodCIDRv4",(e,t)=>{is.init(e,t),$.init(e,t)}),xa=m("ZodCIDRv6",(e,t)=>{cs.init(e,t),$.init(e,t)}),va=m("ZodBase64",(e,t)=>{ls.init(e,t),$.init(e,t)}),_a=m("ZodBase64URL",(e,t)=>{ds.init(e,t),$.init(e,t)}),ya=m("ZodE164",(e,t)=>{ms.init(e,t),$.init(e,t)}),wa=m("ZodJWT",(e,t)=>{ps.init(e,t),$.init(e,t)}),cn=m("ZodNumber",(e,t)=>{en.init(e,t),F.init(e,t),e._zod.processJSONSchema=(r,o,a)=>So(e,r,o),e.gt=(r,o)=>e.check(jt(r,o)),e.gte=(r,o)=>e.check(Ee(r,o)),e.min=(r,o)=>e.check(Ee(r,o)),e.lt=(r,o)=>e.check(wt(r,o)),e.lte=(r,o)=>e.check(Ae(r,o)),e.max=(r,o)=>e.check(Ae(r,o)),e.int=r=>e.check(kt(r)),e.safe=r=>e.check(kt(r)),e.positive=r=>e.check(jt(0,r)),e.nonnegative=r=>e.check(Ee(0,r)),e.negative=r=>e.check(wt(0,r)),e.nonpositive=r=>e.check(Ae(0,r)),e.multipleOf=(r,o)=>e.check(St(r,o)),e.step=(r,o)=>e.check(St(r,o)),e.finite=()=>e;const n=e._zod.bag;e.minValue=Math.max(n.minimum??Number.NEGATIVE_INFINITY,n.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null,e.maxValue=Math.min(n.maximum??Number.POSITIVE_INFINITY,n.exclusiveMaximum??Number.POSITIVE_INFINITY)??null,e.isInt=(n.format??"").includes("int")||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null}),ja=m("ZodNumberFormat",(e,t)=>{fs.init(e,t),cn.init(e,t)});function kt(e){return so(ja,e)}const Sa=m("ZodArray",(e,t)=>{gs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Co(e,n,r,o),e.element=t.element,e.min=(n,r)=>e.check(Ne(n,r)),e.nonempty=n=>e.check(Ne(1,n)),e.max=(n,r)=>e.check(tn(n,r)),e.length=(n,r)=>e.check(nn(n,r)),e.unwrap=()=>e.element});function Na(e,t){return bo(Sa,e,t)}const ka=m("ZodUnion",(e,t)=>{bs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>To(e,n,r,o),e.options=t.options});function Ca(e,t){return new ka({type:"union",options:e,..._(t)})}const Ta=m("ZodIntersection",(e,t)=>{xs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Po(e,n,r,o)});function Pa(e,t){return new Ta({type:"intersection",left:e,right:t})}const za=m("ZodTransform",(e,t)=>{vs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>ko(e,n),e._zod.parse=(n,r)=>{if(r.direction==="backward")throw new Ut(e.constructor.name);n.addIssue=a=>{if(typeof a=="string")n.issues.push(pe(a,n.value,t));else{const i=a;i.fatal&&(i.continue=!1),i.code??(i.code="custom"),i.input??(i.input=n.value),i.inst??(i.inst=e),n.issues.push(pe(i))}};const o=t.transform(n.value,n);return o instanceof Promise?o.then(a=>(n.value=a,n)):(n.value=o,n)}});function $a(e){return new za({type:"transform",transform:e})}const Ma=m("ZodOptional",(e,t)=>{_s.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Zo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Ma({type:"optional",innerType:e})}const Oa=m("ZodNullable",(e,t)=>{ys.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>zo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Tt(e){return new Oa({type:"nullable",innerType:e})}const Ia=m("ZodDefault",(e,t)=>{ws.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Mo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Aa(e,t){return new Ia({type:"default",innerType:e,get defaultValue(){return typeof t=="function"?t():Ht(t)}})}const Ea=m("ZodPrefault",(e,t)=>{js.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Oo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Za(e,t){return new Ea({type:"prefault",innerType:e,get defaultValue(){return typeof t=="function"?t():Ht(t)}})}const Da=m("ZodNonOptional",(e,t)=>{Ss.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>$o(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Ra(e,t){return new Da({type:"nonoptional",innerType:e,..._(t)})}const Fa=m("ZodCatch",(e,t)=>{Ns.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Io(e,n,r,o),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Ua(e,t){return new Fa({type:"catch",innerType:e,catchValue:typeof t=="function"?t:()=>t})}const La=m("ZodPipe",(e,t)=>{ks.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Ao(e,n,r,o),e.in=t.in,e.out=t.out});function Pt(e,t){return new La({type:"pipe",in:e,out:t})}const Ha=m("ZodReadonly",(e,t)=>{Cs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Eo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Va(e){return new Ha({type:"readonly",innerType:e})}const Ya=m("ZodCustom",(e,t)=>{Ts.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>No(e,n)});function Wa(e,t={}){return xo(Ya,e,t)}function Ba(e){return vo(e)}function Q(e){return ro(cn,e)}const qa=/^[A-Za-z0-9\-_+ ]*$/,Ja=/^([A-Za-z0-9 ()/._-]|%\d*[CYmdHMSqvQtwhf$]|%\d*\{[a-z_]+\})*$/,Ga=/^[A-Za-z0-9 ()/._-]*$/,Ka=/^[A-Za-z0-9 _+.@^~<>,-]*$/;function nt(e){return e.includes("..")||e.includes("~/")}const zt=ge().max(64,"Name must be 64 characters or less").regex(qa,"Name can only contain letters, numbers, hyphens, underscores, plus signs, and spaces"),Ze=ge().max(255,"Filename must be 255 characters or less").regex(Ja,"Filename contains invalid characters. Use letters, numbers, underscores, hyphens, strftime codes (%Y, %m, %d), and Motion tokens (%{movienbr}, %v, etc.)").refine(e=>!nt(e),{message:"Filename cannot contain directory traversal sequences (.. or ~/)"}),$t=ge().max(4096,"Path must be 4096 characters or less").regex(Ga,"Path contains invalid characters").refine(e=>!nt(e),{message:"Path cannot contain directory traversal sequences (.. or ~/)"});ge().max(255,"Email must be 255 characters or less").regex(Ka,"Email contains invalid characters");const Mt=Q().int("Framerate must be a whole number").min(1,"Framerate must be at least 1").max(100,"Framerate cannot exceed 100"),Ot=Q().int("Quality must be a whole number").min(1,"Quality must be at least 1%").max(100,"Quality cannot exceed 100%"),Qa=Q().int("Width must be a whole number").min(160,"Width must be at least 160 pixels").max(4096,"Width cannot exceed 4096 pixels"),Xa=Q().int("Height must be a whole number").min(120,"Height must be at least 120 pixels").max(2160,"Height cannot exceed 2160 pixels"),It=Q().int("Port must be a whole number").min(1,"Port must be at least 1").max(65535,"Port cannot exceed 65535"),ei=Q().int("Threshold must be a whole number").min(1,"Threshold must be at least 1").max(2147483647,"Threshold is too large"),ti=Q().int("Noise level must be a whole number").min(0,"Noise level must be at least 0").max(255,"Noise level cannot exceed 255"),ni=Q().int("Log level must be a whole number").min(1,"Log level must be at least 1").max(9,"Log level cannot exceed 9"),ri=Q().int("Device ID must be a whole number").min(1,"Device ID must be at least 1").max(999,"Device ID cannot exceed 999"),si=Q().int("Must be a whole number").min(0,"Must be 0 or greater");ge().transform(e=>{const t=e.toLowerCase();return t==="on"||t==="true"||t==="1"});function oi(e,t){const r={device_name:zt,camera_name:zt,device_id:ri,target_dir:$t,snapshot_filename:Ze,picture_filename:Ze,movie_filename:Ze,log_file:$t,framerate:Mt,stream_maxrate:Mt,width:Qa,height:Xa,stream_quality:Ot,picture_quality:Ot,stream_port:It,webcontrol_port:It,threshold:ei,noise_level:ti,minimum_motion_frames:si,log_level:ni}[e];if(!r)return typeof t=="string"&&nt(t)?{success:!1,error:"Value cannot contain directory traversal sequences (.. or ~/)"}:{success:!0};const o=r.safeParse(t);return o.success?{success:!0}:{success:!1,error:o.error.issues[0]?.message??"Invalid value"}}async function ai(){return await Ce("/0/api/system/reboot",{})}async function ii(){return await Ce("/0/api/system/shutdown",{})}async function ci(){return await Ce("/0/api/system/service-restart",{})}function li({config:e,onChange:t,getError:n,originalConfig:r,systemStatus:o}){const{addToast:a}=Be(),i=o?.actions?.service??!1,c=o?.actions?.power??!1,l=(b,w="")=>e[b]?.value??w,u=(b,w="")=>r?.[b]?.value??w,d=b=>e[b]?.password_set===!0,h=b=>r?.[b]?.password_set===!0,f=async()=>{if(window.confirm("Are you sure you want to reboot the Pi? The system will restart and be unavailable for about a minute."))try{await ai(),a("Rebooting... The system will be back online shortly.","info")}catch(b){a(b.message||"Failed to reboot. Power control may be disabled in config.","error")}},p=async()=>{if(window.confirm("Are you sure you want to shutdown the Pi? You will need to physically power it back on."))try{await ii(),a("Shutting down... The system will power off.","warning")}catch(b){a(b.message||"Failed to shutdown. Power control may be disabled in config.","error")}},v=async()=>{if(window.confirm("Are you sure you want to restart the Motion service? Active streams will be interrupted briefly."))try{await ci(),a("Restarting Motion... Streams will resume shortly.","info")}catch(b){a(b.message||"Failed to restart service. Service control may be disabled in config.","error")}};return s.jsxs(s.Fragment,{children:[s.jsx(E,{title:"Device Controls",description:"Service and system power management",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"flex flex-col gap-4",children:[s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"Service Control"}),s.jsx("button",{onClick:v,disabled:!i,className:`px-4 py-2 rounded-lg text-sm transition-colors ${i?"bg-blue-600/20 text-blue-300 hover:bg-blue-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:i?void 0:"Enable with webcontrol_actions service=on",children:"Restart Motion"}),!i&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions service=on"})," to enable"]})]}),s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"System Power"}),s.jsxs("div",{className:"flex gap-3",children:[s.jsx("button",{onClick:f,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-yellow-600/20 text-yellow-300 hover:bg-yellow-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Restart Pi"}),s.jsx("button",{onClick:p,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-red-600/20 text-red-300 hover:bg-red-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Shutdown Pi"})]}),!c&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions power=on"})," to enable"]})]})]})}),s.jsxs(E,{title:"Authentication",description:"Web interface and stream access credentials",collapsible:!0,defaultOpen:!0,children:[s.jsxs("div",{className:"mb-6",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Web Interface"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Credentials for logging into this web interface. Format: username:password"}),u("webcontrol_authentication","")===""&&u("webcontrol_user_authentication","")===""&&s.jsx("div",{className:"mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg",children:s.jsxs("div",{className:"flex items-start gap-2",children:[s.jsx("svg",{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})}),s.jsxs("div",{className:"flex-1",children:[s.jsx("p",{className:"text-sm font-medium text-blue-300 mb-1",children:"Initial Setup Available"}),s.jsxs("p",{className:"text-xs text-blue-300/80",children:["Configure authentication now to secure your Motion installation. During initial setup, you can set credentials without changing"," ",s.jsx("code",{className:"text-xs bg-surface px-1 rounded",children:"webcontrol_parms"})," in the config file. Once authentication is configured, it will require restart to apply."]})]})]})}),s.jsxs("div",{className:"mb-4",children:[s.jsx("label",{className:"block text-sm font-medium mb-1 text-gray-300",children:"Admin Username"}),s.jsx("input",{type:"text",value:"admin",disabled:!0,className:`w-full px-3 py-2 bg-surface-elevated border border-gray-700 rounded-lg + text-gray-500 cursor-not-allowed`}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Admin username is fixed for security"})]}),s.jsx(S,{label:"Admin Password",value:String(l("webcontrol_authentication","")).split(":")[1]||"",onChange:b=>{const w=String(l("webcontrol_authentication","")).split(":")[0]||"admin";t("webcontrol_authentication",`${w}:${b}`)},type:"password",placeholder:d("webcontrol_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_authentication")?"Password is configured. Enter a new password to change it.":"Administrator password (click eye icon to reveal)",originalValue:h("webcontrol_authentication")?"[set]":""}),s.jsx(S,{label:"Viewer Username",value:String(l("webcontrol_user_authentication","")).split(":")[0]||"",onChange:b=>{const w=String(l("webcontrol_user_authentication","")).split(":")[1]||"";t("webcontrol_user_authentication",b?`${b}:${w}`:"")},helpText:"View-only username (can view streams but not change settings)",error:n?.("webcontrol_user_authentication"),originalValue:String(u("webcontrol_user_authentication","")).split(":")[0]||"",showVisibilityToggle:!1}),s.jsx(S,{label:"Viewer Password",value:String(l("webcontrol_user_authentication","")).split(":")[1]||"",onChange:b=>{const w=String(l("webcontrol_user_authentication","")).split(":")[0]||"";t("webcontrol_user_authentication",w?`${w}:${b}`:"")},type:"password",placeholder:d("webcontrol_user_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_user_authentication")?"Password is configured. Enter a new password to change it.":"View-only password (click eye icon to reveal)",originalValue:h("webcontrol_user_authentication")?"[set]":""})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Direct Stream Access"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Authentication for direct stream URLs (embedded in websites, VLC, home automation)"}),s.jsx(A,{label:"Authentication Mode",value:String(l("webcontrol_auth_method","0")),onChange:b=>t("webcontrol_auth_method",b),options:[{value:"0",label:"None - No authentication required"},{value:"1",label:"Basic - Simple username/password (use with HTTPS)"},{value:"2",label:"Digest - Secure hash-based (recommended)"}],helpText:"Controls authentication for direct stream access and external API clients. The web UI uses session-based login instead.",error:n?.("webcontrol_auth_method")})]})]}),s.jsxs(E,{title:"Daemon",description:"Motion process settings",collapsible:!0,defaultOpen:!1,children:[s.jsx(B,{label:"Run as Daemon",value:l("daemon",!1),onChange:b=>t("daemon",b),helpText:"Run Motion in background mode"}),s.jsx(S,{label:"PID File",value:String(l("pid_file","")),onChange:b=>t("pid_file",b),helpText:"Path to process ID file. Leave empty to let systemd manage the PID.",error:n?.("pid_file"),originalValue:String(u("pid_file",""))}),s.jsx(S,{label:"Log File",value:String(l("log_file","")),onChange:b=>t("log_file",b),helpText:"Path to log file. Leave empty to use journald (view with: journalctl -u motion).",error:n?.("log_file"),originalValue:String(u("log_file",""))}),s.jsx(A,{label:"Log Level",value:String(l("log_level","6")),onChange:b=>t("log_level",b),options:[{value:"1",label:"Emergency"},{value:"2",label:"Alert"},{value:"3",label:"Critical"},{value:"4",label:"Error"},{value:"5",label:"Warning"},{value:"6",label:"Notice"},{value:"7",label:"Info"},{value:"8",label:"Debug"},{value:"9",label:"All"}],helpText:"Verbosity level for logging",error:n?.("log_level")})]}),s.jsxs(E,{title:"Web Server",description:"API server configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(S,{label:"Port",value:String(l("webcontrol_port","8080")),onChange:b=>t("webcontrol_port",b),type:"number",helpText:"Primary web server port",error:n?.("webcontrol_port"),originalValue:String(u("webcontrol_port","8080"))}),s.jsx(B,{label:"Localhost Only",value:l("webcontrol_localhost",!1),onChange:b=>t("webcontrol_localhost",b),helpText:"Restrict access to localhost only (127.0.0.1)"}),s.jsx(B,{label:"TLS/HTTPS",value:l("webcontrol_tls",!1),onChange:b=>t("webcontrol_tls",b),helpText:"Enable HTTPS encryption"}),l("webcontrol_tls",!1)&&s.jsxs(s.Fragment,{children:[s.jsx(S,{label:"TLS Certificate",value:String(l("webcontrol_cert","")),onChange:b=>t("webcontrol_cert",b),helpText:"Path to TLS certificate file (.crt or .pem)",error:n?.("webcontrol_cert")}),s.jsx(S,{label:"TLS Private Key",value:String(l("webcontrol_key","")),onChange:b=>t("webcontrol_key",b),helpText:"Path to TLS private key file (.key or .pem)",error:n?.("webcontrol_key")})]})]})]})}function ui({config:e,onChange:t,getError:n}){const r=(h,f="")=>e[h]?.value??f,o=Number(r("width",640)),a=Number(r("height",480)),i=ot(o,a),c=st.some(h=>h.width===o&&h.height===a),l=h=>{if(h==="custom")return;const{width:f,height:p}=jn(h);t("width",f),t("height",p)},u=h=>{t("width",Number(h))},d=h=>{t("height",Number(h))};return s.jsxs(E,{title:"Device Settings",description:"Basic camera configuration and identification",collapsible:!0,defaultOpen:!1,children:[s.jsx(S,{label:"Camera Name",value:String(r("device_name","")),onChange:h=>t("device_name",h),placeholder:"My Camera",helpText:"Friendly name for this camera",error:n?.("device_name")}),s.jsx(A,{label:"Resolution",value:c?i:"custom",onChange:l,options:[...st.map(h=>({value:ot(h.width,h.height),label:h.label})),{value:"custom",label:"Custom"}],helpText:"Video resolution (width x height)"}),!c&&s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(S,{label:"Width",value:String(o),onChange:u,type:"number",helpText:"Custom width in pixels",error:n?.("width")}),s.jsx(S,{label:"Height",value:String(a),onChange:d,type:"number",helpText:"Custom height in pixels",error:n?.("height")})]}),s.jsx(T,{label:"Framerate",value:Number(r("framerate",15)),onChange:h=>t("framerate",h),min:2,max:30,unit:" fps",helpText:"Frames per second (higher uses more CPU)",error:n?.("framerate")}),s.jsx(A,{label:"Rotation",value:String(r("rotate",0)),onChange:h=>t("rotate",Number(h)),options:wn.map(h=>({value:String(h.value),label:h.label})),helpText:"Rotate camera image"})]})}function di({config:e,onChange:t,getError:n,capabilities:r,originalConfig:o}){const a=(l,u="")=>e[l]?.value??u,i=(l,u="")=>o?.[l]?.value??u,c=!!a("libcam_awb_enable",!1);return s.jsxs(E,{title:"libcamera Controls",description:"Raspberry Pi camera controls (libcamera only)",collapsible:!0,defaultOpen:!1,children:[s.jsx(T,{label:"Brightness",value:Number(a("libcam_brightness",0)),onChange:l=>t("libcam_brightness",l),min:-1,max:1,step:.1,helpText:"Brightness adjustment (-1.0 to 1.0)",error:n?.("libcam_brightness")}),s.jsx(T,{label:"Contrast",value:Number(a("libcam_contrast",1)),onChange:l=>t("libcam_contrast",l),min:0,max:32,step:.5,helpText:"Contrast adjustment (0.0 to 32.0)",error:n?.("libcam_contrast")}),s.jsx(T,{label:"Gain (ISO)",value:Number(a("libcam_gain",1)),onChange:l=>t("libcam_gain",l),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)",error:n?.("libcam_gain")}),s.jsx(B,{label:"Auto White Balance",value:c,onChange:l=>t("libcam_awb_enable",l),helpText:"Enable automatic white balance"}),c&&s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"AWB Mode",value:String(a("libcam_awb_mode",0)),onChange:l=>t("libcam_awb_mode",Number(l)),options:Sn.map(l=>({value:String(l.value),label:l.label})),helpText:"White balance mode"}),r?.AwbLocked!==!1&&s.jsx(B,{label:"Lock AWB",value:!!a("libcam_awb_locked",!1),onChange:l=>t("libcam_awb_locked",l),helpText:"Lock white balance settings"})]}),!c&&s.jsxs(s.Fragment,{children:[r?.ColourTemperature!==!1&&s.jsx(T,{label:"Color Temperature",value:Number(a("libcam_colour_temp",0)),onChange:l=>t("libcam_colour_temp",l),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)",error:n?.("libcam_colour_temp")}),s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(T,{label:"Red Gain",value:Number(a("libcam_colour_gain_r",1)),onChange:l=>t("libcam_colour_gain_r",l),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)",error:n?.("libcam_colour_gain_r")}),s.jsx(T,{label:"Blue Gain",value:Number(a("libcam_colour_gain_b",1)),onChange:l=>t("libcam_colour_gain_b",l),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)",error:n?.("libcam_colour_gain_b")})]}),r?.ColourTemperature===!1&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),r?.AfMode?s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"Autofocus Mode",value:String(a("libcam_af_mode",0)),onChange:l=>t("libcam_af_mode",Number(l)),options:Nn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus control mode"}),Number(a("libcam_af_mode",0))===0&&r?.LensPosition&&s.jsx(T,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),Number(a("libcam_af_mode",0))>0&&s.jsxs(s.Fragment,{children:[r?.AfRange&&s.jsx(A,{label:"Autofocus Range",value:String(a("libcam_af_range",0)),onChange:l=>t("libcam_af_range",Number(l)),options:kn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus range preference"}),r?.AfSpeed&&s.jsx(A,{label:"Autofocus Speed",value:String(a("libcam_af_speed",0)),onChange:l=>t("libcam_af_speed",Number(l)),options:Cn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus adjustment speed"})]})]}):r!==void 0?s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Autofocus:"})," Not supported on this camera.",r?.LensPosition?s.jsx("span",{children:" Manual focus (lens position) is available."}):s.jsx("span",{children:" This camera has fixed focus."})]}):null,!r?.AfMode&&r?.LensPosition&&s.jsx(T,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),s.jsx(S,{label:"Buffer Count",value:String(a("libcam_buffer_count",4)),onChange:l=>t("libcam_buffer_count",Number(l)),type:"number",helpText:"Frame buffers for capture (2-8). Higher values reduce frame drops under load but use more memory. Default 4 works for most setups; increase to 6-8 if seeing drops at high framerates.",error:n?.("libcam_buffer_count"),originalValue:String(i("libcam_buffer_count",4))})]})}function mi({config:e,onChange:t,getError:n}){const r=(g,k="")=>e[g]?.value??k,o=String(r("text_left","")),a=String(r("text_right","")),i=at(o),c=at(a),[l,u]=x.useState(i==="custom"?o:""),[d,h]=x.useState(c==="custom"?a:""),f=g=>{g==="custom"?t("text_left",l):t("text_left",it(g))},p=g=>{g==="custom"?t("text_right",d):t("text_right",it(g))},v=g=>{u(g),t("text_left",g)},b=g=>{h(g),t("text_right",g)},w=[{value:"disabled",label:"Disabled"},{value:"camera-name",label:"Camera Name"},{value:"timestamp",label:"Timestamp"},{value:"custom",label:"Custom Text"}];return s.jsxs(E,{title:"Text Overlay",description:"Add text overlays to video frames",collapsible:!0,defaultOpen:!1,children:[s.jsx(A,{label:"Left Text",value:i,onChange:f,options:w,helpText:"Text displayed in top-left corner"}),i==="custom"&&s.jsx(S,{label:"Custom Left Text",value:l,onChange:v,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_left")}),s.jsx(A,{label:"Right Text",value:c,onChange:p,options:w,helpText:"Text displayed in top-right corner"}),c==="custom"&&s.jsx(S,{label:"Custom Right Text",value:d,onChange:b,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_right")}),s.jsx(T,{label:"Text Scale",value:Number(r("text_scale",1)),onChange:g=>t("text_scale",g),min:1,max:10,unit:"x",helpText:"Text size multiplier (1-10)",error:n?.("text_scale")})]})}const hi=[{value:"100",label:"Full (100%)"},{value:"75",label:"High (75%)"},{value:"50",label:"Medium (50%)"},{value:"25",label:"Low (25%)"},{value:"10",label:"Minimal (10%)"}];function pi({config:e,onChange:t,getError:n}){const r=(a,i="")=>e[a]?.value??i,o=!r("stream_localhost",!1);return s.jsxs(E,{title:"Video Streaming",description:"Live MJPEG stream configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(B,{label:"Enable Video Streaming",value:o,onChange:a=>t("stream_localhost",!a),helpText:"Enable/disable live MJPEG streaming. When disabled, stream is only accessible from localhost."}),o&&s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"Streaming Resolution",value:String(r("stream_preview_scale",100)),onChange:a=>t("stream_preview_scale",Number(a)),options:hi,helpText:"Scale stream as percentage of source resolution. Lower = less bandwidth and CPU."}),s.jsx(T,{label:"Stream Quality",value:Number(r("stream_quality",50)),onChange:a=>t("stream_quality",a),min:1,max:100,unit:"%",helpText:"JPEG compression quality (1-100). Higher = better quality, more bandwidth.",error:n?.("stream_quality")}),s.jsx(T,{label:"Stream Max Framerate",value:Number(r("stream_maxrate",15)),onChange:a=>t("stream_maxrate",a),min:1,max:30,unit:" fps",helpText:"Maximum frames per second (lower = less bandwidth and CPU)",error:n?.("stream_maxrate")}),s.jsx(B,{label:"Show Motion Boxes",value:!!r("stream_motion",!1),onChange:a=>t("stream_motion",a),helpText:"Display motion detection boxes in stream"}),s.jsx(A,{label:"Direct Stream Access Security",value:String(r("webcontrol_auth_method",0)),onChange:a=>t("webcontrol_auth_method",Number(a)),options:Tn.map(a=>({value:String(a.value),label:a.label})),helpText:"Authentication when streams are accessed directly (embedded in other websites, VLC, home automation). None = open access on trusted networks only. Basic = use with HTTPS. Digest = recommended."}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Stream URL:"})," ",s.jsx("code",{children:"http://[hostname]:[port]/[cam]/mjpg/stream"})]}),s.jsxs("p",{className:"mt-1",children:[s.jsx("strong",{children:"Note:"})," Streaming resolution scales the output to reduce bandwidth and CPU usage. Server-side resizing is always performed by Motion."]})]})]})]})}function fi({config:e,onChange:t,getError:n}){const r=(d,h="")=>e[d]?.value??h,o=Number(r("width",640)),a=Number(r("height",480)),i=Number(r("threshold",1500)),c=Pn(i,o,a),l=d=>{const h=Number(d),f=$n(h,o,a);t("threshold",f)},u=[{value:"",label:"Off"},{value:"EedDl",label:"Light"},{value:"EedDl",label:"Medium (default)"},{value:"EedDl",label:"Heavy"}];return s.jsx(E,{title:"Motion Detection",description:"Configure motion detection sensitivity and behavior",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(T,{label:"Threshold",value:c,onChange:d=>l(String(d)),min:0,max:20,step:.1,unit:"%",helpText:`Percentage of frame that must change (${i} pixels at ${o}x${a}). Higher = less sensitive.`,error:n?.("threshold")}),s.jsx(S,{label:"Threshold Maximum",value:String(r("threshold_maximum",0)),onChange:d=>t("threshold_maximum",Number(d)),type:"number",helpText:"Maximum threshold for auto-tuning (0 = disabled)",error:n?.("threshold_maximum")}),s.jsx(B,{label:"Auto-tune Threshold",value:r("threshold_tune",!1),onChange:d=>t("threshold_tune",d),helpText:"Automatically adjust threshold based on noise levels"}),s.jsx(B,{label:"Auto-tune Noise Level",value:r("noise_tune",!1),onChange:d=>t("noise_tune",d),helpText:"Automatically determine optimal noise level"}),s.jsx(T,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:d=>t("noise_level",d),min:1,max:255,helpText:"Noise tolerance (1-255). Lower values detect smaller motions.",error:n?.("noise_level")}),s.jsx(T,{label:"Light Switch Detection",value:Number(r("lightswitch_percent",0)),onChange:d=>t("lightswitch_percent",d),min:0,max:100,unit:"%",helpText:"Ignore sudden brightness changes (0 = disabled). Prevents false triggers from lights turning on/off.",error:n?.("lightswitch_percent")}),s.jsx(A,{label:"Despeckle Filter",value:String(r("despeckle_filter","")),onChange:d=>t("despeckle_filter",d),options:u,helpText:"Remove noise speckles from motion detection"}),s.jsx(T,{label:"Smart Mask Speed",value:Number(r("smart_mask_speed",0)),onChange:d=>t("smart_mask_speed",d),min:0,max:10,helpText:"Auto-mask static areas (0 = disabled, 1-10 = speed). Higher values adapt faster to static objects.",error:n?.("smart_mask_speed")}),s.jsx(A,{label:"Locate Motion Mode",value:String(r("locate_motion_mode","off")),onChange:d=>t("locate_motion_mode",d),options:zn,helpText:"Draw box around motion area. 'Preview' = stream only, 'On' = saved images, 'Both' = both."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Event Timing"}),s.jsx(S,{label:"Event Gap (seconds)",value:String(r("event_gap",60)),onChange:d=>t("event_gap",Number(d)),type:"number",min:"0",helpText:"Seconds of no motion before ending an event. Prevents splitting continuous motion into multiple events.",error:n?.("event_gap")}),s.jsx(S,{label:"Pre-Capture (frames)",value:String(r("pre_capture",0)),onChange:d=>t("pre_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture before motion detected. Uses CPU/memory to buffer frames.",error:n?.("pre_capture")}),s.jsx(S,{label:"Post-Capture (frames)",value:String(r("post_capture",0)),onChange:d=>t("post_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture after motion stops",error:n?.("post_capture")}),s.jsx(S,{label:"Minimum Motion Frames",value:String(r("minimum_motion_frames",1)),onChange:d=>t("minimum_motion_frames",Number(d)),type:"number",min:"1",helpText:"Consecutive frames with motion required to trigger event. Filters brief false positives.",error:n?.("minimum_motion_frames")})]})]})})}function gi({config:e,onChange:t,getError:n}){const r=(f,p="")=>e[f]?.value??p,o=String(r("picture_output","off")),a=Number(r("snapshot_interval",0)),i=Mn(o,a),[c,l]=x.useState(i),u=f=>{l(f);const p=On(f);t("picture_output",p.picture_output),p.snapshot_interval!==void 0&&t("snapshot_interval",p.snapshot_interval)},d=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered (all frames)"},{value:"motion-triggered-one",label:"Motion Triggered (first frame only)"},{value:"best",label:"Best Quality Frame"},{value:"center",label:"Center Frame"},{value:"interval-snapshots",label:"Interval Snapshots"},{value:"manual",label:"Manual Only"}],h=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(E,{title:"Picture Settings",description:"Configure picture capture and snapshots",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(A,{label:"Capture Mode",value:c,onChange:u,options:d,helpText:"When to capture still images during motion events"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Current settings:"})," picture_output=",o,a>0&&`, snapshot_interval=${a}s`]}),c==="motion-triggered"&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"text-xs text-yellow-300 bg-yellow-900/30 border border-yellow-700 p-3 rounded",children:[s.jsx("strong",{children:"Warning:"})," This mode captures every frame during motion. At 15fps, continuous motion can generate 900+ pictures per minute. Configure limits below to prevent runaway capture."]}),s.jsx(S,{label:"Max Pictures Per Event",value:String(r("picture_max_per_event",0)),onChange:f=>t("picture_max_per_event",Number(f)),type:"number",min:"0",max:"100000",helpText:"Maximum pictures per motion event (0 = unlimited)",error:n?.("picture_max_per_event")}),s.jsx(S,{label:"Min Interval Between Pictures (ms)",value:String(r("picture_min_interval",0)),onChange:f=>t("picture_min_interval",Number(f)),type:"number",min:"0",max:"60000",helpText:"Minimum milliseconds between captures (0 = no limit). 1000ms = 1 picture/second.",error:n?.("picture_min_interval")})]}),c==="interval-snapshots"&&s.jsx(S,{label:"Snapshot Interval (seconds)",value:String(r("snapshot_interval",60)),onChange:f=>t("snapshot_interval",Number(f)),type:"number",min:"1",helpText:"Seconds between snapshots (independent of motion)",error:n?.("snapshot_interval")}),s.jsx(T,{label:"Picture Quality",value:Number(r("picture_quality",75)),onChange:f=>t("picture_quality",f),min:1,max:100,unit:"%",helpText:"JPEG quality (1-100). Higher = better quality, larger files.",error:n?.("picture_quality")}),s.jsx(S,{label:"Picture Filename Pattern",value:String(r("picture_filename","%Y%m%d%H%M%S-%q")),onChange:f=>t("picture_filename",f),helpText:`Format codes: ${h}`,error:n?.("picture_filename")}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S-%q"})," → ",s.jsx("code",{children:"2025-01-29/143022-05.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/143022.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/143022.jpg"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S-%q"})," → ",s.jsx("code",{children:"20250129143022-05.jpg"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",h]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]})]})})}function bi(){return qe({queryKey:["deviceInfo"],queryFn:async()=>{const e=await fetch("/0/api/system/status");if(!e.ok)throw new Error("Failed to fetch device info");return e.json()},staleTime:6e4,retry:1})}function At(e){return e?.pi_generation===5}function xi(e){return e?.pi_generation===4}function de(e){return e?.hardware_encoders?.h264_v4l2m2m===!0}function vi(e,t=70){return(e?.temperature?.celsius??0)>t}function _i({config:e,onChange:t,getError:n}){const{data:r}=bi(),o=(g,k="")=>e[g]?.value??k,a=o("movie_output",!1),i=o("movie_output_motion",!1),c=o("emulate_motion",!1),l=In(a,i,c),[u,d]=x.useState(l),h=g=>{d(g);const k=En(g);t("movie_output",k.movie_output),k.movie_output_motion!==void 0&&t("movie_output_motion",k.movie_output_motion),k.emulate_motion!==void 0&&t("emulate_motion",k.emulate_motion)},f=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered"},{value:"continuous",label:"Continuous Recording"}],p=["%Y - Year","%m - Month","%d - Day","%H - Hour","%M - Minute","%S - Second","%v - Event number","%$ - Camera name"].join(", "),v=String(o("movie_container","mp4")),b=()=>!r||de(r)?ct:ct.filter(g=>!te(g.value)),w=()=>!(o("movie_passthrough",!1)||te(v)||v==="webm");return s.jsx(E,{title:"Movie Settings",description:"Configure video recording settings",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(A,{label:"Recording Mode",value:u,onChange:h,options:f,helpText:"When to record video. Motion Triggered = only during events, Continuous = always record."}),s.jsx(T,{label:"Movie Quality",value:Number(o("movie_quality",75)),onChange:g=>t("movie_quality",g),min:1,max:100,unit:"%",helpText:"Video encoding quality (1-100). Higher = better quality, larger files, more CPU.",error:n?.("movie_quality")}),s.jsx(S,{label:"Movie Filename Pattern",value:String(o("movie_filename","%Y%m%d%H%M%S")),onChange:g=>t("movie_filename",g),helpText:`Format codes: ${p}`,error:n?.("movie_filename")}),s.jsx(A,{label:"Container Format",value:String(o("movie_container","mp4")),onChange:g=>t("movie_container",g),options:b(),helpText:"Video container format. Hardware encoding requires v4l2m2m support."}),te(v)&&de(r)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Active:"})," Using h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%."]}),r&&!de(r)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Not Available:"})," This device does not have a hardware H.264 encoder.",At(r)&&" Pi 5 does not include a hardware encoder."," ","Hardware encoding options (h264_v4l2m2m) are hidden. Using software encoding (~40-70% CPU)."]}),te(v)&&!r&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding:"})," Uses h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%. Only available on devices with v4l2m2m support."]}),Ie(v)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"High CPU Warning:"})," H.265/HEVC software encoding uses 80-100% CPU on Raspberry Pi. Not recommended for continuous recording. Consider H.264 for better performance."]}),de(r)&&!te(v)&&!o("movie_passthrough",!1)&&!v.includes("webm")&&!Ie(v)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Available:"}),' This device has a hardware H.264 encoder. Select "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" to reduce CPU from ~40-70% to ~10%.']}),!r&&!te(v)&&!o("movie_passthrough",!1)&&!v.includes("webm")&&!Ie(v)&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-2",children:[s.jsx("strong",{children:"Tip:"}),' If your device has hardware encoding support (e.g., Raspberry Pi 4), consider selecting "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" for ~10% CPU instead of ~40-70% with software encoding.']}),v==="webm"&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"WebM Format:"})," Uses VP8 codec, optimized for web streaming. Encoder preset setting does not apply to VP8."]}),w()&&s.jsx(A,{label:"Encoder Preset",value:String(o("movie_encoder_preset","medium")),onChange:g=>t("movie_encoder_preset",g),options:An.map(g=>({value:g.value,label:g.label})),helpText:"Tradeoff between CPU usage and video quality. Lower presets use less CPU but produce lower quality video. Requires restart to take effect."}),s.jsx(S,{label:"Max Duration (seconds)",value:String(o("movie_max_time",0)),onChange:g=>t("movie_max_time",Number(g)),type:"number",min:"0",helpText:"Maximum movie length (0 = unlimited). Splits long events into multiple files.",error:n?.("movie_max_time")}),s.jsx(B,{label:"Passthrough Mode",value:o("movie_passthrough",!1),onChange:g=>t("movie_passthrough",g),helpText:"Copy codec without re-encoding. Reduces CPU but may cause compatibility issues."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"2025-01-29/143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%v-%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/42-143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%v"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/42.mkv"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S"})," → ",s.jsx("code",{children:"20250129143022.mkv"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",p]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]}),u==="continuous"&&At(r)&&!o("movie_passthrough",!1)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Pi 5 CPU Warning:"})," Pi 5 does not have a hardware H.264 encoder. Continuous recording uses software encoding (~35-60% CPU constant).",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsx("li",{children:'Use encoder preset "Ultrafast" to reduce CPU by ~30%'}),s.jsx("li",{children:"Add active cooling (fan) to prevent thermal throttling"}),s.jsx("li",{children:"Enable passthrough if source is already H.264"})]})]}),u==="continuous"&&xi(r)&&de(r)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording on Pi 4:"})," Camera will record 24/7.",te(v)?s.jsx("span",{children:" Using hardware encoder - expect ~10% CPU usage."}):o("movie_passthrough",!1)?s.jsx("span",{children:" Passthrough mode enabled - expect ~5-10% CPU usage."}):s.jsx("span",{children:" Consider using hardware encoder (MKV/MP4 H.264 Hardware) for ~10% CPU instead of ~40-70%."})]}),u==="continuous"&&!r&&s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording:"})," Camera will record 24/7 regardless of motion. Expected CPU usage on Raspberry Pi:",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 4 with hardware encoder:"})," ~10% CPU"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 5 or Pi 4 software encoding:"})," ~35-60% CPU depending on preset"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Passthrough mode:"})," ~5-10% CPU (if source is H.264)"]})]})]}),vi(r)&&s.jsxs("div",{className:"text-xs text-red-400 bg-red-950/30 p-3 rounded",children:[s.jsx("strong",{children:"High Temperature Warning:"})," Device is running at ",r?.temperature?.celsius.toFixed(1),"°C. Consider reducing encoding quality or adding active cooling."]})]})})}function yi({config:e,onChange:t,getError:n,originalConfig:r}){const o=(c,l="")=>e[c]?.value??l,a=(c,l="")=>r?.[c]?.value??l,i=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(E,{title:"Storage",description:"Base directory and periodic snapshot settings for this camera",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(S,{label:"Base Storage Directory",value:String(o("target_dir","/var/lib/motion")),onChange:c=>t("target_dir",c),helpText:"Root directory for ALL camera files. Picture and movie filename patterns (configured in their sections) create paths relative to this directory.",error:n?.("target_dir"),originalValue:String(a("target_dir","/var/lib/motion"))}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Filename Patterns"}),s.jsx(S,{label:"Snapshot Filename",value:String(o("snapshot_filename","%Y%m%d%H%M%S-snapshot")),onChange:c=>t("snapshot_filename",c),helpText:"Format for periodic snapshot filenames (strftime syntax)",error:n?.("snapshot_filename")}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Example:"})," ",s.jsx("code",{children:"%Y%m%d%H%M%S-snapshot"})," → ",s.jsx("code",{children:"20250129143022-snapshot.jpg"})]}),s.jsxs("p",{children:[s.jsx("strong",{children:"With subdirs:"})," ",s.jsx("code",{children:"%$/%Y-%m-%d/snapshot-%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/snapshot-143022.jpg"})]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-1",children:[s.jsxs("p",{children:["Available codes: ",i]}),s.jsxs("p",{className:"mt-2 text-blue-200",children:[s.jsx("strong",{children:"How it works:"})," The Base Storage Directory above sets where files go. Picture and Movie sections set filename patterns (which can include subdirectories like ",s.jsx("code",{children:"%Y-%m-%d/"}),")."]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"File Cleanup (Future)"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("p",{children:"Automatic file retention and cleanup based on age/size will be available in a future update."}),s.jsxs("p",{className:"mt-2",children:["For now, use ",s.jsx("code",{children:"cleandir_params"})," in the Motion configuration file or manual cleanup scripts."]})]})]}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"💡 Network Storage:"})," For network shares (NFS, SMB), ensure target_dir points to a mounted directory. Test write permissions before starting recording."]})]})})}const X=["sun","mon","tue","wed","thu","fri","sat"],Et={sun:{full:"Sunday",short:"Sun"},mon:{full:"Monday",short:"Mon"},tue:{full:"Tuesday",short:"Tue"},wed:{full:"Wednesday",short:"Wed"},thu:{full:"Thursday",short:"Thu"},fri:{full:"Friday",short:"Fri"},sat:{full:"Saturday",short:"Sat"}},ne=96,fe=15;function ln(){return{sun:new Set,mon:new Set,tue:new Set,wed:new Set,thu:new Set,fri:new Set,sat:new Set}}function wi(e){const t=new Map,n=e.trim().split(/\s+/);for(const r of n){const o=r.indexOf("=");if(o===-1)continue;const a=r.slice(0,o).toLowerCase(),i=r.slice(o+1);t.has(a)||t.set(a,[]),t.get(a).push(i)}return t}function ji(e){if(e.length!==9||e[4]!=="-")return[];const t=parseInt(e.slice(0,2),10),n=parseInt(e.slice(2,4),10),r=parseInt(e.slice(5,7),10),o=parseInt(e.slice(7,9),10);if(isNaN(t)||isNaN(n)||isNaN(r)||isNaN(o)||t<0||t>23||n<0||n>59||r<0||r>23||o<0||o>59)return[];const a=t*4+Math.floor(n/fe),i=r*4+Math.floor(o/fe),c=[];for(let l=a;l<=i&&lc-l),n=[];let r=t[0],o=t[0];for(let c=1;cZt(e[i],e.sun))&&e.sun.size>0){const i=we(Array.from(e.sun));for(const c of i)r.push(`sun-sat=${je(c)}`);return r.join(" ")}if(["mon","tue","wed","thu","fri"].every(i=>Zt(e[i],e.mon))&&e.mon.size>0){const i=we(Array.from(e.mon));for(const c of i)r.push(`mon-fri=${je(c)}`);for(const c of["sat","sun"])if(e[c].size>0){const l=we(Array.from(e[c]));for(const u of l)r.push(`${c}=${je(u)}`)}return r.join(" ")}for(const i of X){if(e[i].size===0)continue;const c=we(Array.from(e[i]));for(const l of c)r.push(`${i}=${je(l)}`)}return r.join(" ")}function Zt(e,t){if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0}function Ni(e,t){const n=Ye(e),r=Ve(t),o=(a,i)=>{const c=a===0?12:a>12?a-12:a,l=a<12?"am":"pm",u=i===0?"":`:${String(i).padStart(2,"0")}`;return`${c}${u}${l}`};return`${o(n.hour,n.min)} - ${o(r.hour,r.min)}`}function ki(e){return X.every(t=>e[t].size===0)}function Ci(e){const t=X.filter(r=>e[r].size>0);return t.length===0?"No time ranges selected":t.length===7?"All days configured":t.map(r=>r.charAt(0).toUpperCase()+r.slice(1,3)).join(", ")}function Ti({value:e,onChange:t}){const{schedule:n,defaultOn:r,action:o}=x.useMemo(()=>Si(e),[e]),a=x.useCallback(p=>{const v=De(p,r,o);t(v)},[t,r,o]),i=x.useCallback(p=>{const v=Re(n);v[p].size===ne?v[p]=new Set:v[p]=new Set(Array.from({length:ne},(b,w)=>w)),a(v)},[n,a]),c=x.useCallback((p,v,b,w)=>{const g=Re(n),k=new Set(n[p]),[V,q]=v<=b?[v,b]:[b,v];for(let O=V;O<=q;O++)w?k.add(O):k.delete(O);g[p]=k,a(g)},[n,a]),l=x.useCallback(p=>{const v=Re(n);v[p]=new Set,a(v)},[n,a]),u=x.useCallback(()=>{a(ln())},[a]),d=x.useCallback(p=>{const v=De(n,p,o);t(v)},[n,o,t]),h=x.useCallback(p=>{const v=De(n,r,p);t(v)},[n,r,t]),f=x.useCallback(p=>{t(p)},[t]);return{schedule:n,defaultOn:r,action:o,updateSchedule:a,toggleDay:i,setRange:c,clearDay:l,clearAll:u,setDefaultOn:d,setAction:h,applyPreset:f}}function Re(e){return{sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)}}const Pi=x.memo(function({isSelected:t,isInDragRange:n,isDragSelect:r,isHourBoundary:o,onPointerDown:a,onPointerEnter:i}){let c;n?c=r?"bg-primary/70":"bg-surface-hover":t?c="bg-primary":c="bg-surface";const l=o?"border-t border-gray-700/50":"";return s.jsx("div",{className:`h-[6px] ${c} ${l} cursor-pointer transition-colors duration-75`,onPointerDown:a,onPointerEnter:i})}),zi=x.memo(function({day:t,schedule:n,dragState:r,onPointerDown:o,onPointerMove:a}){const i=x.useMemo(()=>{if(!r.isDragging||r.startDay!==t)return null;const l=r.startIndex,u=r.currentIndex;return{from:Math.min(l,u),to:Math.max(l,u)}},[r.isDragging,r.startDay,r.startIndex,r.currentIndex,t]),c=x.useMemo(()=>Array.from({length:ne},(l,u)=>({isSelected:n.has(u),isInDragRange:i!==null&&u>=i.from&&u<=i.to,isHourBoundary:u%4===0})),[n,i]);return s.jsx("div",{className:"flex flex-col",children:c.map((l,u)=>s.jsx(Pi,{index:u,isSelected:l.isSelected,isInDragRange:l.isInDragRange,isDragSelect:r.selectMode,isHourBoundary:l.isHourBoundary,onPointerDown:()=>o(u),onPointerEnter:()=>a(u)},u))})}),$i=[0,2,4,6,8,10,12,14,16,18,20,22];function Mi(e){return e===0?"12a":e===12?"12p":e<12?`${e}a`:`${e-12}p`}const Oi=x.memo(function(){return s.jsx("div",{className:"flex flex-col pr-1 text-xs text-gray-500 select-none",children:$i.map(t=>s.jsx("div",{className:"flex items-start justify-end",style:{height:"48px"},children:s.jsx("span",{className:"-mt-1.5",children:Mi(t)})},t))})}),Se={isDragging:!1,startDay:null,startIndex:null,currentIndex:null,selectMode:!0};function Ii({schedule:e,onScheduleChange:t,disabled:n=!1}){const[r,o]=x.useState(Se),a=x.useRef(null),i=x.useCallback((f,p)=>{if(n)return;const v=e[f].has(p);o({isDragging:!0,startDay:f,startIndex:p,currentIndex:p,selectMode:!v})},[e,n]),c=x.useCallback((f,p)=>{!r.isDragging||f!==r.startDay||o(v=>({...v,currentIndex:p}))},[r.isDragging,r.startDay]),l=x.useCallback(()=>{if(!r.isDragging||r.startDay===null||r.startIndex===null||r.currentIndex===null){o(Se);return}const f={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)},p=new Set(e[r.startDay]),[v,b]=r.startIndex<=r.currentIndex?[r.startIndex,r.currentIndex]:[r.currentIndex,r.startIndex];for(let w=v;w<=b;w++)r.selectMode?p.add(w):p.delete(w);f[r.startDay]=p,t(f),o(Se)},[r,e,t]);x.useEffect(()=>{const f=p=>{p.key==="Escape"&&r.isDragging&&o(Se)};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)},[r.isDragging]),x.useEffect(()=>(r.isDragging?(document.body.style.touchAction="none",document.body.style.userSelect="none"):(document.body.style.touchAction="",document.body.style.userSelect=""),()=>{document.body.style.touchAction="",document.body.style.userSelect=""}),[r.isDragging]);const u=x.useCallback(f=>{if(n)return;const p={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)};p[f].size===ne?p[f]=new Set:p[f]=new Set(Array.from({length:ne},(v,b)=>b)),t(p)},[e,t,n]),h=(()=>{if(!r.isDragging||r.startIndex===null||r.currentIndex===null)return null;const f=Math.min(r.startIndex,r.currentIndex),p=Math.max(r.startIndex,r.currentIndex);return Ni(f,p)})();return s.jsxs("div",{className:"select-none",children:[s.jsxs("div",{className:"flex mb-1",children:[s.jsx("div",{className:"w-8 shrink-0"}),s.jsx("div",{className:"grid grid-cols-7 gap-px flex-1",children:X.map(f=>{const p=e[f].size===ne,v=e[f].size>0;return s.jsxs("button",{type:"button",onClick:()=>u(f),disabled:n,className:`text-xs font-medium py-1 rounded-t transition-colors ${p?"bg-primary text-white":v?"bg-primary/30 text-primary":"bg-surface-elevated text-gray-400 hover:bg-surface-hover"} ${n?"cursor-not-allowed opacity-50":"cursor-pointer"}`,title:`Click to ${p?"clear":"select all"} ${Et[f].full}`,children:[s.jsx("span",{className:"hidden sm:inline",children:Et[f].short}),s.jsx("span",{className:"sm:hidden",children:f.charAt(0).toUpperCase()})]},f)})})]}),s.jsxs("div",{className:"flex",children:[s.jsx("div",{className:"w-8 shrink-0",children:s.jsx(Oi,{})}),s.jsx("div",{ref:a,className:`grid grid-cols-7 gap-px bg-surface-elevated flex-1 rounded ${n?"opacity-50":""}`,style:{touchAction:"none"},onPointerUp:l,onPointerLeave:l,onPointerCancel:l,children:X.map(f=>s.jsx(zi,{day:f,schedule:e[f],dragState:r,onPointerDown:p=>i(f,p),onPointerMove:p=>c(f,p)},f))})]}),h&&s.jsx("div",{className:"mt-2 text-center",children:s.jsxs("span",{className:"text-xs bg-surface-elevated px-2 py-1 rounded text-gray-300",children:[r.selectMode?"Selecting":"Deselecting",":"," ",s.jsx("span",{className:"text-white font-medium",children:h})]})}),s.jsx("div",{className:"mt-2 text-xs text-gray-500 text-center",children:"Click and drag to select time ranges. Click day header to toggle entire day."})]})}const Ai=[{label:"Business Hours",value:"default=true action=pause mon-fri=0900-1700",description:"Pause Mon-Fri 9am-5pm"},{label:"Night Watch",value:"default=false action=pause sun-sat=1800-2359 sun-sat=0000-0600",description:"Active 6pm-6am only"},{label:"Weekends Only",value:"default=false action=pause sat=0000-2359 sun=0000-2359",description:"Active Sat & Sun only"},{label:"Always On",value:"default=true action=pause",description:"No schedule restrictions"}],Ei=x.memo(function({defaultOn:t,action:n,onDefaultOnChange:r,onActionChange:o,onApplyPreset:a,onClearAll:i,disabled:c=!1}){return s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Detection Default"}),s.jsxs("select",{value:t?"on":"off",onChange:l=>r(l.target.value==="on"),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"on",children:"On by default"}),s.jsx("option",{value:"off",children:"Off by default"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:t?"Schedule defines when detection is paused":"Schedule defines when detection is active"})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Schedule Action"}),s.jsxs("select",{value:n,onChange:l=>o(l.target.value),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"pause",children:"Pause detection"}),s.jsx("option",{value:"stop",children:"Stop camera"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n==="pause"?"Camera runs but ignores motion":"Camera completely stops during schedule"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-2",children:"Quick Presets"}),s.jsxs("div",{className:"flex flex-wrap gap-2",children:[Ai.map(l=>s.jsx("button",{type:"button",onClick:()=>a(l.value),disabled:c,className:"px-3 py-1.5 text-xs bg-surface-elevated hover:bg-surface-hover border border-gray-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:l.description,children:l.label},l.label)),s.jsx("button",{type:"button",onClick:i,disabled:c,className:"px-3 py-1.5 text-xs bg-danger/20 hover:bg-danger/30 text-danger border border-danger/30 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:"Clear all time ranges",children:"Clear All"})]})]})]})});function Dt({value:e,onChange:t,helpText:n,error:r,disabled:o=!1}){const{schedule:a,defaultOn:i,action:c,updateSchedule:l,setDefaultOn:u,setAction:d,applyPreset:h,clearAll:f}=Ti({value:e,onChange:t}),p=ki(a),v=Ci(a);return s.jsxs("div",{className:"space-y-4",children:[s.jsx(Ei,{defaultOn:i,action:c,onDefaultOnChange:u,onActionChange:d,onApplyPreset:h,onClearAll:f,disabled:o}),s.jsx("div",{className:"border border-gray-700 rounded-lg p-4 bg-surface",children:s.jsx(Ii,{schedule:a,onScheduleChange:l,disabled:o})}),s.jsxs("div",{className:"flex items-center justify-between text-sm",children:[s.jsx("span",{className:"text-gray-400",children:p?s.jsx("span",{className:"text-yellow-400",children:"No time ranges configured"}):s.jsxs(s.Fragment,{children:["Schedule: ",s.jsx("span",{className:"text-white",children:v})]})}),i?s.jsx("span",{className:"text-xs text-gray-500",children:"Detection paused during selected times"}):s.jsx("span",{className:"text-xs text-gray-500",children:"Detection active during selected times"})]}),n&&!r&&s.jsx("p",{className:"text-sm text-gray-400",children:n}),r&&s.jsx("p",{className:"text-sm text-red-400",role:"alert",children:r}),s.jsxs("details",{className:"text-xs",children:[s.jsx("summary",{className:"cursor-pointer text-gray-500 hover:text-gray-400",children:"Show raw schedule format"}),s.jsx("code",{className:"block mt-2 p-2 bg-surface-elevated rounded text-gray-400 break-all",children:e||"(empty)"})]})]})}function Zi({config:e,onChange:t,getError:n}){const r=(d,h="")=>e[d]?.value??h,o=String(r("schedule_params","")),a=o.trim()!=="",i=d=>{d?t("schedule_params","default=true action=pause mon-fri=0900-1700"):t("schedule_params","")},c=String(r("picture_schedule_params","")),l=c.trim()!=="",u=d=>{d?t("picture_schedule_params","default=false action=pause mon-fri=0900-1700"):t("picture_schedule_params","")};return s.jsx(E,{title:"Schedules",description:"Configure when motion detection and continuous recording are active",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-6",children:[s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Motion Detection Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when motion detection is active or paused"}),s.jsx(B,{label:"Enable Motion Detection Schedule",value:a,onChange:i,helpText:"When enabled, motion detection follows the schedule below"}),a&&s.jsx(Dt,{value:o,onChange:d=>t("schedule_params",d),error:n?.("schedule_params")})]}),s.jsx("div",{className:"border-t border-gray-700"}),s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Continuous Recording Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when continuous picture capture (timelapse) is active"}),s.jsx(B,{label:"Enable Continuous Recording Schedule",value:l,onChange:u,helpText:"When enabled, continuous recording follows the schedule below"}),l&&s.jsx(Dt,{value:c,onChange:d=>t("picture_schedule_params",d),error:n?.("picture_schedule_params")})]})]})})}const We={gridColumns:2,gridRows:2,fitFramesVertically:!1,playbackFramerateFactor:1,playbackResolutionFactor:1,theme:"dark"};function Di(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{...We,...t}}catch(t){console.error("Failed to parse preferences:",t)}return We}function Ri(){const[e,t]=x.useState(Di),n=(r,o)=>{const a={...e,[r]:o};t(a),localStorage.setItem("motion-ui-preferences",JSON.stringify(a))};return s.jsx(E,{title:"UI Preferences",description:"User interface preferences (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Dashboard Layout"}),s.jsx(T,{label:"Grid Columns",value:e.gridColumns,onChange:r=>n("gridColumns",r),min:1,max:4,helpText:"Number of camera columns in dashboard grid (1-4)"}),s.jsx(T,{label:"Grid Rows",value:e.gridRows,onChange:r=>n("gridRows",r),min:1,max:4,helpText:"Number of camera rows in dashboard grid (1-4)"}),s.jsx(B,{label:"Fit Frames Vertically",value:e.fitFramesVertically,onChange:r=>n("fitFramesVertically",r),helpText:"Fit camera frames to viewport height instead of width"})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Playback Settings"}),s.jsx(T,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:r=>n("playbackFramerateFactor",r),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(T,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:r=>n("playbackResolutionFactor",r),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Appearance"}),s.jsx(A,{label:"Theme",value:e.theme,onChange:r=>n("theme",r),options:[{value:"dark",label:"Dark"},{value:"light",label:"Light (Coming Soon)"},{value:"auto",label:"Auto (System Preference)"}],helpText:"UI color theme",disabled:!0}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Light theme and auto theme switching will be available in a future update."]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("button",{onClick:()=>{localStorage.removeItem("motion-ui-preferences"),t(We)},className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors text-sm",children:"Reset to Defaults"}),s.jsx("p",{className:"text-xs text-gray-400 mt-2",children:"Clears all saved preferences and returns to default settings"})]})]})})}const Fe={playbackFramerateFactor:1,playbackResolutionFactor:1};function Fi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{playbackFramerateFactor:t.playbackFramerateFactor??Fe.playbackFramerateFactor,playbackResolutionFactor:t.playbackResolutionFactor??Fe.playbackResolutionFactor}}catch(t){console.error("Failed to parse preferences:",t)}return Fe}function Ui(){const[e,t]=x.useState(Fi),n=(r,o)=>{const a={...e,[r]:o};t(a);const i=localStorage.getItem("motion-ui-preferences");let c={};if(i)try{c=JSON.parse(i)}catch(l){console.error("Failed to parse existing preferences:",l)}localStorage.setItem("motion-ui-preferences",JSON.stringify({...c,...a}))};return s.jsx(E,{title:"Playback Settings",description:"Video playback preferences for this camera (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsx(T,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:r=>n("playbackFramerateFactor",r),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(T,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:r=>n("playbackResolutionFactor",r),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]})})}function Li({cameraId:e}){const{addToast:t}=Be(),n=Rt(),r=x.useRef(null),o=x.useRef(null),[a,i]=x.useState("motion"),[c,l]=x.useState("rectangle"),[u,d]=x.useState(!1),[h,f]=x.useState(null),[p,v]=x.useState(null),[b,w]=x.useState([]),[g,k]=x.useState([]),[V,q]=x.useState(!1),[O,oe]=x.useState(!1),{data:ee,isLoading:U}=qe({queryKey:["mask",e,a],queryFn:()=>Ft(`/${e}/api/mask/${a}`)}),[j,I]=x.useState({width:640,height:480}),M=rt({mutationFn:N=>Ce(`/${e}/api/mask/${a}`,N),onSuccess:()=>{t(`${a==="motion"?"Motion":"Privacy"} mask saved`,"success"),n.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to save mask","error")}}),Y=rt({mutationFn:()=>gn(`/${e}/api/mask/${a}`),onSuccess:()=>{t("Mask deleted","success"),w([]),n.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to delete mask","error")}}),K=x.useCallback(N=>{const y=r.current;if(!y)return{x:0,y:0};const C=y.getBoundingClientRect(),G=j.width/C.width,ae=j.height/C.height;return{x:Math.round((N.clientX-C.left)*G),y:Math.round((N.clientY-C.top)*ae)}},[j]),L=x.useCallback(()=>{const N=r.current;if(!N)return;const y=N.getContext("2d");if(y&&(y.clearRect(0,0,N.width,N.height),y.fillStyle="rgba(255, 0, 0, 0.4)",y.strokeStyle="rgba(255, 0, 0, 0.8)",y.lineWidth=2,b.forEach(C=>{C.length<3||(y.beginPath(),y.moveTo(C[0].x,C[0].y),C.slice(1).forEach(G=>y.lineTo(G.x,G.y)),y.closePath(),y.fill(),y.stroke())}),g.length>0&&(y.beginPath(),y.moveTo(g[0].x,g[0].y),g.slice(1).forEach(C=>y.lineTo(C.x,C.y)),p&&y.lineTo(p.x,p.y),y.stroke(),y.fillStyle="rgba(255, 255, 0, 0.8)",g.forEach(C=>{y.beginPath(),y.arc(C.x,C.y,4,0,Math.PI*2),y.fill()})),c==="rectangle"&&u&&h&&p)){y.fillStyle="rgba(255, 0, 0, 0.4)",y.strokeStyle="rgba(255, 0, 0, 0.8)";const C=Math.min(h.x,p.x),G=Math.min(h.y,p.y),ae=Math.abs(p.x-h.x),xe=Math.abs(p.y-h.y);y.fillRect(C,G,ae,xe),y.strokeRect(C,G,ae,xe)}},[b,g,p,h,u,c]);x.useEffect(()=>{L()},[L]);const $e=x.useCallback(N=>{const y=K(N);c==="rectangle"?(d(!0),f(y),v(y)):k(C=>[...C,y])},[c,K]),be=x.useCallback(N=>{const y=K(N);v(y)},[K]),W=x.useCallback(()=>{if(c==="rectangle"&&u&&h&&p){const N=Math.min(h.x,p.x),y=Math.min(h.y,p.y),C=Math.max(h.x,p.x),G=Math.max(h.y,p.y);if(C-N>5&&G-y>5){const ae=[{x:N,y},{x:C,y},{x:C,y:G},{x:N,y:G}];w(xe=>[...xe,ae])}}d(!1),f(null)},[c,u,h,p]),Me=x.useCallback(()=>{c==="polygon"&&g.length>=3&&(w(N=>[...N,g]),k([]))},[c,g]),Oe=x.useCallback(()=>{w([]),k([]),f(null),v(null),d(!1)},[]),un=x.useCallback(()=>{g.length>0?k(N=>N.slice(0,-1)):b.length>0&&w(N=>N.slice(0,-1))},[g.length,b.length]),dn=x.useCallback(()=>{if(b.length===0){t("Draw at least one mask area first","warning");return}M.mutate({polygons:b,width:j.width,height:j.height,invert:V})},[b,j,V,M,t]),mn=x.useCallback(()=>{window.confirm(`Delete the ${a} mask? This cannot be undone.`)&&Y.mutate()},[a,Y]),hn=x.useCallback(N=>{const y=N.currentTarget;I({width:y.naturalWidth,height:y.naturalHeight}),oe(!1)},[]),pn=x.useMemo(()=>{const N=fn(),y=`/${e}/mjpg/stream`;return N?`${y}?token=${encodeURIComponent(N)}`:y},[e]);return s.jsxs(E,{title:"Mask Editor",description:"Draw mask areas on the camera feed to define motion detection or privacy zones",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"flex gap-4 mb-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Mask Type"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>{i("motion"),Oe()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="motion"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Motion"}),s.jsx("button",{onClick:()=>{i("privacy"),Oe()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="privacy"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Privacy"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Draw Mode"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>l("rectangle"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="rectangle"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Rectangle"}),s.jsx("button",{onClick:()=>l("polygon"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="polygon"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Polygon"})]})]}),s.jsx("div",{className:"flex items-end",children:s.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[s.jsx("input",{type:"checkbox",checked:V,onChange:N=>q(N.target.checked),className:"rounded"}),"Invert (detect outside areas)"]})})]}),U?s.jsx("div",{className:"text-sm text-gray-400 mb-4",children:"Loading mask info..."}):ee?.exists?s.jsxs("div",{className:"text-sm text-green-500 mb-4",children:["Current mask: ",ee.path," (",ee.width,"x",ee.height,")"]}):s.jsxs("div",{className:"text-sm text-gray-400 mb-4",children:["No ",a," mask configured"]}),s.jsxs("div",{ref:o,className:"relative bg-black rounded-lg overflow-hidden mb-4",children:[O?s.jsx("div",{className:"aspect-video bg-surface flex items-center justify-center text-gray-400",children:s.jsxs("div",{className:"text-center",children:[s.jsx("p",{children:"Camera stream unavailable"}),s.jsx("p",{className:"text-xs mt-1",children:"Draw on blank canvas (640x480)"})]})}):s.jsx("img",{src:pn,alt:"Camera stream",onLoad:hn,onError:()=>oe(!0),className:"w-full h-auto",style:{display:"block"}}),s.jsx("canvas",{ref:r,width:j.width,height:j.height,onMouseDown:$e,onMouseMove:be,onMouseUp:W,onMouseLeave:W,onDoubleClick:Me,className:"absolute inset-0 w-full h-full cursor-crosshair",style:{touchAction:"none"}})]}),s.jsx("div",{className:"text-xs text-gray-400 mb-4",children:c==="rectangle"?s.jsxs("p",{children:["Click and drag to draw rectangles. Red areas will be ",a==="motion"?"ignored for motion detection":"blacked out for privacy","."]}):s.jsx("p",{children:"Click to add polygon points. Double-click to complete the polygon."})}),s.jsxs("div",{className:"flex gap-3 flex-wrap",children:[s.jsx("button",{onClick:un,disabled:b.length===0&&g.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Undo"}),s.jsx("button",{onClick:Oe,disabled:b.length===0&&g.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Clear All"}),s.jsx("button",{onClick:dn,disabled:M.isPending||b.length===0,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:M.isPending?"Saving...":"Save Mask"}),ee?.exists&&s.jsx("button",{onClick:mn,disabled:Y.isPending,className:"px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 rounded transition-colors disabled:opacity-50",children:Y.isPending?"Deleting...":"Delete Mask"})]}),b.length>0&&s.jsxs("div",{className:"text-xs text-gray-400 mt-3",children:[b.length," mask area",b.length!==1?"s":""," drawn"]})]})}const me={custom:{label:"Custom Command",description:"Enter your own shell command",command:""},webhook:{label:"Webhook (HTTP POST)",description:"Send HTTP POST to a URL",command:`curl -s -X POST -H "Content-Type: application/json" -d '{"camera":"%t","event":"%v","time":"%Y-%m-%d %T"}' "YOUR_WEBHOOK_URL"`},telegram:{label:"Telegram Bot",description:"Send message via Telegram Bot API",command:'curl -s -X POST "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage" -d "chat_id=YOUR_CHAT_ID&text=Motion detected on %t at %Y-%m-%d %T"'},email:{label:"Email (msmtp)",description:"Send email using msmtp",command:'echo -e "Subject: Motion Alert\\n\\nMotion detected on camera %t at %Y-%m-%d %T" | msmtp recipient@example.com'},pushover:{label:"Pushover",description:"Send push notification via Pushover",command:'curl -s -F "token=YOUR_APP_TOKEN" -F "user=YOUR_USER_KEY" -F "message=Motion on %t at %T" https://api.pushover.net/1/messages.json'}};function Hi({config:e,onChange:t,getError:n}){const[r,o]=x.useState("custom"),a=(c,l="")=>e[c]?.value??l,i=c=>{const l=me[r];l.command&&t(c,l.command)};return s.jsxs(E,{title:"Notifications & Scripts",description:"Configure commands that run on motion events. Use script hooks to send notifications via webhooks, Telegram, email, or custom scripts.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsx("h4",{className:"font-medium mb-2",children:"Available Variables"}),s.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-gray-400",children:[s.jsx("code",{children:"%f"})," ",s.jsx("span",{children:"Filename"}),s.jsx("code",{children:"%t"})," ",s.jsx("span",{children:"Camera name"}),s.jsx("code",{children:"%v"})," ",s.jsx("span",{children:"Event number"}),s.jsx("code",{children:"%Y"})," ",s.jsx("span",{children:"Year (4 digit)"}),s.jsx("code",{children:"%m"})," ",s.jsx("span",{children:"Month (01-12)"}),s.jsx("code",{children:"%d"})," ",s.jsx("span",{children:"Day (01-31)"}),s.jsx("code",{children:"%H"})," ",s.jsx("span",{children:"Hour (00-23)"}),s.jsx("code",{children:"%M"})," ",s.jsx("span",{children:"Minute (00-59)"}),s.jsx("code",{children:"%S"})," ",s.jsx("span",{children:"Second (00-59)"}),s.jsx("code",{children:"%T"})," ",s.jsx("span",{children:"Time HH:MM:SS"})]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-3",children:Object.keys(me).map(c=>s.jsx("button",{onClick:()=>o(c),className:`px-3 py-1.5 text-sm rounded transition-colors ${r===c?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:me[c].label},c))}),r!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-3",children:[s.jsx("p",{children:me[r].description}),s.jsx("pre",{className:"mt-2 p-2 bg-surface rounded text-xs overflow-x-auto whitespace-pre-wrap break-all",children:me[r].command})]}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>i("on_event_start"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Event Start"}),s.jsx("button",{onClick:()=>i("on_picture_save"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Picture Save"}),s.jsx("button",{onClick:()=>i("on_movie_end"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Movie End"})]})]}),s.jsx(S,{label:"On Event Start",value:String(a("on_event_start","")),onChange:c=>t("on_event_start",c),placeholder:"Command to run when motion event starts",helpText:"Executed when a new motion event begins",error:n?.("on_event_start")}),s.jsx(S,{label:"On Event End",value:String(a("on_event_end","")),onChange:c=>t("on_event_end",c),placeholder:"Command to run when motion event ends",helpText:"Executed when motion event ends (after gap timeout)",error:n?.("on_event_end")}),s.jsx(S,{label:"On Motion Detected",value:String(a("on_motion_detected","")),onChange:c=>t("on_motion_detected",c),placeholder:"Command to run on each motion frame",helpText:"Executed on every frame with motion (can be frequent!)",error:n?.("on_motion_detected")}),s.jsx(S,{label:"On Picture Save",value:String(a("on_picture_save","")),onChange:c=>t("on_picture_save",c),placeholder:"Command to run when picture is saved",helpText:"Executed after a snapshot is saved (%f = filename)",error:n?.("on_picture_save")}),s.jsx(S,{label:"On Movie Start",value:String(a("on_movie_start","")),onChange:c=>t("on_movie_start",c),placeholder:"Command to run when recording starts",helpText:"Executed when video recording begins",error:n?.("on_movie_start")}),s.jsx(S,{label:"On Movie End",value:String(a("on_movie_end","")),onChange:c=>t("on_movie_end",c),placeholder:"Command to run when recording ends",helpText:"Executed when video recording is complete (%f = filename)",error:n?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Example: Send Picture via Telegram"}),s.jsx("pre",{className:"text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap",children:`# On Picture Save: +curl -F chat_id="YOUR_CHAT_ID" \\ + -F photo=@"%f" \\ + -F caption="Motion on %t at %T" \\ + "https://api.telegram.org/botYOUR_TOKEN/sendPhoto"`})]})]})}const ie={rclone:{label:"Rclone",description:"Universal cloud storage sync (supports 40+ providers)",pictureCmd:'rclone copy "%f" remote:motion/pictures/%Y%m%d/',movieCmd:'rclone copy "%f" remote:motion/movies/%Y%m%d/'},s3:{label:"AWS S3",description:"Amazon S3 or compatible storage (MinIO, DigitalOcean Spaces)",pictureCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/pictures/%Y%m%d/',movieCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/movies/%Y%m%d/'},gdrive:{label:"Google Drive",description:"Upload via gdrive CLI",pictureCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"',movieCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"'},dropbox:{label:"Dropbox",description:"Upload via Dropbox-Uploader script",pictureCmd:'dropbox_uploader.sh upload "%f" /motion/pictures/',movieCmd:'dropbox_uploader.sh upload "%f" /motion/movies/'},sftp:{label:"SFTP/SCP",description:"Upload to remote server via SSH",pictureCmd:'scp "%f" user@server:/backup/motion/pictures/',movieCmd:'scp "%f" user@server:/backup/motion/movies/'},custom:{label:"Custom",description:"Enter your own upload command",pictureCmd:"",movieCmd:""}};function Vi({config:e,onChange:t,getError:n}){const[r,o]=x.useState("rclone"),a=(u,d="")=>e[u]?.value??d,i=String(a("on_picture_save","")),c=String(a("on_movie_end","")),l=()=>{const u=ie[r];u.pictureCmd&&t("on_picture_save",u.pictureCmd),u.movieCmd&&t("on_movie_end",u.movieCmd)};return s.jsxs(E,{title:"Cloud Upload",description:"Configure automatic upload of pictures and videos to cloud storage. Uses the same event hooks as notifications.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsxs("p",{className:"text-gray-400 mb-2",children:["Cloud uploads are triggered by the ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_picture_save"})," and ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_movie_end"})," event hooks. Select a provider template below or enter a custom command."]}),s.jsxs("p",{className:"text-xs text-gray-500",children:[s.jsx("strong",{children:"Note:"})," You must install the required CLI tools (rclone, aws-cli, etc.) on your Pi before uploads will work."]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Cloud Provider Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:Object.keys(ie).map(u=>s.jsx("button",{onClick:()=>o(u),className:`px-3 py-1.5 text-sm rounded transition-colors ${r===u?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:ie[u].label},u))}),r!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-4",children:[s.jsx("p",{className:"mb-2",children:ie[r].description}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Picture:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[r].pictureCmd})]}),s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Movie:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[r].movieCmd})]})]})]}),s.jsx("button",{onClick:l,disabled:r==="custom",className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:"Apply Template"})]}),s.jsx(S,{label:"Picture Upload Command",value:i,onChange:u=>t("on_picture_save",u),placeholder:"Command to upload pictures",helpText:"Runs after each picture is saved. %f = filename, %Y%m%d = date",error:n?.("on_picture_save")}),s.jsx(S,{label:"Movie Upload Command",value:c,onChange:u=>t("on_movie_end",u),placeholder:"Command to upload videos",helpText:"Runs after each video recording completes. %f = filename",error:n?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Guides"}),s.jsxs("div",{className:"space-y-4 text-xs text-gray-400",children:[s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"Rclone Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install rclone +sudo apt install rclone + +# Configure a remote (interactive) +rclone config + +# Test upload +rclone copy /path/to/file remote:folder/`})]}),s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"AWS S3 Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install AWS CLI +sudo apt install awscli + +# Configure credentials +aws configure + +# Test upload +aws s3 cp /path/to/file s3://bucket/`})]})]})]})]})}function Bi(){const{role:e}=bn(),{addToast:t}=Be(),[n,r]=x.useState("0"),[o,a]=x.useState({}),[i,c]=x.useState({}),[l,u]=x.useState(!1),d=Rt(),{data:h,isLoading:f,error:p}=qe({queryKey:["config"],queryFn:async()=>{const j=await Ft("/0/api/config");return j.csrf_token&&yn(j.csrf_token),j}}),v=xn(),{data:b}=vn(),{data:w}=Zn(Number(n));x.useEffect(()=>{a({}),c({})},[n]);const g=x.useCallback((j,I)=>{a(Y=>({...Y,[j]:I}));const M=oi(j,String(I));c(Y=>{if(M.success){const{[j]:K,...L}=Y;return L}else return{...Y,[j]:M.error??"Invalid value"}})},[]),k=Object.keys(o).length>0,V=Object.keys(i).length>0,q=x.useMemo(()=>{if(!h)return{};const j=h.configuration.default||{};if(n==="0")return j;{const I=h.configuration[`cam${n}`]||{};return{...j,...I}}},[h,n]),O=x.useMemo(()=>{if(!h)return{};const j={...q};for(const[I,M]of Object.entries(o))j[I]?j[I]={...j[I],value:M}:j[I]={value:M,enabled:!0,category:0,type:"string"};return j},[h,q,o]),oe=async()=>{if(!k){t("No changes to save","info");return}if(V){t("Please fix validation errors before saving","error");return}u(!0);const j=parseInt(n,10);try{const I=await v.mutateAsync({camId:j,changes:o});await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]});const M=I?.summary,Y=I?.applied||[];if(!M)t(`Saved ${Object.keys(o).length} setting(s)`,"success"),a({});else{const K=Y.filter(L=>!L.error&&L.hot_reload===!1).map(L=>L.param);if(K.length>0){t(`Restarting camera to apply ${K.length} setting(s): ${K.join(", ")}...`,"info"),a({});const L=await _n(j);Rn(j),window.dispatchEvent(new CustomEvent(Fn,{detail:{cameraId:j}})),await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]}),L?t(`Applied ${K.length} setting(s). Camera restarted successfully.`,"success"):t("Settings saved. Camera is restarting - refresh page if stream doesn't recover.","warning")}else if(M.errors>0){const L=Y.filter(W=>W.error).map(W=>W.param),$e=Y.filter(W=>!W.error).map(W=>W.param),be={};for(const[W,Me]of Object.entries(o))$e.includes(W)||(be[W]=Me);a(be),M.success>0?t(`Saved ${M.success} setting(s). ${M.errors} failed: ${L.join(", ")}`,"warning"):t(`Failed to save settings: ${L.join(", ")}`,"error")}else t(`Successfully saved ${M.success} setting(s)`,"success"),a({})}}catch(I){console.error("Failed to save settings:",I),t("Failed to save settings. Check browser console for details.","error")}finally{u(!1)}},ee=()=>{a({}),c({}),t("Changes discarded","info")},U=j=>i[j];return e!=="admin"?s.jsx("div",{className:"p-4 sm:p-6",children:s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[s.jsx("svg",{className:"w-16 h-16 mx-auto text-yellow-500 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),s.jsx("h1",{className:"text-2xl font-bold mb-2",children:"Admin Access Required"}),s.jsx("p",{className:"text-gray-400",children:"You must be logged in as an administrator to access settings."})]})}):f?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsxs("div",{className:"animate-pulse",children:[s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"}),s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"})]})]}):p?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsx("div",{className:"bg-danger/10 border border-danger rounded-lg p-4",children:s.jsx("p",{className:"text-danger",children:"Failed to load configuration"})})]}):h?s.jsxs("div",{className:"p-6",children:[s.jsx("div",{className:"sticky top-[73px] z-40 -mx-6 px-6 py-3 bg-surface/95 backdrop-blur border-b border-gray-800 mb-6",children:s.jsxs("div",{className:"flex items-center justify-between",children:[s.jsxs("div",{className:"flex items-center gap-4",children:[s.jsx("h2",{className:"text-2xl font-bold",children:"Settings"}),s.jsx("div",{children:s.jsxs("select",{value:n,onChange:j=>r(j.target.value),className:"px-3 py-1.5 bg-surface-elevated border border-gray-700 rounded-lg text-sm",children:[s.jsx("option",{value:"0",children:"Global Settings"}),h.cameras&&Object.entries(h.cameras).map(([j,I])=>{if(j==="count"||typeof I=="number")return null;const M=I;return s.jsx("option",{value:String(M.id),children:M.name||`Camera ${M.id}`},M.id)})]})})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[k&&!V&&s.jsx("span",{className:"text-yellow-200 text-sm",children:"Unsaved changes"}),V&&s.jsx("span",{className:"text-red-200 text-sm",children:"Fix errors below"}),k&&s.jsx("button",{onClick:ee,disabled:l,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded-lg transition-colors disabled:opacity-50",children:"Discard"}),s.jsx("button",{onClick:oe,disabled:!k||l||V,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:l?"Saving...":k?"Save Changes":"Saved"})]})]})}),n==="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-6",children:s.jsx("p",{className:"text-sm text-blue-200",children:"Global settings apply to the Motion daemon and web server. To configure camera-specific settings, select a camera from the dropdown above."})}),s.jsx(li,{config:O,onChange:g,getError:U,originalConfig:q,systemStatus:b}),s.jsx(Ri,{}),s.jsx(E,{title:"About",description:"Motion version information",collapsible:!0,defaultOpen:!1,children:s.jsxs("p",{className:"text-sm text-gray-400",children:["Motion Version: ",h.version]})})]}),n!=="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-surface-elevated rounded-lg p-4 mb-6",children:s.jsx(Dn,{cameraId:Number(n),readOnly:!1})}),s.jsx(ui,{config:O,onChange:g,getError:U}),s.jsx(di,{config:O,onChange:g,getError:U,capabilities:w,originalConfig:q}),s.jsx(_i,{config:O,onChange:g,getError:U}),s.jsx(pi,{config:O,onChange:g,getError:U}),s.jsx(gi,{config:O,onChange:g,getError:U}),s.jsx(fi,{config:O,onChange:g,getError:U}),s.jsx(Li,{cameraId:parseInt(n,10)}),s.jsx(Zi,{config:O,onChange:g,getError:U}),s.jsx(yi,{config:O,onChange:g,getError:U,originalConfig:q}),s.jsx(mi,{config:O,onChange:g,getError:U}),s.jsx(Hi,{config:O,onChange:g,getError:U}),s.jsx(Vi,{config:O,onChange:g,getError:U}),s.jsx(Ui,{})]})]}):null}export{Bi as Settings}; diff --git a/data/webui/assets/index-BK6lPNRb.css b/data/webui/assets/index-BK6lPNRb.css new file mode 100644 index 00000000..0eed9af4 --- /dev/null +++ b/data/webui/assets/index-BK6lPNRb.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}body{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-1{left:.25rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-4{top:1rem}.top-\[73px\]{top:73px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[101\]{z-index:101}.z-\[150\]{z-index:150}.z-\[60\]{z-index:60}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1\.5{margin-top:-.375rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-\[6px\]{height:6px}.h-auto{height:auto}.h-full{height:100%}.max-h-\[70vh\]{max-height:70vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[250px\]{min-width:250px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-grab{cursor:grab}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-px{gap:1px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-blue-500\/20{border-color:#3b82f633}.border-blue-500\/30{border-color:#3b82f64d}.border-danger{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-danger\/30{border-color:#ef44444d}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-700\/50{border-color:#37415180}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-600\/30{border-color:#dc26264d}.border-surface{--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.border-surface-elevated{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-yellow-500\/50{border-color:#eab30880}.border-yellow-600\/30{border-color:#ca8a044d}.border-yellow-700{--tw-border-opacity: 1;border-color:rgb(161 98 7 / var(--tw-border-opacity, 1))}.bg-amber-950\/30{background-color:#451a034d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600\/20{background-color:#2563eb33}.bg-blue-950\/30{background-color:#1725544d}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-danger\/10{background-color:#ef44441a}.bg-danger\/20{background-color:#ef444433}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600\/20{background-color:#4b556333}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-950\/30{background-color:#052e164d}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-primary\/20{background-color:#3b82f633}.bg-primary\/30{background-color:#3b82f64d}.bg-primary\/70{background-color:#3b82f6b3}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/10{background-color:#dc26261a}.bg-red-600\/20{background-color:#dc262633}.bg-red-600\/80{background-color:#dc2626cc}.bg-red-950\/30{background-color:#450a0a4d}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.bg-surface-elevated{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-surface-hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.bg-surface\/95{background-color:#1a1a1af2}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.bg-yellow-600\/10{background-color:#ca8a041a}.bg-yellow-600\/20{background-color:#ca8a0433}.bg-yellow-900\/30{background-color:#713f124d}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-4{padding-left:1rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-300\/80{color:#93c5fdcc}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in{animation:slide-in .3s ease-out}.hover\:border-danger:hover{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.hover\:bg-blue-600\/30:hover{background-color:#2563eb4d}.hover\:bg-danger\/20:hover{background-color:#ef444433}.hover\:bg-danger\/30:hover{background-color:#ef44444d}.hover\:bg-danger\/80:hover{background-color:#ef4444cc}.hover\:bg-primary-hover:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-red-600\/40:hover{background-color:#dc262666}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-elevated:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-hover:hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-600\/30:hover{background-color:#ca8a044d}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-primary:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-2:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-primary:hover{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-yellow-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(234 179 8 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width: 0px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-primary\/30:disabled{background-color:#3b82f64d}.disabled\:bg-surface\/30:disabled{background-color:#1a1a1a4d}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group:focus-within .group-focus-within\:opacity-100{opacity:1}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:mb-6{margin-bottom:1.5rem}.sm\:inline{display:inline}.sm\:hidden{display:none}.sm\:gap-6{gap:1.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:py-8{padding-top:2rem;padding-bottom:2rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/data/webui/assets/index-DCzq8GhF.js b/data/webui/assets/index-DCzq8GhF.js new file mode 100644 index 00000000..d743da22 --- /dev/null +++ b/data/webui/assets/index-DCzq8GhF.js @@ -0,0 +1,17 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-D701LIV_.js","assets/parameterMappings-CUuSfEkB.js","assets/Settings-Ca5UNPDy.js"])))=>i.map(i=>d[i]); +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))r(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const m of h.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function c(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function r(d){if(d.ep)return;d.ep=!0;const h=c(d);fetch(d.href,h)}})();function om(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var tr={exports:{}},kn={};var Hd;function E0(){if(Hd)return kn;Hd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function c(r,d,h){var m=null;if(h!==void 0&&(m=""+h),d.key!==void 0&&(m=""+d.key),"key"in d){h={};for(var v in d)v!=="key"&&(h[v]=d[v])}else h=d;return d=h.ref,{$$typeof:n,type:r,key:m,ref:d!==void 0?d:null,props:h}}return kn.Fragment=s,kn.jsx=c,kn.jsxs=c,kn}var Bd;function T0(){return Bd||(Bd=1,tr.exports=E0()),tr.exports}var _=T0(),er={exports:{}},P={};var Qd;function x0(){if(Qd)return P;Qd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),m=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),D=Symbol.iterator;function q(b){return b===null||typeof b!="object"?null:(b=D&&b[D]||b["@@iterator"],typeof b=="function"?b:null)}var H={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,Y={};function Q(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}Q.prototype.isReactComponent={},Q.prototype.setState=function(b,w){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,w,"setState")},Q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function X(){}X.prototype=Q.prototype;function J(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}var ht=J.prototype=new X;ht.constructor=J,B(ht,Q.prototype),ht.isPureReactComponent=!0;var ct=Array.isArray;function Rt(){}var F={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(b,w,G){var K=G.ref;return{$$typeof:n,type:b,key:w,ref:K!==void 0?K:null,props:G}}function Qt(b,w){return zt(b.type,w,b.props)}function $t(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function te(b){var w={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(G){return w[G]})}var Yl=/\/+/g;function Ze(b,w){return typeof b=="object"&&b!==null&&b.key!=null?te(""+b.key):w.toString(36)}function Ne(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Rt,Rt):(b.status="pending",b.then(function(w){b.status==="pending"&&(b.status="fulfilled",b.value=w)},function(w){b.status==="pending"&&(b.status="rejected",b.reason=w)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function U(b,w,G,K,I){var lt=typeof b;(lt==="undefined"||lt==="boolean")&&(b=null);var yt=!1;if(b===null)yt=!0;else switch(lt){case"bigint":case"string":case"number":yt=!0;break;case"object":switch(b.$$typeof){case n:case s:yt=!0;break;case T:return yt=b._init,U(yt(b._payload),w,G,K,I)}}if(yt)return I=I(b),yt=K===""?"."+Ze(b,0):K,ct(I)?(G="",yt!=null&&(G=yt.replace(Yl,"$&/")+"/"),U(I,w,G,"",function(tn){return tn})):I!=null&&($t(I)&&(I=Qt(I,G+(I.key==null||b&&b.key===I.key?"":(""+I.key).replace(Yl,"$&/")+"/")+yt)),w.push(I)),1;yt=0;var Wt=K===""?".":K+":";if(ct(b))for(var Ut=0;Ut>>1,Tt=U[gt];if(0>>1;gtd(G,W))Kd(I,G)?(U[gt]=I,U[K]=W,gt=K):(U[gt]=G,U[w]=W,gt=w);else if(Kd(I,W))U[gt]=I,U[K]=W,gt=K;else break t}}return L}function d(U,L){var W=U.sortIndex-L.sortIndex;return W!==0?W:U.id-L.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;n.unstable_now=function(){return h.now()}}else{var m=Date,v=m.now();n.unstable_now=function(){return m.now()-v}}var p=[],y=[],T=1,x=null,D=3,q=!1,H=!1,B=!1,Y=!1,Q=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,J=typeof setImmediate<"u"?setImmediate:null;function ht(U){for(var L=c(y);L!==null;){if(L.callback===null)r(y);else if(L.startTime<=U)r(y),L.sortIndex=L.expirationTime,s(p,L);else break;L=c(y)}}function ct(U){if(B=!1,ht(U),!H)if(c(p)!==null)H=!0,Rt||(Rt=!0,te());else{var L=c(y);L!==null&&Ne(ct,L.startTime-U)}}var Rt=!1,F=-1,rt=5,zt=-1;function Qt(){return Y?!0:!(n.unstable_now()-ztU&&Qt());){var gt=x.callback;if(typeof gt=="function"){x.callback=null,D=x.priorityLevel;var Tt=gt(x.expirationTime<=U);if(U=n.unstable_now(),typeof Tt=="function"){x.callback=Tt,ht(U),L=!0;break e}x===c(p)&&r(p),ht(U)}else r(p);x=c(p)}if(x!==null)L=!0;else{var b=c(y);b!==null&&Ne(ct,b.startTime-U),L=!1}}break t}finally{x=null,D=W,q=!1}L=void 0}}finally{L?te():Rt=!1}}}var te;if(typeof J=="function")te=function(){J($t)};else if(typeof MessageChannel<"u"){var Yl=new MessageChannel,Ze=Yl.port2;Yl.port1.onmessage=$t,te=function(){Ze.postMessage(null)}}else te=function(){Q($t,0)};function Ne(U,L){F=Q(function(){U(n.unstable_now())},L)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(U){U.callback=null},n.unstable_forceFrameRate=function(U){0>U||125gt?(U.sortIndex=W,s(y,U),c(p)===null&&U===c(y)&&(B?(X(F),F=-1):B=!0,Ne(ct,W-gt))):(U.sortIndex=Tt,s(p,U),H||q||(H=!0,Rt||(Rt=!0,te()))),U},n.unstable_shouldYield=Qt,n.unstable_wrapCallback=function(U){var L=D;return function(){var W=D;D=L;try{return U.apply(this,arguments)}finally{D=W}}}})(nr)),nr}var Gd;function A0(){return Gd||(Gd=1,ar.exports=O0()),ar.exports}var ur={exports:{}},Ft={};var Xd;function C0(){if(Xd)return Ft;Xd=1;var n=gr();function s(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),ur.exports=C0(),ur.exports}var Kd;function z0(){if(Kd)return Fn;Kd=1;var n=A0(),s=gr(),c=M0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=gt[Tt],gt[Tt]=null,Tt--)}function G(t,e){Tt++,gt[Tt]=t.current,t.current=e}var K=b(null),I=b(null),lt=b(null),yt=b(null);function Wt(t,e){switch(G(lt,e),G(I,t),G(K,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?id(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=id(e),t=sd(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}w(K),G(K,t)}function Ut(){w(K),w(I),w(lt)}function tn(t){t.memoizedState!==null&&G(yt,t);var e=K.current,l=sd(e,t.type);e!==l&&(G(I,t),G(K,l))}function uu(t){I.current===t&&(w(K),w(I)),yt.current===t&&(w(yt),Zn._currentValue=W)}var qi,jr;function Gl(t){if(qi===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);qi=e&&e[1]||"",jr=-1)":-1u||g[a]!==A[u]){var z=` +`+g[a].replace(" at new "," at ");return t.displayName&&z.includes("")&&(z=z.replace("",t.displayName)),z}while(1<=a&&0<=u);break}}}finally{Hi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Gl(l):""}function Pm(t,e){switch(t.tag){case 26:case 27:case 5:return Gl(t.type);case 16:return Gl("Lazy");case 13:return t.child!==e&&e!==null?Gl("Suspense Fallback"):Gl("Suspense");case 19:return Gl("SuspenseList");case 0:case 15:return Bi(t.type,!1);case 11:return Bi(t.type.render,!1);case 1:return Bi(t.type,!0);case 31:return Gl("Activity");default:return""}}function wr(t){try{var e="",l=null;do e+=Pm(t,l),l=t,t=t.return;while(t);return e}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var Qi=Object.prototype.hasOwnProperty,Li=n.unstable_scheduleCallback,Yi=n.unstable_cancelCallback,Im=n.unstable_shouldYield,ty=n.unstable_requestPaint,re=n.unstable_now,ey=n.unstable_getCurrentPriorityLevel,qr=n.unstable_ImmediatePriority,Hr=n.unstable_UserBlockingPriority,iu=n.unstable_NormalPriority,ly=n.unstable_LowPriority,Br=n.unstable_IdlePriority,ay=n.log,ny=n.unstable_setDisableYieldValue,en=null,fe=null;function hl(t){if(typeof ay=="function"&&ny(t),fe&&typeof fe.setStrictMode=="function")try{fe.setStrictMode(en,t)}catch{}}var oe=Math.clz32?Math.clz32:sy,uy=Math.log,iy=Math.LN2;function sy(t){return t>>>=0,t===0?32:31-(uy(t)/iy|0)|0}var su=256,cu=262144,ru=4194304;function Xl(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function fu(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,i=t.suspendedLanes,f=t.pingedLanes;t=t.warmLanes;var o=a&134217727;return o!==0?(a=o&~i,a!==0?u=Xl(a):(f&=o,f!==0?u=Xl(f):l||(l=o&~t,l!==0&&(u=Xl(l))))):(o=a&~i,o!==0?u=Xl(o):f!==0?u=Xl(f):l||(l=a&~t,l!==0&&(u=Xl(l)))),u===0?0:e!==0&&e!==u&&(e&i)===0&&(i=u&-u,l=e&-e,i>=l||i===32&&(l&4194048)!==0)?e:u}function ln(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function cy(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Qr(){var t=ru;return ru<<=1,(ru&62914560)===0&&(ru=4194304),t}function Gi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function an(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ry(t,e,l,a,u,i){var f=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var o=t.entanglements,g=t.expirationTimes,A=t.hiddenUpdates;for(l=f&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var yy=/[\n"\\]/g;function Te(t){return t.replace(yy,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function ki(t,e,l,a,u,i,f,o){t.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?t.type=f:t.removeAttribute("type"),e!=null?f==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Ee(e)):t.value!==""+Ee(e)&&(t.value=""+Ee(e)):f!=="submit"&&f!=="reset"||t.removeAttribute("value"),e!=null?Fi(t,f,Ee(e)):l!=null?Fi(t,f,Ee(l)):a!=null&&t.removeAttribute("value"),u==null&&i!=null&&(t.defaultChecked=!!i),u!=null&&(t.checked=u&&typeof u!="function"&&typeof u!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?t.name=""+Ee(o):t.removeAttribute("name")}function Pr(t,e,l,a,u,i,f,o){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.type=i),e!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||e!=null)){Ji(t);return}l=l!=null?""+Ee(l):"",e=e!=null?""+Ee(e):l,o||e===t.value||(t.value=e),t.defaultValue=e}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=o?t.checked:!!a,t.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(t.name=f),Ji(t)}function Fi(t,e,l){e==="number"&&du(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function pa(t,e,l,a){if(t=t.options,e){e={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ts=!1;if(Je)try{var cn={};Object.defineProperty(cn,"passive",{get:function(){ts=!0}}),window.addEventListener("test",cn,cn),window.removeEventListener("test",cn,cn)}catch{ts=!1}var ml=null,es=null,yu=null;function uf(){if(yu)return yu;var t,e=es,l=e.length,a,u="value"in ml?ml.value:ml.textContent,i=u.length;for(t=0;t=on),hf=" ",df=!1;function mf(t,e){switch(t){case"keyup":return Xy.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function yf(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ea=!1;function Ky(t,e){switch(t){case"compositionend":return yf(e);case"keypress":return e.which!==32?null:(df=!0,hf);case"textInput":return t=e.data,t===hf&&df?null:t;default:return null}}function Vy(t,e){if(Ea)return t==="compositionend"||!is&&mf(t,e)?(t=uf(),yu=es=ml=null,Ea=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=xf(l)}}function Of(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?Of(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function Af(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=du(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=du(t.document)}return e}function rs(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var tv=Je&&"documentMode"in document&&11>=document.documentMode,Ta=null,fs=null,yn=null,os=!1;function Cf(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;os||Ta==null||Ta!==du(a)||(a=Ta,"selectionStart"in a&&rs(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),yn&&mn(yn,a)||(yn=a,a=ci(fs,"onSelect"),0>=f,u-=f,Be=1<<32-oe(e)+u|l<et?(it=V,V=null):it=V.sibling;var ot=C(E,V,O[et],N);if(ot===null){V===null&&(V=it);break}t&&V&&ot.alternate===null&&e(E,V),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot,V=it}if(et===O.length)return l(E,V),st&&Fe(E,et),k;if(V===null){for(;etet?(it=V,V=null):it=V.sibling;var ql=C(E,V,ot.value,N);if(ql===null){V===null&&(V=it);break}t&&V&&ql.alternate===null&&e(E,V),S=i(ql,S,et),ft===null?k=ql:ft.sibling=ql,ft=ql,V=it}if(ot.done)return l(E,V),st&&Fe(E,et),k;if(V===null){for(;!ot.done;et++,ot=O.next())ot=j(E,ot.value,N),ot!==null&&(S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return st&&Fe(E,et),k}for(V=a(V);!ot.done;et++,ot=O.next())ot=M(V,E,et,ot.value,N),ot!==null&&(t&&ot.alternate!==null&&V.delete(ot.key===null?et:ot.key),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return t&&V.forEach(function(b0){return e(E,b0)}),st&&Fe(E,et),k}function Et(E,S,O,N){if(typeof O=="object"&&O!==null&&O.type===B&&O.key===null&&(O=O.props.children),typeof O=="object"&&O!==null){switch(O.$$typeof){case q:t:{for(var k=O.key;S!==null;){if(S.key===k){if(k=O.type,k===B){if(S.tag===7){l(E,S.sibling),N=u(S,O.props.children),N.return=E,E=N;break t}}else if(S.elementType===k||typeof k=="object"&&k!==null&&k.$$typeof===rt&&ta(k)===S.type){l(E,S.sibling),N=u(S,O.props),En(N,O),N.return=E,E=N;break t}l(E,S);break}else e(E,S);S=S.sibling}O.type===B?(N=Fl(O.props.children,E.mode,N,O.key),N.return=E,E=N):(N=Ou(O.type,O.key,O.props,null,E.mode,N),En(N,O),N.return=E,E=N)}return f(E);case H:t:{for(k=O.key;S!==null;){if(S.key===k)if(S.tag===4&&S.stateNode.containerInfo===O.containerInfo&&S.stateNode.implementation===O.implementation){l(E,S.sibling),N=u(S,O.children||[]),N.return=E,E=N;break t}else{l(E,S);break}else e(E,S);S=S.sibling}N=gs(O,E.mode,N),N.return=E,E=N}return f(E);case rt:return O=ta(O),Et(E,S,O,N)}if(Ne(O))return Z(E,S,O,N);if(te(O)){if(k=te(O),typeof k!="function")throw Error(r(150));return O=k.call(O),$(E,S,O,N)}if(typeof O.then=="function")return Et(E,S,Uu(O),N);if(O.$$typeof===J)return Et(E,S,Mu(E,O),N);Nu(E,O)}return typeof O=="string"&&O!==""||typeof O=="number"||typeof O=="bigint"?(O=""+O,S!==null&&S.tag===6?(l(E,S.sibling),N=u(S,O),N.return=E,E=N):(l(E,S),N=ps(O,E.mode,N),N.return=E,E=N),f(E)):l(E,S)}return function(E,S,O,N){try{bn=0;var k=Et(E,S,O,N);return Na=null,k}catch(V){if(V===Ua||V===_u)throw V;var ft=de(29,V,null,E.mode);return ft.lanes=N,ft.return=E,ft}}}var la=$f(!0),Wf=$f(!1),Sl=!1;function _s(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ds(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function bl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function El(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(dt&2)!==0){var u=a.pending;return u===null?e.next=e:(e.next=u.next,u.next=e),a.pending=e,e=Ru(t),jf(t,null,l),e}return xu(t,a,e,l),Ru(t)}function Tn(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Yr(t,l)}}function Us(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var u=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var f={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?u=i=f:i=i.next=f,l=l.next}while(l!==null);i===null?u=i=e:i=i.next=e}else u=i=e;l={baseState:a.baseState,firstBaseUpdate:u,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var Ns=!1;function xn(){if(Ns){var t=Da;if(t!==null)throw t}}function Rn(t,e,l,a){Ns=!1;var u=t.updateQueue;Sl=!1;var i=u.firstBaseUpdate,f=u.lastBaseUpdate,o=u.shared.pending;if(o!==null){u.shared.pending=null;var g=o,A=g.next;g.next=null,f===null?i=A:f.next=A,f=g;var z=t.alternate;z!==null&&(z=z.updateQueue,o=z.lastBaseUpdate,o!==f&&(o===null?z.firstBaseUpdate=A:o.next=A,z.lastBaseUpdate=g))}if(i!==null){var j=u.baseState;f=0,z=A=g=null,o=i;do{var C=o.lane&-536870913,M=C!==o.lane;if(M?(ut&C)===C:(a&C)===C){C!==0&&C===_a&&(Ns=!0),z!==null&&(z=z.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});t:{var Z=t,$=o;C=e;var Et=l;switch($.tag){case 1:if(Z=$.payload,typeof Z=="function"){j=Z.call(Et,j,C);break t}j=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=$.payload,C=typeof Z=="function"?Z.call(Et,j,C):Z,C==null)break t;j=x({},j,C);break t;case 2:Sl=!0}}C=o.callback,C!==null&&(t.flags|=64,M&&(t.flags|=8192),M=u.callbacks,M===null?u.callbacks=[C]:M.push(C))}else M={lane:C,tag:o.tag,payload:o.payload,callback:o.callback,next:null},z===null?(A=z=M,g=j):z=z.next=M,f|=C;if(o=o.next,o===null){if(o=u.shared.pending,o===null)break;M=o,o=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);z===null&&(g=j),u.baseState=g,u.firstBaseUpdate=A,u.lastBaseUpdate=z,i===null&&(u.shared.lanes=0),Al|=f,t.lanes=f,t.memoizedState=j}}function Pf(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function If(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;ti?i:8;var f=U.T,o={};U.T=o,Ps(t,!1,e,l);try{var g=u(),A=U.S;if(A!==null&&A(o,g),g!==null&&typeof g=="object"&&typeof g.then=="function"){var z=rv(g,a);Cn(t,e,z,ge(t))}else Cn(t,e,a,ge(t))}catch(j){Cn(t,e,{then:function(){},status:"rejected",reason:j},ge())}finally{L.p=i,f!==null&&o.types!==null&&(f.types=o.types),U.T=f}}function yv(){}function $s(t,e,l,a){if(t.tag!==5)throw Error(r(476));var u=Uo(t).queue;Do(t,u,e,W,l===null?yv:function(){return No(t),l(a)})}function Uo(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ie,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ie,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function No(t){var e=Uo(t);e.next===null&&(e=t.alternate.memoizedState),Cn(t,e.next.queue,{},ge())}function Ws(){return Vt(Zn)}function jo(){return jt().memoizedState}function wo(){return jt().memoizedState}function vv(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ge();t=bl(l);var a=El(e,t,l);a!==null&&(se(a,e,l),Tn(a,e,l)),e={cache:As()},t.payload=e;return}e=e.return}}function pv(t,e,l){var a=ge();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Xu(t)?Ho(e,l):(l=ys(t,e,l,a),l!==null&&(se(l,t,a),Bo(l,e,a)))}function qo(t,e,l){var a=ge();Cn(t,e,l,a)}function Cn(t,e,l,a){var u={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Xu(t))Ho(e,u);else{var i=t.alternate;if(t.lanes===0&&(i===null||i.lanes===0)&&(i=e.lastRenderedReducer,i!==null))try{var f=e.lastRenderedState,o=i(f,l);if(u.hasEagerState=!0,u.eagerState=o,he(o,f))return xu(t,e,u,0),xt===null&&Tu(),!1}catch{}if(l=ys(t,e,u,a),l!==null)return se(l,t,a),Bo(l,e,a),!0}return!1}function Ps(t,e,l,a){if(a={lane:2,revertLane:_c(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Xu(t)){if(e)throw Error(r(479))}else e=ys(t,l,a,2),e!==null&&se(e,t,2)}function Xu(t){var e=t.alternate;return t===tt||e!==null&&e===tt}function Ho(t,e){wa=qu=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function Bo(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Yr(t,l)}}var Mn={readContext:Vt,use:Qu,useCallback:_t,useContext:_t,useEffect:_t,useImperativeHandle:_t,useLayoutEffect:_t,useInsertionEffect:_t,useMemo:_t,useReducer:_t,useRef:_t,useState:_t,useDebugValue:_t,useDeferredValue:_t,useTransition:_t,useSyncExternalStore:_t,useId:_t,useHostTransitionStatus:_t,useFormState:_t,useActionState:_t,useOptimistic:_t,useMemoCache:_t,useCacheRefresh:_t};Mn.useEffectEvent=_t;var Qo={readContext:Vt,use:Qu,useCallback:function(t,e){return Pt().memoizedState=[t,e===void 0?null:e],t},useContext:Vt,useEffect:To,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Yu(4194308,4,Ao.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Yu(4194308,4,t,e)},useInsertionEffect:function(t,e){Yu(4,2,t,e)},useMemo:function(t,e){var l=Pt();e=e===void 0?null:e;var a=t();if(aa){hl(!0);try{t()}finally{hl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=Pt();if(l!==void 0){var u=l(e);if(aa){hl(!0);try{l(e)}finally{hl(!1)}}}else u=e;return a.memoizedState=a.baseState=u,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:u},a.queue=t,t=t.dispatch=pv.bind(null,tt,t),[a.memoizedState,t]},useRef:function(t){var e=Pt();return t={current:t},e.memoizedState=t},useState:function(t){t=Ks(t);var e=t.queue,l=qo.bind(null,tt,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:ks,useDeferredValue:function(t,e){var l=Pt();return Fs(l,t,e)},useTransition:function(){var t=Ks(!1);return t=Do.bind(null,tt,t.queue,!0,!1),Pt().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=tt,u=Pt();if(st){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),xt===null)throw Error(r(349));(ut&127)!==0||uo(a,e,l)}u.memoizedState=l;var i={value:l,getSnapshot:e};return u.queue=i,To(so.bind(null,a,i,t),[t]),a.flags|=2048,Ha(9,{destroy:void 0},io.bind(null,a,i,l,e),null),l},useId:function(){var t=Pt(),e=xt.identifierPrefix;if(st){var l=Qe,a=Be;l=(a&~(1<<32-oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=Hu++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?f.createElement(u,{is:a.is}):f.createElement(u)}}i[Zt]=e,i[ee]=a;t:for(f=e.child;f!==null;){if(f.tag===5||f.tag===6)i.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break t;for(;f.sibling===null;){if(f.return===null||f.return===e)break t;f=f.return}f.sibling.return=f.return,f=f.sibling}e.stateNode=i;t:switch(kt(i,u,a),u){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&el(e)}}return At(e),hc(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&el(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=lt.current,Ma(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,u=Kt,u!==null)switch(u.tag){case 27:case 5:a=u.memoizedProps}t[Zt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||nd(t.nodeValue,l)),t||pl(e,!0)}else t=ri(t).createTextNode(a),t[Zt]=e,e.stateNode=t}return At(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=Ma(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Zt]=e}else $l(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),t=!1}else l=Ts(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ye(e),e):(ye(e),null);if((e.flags&128)!==0)throw Error(r(558))}return At(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(u=Ma(e),a!==null&&a.dehydrated!==null){if(t===null){if(!u)throw Error(r(318));if(u=e.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(r(317));u[Zt]=e}else $l(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),u=!1}else u=Ts(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=u),u=!0;if(!u)return e.flags&256?(ye(e),e):(ye(e),null)}return ye(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,u=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(u=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==u&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),ku(e,e.updateQueue),At(e),null);case 4:return Ut(),t===null&&jc(e.stateNode.containerInfo),At(e),null;case 10:return We(e.type),At(e),null;case 19:if(w(Nt),a=e.memoizedState,a===null)return At(e),null;if(u=(e.flags&128)!==0,i=a.rendering,i===null)if(u)_n(a,!1);else{if(Dt!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(i=wu(t),i!==null){for(e.flags|=128,_n(a,!1),t=i.updateQueue,e.updateQueue=t,ku(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)wf(l,t),l=l.sibling;return G(Nt,Nt.current&1|2),st&&Fe(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&re()>Iu&&(e.flags|=128,u=!0,_n(a,!1),e.lanes=4194304)}else{if(!u)if(t=wu(i),t!==null){if(e.flags|=128,u=!0,t=t.updateQueue,e.updateQueue=t,ku(e,t),_n(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!st)return At(e),null}else 2*re()-a.renderingStartTime>Iu&&l!==536870912&&(e.flags|=128,u=!0,_n(a,!1),e.lanes=4194304);a.isBackwards?(i.sibling=e.child,e.child=i):(t=a.last,t!==null?t.sibling=i:e.child=i,a.last=i)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=re(),t.sibling=null,l=Nt.current,G(Nt,u?l&1|2:l&1),st&&Fe(e,a.treeForkCount),t):(At(e),null);case 22:case 23:return ye(e),ws(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(At(e),e.subtreeFlags&6&&(e.flags|=8192)):At(e),l=e.updateQueue,l!==null&&ku(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&w(Il),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),We(wt),At(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function Tv(t,e){switch(bs(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return We(wt),Ut(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return uu(e),null;case 31:if(e.memoizedState!==null){if(ye(e),e.alternate===null)throw Error(r(340));$l()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ye(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));$l()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return w(Nt),null;case 4:return Ut(),null;case 10:return We(e.type),null;case 22:case 23:return ye(e),ws(),t!==null&&w(Il),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return We(wt),null;case 25:return null;default:return null}}function rh(t,e){switch(bs(e),e.tag){case 3:We(wt),Ut();break;case 26:case 27:case 5:uu(e);break;case 4:Ut();break;case 31:e.memoizedState!==null&&ye(e);break;case 13:ye(e);break;case 19:w(Nt);break;case 10:We(e.type);break;case 22:case 23:ye(e),ws(),t!==null&&w(Il);break;case 24:We(wt)}}function Dn(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var u=a.next;l=u;do{if((l.tag&t)===t){a=void 0;var i=l.create,f=l.inst;a=i(),f.destroy=a}l=l.next}while(l!==u)}}catch(o){pt(e,e.return,o)}}function Rl(t,e,l){try{var a=e.updateQueue,u=a!==null?a.lastEffect:null;if(u!==null){var i=u.next;a=i;do{if((a.tag&t)===t){var f=a.inst,o=f.destroy;if(o!==void 0){f.destroy=void 0,u=e;var g=l,A=o;try{A()}catch(z){pt(u,g,z)}}}a=a.next}while(a!==i)}}catch(z){pt(e,e.return,z)}}function fh(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{If(e,l)}catch(a){pt(t,t.return,a)}}}function oh(t,e,l){l.props=na(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){pt(t,e,a)}}function Un(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(u){pt(t,e,u)}}function Le(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(u){pt(t,e,u)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(u){pt(t,e,u)}else l.current=null}function hh(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(u){pt(t,t.return,u)}}function dc(t,e,l){try{var a=t.stateNode;Zv(a,t.type,l,e),a[ee]=e}catch(u){pt(t,t.return,u)}}function dh(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&Dl(t.type)||t.tag===4}function mc(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||dh(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&Dl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function yc(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=Ve));else if(a!==4&&(a===27&&Dl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(yc(t,e,l),t=t.sibling;t!==null;)yc(t,e,l),t=t.sibling}function Fu(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&Dl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Fu(t,e,l),t=t.sibling;t!==null;)Fu(t,e,l),t=t.sibling}function mh(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,u=e.attributes;u.length;)e.removeAttributeNode(u[0]);kt(e,a,l),e[Zt]=t,e[ee]=l}catch(i){pt(t,t.return,i)}}var ll=!1,Bt=!1,vc=!1,yh=typeof WeakSet=="function"?WeakSet:Set,Xt=null;function xv(t,e){if(t=t.containerInfo,Hc=vi,t=Af(t),rs(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var u=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break t}var f=0,o=-1,g=-1,A=0,z=0,j=t,C=null;e:for(;;){for(var M;j!==l||u!==0&&j.nodeType!==3||(o=f+u),j!==i||a!==0&&j.nodeType!==3||(g=f+a),j.nodeType===3&&(f+=j.nodeValue.length),(M=j.firstChild)!==null;)C=j,j=M;for(;;){if(j===t)break e;if(C===l&&++A===u&&(o=f),C===i&&++z===a&&(g=f),(M=j.nextSibling)!==null)break;j=C,C=j.parentNode}j=M}l=o===-1||g===-1?null:{start:o,end:g}}else l=null}l=l||{start:0,end:0}}else l=null;for(Bc={focusedElem:t,selectionRange:l},vi=!1,Xt=e;Xt!==null;)if(e=Xt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Xt=t;else for(;Xt!==null;){switch(e=Xt,i=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),kt(i,a,l),i[Zt]=t,Gt(i),a=i;break t;case"link":var f=Ed("link","href",u).get(a+(l.href||""));if(f){for(var o=0;oEt&&(f=Et,Et=$,$=f);var E=Rf(o,$),S=Rf(o,Et);if(E&&S&&(M.rangeCount!==1||M.anchorNode!==E.node||M.anchorOffset!==E.offset||M.focusNode!==S.node||M.focusOffset!==S.offset)){var O=j.createRange();O.setStart(E.node,E.offset),M.removeAllRanges(),$>Et?(M.addRange(O),M.extend(S.node,S.offset)):(O.setEnd(S.node,S.offset),M.addRange(O))}}}}for(j=[],M=o;M=M.parentNode;)M.nodeType===1&&j.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,U.T=null,l=xc,xc=null;var i=Ml,f=sl;if(Lt=0,Ga=Ml=null,sl=0,(dt&6)!==0)throw Error(r(331));var o=dt;if(dt|=4,Ah(i.current),xh(i,i.current,f,l),dt=o,Bn(0,!1),fe&&typeof fe.onPostCommitFiberRoot=="function")try{fe.onPostCommitFiberRoot(en,i)}catch{}return!0}finally{L.p=u,U.T=a,Zh(t,e)}}function Vh(t,e,l){e=Re(l,e),e=lc(t.stateNode,e,2),t=El(t,e,2),t!==null&&(an(t,2),Ye(t))}function pt(t,e,l){if(t.tag===3)Vh(t,t,l);else for(;e!==null;){if(e.tag===3){Vh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Cl===null||!Cl.has(a))){t=Re(l,t),l=Jo(2),a=El(e,l,2),a!==null&&(ko(l,a,e,t),an(a,2),Ye(a));break}}e=e.return}}function Cc(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new Av;var u=new Set;a.set(e,u)}else u=a.get(e),u===void 0&&(u=new Set,a.set(e,u));u.has(l)||(Sc=!0,u.add(l),t=Dv.bind(null,t,e,l),e.then(t,t))}function Dv(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,xt===t&&(ut&l)===l&&(Dt===4||Dt===3&&(ut&62914560)===ut&&300>re()-Pu?(dt&2)===0&&Xa(t,0):bc|=l,Ya===ut&&(Ya=0)),Ye(t)}function Jh(t,e){e===0&&(e=Qr()),t=kl(t,e),t!==null&&(an(t,e),Ye(t))}function Uv(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Jh(t,l)}function Nv(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,u=t.memoizedState;u!==null&&(l=u.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),Jh(t,l)}function jv(t,e){return Li(t,e)}var ui=null,Ka=null,Mc=!1,ii=!1,zc=!1,_l=0;function Ye(t){t!==Ka&&t.next===null&&(Ka===null?ui=Ka=t:Ka=Ka.next=t),ii=!0,Mc||(Mc=!0,qv())}function Bn(t,e){if(!zc&&ii){zc=!0;do for(var l=!1,a=ui;a!==null;){if(t!==0){var u=a.pendingLanes;if(u===0)var i=0;else{var f=a.suspendedLanes,o=a.pingedLanes;i=(1<<31-oe(42|t)+1)-1,i&=u&~(f&~o),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Wh(a,i))}else i=ut,i=fu(a,a===xt?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||ln(a,i)||(l=!0,Wh(a,i));a=a.next}while(l);zc=!1}}function wv(){kh()}function kh(){ii=Mc=!1;var t=0;_l!==0&&Vv()&&(t=_l);for(var e=re(),l=null,a=ui;a!==null;){var u=a.next,i=Fh(a,e);i===0?(a.next=null,l===null?ui=u:l.next=u,u===null&&(Ka=l)):(l=a,(t!==0||(i&3)!==0)&&(ii=!0)),a=u}Lt!==0&&Lt!==5||Bn(t),_l!==0&&(_l=0)}function Fh(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,u=t.expirationTimes,i=t.pendingLanes&-62914561;0o)break;var z=g.transferSize,j=g.initiatorType;z&&ud(j)&&(g=g.responseEnd,f+=z*(g"u"?null:document;function pd(t,e,l){var a=Va;if(a&&typeof e=="string"&&e){var u=Te(e);u='link[rel="'+t+'"][href="'+u+'"]',typeof l=="string"&&(u+='[crossorigin="'+l+'"]'),vd.has(u)||(vd.add(u),t={rel:t,crossOrigin:l,href:e},a.querySelector(u)===null&&(e=a.createElement("link"),kt(e,"link",t),Gt(e),a.head.appendChild(e)))}}function e0(t){cl.D(t),pd("dns-prefetch",t,null)}function l0(t,e){cl.C(t,e),pd("preconnect",t,e)}function a0(t,e,l){cl.L(t,e,l);var a=Va;if(a&&t&&e){var u='link[rel="preload"][as="'+Te(e)+'"]';e==="image"&&l&&l.imageSrcSet?(u+='[imagesrcset="'+Te(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(u+='[imagesizes="'+Te(l.imageSizes)+'"]')):u+='[href="'+Te(t)+'"]';var i=u;switch(e){case"style":i=Ja(t);break;case"script":i=ka(t)}_e.has(i)||(t=x({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),_e.set(i,t),a.querySelector(u)!==null||e==="style"&&a.querySelector(Gn(i))||e==="script"&&a.querySelector(Xn(i))||(e=a.createElement("link"),kt(e,"link",t),Gt(e),a.head.appendChild(e)))}}function n0(t,e){cl.m(t,e);var l=Va;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",u='link[rel="modulepreload"][as="'+Te(a)+'"][href="'+Te(t)+'"]',i=u;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=ka(t)}if(!_e.has(i)&&(t=x({rel:"modulepreload",href:t},e),_e.set(i,t),l.querySelector(u)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Xn(i)))return}a=l.createElement("link"),kt(a,"link",t),Gt(a),l.head.appendChild(a)}}}function u0(t,e,l){cl.S(t,e,l);var a=Va;if(a&&t){var u=ya(a).hoistableStyles,i=Ja(t);e=e||"default";var f=u.get(i);if(!f){var o={loading:0,preload:null};if(f=a.querySelector(Gn(i)))o.loading=5;else{t=x({rel:"stylesheet",href:t,"data-precedence":e},l),(l=_e.get(i))&&Kc(t,l);var g=f=a.createElement("link");Gt(g),kt(g,"link",t),g._p=new Promise(function(A,z){g.onload=A,g.onerror=z}),g.addEventListener("load",function(){o.loading|=1}),g.addEventListener("error",function(){o.loading|=2}),o.loading|=4,oi(f,e,a)}f={type:"stylesheet",instance:f,count:1,state:o},u.set(i,f)}}}function i0(t,e){cl.X(t,e);var l=Va;if(l&&t){var a=ya(l).hoistableScripts,u=ka(t),i=a.get(u);i||(i=l.querySelector(Xn(u)),i||(t=x({src:t,async:!0},e),(e=_e.get(u))&&Vc(t,e),i=l.createElement("script"),Gt(i),kt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function s0(t,e){cl.M(t,e);var l=Va;if(l&&t){var a=ya(l).hoistableScripts,u=ka(t),i=a.get(u);i||(i=l.querySelector(Xn(u)),i||(t=x({src:t,async:!0,type:"module"},e),(e=_e.get(u))&&Vc(t,e),i=l.createElement("script"),Gt(i),kt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function gd(t,e,l,a){var u=(u=lt.current)?fi(u):null;if(!u)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=Ja(l.href),l=ya(u).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=Ja(l.href);var i=ya(u).hoistableStyles,f=i.get(t);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(t,f),(i=u.querySelector(Gn(t)))&&!i._p&&(f.instance=i,f.state.loading=5),_e.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},_e.set(t,l),i||c0(u,t,l,f.state))),e&&a===null)throw Error(r(528,""));return f}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=ka(l),l=ya(u).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function Ja(t){return'href="'+Te(t)+'"'}function Gn(t){return'link[rel="stylesheet"]['+t+"]"}function Sd(t){return x({},t,{"data-precedence":t.precedence,precedence:null})}function c0(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),kt(e,"link",l),Gt(e),t.head.appendChild(e))}function ka(t){return'[src="'+Te(t)+'"]'}function Xn(t){return"script[async]"+t}function bd(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Te(l.href)+'"]');if(a)return e.instance=a,Gt(a),a;var u=x({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),Gt(a),kt(a,"style",u),oi(a,l.precedence,t),e.instance=a;case"stylesheet":u=Ja(l.href);var i=t.querySelector(Gn(u));if(i)return e.state.loading|=4,e.instance=i,Gt(i),i;a=Sd(l),(u=_e.get(u))&&Kc(a,u),i=(t.ownerDocument||t).createElement("link"),Gt(i);var f=i;return f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),kt(i,"link",a),e.state.loading|=4,oi(i,l.precedence,t),e.instance=i;case"script":return i=ka(l.src),(u=t.querySelector(Xn(i)))?(e.instance=u,Gt(u),u):(a=l,(u=_e.get(i))&&(a=x({},l),Vc(a,u)),t=t.ownerDocument||t,u=t.createElement("script"),Gt(u),kt(u,"link",a),t.head.appendChild(u),e.instance=u);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,oi(a,l.precedence,t));return e.instance}function oi(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=a.length?a[a.length-1]:null,i=u,f=0;f title"):null)}function r0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function xd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function f0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var u=Ja(a.href),i=e.querySelector(Gn(u));if(i){e=i._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=di.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=i,Gt(i);return}i=e.ownerDocument||e,a=Sd(a),(u=_e.get(u))&&Kc(a,u),i=i.createElement("link"),Gt(i);var f=i;f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),kt(i,"link",a),l.instance=i}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=di.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var Jc=0;function o0(t,e){return t.stylesheets&&t.count===0&&yi(t,t.stylesheets),0Jc?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(u)}}:null}function di(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)yi(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var mi=null;function yi(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,mi=new Map,e.forEach(h0,t),mi=null,di.call(t))}function h0(t,e){if(!(e.state.loading&4)){var l=mi.get(t);if(l)var a=l.get(null);else{l=new Map,mi.set(t,l);for(var u=t.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),lr.exports=z0(),lr.exports}var D0=_0();const U0=om(D0);var Jd="popstate";function N0(n={}){function s(r,d){let{pathname:h,search:m,hash:v}=r.location;return fr("",{pathname:h,search:m,hash:v},d.state&&d.state.usr||null,d.state&&d.state.key||"default")}function c(r,d){return typeof d=="string"?d:Pn(d)}return w0(s,c,null,n)}function Mt(n,s){if(n===!1||n===null||typeof n>"u")throw new Error(s)}function He(n,s){if(!n){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function j0(){return Math.random().toString(36).substring(2,10)}function kd(n,s){return{usr:n.state,key:n.key,idx:s}}function fr(n,s,c=null,r){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof s=="string"?Wa(s):s,state:c,key:s&&s.key||r||j0()}}function Pn({pathname:n="/",search:s="",hash:c=""}){return s&&s!=="?"&&(n+=s.charAt(0)==="?"?s:"?"+s),c&&c!=="#"&&(n+=c.charAt(0)==="#"?c:"#"+c),n}function Wa(n){let s={};if(n){let c=n.indexOf("#");c>=0&&(s.hash=n.substring(c),n=n.substring(0,c));let r=n.indexOf("?");r>=0&&(s.search=n.substring(r),n=n.substring(0,r)),n&&(s.pathname=n)}return s}function w0(n,s,c,r={}){let{window:d=document.defaultView,v5Compat:h=!1}=r,m=d.history,v="POP",p=null,y=T();y==null&&(y=0,m.replaceState({...m.state,idx:y},""));function T(){return(m.state||{idx:null}).idx}function x(){v="POP";let Y=T(),Q=Y==null?null:Y-y;y=Y,p&&p({action:v,location:B.location,delta:Q})}function D(Y,Q){v="PUSH";let X=fr(B.location,Y,Q);y=T()+1;let J=kd(X,y),ht=B.createHref(X);try{m.pushState(J,"",ht)}catch(ct){if(ct instanceof DOMException&&ct.name==="DataCloneError")throw ct;d.location.assign(ht)}h&&p&&p({action:v,location:B.location,delta:1})}function q(Y,Q){v="REPLACE";let X=fr(B.location,Y,Q);y=T();let J=kd(X,y),ht=B.createHref(X);m.replaceState(J,"",ht),h&&p&&p({action:v,location:B.location,delta:0})}function H(Y){return q0(Y)}let B={get action(){return v},get location(){return n(d,m)},listen(Y){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(Jd,x),p=Y,()=>{d.removeEventListener(Jd,x),p=null}},createHref(Y){return s(d,Y)},createURL:H,encodeLocation(Y){let Q=H(Y);return{pathname:Q.pathname,search:Q.search,hash:Q.hash}},push:D,replace:q,go(Y){return m.go(Y)}};return B}function q0(n,s=!1){let c="http://localhost";typeof window<"u"&&(c=window.location.origin!=="null"?window.location.origin:window.location.href),Mt(c,"No window.location.(origin|href) available to create URL");let r=typeof n=="string"?n:Pn(n);return r=r.replace(/ $/,"%20"),!s&&r.startsWith("//")&&(r=c+r),new URL(r,c)}function hm(n,s,c="/"){return H0(n,s,c,!1)}function H0(n,s,c,r){let d=typeof s=="string"?Wa(s):s,h=fl(d.pathname||"/",c);if(h==null)return null;let m=dm(n);B0(m);let v=null;for(let p=0;v==null&&p{let T={relativePath:y===void 0?m.path||"":y,caseSensitive:m.caseSensitive===!0,childrenIndex:v,route:m};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&p)return;Mt(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let x=rl([r,T.relativePath]),D=c.concat(T);m.children&&m.children.length>0&&(Mt(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),dm(m.children,s,D,x,p)),!(m.path==null&&!m.index)&&s.push({path:x,score:K0(x,m.index),routesMeta:D})};return n.forEach((m,v)=>{if(m.path===""||!m.path?.includes("?"))h(m,v);else for(let p of mm(m.path))h(m,v,!0,p)}),s}function mm(n){let s=n.split("/");if(s.length===0)return[];let[c,...r]=s,d=c.endsWith("?"),h=c.replace(/\?$/,"");if(r.length===0)return d?[h,""]:[h];let m=mm(r.join("/")),v=[];return v.push(...m.map(p=>p===""?h:[h,p].join("/"))),d&&v.push(...m),v.map(p=>n.startsWith("/")&&p===""?"/":p)}function B0(n){n.sort((s,c)=>s.score!==c.score?c.score-s.score:V0(s.routesMeta.map(r=>r.childrenIndex),c.routesMeta.map(r=>r.childrenIndex)))}var Q0=/^:[\w-]+$/,L0=3,Y0=2,G0=1,X0=10,Z0=-2,Fd=n=>n==="*";function K0(n,s){let c=n.split("/"),r=c.length;return c.some(Fd)&&(r+=Z0),s&&(r+=Y0),c.filter(d=>!Fd(d)).reduce((d,h)=>d+(Q0.test(h)?L0:h===""?G0:X0),r)}function V0(n,s){return n.length===s.length&&n.slice(0,-1).every((r,d)=>r===s[d])?n[n.length-1]-s[s.length-1]:0}function J0(n,s,c=!1){let{routesMeta:r}=n,d={},h="/",m=[];for(let v=0;v{if(T==="*"){let H=v[D]||"";m=h.slice(0,h.length-H.length).replace(/(.)\/+$/,"$1")}const q=v[D];return x&&!q?y[T]=void 0:y[T]=(q||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:m,pattern:n}}function k0(n,s=!1,c=!0){He(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],d="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(m,v,p)=>(r.push({paramName:v,isOptional:p!=null}),p?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),d+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):c?d+="\\/*$":n!==""&&n!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,s?void 0:"i"),r]}function F0(n){try{return n.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return He(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),n}}function fl(n,s){if(s==="/")return n;if(!n.toLowerCase().startsWith(s.toLowerCase()))return null;let c=s.endsWith("/")?s.length-1:s.length,r=n.charAt(c);return r&&r!=="/"?null:n.slice(c)||"/"}var ym=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,$0=n=>ym.test(n);function W0(n,s="/"){let{pathname:c,search:r="",hash:d=""}=typeof n=="string"?Wa(n):n,h;if(c)if($0(c))h=c;else{if(c.includes("//")){let m=c;c=c.replace(/\/\/+/g,"/"),He(!1,`Pathnames cannot have embedded double slashes - normalizing ${m} -> ${c}`)}c.startsWith("/")?h=$d(c.substring(1),"/"):h=$d(c,s)}else h=s;return{pathname:h,search:tp(r),hash:ep(d)}}function $d(n,s){let c=s.replace(/\/+$/,"").split("/");return n.split("/").forEach(d=>{d===".."?c.length>1&&c.pop():d!=="."&&c.push(d)}),c.length>1?c.join("/"):"/"}function ir(n,s,c,r){return`Cannot include a '${n}' character in a manually specified \`to.${s}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${c}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function P0(n){return n.filter((s,c)=>c===0||s.route.path&&s.route.path.length>0)}function vm(n){let s=P0(n);return s.map((c,r)=>r===s.length-1?c.pathname:c.pathnameBase)}function pm(n,s,c,r=!1){let d;typeof n=="string"?d=Wa(n):(d={...n},Mt(!d.pathname||!d.pathname.includes("?"),ir("?","pathname","search",d)),Mt(!d.pathname||!d.pathname.includes("#"),ir("#","pathname","hash",d)),Mt(!d.search||!d.search.includes("#"),ir("#","search","hash",d)));let h=n===""||d.pathname==="",m=h?"/":d.pathname,v;if(m==null)v=c;else{let x=s.length-1;if(!r&&m.startsWith("..")){let D=m.split("/");for(;D[0]==="..";)D.shift(),x-=1;d.pathname=D.join("/")}v=x>=0?s[x]:"/"}let p=W0(d,v),y=m&&m!=="/"&&m.endsWith("/"),T=(h||m===".")&&c.endsWith("/");return!p.pathname.endsWith("/")&&(y||T)&&(p.pathname+="/"),p}var rl=n=>n.join("/").replace(/\/\/+/g,"/"),I0=n=>n.replace(/\/+$/,"").replace(/^\/*/,"/"),tp=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,ep=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,lp=class{constructor(n,s,c,r=!1){this.status=n,this.statusText=s||"",this.internal=r,c instanceof Error?(this.data=c.toString(),this.error=c):this.data=c}};function ap(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function np(n){return n.map(s=>s.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var gm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Sm(n,s){let c=n;if(typeof c!="string"||!ym.test(c))return{absoluteURL:void 0,isExternal:!1,to:c};let r=c,d=!1;if(gm)try{let h=new URL(window.location.href),m=c.startsWith("//")?new URL(h.protocol+c):new URL(c),v=fl(m.pathname,s);m.origin===h.origin&&v!=null?c=v+m.search+m.hash:d=!0}catch{He(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:d,to:c}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var bm=["POST","PUT","PATCH","DELETE"];new Set(bm);var up=["GET",...bm];new Set(up);var Pa=R.createContext(null);Pa.displayName="DataRouter";var _i=R.createContext(null);_i.displayName="DataRouterState";var ip=R.createContext(!1),Em=R.createContext({isTransitioning:!1});Em.displayName="ViewTransition";var sp=R.createContext(new Map);sp.displayName="Fetchers";var cp=R.createContext(null);cp.displayName="Await";var Ue=R.createContext(null);Ue.displayName="Navigation";var eu=R.createContext(null);eu.displayName="Location";var Xe=R.createContext({outlet:null,matches:[],isDataRoute:!1});Xe.displayName="Route";var Sr=R.createContext(null);Sr.displayName="RouteError";var Tm="REACT_ROUTER_ERROR",rp="REDIRECT",fp="ROUTE_ERROR_RESPONSE";function op(n){if(n.startsWith(`${Tm}:${rp}:{`))try{let s=JSON.parse(n.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function hp(n){if(n.startsWith(`${Tm}:${fp}:{`))try{let s=JSON.parse(n.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new lp(s.status,s.statusText,s.data)}catch{}}function dp(n,{relative:s}={}){Mt(lu(),"useHref() may be used only in the context of a component.");let{basename:c,navigator:r}=R.useContext(Ue),{hash:d,pathname:h,search:m}=au(n,{relative:s}),v=h;return c!=="/"&&(v=h==="/"?c:rl([c,h])),r.createHref({pathname:v,search:m,hash:d})}function lu(){return R.useContext(eu)!=null}function fa(){return Mt(lu(),"useLocation() may be used only in the context of a component."),R.useContext(eu).location}var xm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Rm(n){R.useContext(Ue).static||R.useLayoutEffect(n)}function Om(){let{isDataRoute:n}=R.useContext(Xe);return n?Mp():mp()}function mp(){Mt(lu(),"useNavigate() may be used only in the context of a component.");let n=R.useContext(Pa),{basename:s,navigator:c}=R.useContext(Ue),{matches:r}=R.useContext(Xe),{pathname:d}=fa(),h=JSON.stringify(vm(r)),m=R.useRef(!1);return Rm(()=>{m.current=!0}),R.useCallback((p,y={})=>{if(He(m.current,xm),!m.current)return;if(typeof p=="number"){c.go(p);return}let T=pm(p,JSON.parse(h),d,y.relative==="path");n==null&&s!=="/"&&(T.pathname=T.pathname==="/"?s:rl([s,T.pathname])),(y.replace?c.replace:c.push)(T,y.state,y)},[s,c,h,d,n])}var yp=R.createContext(null);function vp(n){let s=R.useContext(Xe).outlet;return R.useMemo(()=>s&&R.createElement(yp.Provider,{value:n},s),[s,n])}function au(n,{relative:s}={}){let{matches:c}=R.useContext(Xe),{pathname:r}=fa(),d=JSON.stringify(vm(c));return R.useMemo(()=>pm(n,JSON.parse(d),r,s==="path"),[n,d,r,s])}function pp(n,s){return Am(n,s)}function Am(n,s,c,r,d){Mt(lu(),"useRoutes() may be used only in the context of a component.");let{navigator:h}=R.useContext(Ue),{matches:m}=R.useContext(Xe),v=m[m.length-1],p=v?v.params:{},y=v?v.pathname:"/",T=v?v.pathnameBase:"/",x=v&&v.route;{let X=x&&x.path||"";Mm(y,!x||X.endsWith("*")||X.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${y}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let D=fa(),q;if(s){let X=typeof s=="string"?Wa(s):s;Mt(T==="/"||X.pathname?.startsWith(T),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${T}" but pathname "${X.pathname}" was given in the \`location\` prop.`),q=X}else q=D;let H=q.pathname||"/",B=H;if(T!=="/"){let X=T.replace(/^\//,"").split("/");B="/"+H.replace(/^\//,"").split("/").slice(X.length).join("/")}let Y=hm(n,{pathname:B});He(x||Y!=null,`No routes matched location "${q.pathname}${q.search}${q.hash}" `),He(Y==null||Y[Y.length-1].route.element!==void 0||Y[Y.length-1].route.Component!==void 0||Y[Y.length-1].route.lazy!==void 0,`Matched leaf route at location "${q.pathname}${q.search}${q.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let Q=Tp(Y&&Y.map(X=>Object.assign({},X,{params:Object.assign({},p,X.params),pathname:rl([T,h.encodeLocation?h.encodeLocation(X.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:X.pathname]),pathnameBase:X.pathnameBase==="/"?T:rl([T,h.encodeLocation?h.encodeLocation(X.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:X.pathnameBase])})),m,c,r,d);return s&&Q?R.createElement(eu.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...q},navigationType:"POP"}},Q):Q}function gp(){let n=Cp(),s=ap(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),c=n instanceof Error?n.stack:null,r="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:r},h={padding:"2px 4px",backgroundColor:r},m=null;return console.error("Error handled by React Router default ErrorBoundary:",n),m=R.createElement(R.Fragment,null,R.createElement("p",null,"💿 Hey developer 👋"),R.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",R.createElement("code",{style:h},"ErrorBoundary")," or"," ",R.createElement("code",{style:h},"errorElement")," prop on your route.")),R.createElement(R.Fragment,null,R.createElement("h2",null,"Unexpected Application Error!"),R.createElement("h3",{style:{fontStyle:"italic"}},s),c?R.createElement("pre",{style:d},c):null,m)}var Sp=R.createElement(gp,null),Cm=class extends R.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,s){return s.location!==n.location||s.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:s.error,location:s.location,revalidation:n.revalidation||s.revalidation}}componentDidCatch(n,s){this.props.onError?this.props.onError(n,s):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const c=hp(n.digest);c&&(n=c)}let s=n!==void 0?R.createElement(Xe.Provider,{value:this.props.routeContext},R.createElement(Sr.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?R.createElement(bp,{error:n},s):s}};Cm.contextType=ip;var sr=new WeakMap;function bp({children:n,error:s}){let{basename:c}=R.useContext(Ue);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let r=op(s.digest);if(r){let d=sr.get(s);if(d)throw d;let h=Sm(r.location,c);if(gm&&!sr.get(s))if(h.isExternal||r.reloadDocument)window.location.href=h.absoluteURL||h.to;else{const m=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:r.replace}));throw sr.set(s,m),m}return R.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return n}function Ep({routeContext:n,match:s,children:c}){let r=R.useContext(Pa);return r&&r.static&&r.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=s.route.id),R.createElement(Xe.Provider,{value:n},c)}function Tp(n,s=[],c=null,r=null,d=null){if(n==null){if(!c)return null;if(c.errors)n=c.matches;else if(s.length===0&&!c.initialized&&c.matches.length>0)n=c.matches;else return null}let h=n,m=c?.errors;if(m!=null){let T=h.findIndex(x=>x.route.id&&m?.[x.route.id]!==void 0);Mt(T>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(m).join(",")}`),h=h.slice(0,Math.min(h.length,T+1))}let v=!1,p=-1;if(c)for(let T=0;T=0?h=h.slice(0,p+1):h=[h[0]];break}}}let y=c&&r?(T,x)=>{r(T,{location:c.location,params:c.matches?.[0]?.params??{},unstable_pattern:np(c.matches),errorInfo:x})}:void 0;return h.reduceRight((T,x,D)=>{let q,H=!1,B=null,Y=null;c&&(q=m&&x.route.id?m[x.route.id]:void 0,B=x.route.errorElement||Sp,v&&(p<0&&D===0?(Mm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),H=!0,Y=null):p===D&&(H=!0,Y=x.route.hydrateFallbackElement||null)));let Q=s.concat(h.slice(0,D+1)),X=()=>{let J;return q?J=B:H?J=Y:x.route.Component?J=R.createElement(x.route.Component,null):x.route.element?J=x.route.element:J=T,R.createElement(Ep,{match:x,routeContext:{outlet:T,matches:Q,isDataRoute:c!=null},children:J})};return c&&(x.route.ErrorBoundary||x.route.errorElement||D===0)?R.createElement(Cm,{location:c.location,revalidation:c.revalidation,component:B,error:q,children:X(),routeContext:{outlet:null,matches:Q,isDataRoute:!0},onError:y}):X()},null)}function br(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function xp(n){let s=R.useContext(Pa);return Mt(s,br(n)),s}function Rp(n){let s=R.useContext(_i);return Mt(s,br(n)),s}function Op(n){let s=R.useContext(Xe);return Mt(s,br(n)),s}function Er(n){let s=Op(n),c=s.matches[s.matches.length-1];return Mt(c.route.id,`${n} can only be used on routes that contain a unique "id"`),c.route.id}function Ap(){return Er("useRouteId")}function Cp(){let n=R.useContext(Sr),s=Rp("useRouteError"),c=Er("useRouteError");return n!==void 0?n:s.errors?.[c]}function Mp(){let{router:n}=xp("useNavigate"),s=Er("useNavigate"),c=R.useRef(!1);return Rm(()=>{c.current=!0}),R.useCallback(async(d,h={})=>{He(c.current,xm),c.current&&(typeof d=="number"?await n.navigate(d):await n.navigate(d,{fromRouteId:s,...h}))},[n,s])}var Wd={};function Mm(n,s,c){!s&&!Wd[n]&&(Wd[n]=!0,He(!1,c))}R.memo(zp);function zp({routes:n,future:s,state:c,onError:r}){return Am(n,void 0,c,r,s)}function _p(n){return vp(n.context)}function Wn(n){Mt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Dp({basename:n="/",children:s=null,location:c,navigationType:r="POP",navigator:d,static:h=!1,unstable_useTransitions:m}){Mt(!lu(),"You cannot render a inside another . You should never have more than one in your app.");let v=n.replace(/^\/*/,"/"),p=R.useMemo(()=>({basename:v,navigator:d,static:h,unstable_useTransitions:m,future:{}}),[v,d,h,m]);typeof c=="string"&&(c=Wa(c));let{pathname:y="/",search:T="",hash:x="",state:D=null,key:q="default"}=c,H=R.useMemo(()=>{let B=fl(y,v);return B==null?null:{location:{pathname:B,search:T,hash:x,state:D,key:q},navigationType:r}},[v,y,T,x,D,q,r]);return He(H!=null,` is not able to match the URL "${y}${T}${x}" because it does not start with the basename, so the won't render anything.`),H==null?null:R.createElement(Ue.Provider,{value:p},R.createElement(eu.Provider,{children:s,value:H}))}function Up({children:n,location:s}){return pp(or(n),s)}function or(n,s=[]){let c=[];return R.Children.forEach(n,(r,d)=>{if(!R.isValidElement(r))return;let h=[...s,d];if(r.type===R.Fragment){c.push.apply(c,or(r.props.children,h));return}Mt(r.type===Wn,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),Mt(!r.props.index||!r.props.children,"An index route cannot have child routes.");let m={id:r.props.id||h.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(m.children=or(r.props.children,h)),c.push(m)}),c}var Oi="get",Ai="application/x-www-form-urlencoded";function Di(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function Np(n){return Di(n)&&n.tagName.toLowerCase()==="button"}function jp(n){return Di(n)&&n.tagName.toLowerCase()==="form"}function wp(n){return Di(n)&&n.tagName.toLowerCase()==="input"}function qp(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function Hp(n,s){return n.button===0&&(!s||s==="_self")&&!qp(n)}var xi=null;function Bp(){if(xi===null)try{new FormData(document.createElement("form"),0),xi=!1}catch{xi=!0}return xi}var Qp=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function cr(n){return n!=null&&!Qp.has(n)?(He(!1,`"${n}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Ai}"`),null):n}function Lp(n,s){let c,r,d,h,m;if(jp(n)){let v=n.getAttribute("action");r=v?fl(v,s):null,c=n.getAttribute("method")||Oi,d=cr(n.getAttribute("enctype"))||Ai,h=new FormData(n)}else if(Np(n)||wp(n)&&(n.type==="submit"||n.type==="image")){let v=n.form;if(v==null)throw new Error('Cannot submit a + + + + {/* Content */} +
+ {children} +
+ + + ) +} diff --git a/frontend/src/components/CameraStream.tsx b/frontend/src/components/CameraStream.tsx new file mode 100644 index 00000000..63abd7df --- /dev/null +++ b/frontend/src/components/CameraStream.tsx @@ -0,0 +1,146 @@ +import { useState, useCallback, useEffect, useRef } from 'react' +import { useMjpegStream } from '@/hooks/useMjpegStream' +import { getStoredRestartTimestamp } from '@/lib/cameraRestart' + +interface CameraStreamProps { + cameraId: number + className?: string + onStreamFpsChange?: (fps: number) => void // Callback for streaming FPS updates +} + +// Custom event name for camera restart notification +export const CAMERA_RESTARTED_EVENT = 'camera-restarted' + +export function CameraStream({ cameraId, className = '', onStreamFpsChange }: CameraStreamProps) { + // Track the last known restart timestamp to detect changes + const lastKnownRestartRef = useRef(getStoredRestartTimestamp(cameraId)) + + // Initialize streamKey from stored restart timestamp + // This ensures fresh connections after navigation back from Settings + const [streamKey, setStreamKey] = useState(() => getStoredRestartTimestamp(cameraId)) + + // Use MJPEG parser for client-side frame counting + const { imageUrl, streamFps, isConnected, error } = useMjpegStream(cameraId, streamKey) + + // Store callback in ref to avoid triggering effect when callback reference changes + // This prevents render loops when parent passes inline arrow functions + const onStreamFpsChangeRef = useRef(onStreamFpsChange) + + // Update ref whenever callback changes (synchronous to avoid stale closures) + useEffect(() => { + onStreamFpsChangeRef.current = onStreamFpsChange + }) + + // Notify parent of streaming FPS changes - only triggers when fps actually changes + useEffect(() => { + if (onStreamFpsChangeRef.current) { + onStreamFpsChangeRef.current(streamFps) + } + }, [streamFps]) // ← Only streamFps in deps, NOT onStreamFpsChange + + // Force stream reconnection by changing key + const handleReconnect = useCallback(() => { + // Add small delay before retry to avoid hammering + setTimeout(() => { + setStreamKey((k) => k + 1) + }, 2000) + }, []) + + // Listen for camera restart events to force reconnection (same-page scenario) + useEffect(() => { + const handleCameraRestarted = (event: CustomEvent<{ cameraId?: number }>) => { + // Reconnect if event is for this camera or all cameras (cameraId undefined or 0) + const eventCamId = event.detail?.cameraId + if (!eventCamId || eventCamId === 0 || eventCamId === cameraId) { + // Force new connection by using current timestamp + const newTimestamp = Date.now() + lastKnownRestartRef.current = newTimestamp + setStreamKey(newTimestamp) + } + } + + window.addEventListener(CAMERA_RESTARTED_EVENT, handleCameraRestarted as EventListener) + return () => { + window.removeEventListener(CAMERA_RESTARTED_EVENT, handleCameraRestarted as EventListener) + } + }, [cameraId]) + + // Check for restart timestamp changes on mount, when camera changes, + // and periodically. This handles: + // - Cross-navigation: Settings restarts camera while Dashboard is unmounted + // - Multi-tab: Another tab triggers restart + // - Edge cases: React batching delays event handling + useEffect(() => { + const checkForRestart = () => { + const storedTimestamp = getStoredRestartTimestamp(cameraId) + // Also check global timestamp (camera 0) for "restart all" scenarios + const globalTimestamp = cameraId !== 0 ? getStoredRestartTimestamp(0) : 0 + const latestTimestamp = Math.max(storedTimestamp, globalTimestamp) + + if (latestTimestamp > lastKnownRestartRef.current) { + // A restart happened while we were unmounted or in another tab + lastKnownRestartRef.current = latestTimestamp + setStreamKey(latestTimestamp) + } + } + + // Check immediately on mount + checkForRestart() + + // Periodically check for restart changes (every 5 seconds) + // This handles multi-tab scenarios and edge cases + const intervalId = setInterval(checkForRestart, 5000) + + return () => clearInterval(intervalId) + }, [cameraId]) + + // Handle connection errors with auto-retry + useEffect(() => { + if (error && !isConnected) { + handleReconnect() + } + }, [error, isConnected, handleReconnect]) + + if (error && !imageUrl) { + return ( +
+
+
+ + + +

{error}

+
+
+
+ ) + } + + if (!imageUrl) { + return ( +
+
+ {/* Loading spinner in top-right corner */} +
+ + + + +
+
+
+ ) + } + + return ( +
+
+ {`Camera +
+
+ ) +} diff --git a/frontend/src/components/ConfigurationPresets.tsx b/frontend/src/components/ConfigurationPresets.tsx new file mode 100644 index 00000000..5377bce6 --- /dev/null +++ b/frontend/src/components/ConfigurationPresets.tsx @@ -0,0 +1,173 @@ +import { useState } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { useProfiles, useApplyProfile } from '../hooks/useProfiles'; +import { ProfileSaveDialog } from './ProfileSaveDialog'; +import { useToast } from './Toast'; +import { applyRestartRequiredChanges } from '@/api/client'; + +interface ConfigurationPresetsProps { + cameraId: number; + readOnly?: boolean; // Hide save button when true (for Dashboard bottom sheet) +} + +/** + * Configuration Presets Component + * + * Allows users to: + * - Select from saved configuration profiles + * - Apply a profile to quickly change camera settings + * - Save current settings as a new profile + * - Manage existing profiles (delete, set as default) + */ +export function ConfigurationPresets({ cameraId, readOnly = false }: ConfigurationPresetsProps) { + const queryClient = useQueryClient(); + const { data: profiles, isLoading, error } = useProfiles(cameraId); + const { mutate: applyProfile, isPending: isApplying } = useApplyProfile(); + const { addToast } = useToast(); + + const [selectedProfileId, setSelectedProfileId] = useState(null); + const [showSaveDialog, setShowSaveDialog] = useState(false); + + const handleApply = () => { + if (selectedProfileId) { + const profile = profiles?.find(p => p.profile_id === selectedProfileId); + const profileName = profile?.name || 'profile'; + + applyProfile(selectedProfileId, { + onSuccess: async (requiresRestart) => { + if (requiresRestart.length > 0) { + addToast( + `Profile "${profileName}" applied. Restarting camera...`, + 'info' + ); + try { + await applyRestartRequiredChanges(cameraId); + await new Promise(resolve => setTimeout(resolve, 2000)); + await queryClient.invalidateQueries({ queryKey: ['config'] }); + addToast( + `Profile "${profileName}" applied. Camera restarted.`, + 'success' + ); + } catch (err) { + console.error('Failed to restart camera:', err); + addToast( + `Profile applied but camera restart failed. Please restart manually.`, + 'warning' + ); + } + } else { + addToast( + `Profile "${profileName}" applied successfully`, + 'success' + ); + } + setSelectedProfileId(null); + }, + onError: (error) => { + addToast( + `Failed to apply profile: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'error' + ); + }, + }); + } + }; + + if (error) { + return ( +
+
+

Failed to load profiles

+

+ {error instanceof Error ? error.message : 'Unable to connect to profiles API'} +

+
+
+ ); + } + + return ( + <> +
+ +
+ {/* Preset selector dropdown */} +
+ +
+ + + +
+
+ + {/* Save button (hidden in read-only mode) */} + {!readOnly && ( + + )} + + {/* Apply button */} + +
+

+ Quick switch camera settings • {profiles?.length || 0} preset{profiles?.length !== 1 ? 's' : ''} +

+
+ + {/* Save dialog */} + {showSaveDialog && ( + setShowSaveDialog(false)} + /> + )} + + ); +} diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..a52ce7d2 --- /dev/null +++ b/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,65 @@ +import { Component, type ReactNode } from 'react' + +interface Props { + children: ReactNode + fallback?: ReactNode +} + +interface State { + hasError: boolean + error?: Error +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props) + this.state = { hasError: false } + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('ErrorBoundary caught:', error, errorInfo) + } + + handleReset = () => { + this.setState({ hasError: false, error: undefined }) + } + + render() { + if (this.state.hasError) { + return ( + this.props.fallback || ( +
+
+

+ Something went wrong +

+

+ {this.state.error?.message || 'An unexpected error occurred'} +

+
+ + +
+
+
+ ) + ) + } + + return this.props.children + } +} diff --git a/frontend/src/components/FullscreenButton.tsx b/frontend/src/components/FullscreenButton.tsx new file mode 100644 index 00000000..d05b6376 --- /dev/null +++ b/frontend/src/components/FullscreenButton.tsx @@ -0,0 +1,71 @@ +import { useState, useEffect } from 'react' + +interface FullscreenButtonProps { + cameraId: number +} + +export function FullscreenButton({ cameraId }: FullscreenButtonProps) { + const [isFullscreen, setIsFullscreen] = useState(false) + + const toggleFullscreen = (e: React.MouseEvent) => { + e.stopPropagation() + // Find the camera stream container + const container = document.querySelector(`[data-camera-id="${cameraId}"]`) + if (!container) return + + if (!isFullscreen) { + if (container.requestFullscreen) { + container.requestFullscreen() + } + } else { + if (document.exitFullscreen) { + document.exitFullscreen() + } + } + } + + // Listen for fullscreen changes + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement) + } + + document.addEventListener('fullscreenchange', handleFullscreenChange) + return () => { + document.removeEventListener('fullscreenchange', handleFullscreenChange) + } + }, []) + + return ( + + ) +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 00000000..10b195c6 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,145 @@ +import { useState, memo } from 'react' +import { Outlet, Link } from 'react-router-dom' +import { useQueryClient } from '@tanstack/react-query' +import { useAuthContext } from '@/contexts/AuthContext' +import { logout } from '@/api/auth' +import { SystemStatus, VersionDisplay } from '@/components/SystemStatus' + +export const Layout = memo(function Layout() { + const queryClient = useQueryClient() + const { isAuthenticated, role, authRequired } = useAuthContext() + const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + + const handleLogout = async () => { + await logout() + // Invalidate auth queries - this triggers AuthContext and AuthGate to update + queryClient.invalidateQueries({ queryKey: ['auth'] }) + } + + return ( +
+
+
+ + + {/* Mobile menu dropdown */} + {mobileMenuOpen && ( +
+
+ setMobileMenuOpen(false)} + className="px-3 py-2 rounded-lg hover:bg-surface transition-colors" + > + Dashboard + + {role === 'admin' && ( + setMobileMenuOpen(false)} + className="px-3 py-2 rounded-lg hover:bg-surface transition-colors" + > + Settings + + )} + setMobileMenuOpen(false)} + className="px-3 py-2 rounded-lg hover:bg-surface transition-colors" + > + Media + + {authRequired && isAuthenticated && ( + + )} + {!authRequired && ( +
+ No Authentication +
+ )} + + {/* Mobile system stats */} + +
+
+ )} +
+
+ +
+ +
+
+ ) +}) diff --git a/frontend/src/components/LoginPage.tsx b/frontend/src/components/LoginPage.tsx new file mode 100644 index 00000000..c1a85067 --- /dev/null +++ b/frontend/src/components/LoginPage.tsx @@ -0,0 +1,127 @@ +import { useState, useCallback, useRef, useEffect, type FormEvent } from 'react'; +import { login } from '@/api/auth'; + +interface LoginPageProps { + onSuccess: () => void; +} + +export function LoginPage({ onSuccess }: LoginPageProps) { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [rememberMe, setRememberMe] = useState(false); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const usernameRef = useRef(null); + + // Focus username on mount + useEffect(() => { + usernameRef.current?.focus(); + }, []); + + const handleSubmit = useCallback(async (e: FormEvent) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + const result = await login(username, password, rememberMe); + + setIsLoading(false); + + if (result.success) { + onSuccess(); + } else { + setError(result.error ?? 'Login failed'); + } + }, [username, password, rememberMe, onSuccess]); + + return ( +
+
+ {/* Logo */} +
+

Motion

+

Video Motion Detection

+
+ + {/* Login Card */} +
+

Sign In

+ + +
+ + setUsername(e.target.value)} + className="w-full px-3 py-2 bg-surface border border-gray-700 rounded-lg + focus:outline-none focus:ring-2 focus:ring-primary" + required + autoComplete="username" + disabled={isLoading} + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full px-3 py-2 bg-surface border border-gray-700 rounded-lg + focus:outline-none focus:ring-2 focus:ring-primary" + required + autoComplete="current-password" + disabled={isLoading} + /> +
+ +
+ +
+ + {error && ( +
+ {error} +
+ )} + + + +
+ + {/* Security note */} +

+ Use HTTPS in production for secure authentication +

+
+
+ ); +} diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx new file mode 100644 index 00000000..0c7e66f4 --- /dev/null +++ b/frontend/src/components/Pagination.tsx @@ -0,0 +1,49 @@ +interface PaginationProps { + offset: number; + limit: number; + total: number; + onPageChange: (newOffset: number) => void; + context?: string; // Optional context like "on Jan 15, 2025" +} + +export function Pagination({ offset, limit, total, onPageChange, context }: PaginationProps) { + const currentPage = Math.floor(offset / limit) + 1; + const totalPages = Math.ceil(total / limit); + const displayStart = total === 0 ? 0 : offset + 1; + const displayEnd = Math.min(offset + limit, total); + + const canGoPrevious = offset > 0; + const canGoNext = offset + limit < total; + + return ( +
+ + Displaying {displayStart}-{displayEnd} of {total} + {context && {context}} + + {totalPages > 1 && ( +
+ + + Page {currentPage} of {totalPages} + + +
+ )} +
+ ); +} diff --git a/frontend/src/components/ProfileSaveDialog.tsx b/frontend/src/components/ProfileSaveDialog.tsx new file mode 100644 index 00000000..4e11fe3b --- /dev/null +++ b/frontend/src/components/ProfileSaveDialog.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import { useCreateProfile } from '../hooks/useProfiles'; + +interface ProfileSaveDialogProps { + cameraId: number; + onClose: () => void; +} + +/** + * Dialog for saving current camera settings as a new profile + */ +export function ProfileSaveDialog({ cameraId, onClose }: ProfileSaveDialogProps) { + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const { mutate: createProfile, isPending } = useCreateProfile(); + + const handleSave = () => { + if (!name.trim()) { + return; + } + + createProfile( + { + name: name.trim(), + description: description.trim() || undefined, + camera_id: cameraId, + snapshot_current: true, // Capture current settings + }, + { + onSuccess: () => { + onClose(); + }, + onError: (error) => { + console.error('Failed to save profile:', error); + // TODO: Show error notification + }, + } + ); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSave(); + } else if (e.key === 'Escape') { + onClose(); + } + }; + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+

Save Configuration Preset

+ +
+ + {/* Form */} +
+ {/* Name field */} +
+ + setName(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="e.g., Daytime, Nighttime" + className="w-full px-3 py-2 bg-surface-elevated border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary" + autoFocus + maxLength={50} + /> +
+ + {/* Description field */} +
+ + \n" - "
\n\n" - "
\n\n"; - -} - -/* Create the javascript function send_config */ -void cls_webu_html::script_nav() -{ - webua->resp_page += - " function nav_open() {\n" - " document.getElementById('divnav_main').style.width = '10rem';\n" - " document.getElementById('divmain').style.marginLeft = '10rem';\n" - " document.getElementById('menu_btn').style.display= 'none';\n" - " }\n\n" - - " function nav_close() {\n" - " document.getElementById('divnav_main').style.width = '0rem';\n" - " document.getElementById('divmain').style.marginLeft = '0rem';\n" - " document.getElementById('menu_btn').style.display= 'inline';\n" - " }\n\n"; -} - -/* Create the javascript function send_config */ -void cls_webu_html::script_send_config() -{ - webua->resp_page += - " function send_config(category) {\n" - " var formData = new FormData();\n" - " var request = new XMLHttpRequest();\n" - " var xmlhttp = new XMLHttpRequest();\n" - " var camid = document.getElementsByName('camdrop')[0].value;\n\n" - - " if (camid == 0) {\n" - " var pCfg = pData['configuration']['default'];\n" - " } else {\n" - " var pCfg = pData['configuration']['cam'+camid];\n" - " }\n\n" - - " xmlhttp.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " pData = JSON.parse(this.responseText);\n" - " }\n" - " };\n" - - " request.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " xmlhttp.open('GET', pHostFull+'/0/config.json');\n" - " xmlhttp.send();\n\n" - " }\n" - " };\n" - - " formData.append('command', 'config');\n" - " formData.append('camid', camid);\n\n" - " for (jkey in pCfg) {\n" - " if (document.getElementsByName(jkey)[0] != null) {\n" - " if (pCfg[jkey].category == category) {\n" - " if (document.getElementsByName(jkey)[0].type == 'checkbox') {\n" - " formData.append(jkey, document.getElementsByName(jkey)[0].checked);\n" - " } else {\n" - " formData.append(jkey, document.getElementsByName(jkey)[0].value);\n" - " }\n" - " }\n" - " }\n" - " }\n" - " request.open('POST', pHostFull);\n" - " request.send(formData);\n\n" - " }\n\n"; -} - -/* Create the send_action javascript function */ -void cls_webu_html::script_send_action() -{ - webua->resp_page += - " function send_action(actval) {\n\n" - - " var dsp_cam = document.getElementById('div_cam').style.display;\n" - " if ((dsp_cam == 'none' || dsp_cam == '') && (actval != 'config_write')) {\n" - " return;\n" - " }\n\n" - - " var formData = new FormData();\n" - " var camid;\n" - " var ans;\n\n" - - " camid = assign_camid();\n\n" - - " if (actval == 'action_user') {\n" - " ans = prompt('Enter user parameter');\n" - " } else {\n" - " ans = '';\n" - " }\n\n" - - " formData.append('command', actval);\n" - " formData.append('camid', camid);\n" - " formData.append('user', ans);\n\n" - " var request = new XMLHttpRequest();\n" - " request.open('POST', pHostFull);\n" - " request.send(formData);\n\n" - " return;\n" - " }\n\n"; -} - -/* Create the send_reload javascript function */ -void cls_webu_html::script_send_reload() -{ - webua->resp_page += - " function send_reload(actval) {\n\n" - " var formData = new FormData();\n" - " var request = new XMLHttpRequest();\n" - " var xmlhttp = new XMLHttpRequest();\n" - " var camid;\n" - " var ans;\n\n" - - " camid = assign_camid();\n\n" - - " if (actval == 'camera_delete') {\n" - " ans = confirm('Delete camera ' + camid);\n" - " if (ans == false) {\n" - " return;\n" - " }\n" - " }\n\n" - - " xmlhttp.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " pData = JSON.parse(this.responseText);\n" - " gIndxCam = -1;\n" - " assign_config_nav();\n" - " assign_vals(0);\n" - " assign_cams();\n" - " }\n" - " };\n" - - " request.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " xmlhttp.open('GET', pHostFull+'/0/config.json');\n" - " xmlhttp.send();\n\n" - " }\n" - " };\n" - - " formData.append('command', actval);\n" - " formData.append('camid', camid);\n\n" - - " request.open('POST', pHostFull);\n" - " request.send(formData);\n\n" - - " }\n\n"; -} - -/* Create the javascript function dropchange_cam */ -void cls_webu_html::script_dropchange_cam() -{ - webua->resp_page += - " function dropchange_cam(camobj) {\n" - " var indx;\n\n" - - " assign_vals(camobj.value);\n\n" - - " var sect = document.getElementsByName('camdrop');\n" - " for (indx = 0; indx < sect.length; indx++) {\n" - " sect.item(indx).selectedIndex =camobj.selectedIndex;\n" - " }\n\n" - - " gIndxCam = -1;\n" - " for (indx = 0; indx < pData['cameras']['count']; indx++) {\n" - " if (pData['cameras'][indx]['id'] == camobj.value) {\n" - " gIndxCam = indx;\n" - " }\n" - " }\n\n" - - " if (gIndxCam == -1) {\n" - " document.getElementById('cfgpic').src =\n" - " pHostFull+\"/0/mjpg/stream\";\n" - " } else {\n" - " document.getElementById('cfgpic').src =\n" - " pData['cameras'][gIndxCam]['url'] + \"mjpg/stream\" ;\n" - " }\n\n" - - " }\n\n"; -} - -/* Create the javascript function config_hideall */ -void cls_webu_html::script_config_hideall() -{ - webua->resp_page += - " function config_hideall() {\n" - " var sect = document.getElementsByClassName('cls_config');\n" - " for (var i = 0; i < sect.length; i++) {\n" - " sect.item(i).style.display='none';\n" - " }\n" - " return;\n" - " }\n\n"; -} - -/* Create the javascript function config_click */ -void cls_webu_html::script_config_click() -{ - webua->resp_page += - " function config_click(actval) {\n" - " config_hideall();\n" - " document.getElementById('div_cam').style.display='none';\n" - " document.getElementById('div_movies').style.display='none';\n" - " document.getElementById('div_config').style.display='inline';\n" - " document.getElementById('div_' + actval).style.display='inline';\n" - " cams_reset();\n" - " }\n\n"; -} - -/* Create the javascript function assign_camid */ -void cls_webu_html::script_assign_camid() -{ - webua->resp_page += - " function assign_camid() {\n" - " if (gIndxCam == -1 ) {\n" - " camid = 0;\n" - " } else {\n" - " camid = pData['cameras'][gIndxCam]['id'];\n" - " }\n\n" - " return camid; \n" - " }\n\n"; -} - -/* Create the javascript function assign_version */ -void cls_webu_html::script_assign_version() -{ - webua->resp_page += - " function assign_version() {\n" - " var verstr ='Motion \\n'+pData['version'] +'';\n" - " document.getElementById('divnav_version').innerHTML = verstr;\n" - " }\n\n"; -} - -/* Create the javascript function assign_cams */ -void cls_webu_html::script_assign_cams() -{ - webua->resp_page += - " function assign_cams() {\n" - " var camcnt = pData['cameras']['count'];\n" - " var indx = 0;\n" - " var html_drop = \"\\n\";\n" - " var html_nav = \"\\n\";\n" - " var html_mov = \"\\n\";\n\n" - " html_drop += \" \\n\";\n\n" - " var sect = document.getElementsByClassName(\"cls_camdrop\");\n" - " for (indx = 0; indx < sect.length; indx++) {\n" - " sect.item(indx).innerHTML = html_drop;\n" - " }\n\n" - " document.getElementById(\"divnav_cam\").innerHTML = html_nav;\n\n" - " document.getElementById(\"divnav_movies\").innerHTML = html_mov;\n\n" - " return;\n" - " }\n\n"; -} - -/* Create the javascript function assign_actions */ -void cls_webu_html::script_assign_actions() -{ - int indx; - ctx_params_item *itm; - - webua->resp_page += - " function assign_actions() {\n" - " var html_actions = \"\\n\";\n" - " html_actions += \" \";\n"; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - itm = &webu->wb_actions->params_array[indx]; - if ((itm->param_name == "snapshot") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Snapshot\\n\";\n\n" - ; - } else if ((itm->param_name == "event") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Start Event\\n\";\n\n" - - " html_actions += \"\";\n" - " html_actions += \"End Event\\n\";\n\n" - ; - } else if ((itm->param_name == "pause") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Pause On\\n\";\n\n" - - " html_actions += \"\";\n" - " html_actions += \"Pause Off\\n\";\n\n" - - " html_actions += \"\";\n" - " html_actions += \"Pause Schedule\\n\";\n\n" - ; - } else if ((itm->param_name == "camera_add") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Add Camera\\n\";\n\n" - ; - } else if ((itm->param_name == "camera_delete") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Delete Camera\\n\";\n\n" - ; - } else if ((itm->param_name == "config_write") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Save Config\\n\";\n\n" - ; - } else if ((itm->param_name == "stop") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Stop\\n\";\n\n" - ; - } else if ((itm->param_name == "restart") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Start/Restart\\n\";\n\n" - ; - } else if ((itm->param_name == "action_user") && - (itm->param_value == "on")) { - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"User Action\\n\";\n\n" - ; - } - } - - webua->resp_page += - " html_actions += \"\";\n" - " html_actions += \"Show/hide log\\n\";\n\n"; - - webua->resp_page += - " document.getElementById(\"divnav_actions\").innerHTML = html_actions;\n\n" - " return;\n" - - " }\n\n"; -} - -/* Create the javascript function assign_vals */ -void cls_webu_html::script_assign_vals() -{ - webua->resp_page += - " function assign_vals(camid) {\n" - " var pCfg;\n\n" - - " if (camid == 0) {\n" - " pCfg = pData[\"configuration\"][\"default\"];\n" - " } else {\n" - " pCfg = pData[\"configuration\"][\"cam\"+camid];\n" - " }\n\n" - - " for (jkey in pCfg) {\n" - " if (document.getElementsByName(jkey)[0] != null) {\n" - " if (pCfg[jkey].enabled) {\n" - " document.getElementsByName(jkey)[0].disabled = false;\n" - " if (document.getElementsByName(jkey)[0].type == \"checkbox\") {\n" - " document.getElementsByName(jkey)[0].checked = pCfg[jkey].value;\n" - " } else {\n" - " document.getElementsByName(jkey)[0].value = pCfg[jkey].value;\n" - " }\n" - " } else {\n" - " document.getElementsByName(jkey)[0].disabled = true;\n" - " document.getElementsByName(jkey)[0].value = '';\n" - " }\n" - " } else {\n" - " console.log('Uncoded ' + jkey + ' : ' + pCfg[jkey].value);\n" - " }\n" - " }\n" - " }\n\n"; -} - -/* Create the javascript function assign_config_nav */ -void cls_webu_html::script_assign_config_nav() -{ - webua->resp_page += - " function assign_config_nav() {\n" - " var pCfg = pData['configuration']['default'];\n" - " var pCat = pData['categories'];\n" - " var html_nav = \"\\n\";\n\n" - - " for (jcat in pCat) {\n" - " html_nav += \"\";\n" - " html_nav += pCat[jcat][\"display\"]+\"\\n\";\n\n" - " }\n\n" - - " document.getElementById(\"divnav_config\").innerHTML = html_nav;\n\n" - - " }\n\n"; -} - -/* Create the javascript function assign_config_item */ -void cls_webu_html::script_assign_config_item() -{ - webua->resp_page += - " function assign_config_item(jkey) {\n" - " var pCfg = pData['configuration']['default'];\n" - " var html_cfg = \"\";\n" - " var indx_lst = 0;\n\n" - - " html_cfg += \"\\n\";\n\n" - " if (pCfg[jkey][\"type\"] == \"string\") {\n" - " html_cfg += \"\";\n\n" - " } else if (pCfg[jkey][\"type\"] == \"bool\") {\n" - " html_cfg += \"\";\n\n" - " } else if (pCfg[jkey][\"type\"] == \"int\") {\n" - " html_cfg += \"\";\n\n" - " } else if (pCfg[jkey][\"type\"] == \"list\") {\n" - " html_cfg += \"\";\n" - " }\n" - " html_cfg += \"\\n\";\n\n" - - " return html_cfg;\n\n" - - " }\n\n"; -} - -/* Create the javascript function assign_config_cat */ -void cls_webu_html::script_assign_config_cat() -{ - webua->resp_page += - " function assign_config_cat(jcat) {\n" - " var pCfg = pData['configuration']['default'];\n" - " var pCat = pData['categories'];\n" - " var html_cfg = \"\";\n\n" - - " html_cfg += \"\\n\";\n\n" - - " return html_cfg;\n\n" - - " }\n\n"; -} - -/* Create the javascript function assign_config */ -void cls_webu_html::script_assign_config() -{ - webua->resp_page += - " function assign_config() {\n" - " var pCat = pData['categories'];\n" - " var html_cfg = \"\";\n\n" - - " assign_config_nav();\n\n" - - " for (jcat in pCat) {\n" - " html_cfg += assign_config_cat(jcat);\n" - " }\n\n" - - " html_cfg += \"


\";\n" - " html_cfg += \"\\n\";\n" - " html_cfg += \"
\\n;\"\n\n" - - - " document.getElementById(\"div_config\").innerHTML = html_cfg;\n\n" - - " }\n\n"; -} - -/* Create the javascript function init_form */ -void cls_webu_html::script_initform() -{ - webua->resp_page += - " function initform() {\n" - " var xmlhttp = new XMLHttpRequest();\n\n" - - " pHostFull = '//' + window.location.hostname;\n" - " pHostFull = pHostFull + ':' + window.location.port;\n\n" - - " xmlhttp.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " pData = JSON.parse(this.responseText);\n" - " gIndxCam = -1;\n" - " gGetImgs = 1;\n" - " gIndxScan = -1;\n" - " gLogNbr = 0;\n\n" - - " assign_config();\n" - " assign_version();\n" - " assign_vals(0);\n" - " assign_cams();\n" - " assign_actions();\n" - " cams_all_click();\n" - " nav_close();\n" - - " }\n" - " };\n" - " xmlhttp.open('GET', pHostFull+'/0/config.json');\n" - " xmlhttp.send();\n" - " }\n\n"; -} - -/* Create the javascript function display_cameras */ -void cls_webu_html::script_display_cameras() -{ - webua->resp_page += - " function display_cameras() {\n" - " document.getElementById('divnav_config').style.display = 'none';\n" - " document.getElementById('divnav_actions').style.display = 'none';\n" - " document.getElementById('divnav_movies').style.display = 'none';\n" - " if (document.getElementById('divnav_cam').style.display == 'block'){\n" - " document.getElementById('divnav_cam').style.display = 'none';\n" - " } else {\n" - " document.getElementById('divnav_cam').style.display = 'block';\n" - " }\n" - " }\n\n"; -} - -/* Create the javascript function display_config */ -void cls_webu_html::script_display_config() -{ - webua->resp_page += - " function display_config() {\n" - " document.getElementById('divnav_cam').style.display = 'none';\n" - " document.getElementById('divnav_actions').style.display = 'none';\n" - " document.getElementById('divnav_movies').style.display = 'none';\n" - " if (document.getElementById('divnav_config').style.display == 'block') {\n" - " document.getElementById('divnav_config').style.display = 'none';\n" - " } else {\n" - " document.getElementById('divnav_config').style.display = 'block';\n" - " }\n" - " gIndxScan = -1; \n" - " cams_timer_stop();\n" - " }\n\n"; -} - -/* Create the javascript function display_movies */ -void cls_webu_html::script_display_movies() -{ - webua->resp_page += - " function display_movies() {\n" - " document.getElementById('divnav_cam').style.display = 'none';\n" - " document.getElementById('divnav_actions').style.display = 'none';\n" - " document.getElementById('divnav_config').style.display = 'none';\n" - " if (document.getElementById('divnav_movies').style.display == 'block') {\n" - " document.getElementById('divnav_movies').style.display = 'none';\n" - " } else {\n" - " document.getElementById('divnav_movies').style.display = 'block';\n" - " }\n" - " gIndxScan = -1; \n" - " cams_timer_stop();\n" - " }\n\n"; -} - -/* Create the javascript function display_actions */ -void cls_webu_html::script_display_actions() -{ - webua->resp_page += - " function display_actions() {\n" - " document.getElementById('divnav_cam').style.display = 'none';\n" - " document.getElementById('divnav_config').style.display = 'none';\n" - " if (document.getElementById('divnav_actions').style.display == 'block') {\n" - " document.getElementById('divnav_actions').style.display = 'none';\n" - " } else {\n" - " document.getElementById('divnav_actions').style.display = 'block';\n" - " }\n" - " gIndxScan = -1; \n" - " }\n\n"; -} - -/* Create the camera_buttons_ptz javascript function */ -void cls_webu_html::script_camera_buttons_ptz() -{ - webua->resp_page += - " function camera_buttons_ptz() {\n\n" - " var html_preview = \"\";\n" - - " html_preview += \"\";\n" - " html_preview += \"\\n\";\n" - - " html_preview += \" \\n\";\n" - - " html_preview += \" \\n\";\n" - - " html_preview += \" \\n\";\n" - - " html_preview += \" \\n\";\n" - - " html_preview += \" \\n\";\n" - - " html_preview += \" \\n\";\n" - " html_preview += \"\\n\";\n" - " html_preview += \"
    
    
    

\";\n" - - " return html_preview;\n\n" - - " }\n\n"; -} - -void cls_webu_html::script_image_picall() -{ - webua->resp_page += - " function image_picall() {\n\n" - " document.getElementById('picall').addEventListener('click',function(event){\n" - " bounds=this.getBoundingClientRect();\n" - " var locx,locy,locw, loch,pctx,pcty;\n" - " var indx, camcnt, caminfo;\n" - " locx = Math.floor(event.pageX - bounds.left - window.scrollX);\n" - " locy = Math.floor(event.pageY - bounds.top - window.scrollY);\n" - " locw = Math.floor(bounds.width);\n" - " loch = Math.floor(bounds.height);\n" - " pctx = ((locx*100)/locw);\n" - " pcty = ((locy*100)/loch);\n" - " camcnt = pData['cameras']['count'];\n" - " for (indx=0; indx= pData['cameras'][indx]['all_xpct_st']) &&\n" - " (pctx <= pData['cameras'][indx]['all_xpct_en']) &&\n" - " (pcty >= pData['cameras'][indx]['all_ypct_st']) &&\n" - " (pcty <= pData['cameras'][indx]['all_ypct_en'])) {\n" - " cams_one_click(indx);\n" - " }\n" - " }\n" - " });\n" - " }\n\n"; -} - -/* Create the image_pantilt javascript function */ -void cls_webu_html::script_image_pantilt() -{ - webua->resp_page += - " function image_pantilt() {\n\n" - " if (gIndxCam == -1 ) {\n" - " return;\n" - " }\n\n" - " document.getElementById('pic'+ gIndxCam).addEventListener('click',function(event){\n" - " bounds=this.getBoundingClientRect();\n" - " var x = Math.floor(event.pageX - bounds.left - window.scrollX);\n" - " var y = Math.floor(event.pageY - bounds.top - window.scrollY);\n" - " var w = Math.floor(bounds.width);\n" - " var h = Math.floor(bounds.height);\n" - " var qtr_x = Math.floor(bounds.width/4);\n" - " var qtr_y = Math.floor(bounds.height/4);\n" - " if ((x > qtr_x) && (x < (w - qtr_x)) && (y < qtr_y)) {\n" - " send_action('tilt_up');\n" - " } else if ((x > qtr_x) && (x < (w - qtr_x)) && (y >(h - qtr_y))) {\n" - " send_action('tilt_down');\n" - " } else if ((x < qtr_x) && (y > qtr_y) && (y < (h - qtr_y))) {\n" - " send_action('pan_left');\n" - " } else if ((x >(w - qtr_x)) && (y > qtr_y) && (y < (h - qtr_y))) {\n" - " send_action('pan_right');\n" - " }\n" - " });\n" - " }\n\n"; -} - -/* Create the cams_reset javascript function */ -void cls_webu_html::script_cams_reset() -{ - webua->resp_page += - " function cams_timer_stop() {\n" - " clearInterval(cams_one_timer);\n" - " clearInterval(cams_all_timer);\n" - " clearInterval(cams_scan_timer);\n" - " }\n\n"; - - webua->resp_page += - " function cams_reset() {\n" - " var indx, camcnt;\n" - " camcnt = pData['cameras']['count'];\n" - " for (indx=0; indxresp_page += - " function cams_one_click(index_cam) {\n\n" - " var html_preview = \"\";\n" - " var camid;\n\n" - " config_hideall();\n" - " cams_timer_stop();\n" - " gIndxCam = index_cam;\n\n" - " gIndxScan = -1; \n\n" - - " if (gIndxCam == -1 ) {\n" - " return;\n" - " }\n\n" - - " camid = pData['cameras'][index_cam].id;\n" - - " if ((pData['configuration']['cam'+camid].stream_preview_ptz.value == true)) {\n" - " html_preview += camera_buttons_ptz();\n" - " }\n\n" - " if (pData['configuration']['cam'+camid].stream_preview_method.value == 'static') {\n" - " html_preview += \"\\n\";\n" - " } else { \n" - " html_preview += \"\\n\";\n" - " }\n" - " document.getElementById('div_config').style.display='none';\n" - " document.getElementById('div_movies').style.display = 'none';\n" - " cams_reset();\n" - " document.getElementById('div_cam').style.display='block';\n" - " document.getElementById('div_cam').innerHTML = html_preview;\n\n" - " image_pantilt();\n\n" - " cams_one_timer = setInterval(cams_one_fnc, 1000);\n\n" - " }\n\n"; -} - -/* Create the cams_all_click javascript function */ -void cls_webu_html::script_cams_all_click() -{ - webua->resp_page += - " function cams_all_click() {\n\n" - " var html_preview = \"\";\n" - " var indx, chk;\n" - " var camid;\n\n" - - " config_hideall();\n" - " cams_timer_stop();\n" - " gIndxCam = -1;\n" - " gIndxScan = -1; \n\n" - " var camcnt = pData['cameras']['count'];\n" - " html_preview += \"\";\n" - " chk = 0;\n" - " for (indx=0; indx\\n\";\n" - " if (pData['configuration']['cam'+camid].stream_preview_newline.value == true) {\n" - " html_preview += \"
\\n\";\n" - " }\n" - " } else { \n" - " html_preview += \"\\n\";\n" - " if (pData['configuration']['cam'+camid].stream_preview_newline.value == true) {\n" - " html_preview += \"
\\n\";\n" - " }\n" - " } \n" - " }\n" - " } else { \n" - " html_preview += \"\\n\";\n" - " }\n" - " document.getElementById('div_config').style.display='none';\n" - " document.getElementById('div_movies').style.display = 'none';\n" - " cams_reset();\n" - " document.getElementById('div_cam').style.display='block';\n" - " document.getElementById('div_cam').innerHTML = html_preview;\n" - " if (chk == 0) {\n" - " cams_all_timer = setInterval(cams_all_fnc, 1000);\n" - " } else {\n" - " image_picall();\n" - " }\n\n" - " }\n\n"; -} - -/* Create the movies_page javascript function */ -void cls_webu_html::script_movies_page() -{ - webua->resp_page += - " function movies_page() {\n\n" - " var html_tab = \"
\";\n" - " var indx, movcnt, camid, uri;\n" - " var fname,fsize,fdate;\n\n" - - " if (gIndxCam == -1 ) {\n" - " return;\n" - " }\n\n" - - " camid = assign_camid();\n" - " uri = pHostFull+'/'+camid+'/movies/';\n\n" - - " movcnt = pMovies['movies'][gIndxCam].count;\n" - " html_tab +=\"\";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n\n" - - " for (indx = 0; indx < movcnt; indx++) {\n" - " fname = pMovies['movies'][gIndxCam][indx]['name'];\n" - " fsize = pMovies['movies'][gIndxCam][indx]['size'];\n" - " fdate = pMovies['movies'][gIndxCam][indx]['date'];\n\n" - " ftime = pMovies['movies'][gIndxCam][indx]['time'];\n\n" - " fdavg = pMovies['movies'][gIndxCam][indx]['diff_avg'];\n\n" - " fsmin = pMovies['movies'][gIndxCam][indx]['sdev_min'];\n\n" - " fsmax = pMovies['movies'][gIndxCam][indx]['sdev_max'];\n\n" - " fsavg = pMovies['movies'][gIndxCam][indx]['sdev_avg'];\n\n" - - " html_tab +=\"\";\n" - " html_tab +=\" \";\n" - - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - " html_tab +=\" \";\n" - - " html_tab +=\" \";\n" - " html_tab +=\"\";\n" - " }\n" - " html_tab +=\"
NameSizeDatetimediff_avgsdev_minsdev_maxsdev_avg
\" + fname + \"\"+fsize+\"\"+fdate+\"\"+ftime+\"\"+fdavg+\"\"+fsmin+\"\"+fsmax+\"\"+fsavg+\"
\";\n" - " html_tab +=\"
\";\n\n" - - " document.getElementById('div_config').style.display='none';\n" - " document.getElementById('div_cam').style.display='none';\n" - " cams_reset();\n" - " document.getElementById('div_movies').style.display='block';\n" - " document.getElementById('div_movies').innerHTML = html_tab;\n\n" - " }\n\n"; -} - -/* Create the movies_page javascript function */ -void cls_webu_html::script_movies_click() -{ - webua->resp_page += - " function movies_click(index_cam) {\n" - " var camid, indx, camcnt, uri;\n\n" - - " gIndxCam = index_cam;\n" - " gIndxScan = -1; \n" - " camid = assign_camid();\n" - " uri = pHostFull+'/'+camid+'/movies.json';\n\n" - " config_hideall();\n" - " cams_reset();\n" - " var xmlhttp = new XMLHttpRequest();\n" - " xmlhttp.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " pMovies = JSON.parse(this.responseText);\n" - " movies_page();\n" - " }\n" - " };\n" - " xmlhttp.open('GET', uri);\n" - " xmlhttp.send();\n" - " }\n\n"; -} - -/* Create the cams_scan_click javascript function */ -void cls_webu_html::script_cams_scan_click() -{ - webua->resp_page += - " function cams_scan_click() {\n\n" - " cams_timer_stop();\n\n" - " gIndxCam = -1; \n" - " gIndxScan = 0; \n\n" - " cams_scan_timer = setInterval(cams_scan_fnc, 5);\n" - " }\n\n"; -} - -/* Create the cams_one_fnc javascript function */ -void cls_webu_html::script_cams_one_fnc() -{ - webua->resp_page += - " function cams_one_fnc () {\n" - " var img = new Image();\n" - " var camid;\n\n" - " if (gIndxCam == -1 ) {\n" - " return;\n" - " }\n\n" - " camid = pData['cameras'][gIndxCam]['id'];\n\n" - " if (pData['configuration']['cam'+camid].stream_preview_method.value == 'static') {\n" - " pic_url[0] = pData['cameras'][gIndxCam]['url'] + \"static/stream/t\" + new Date().getTime();\n" - " img.src = pic_url[0];\n" - " document.getElementById('pic'+gIndxCam).src = pic_url[0];\n" - " }\n" - " }\n\n "; -} - -/* Create the cams_all_fnc javascript function */ -void cls_webu_html::script_cams_all_fnc() -{ - webua->resp_page += - " function cams_all_fnc () {\n" - " var previndx = gGetImgs;\n" - " gGetImgs++;\n" - " if (gGetImgs >= pData['cameras']['count']) {\n" - " gGetImgs = 0;\n" - " }\n" - " camid = pData['cameras'][gGetImgs]['id'];\n" - " if (pData['configuration']['cam'+camid].stream_preview_method.value == 'static') {\n" - " document.getElementById('pic'+previndx).src =\n" - " pData['cameras'][previndx]['url'] + \"static/stream/t\" + new Date().getTime();\n" - " document.getElementById('pic'+gGetImgs).src =\n" - " pData['cameras'][gGetImgs]['url'] + \"mjpg/stream\";\n" - " }\n" - " }\n\n"; -} - -/* Create the scancam_function javascript function */ -void cls_webu_html::script_cams_scan_fnc() -{ - webua->resp_page += - " function cams_scan_fnc() {\n" - " var html_preview = \"\";\n" - " var camid;\n" - " var camcnt = pData['cameras']['count'];\n\n" - " cams_reset();\n" - - " if(gIndxScan == -1) {\n" - " clearInterval(cams_scan_timer);\n" - " return;\n" - " }\n\n" - - " if(gIndxScan == (camcnt-1)) {\n" - " gIndxScan = 0;\n" - " } else { \n" - " gIndxScan++;\n" - " }\n\n" - - " camid = pData['cameras'][gIndxScan]['id'];\n" - " clearInterval(cams_scan_timer);\n" - - " cams_scan_timer = setInterval(cams_scan_fnc,\n" - " pData['configuration']['cam'+camid].stream_scan_time.value * 1000 \n" - " );\n" - - " html_preview += \"\\n\";\n" - " document.getElementById('div_config').style.display='none';\n" - " document.getElementById('div_movies').style.display='none';\n" - " cams_reset();\n" - " document.getElementById('div_cam').style.display='block';\n" - " document.getElementById('div_cam').innerHTML = html_preview;\n" - " };\n\n"; -} - -void cls_webu_html::script_log_display() -{ - webua->resp_page += - " function log_display() {\n" - " var itm, msg, nbr, indx, txtalog;\n" - " txtalog = document.getElementById('txta_log').value;\n" - " for (indx = 0; indx < 1000; indx++) {\n" - " itm = pLog[indx];\n" - " if (typeof(itm) != 'undefined') {\n" - " msg = pLog[indx]['logmsg'];\n" - " if (typeof(msg) != 'undefined') {\n" - " gLogNbr = pLog[indx]['lognbr'];\n" - " if (txtalog.length > 2000) {\n" - " txtalog = txtalog.substring(txtalog.length - 2000);\n" - " txtalog = txtalog.substring(txtalog.search('\\n'));\n" - " }\n" - " txtalog += '\\n' + msg;\n" - " }\n" - " }\n" - " }\n" - " document.getElementById('txta_log').enabled = true;\n" - " document.getElementById('txta_log').value = txtalog;\n" - " document.getElementById('txta_log').scrollTop =\n" - " document.getElementById('txta_log').scrollHeight;\n" - " document.getElementById('txta_log').enabled = false;\n" - " }\n\n"; - -} - -void cls_webu_html::script_log_get() -{ - webua->resp_page += - " function log_get() {\n" - " var xmlhttp = new XMLHttpRequest();\n" - " xmlhttp.onreadystatechange = function() {\n" - " if (this.readyState == 4 && this.status == 200) {\n" - " pLog = JSON.parse(this.responseText);\n" - " log_display();\n" - " }\n" - " };\n" - " xmlhttp.open('GET', pHostFull+'/0/log/'+ gLogNbr);\n" - " xmlhttp.send();\n" - " }\n\n"; - -} - -void cls_webu_html::script_log_showhide() -{ - webua->resp_page += - " function log_showhide() {\n" - " if (document.getElementById('div_log').style.display == 'none') {\n" - " document.getElementById('div_log').style.display='block';\n" - " document.getElementById('txta_log').value = '';\n" - " log_timer = setInterval(log_get, 2000);\n" - " } else {\n" - " document.getElementById('div_log').style.display='none';\n" - " document.getElementById('txta_log').value = '';\n" - " clearInterval(log_timer);\n" - " }\n" - " }\n\n"; - -} - -/* Call all the functions to create the java scripts of page*/ -void cls_webu_html::script() -{ - webua->resp_page += " \n\n"; -} - -/* Create the body section of the web page */ -void cls_webu_html::body() -{ - webua->resp_page += "\n"; - - navbar(); - - divmain(); - - script(); - - webua->resp_page += "\n"; -} - -void cls_webu_html::default_page() -{ - webua->resp_page += "\n" - "\n"; - head(); - body(); - webua->resp_page += "\n"; -} - -void cls_webu_html::user_page() -{ - char response[PATH_MAX]; - FILE *fp = NULL; - - webua->resp_page = ""; - fp = myfopen(app->cfg->webcontrol_html.c_str(), "re"); - if (fp == NULL) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO - , _("Invalid user html file: %s") - , app->cfg->webcontrol_html.c_str()); - } else { - while (fgets(response, PATH_MAX-1, fp)) { - webua->resp_page += response; - } - myfclose(fp); - } -} - -void cls_webu_html::main() -{ - pthread_mutex_lock(&app->mutex_post); - if (app->cfg->webcontrol_interface == "user") { - user_page(); - } else { - default_page(); - } - pthread_mutex_unlock(&app->mutex_post); - - if (webua->resp_page == "") { - webua->bad_request(); - } else { - webua->mhd_send(); - } -} - -cls_webu_html::cls_webu_html(cls_webu_ans *p_webua) -{ - app = p_webua->app; - webu = p_webua->webu; - webua = p_webua; -} - -cls_webu_html::~cls_webu_html() -{ - app = nullptr; - webu = nullptr; - webua = nullptr; -} diff --git a/src/webu_html.hpp b/src/webu_html.hpp deleted file mode 100644 index 0d19c218..00000000 --- a/src/webu_html.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This file is part of Motion. - * - * Motion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Motion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Motion. If not, see . - * -*/ - -#ifndef _INCLUDE_WEBU_HTML_HPP_ -#define _INCLUDE_WEBU_HTML_HPP_ - class cls_webu_html { - public: - cls_webu_html(cls_webu_ans *p_webua); - ~cls_webu_html(); - void main(); - private: - cls_motapp *app; - cls_webu *webu; - cls_webu_ans *webua; - - void style_navbar(); - void style_config(); - void style_base(); - void style(); - void head(); - void navbar(); - void divmain(); - void script_nav(); - void script_send_config(); - void script_send_action(); - void script_send_reload(); - void script_dropchange_cam(); - void script_config_hideall(); - void script_config_click(); - void script_assign_camid(); - void script_assign_version(); - void script_assign_cams(); - void script_assign_actions(); - void script_assign_vals(); - void script_assign_config_nav(); - void script_assign_config_item(); - void script_assign_config_cat(); - void script_assign_config(); - void script_initform(); - void script_display_cameras(); - void script_display_config(); - void script_display_movies(); - void script_display_actions(); - void script_camera_buttons_ptz(); - void script_image_picall(); - void script_image_pantilt(); - void script_cams_reset(); - void script_cams_one_click(); - void script_cams_all_click(); - void script_movies_page(); - void script_movies_click(); - void script_cams_scan_click(); - void script_cams_one_fnc(); - void script_cams_all_fnc(); - void script_cams_scan_fnc(); - void script_log_display(); - void script_log_get(); - void script_log_showhide(); - void script(); - void body(); - void default_page(); - void user_page(); - }; - -#endif /* _INCLUDE_WEBU_HTML_HPP_ */ diff --git a/src/webu_json.cpp b/src/webu_json.cpp index 3c81164b..9ba782ed 100644 --- a/src/webu_json.cpp +++ b/src/webu_json.cpp @@ -14,17 +14,177 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_json.cpp - JSON REST API Implementation + * + * This module implements the JSON REST API for configuration management, + * camera control, status queries, and profile operations, serving as the + * primary interface between the React frontend and Motion backend. + * + */ #include "motion.hpp" #include "util.hpp" #include "camera.hpp" #include "conf.hpp" +#include "conf_profile.hpp" #include "logger.hpp" #include "webu.hpp" #include "webu_ans.hpp" +#include "webu_auth.hpp" #include "webu_json.hpp" #include "dbse.hpp" +#include "libcam.hpp" +#include "json_parse.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* CPU-efficient polygon fill using scanline algorithm + * Fills polygon interior with specified value in bitmap + * O(height * edges) complexity, minimal memory allocation + */ +static void fill_polygon(u_char *bitmap, int width, int height, + const std::vector> &polygon, u_char fill_val) +{ + if (polygon.size() < 3) return; + + /* Find vertical bounds */ + int min_y = height, max_y = 0; + for (const auto &pt : polygon) { + if (pt.second < min_y) min_y = pt.second; + if (pt.second > max_y) max_y = pt.second; + } + + /* Clamp to image bounds */ + if (min_y < 0) min_y = 0; + if (max_y >= height) max_y = height - 1; + + /* Scanline fill */ + std::vector x_intersects; + for (int y = min_y; y <= max_y; y++) { + x_intersects.clear(); + + /* Find intersections with polygon edges */ + size_t n = polygon.size(); + for (size_t i = 0; i < n; i++) { + int x1 = polygon[i].first; + int y1 = polygon[i].second; + int x2 = polygon[(i + 1) % n].first; + int y2 = polygon[(i + 1) % n].second; + + /* Check if edge crosses this scanline */ + if ((y1 <= y && y2 > y) || (y2 <= y && y1 > y)) { + /* Compute x intersection using integer math to avoid float */ + int x = x1 + ((y - y1) * (x2 - x1)) / (y2 - y1); + x_intersects.push_back(x); + } + } + + /* Sort intersections */ + std::sort(x_intersects.begin(), x_intersects.end()); + + /* Fill between pairs */ + for (size_t i = 0; i + 1 < x_intersects.size(); i += 2) { + int xs = x_intersects[i]; + int xe = x_intersects[i + 1]; + + /* Clamp to image bounds */ + if (xs < 0) xs = 0; + if (xe >= width) xe = width - 1; + + /* Fill the span */ + for (int x = xs; x <= xe; x++) { + bitmap[y * width + x] = fill_val; + } + } + } +} + +/* Generate auto-path for mask file in target_dir */ +static std::string build_mask_path(cls_camera *cam, const std::string &type) +{ + std::string target = cam->cfg->target_dir; + if (target.empty()) { + target = "/var/lib/motion"; + } + /* Remove trailing slash */ + if (!target.empty() && target.back() == '/') { + target.pop_back(); + } + return target + "/cam" + std::to_string(cam->cfg->device_id) + + "_" + type + ".pgm"; +} + +/* Hot-reload parameter dispatch table + * Maps parameter names to lambda functions that apply the change to a camera + * This replaces the 28-branch if/else chain with O(1) hash map lookup + */ +namespace { + using HotReloadFunc = std::function; + + const std::unordered_map hot_reload_map = { + {"libcam_brightness", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_brightness(atof(val.c_str())); + }}, + {"libcam_contrast", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_contrast(atof(val.c_str())); + }}, + {"libcam_gain", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_gain(atof(val.c_str())); + }}, + {"libcam_awb_enable", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_awb_enable(val == "true" || val == "1"); + }}, + {"libcam_awb_mode", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_awb_mode(atoi(val.c_str())); + }}, + {"libcam_awb_locked", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_awb_locked(val == "true" || val == "1"); + }}, + {"libcam_colour_temp", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_colour_temp(atoi(val.c_str())); + }}, + {"libcam_colour_gain_r", [](cls_camera *cam, const std::string &val) { + float r = atof(val.c_str()); + float b = cam->cfg->parm_cam.libcam_colour_gain_b; + cam->set_libcam_colour_gains(r, b); + }}, + {"libcam_colour_gain_b", [](cls_camera *cam, const std::string &val) { + float r = cam->cfg->parm_cam.libcam_colour_gain_r; + float b = atof(val.c_str()); + cam->set_libcam_colour_gains(r, b); + }}, + {"libcam_af_mode", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_af_mode(atoi(val.c_str())); + }}, + {"libcam_lens_position", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_lens_position(atof(val.c_str())); + }}, + {"libcam_af_range", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_af_range(atoi(val.c_str())); + }}, + {"libcam_af_speed", [](cls_camera *cam, const std::string &val) { + cam->set_libcam_af_speed(atoi(val.c_str())); + }}, + {"libcam_af_trigger", [](cls_camera *cam, const std::string &val) { + int v = atoi(val.c_str()); + if (v == 0) { + cam->trigger_libcam_af_scan(); + } else { + cam->cancel_libcam_af_scan(); + } + }}, + }; +} std::string cls_webu_json::escstr(std::string invar) { @@ -40,13 +200,73 @@ std::string cls_webu_json::escstr(std::string invar) return outvar; } +void cls_webu_json::parms_item_detail(cls_config *conf, std::string pNm) +{ + ctx_params *params; + ctx_params_item *itm; + int indx; + + params = new ctx_params; + params->params_cnt = 0; + mylower(pNm); + + if (pNm == "v4l2_params") { + util_parms_parse(params, pNm, conf->v4l2_params); + } else if (pNm == "netcam_params") { + util_parms_parse(params, pNm, conf->netcam_params); + } else if (pNm == "netcam_high_params") { + util_parms_parse(params, pNm, conf->netcam_high_params); + } else if (pNm == "libcam_params") { + util_parms_parse(params, pNm, conf->libcam_params); + } else if (pNm == "schedule_params") { + util_parms_parse(params, pNm, conf->schedule_params); + } else if (pNm == "picture_schedule_params") { + util_parms_parse(params, pNm, conf->picture_schedule_params); + } else if (pNm == "cleandir_params") { + util_parms_parse(params, pNm, conf->cleandir_params); + } else if (pNm == "secondary_params") { + util_parms_parse(params, pNm, conf->secondary_params); + } else if (pNm == "webcontrol_actions") { + util_parms_parse(params, pNm, conf->webcontrol_actions); + } else if (pNm == "webcontrol_headers") { + util_parms_parse(params, pNm, conf->webcontrol_headers); + } else if (pNm == "stream_preview_params") { + util_parms_parse(params, pNm, conf->stream_preview_params); + } else if (pNm == "snd_params") { + util_parms_parse(params, pNm, conf->snd_params); + } + + webua->resp_page += ",\"count\":"; + webua->resp_page += std::to_string(params->params_cnt); + + if (params->params_cnt > 0) { + webua->resp_page += ",\"parsed\" :{"; + for (indx=0; indxparams_cnt; indx++) { + itm = ¶ms->params_array[indx]; + if (indx != 0) { + webua->resp_page += ","; + } + webua->resp_page += "\""+std::to_string(indx)+"\":"; + webua->resp_page += "{\"name\":\""+itm->param_name+"\","; + webua->resp_page += "\"value\":\""+itm->param_value+"\"}"; + + } + webua->resp_page += "}"; + } + + mydelete(params); + +} + void cls_webu_json::parms_item(cls_config *conf, int indx_parm) { std::string parm_orig, parm_val, parm_list, parm_enable; + std::string parm_name = config_parms[indx_parm].parm_name; + bool password_set = false; parm_orig = ""; parm_val = ""; - parm_list = ""; + parm_list = "[]"; // Default to empty JSON array for valid JSON if (app->cfg->webcontrol_parms < PARM_LEVEL_LIMITED) { parm_enable = "false"; @@ -57,7 +277,23 @@ void cls_webu_json::parms_item(cls_config *conf, int indx_parm) conf->edit_get(config_parms[indx_parm].parm_name , parm_orig, config_parms[indx_parm].parm_cat); - parm_val = escstr(parm_orig); + /* Mask password values for authentication parameters + * Returns username with empty password, plus password_set flag */ + if (parm_name == "webcontrol_authentication" || + parm_name == "webcontrol_user_authentication") { + size_t colon_pos = parm_orig.find(':'); + if (colon_pos != std::string::npos) { + std::string username = parm_orig.substr(0, colon_pos); + std::string password = parm_orig.substr(colon_pos + 1); + password_set = !password.empty(); + /* Return username with empty password portion */ + parm_val = escstr(username) + ":"; + } else { + parm_val = ""; + } + } else { + parm_val = escstr(parm_orig); + } if (config_parms[indx_parm].parm_type == PARM_TYP_INT) { webua->resp_page += @@ -92,7 +328,6 @@ void cls_webu_json::parms_item(cls_config *conf, int indx_parm) } else if (config_parms[indx_parm].parm_type == PARM_TYP_LIST) { conf->edit_list(config_parms[indx_parm].parm_name , parm_list, config_parms[indx_parm].parm_cat); - webua->resp_page += "\"" + config_parms[indx_parm].parm_name + "\"" + ":{" + @@ -102,16 +337,30 @@ void cls_webu_json::parms_item(cls_config *conf, int indx_parm) ",\"type\":\"" + conf->type_desc(config_parms[indx_parm].parm_type) + "\"" + ",\"list\":" + parm_list + "}"; - - } else { + } else if (config_parms[indx_parm].parm_type == PARM_TYP_PARAMS) { webua->resp_page += "\"" + config_parms[indx_parm].parm_name + "\"" + ":{" + " \"value\":\"" + parm_val + "\"" + ",\"enabled\":" + parm_enable + ",\"category\":" + std::to_string(config_parms[indx_parm].parm_cat) + - ",\"type\":\""+ conf->type_desc(config_parms[indx_parm].parm_type) + "\"" + - "}"; + ",\"type\":\""+ conf->type_desc(config_parms[indx_parm].parm_type) + "\""; + parms_item_detail(conf, config_parms[indx_parm].parm_name); + webua->resp_page += "}"; + } else { + webua->resp_page += + "\"" + parm_name + "\"" + + ":{" + + " \"value\":\"" + parm_val + "\"" + + ",\"enabled\":" + parm_enable + + ",\"category\":" + std::to_string(config_parms[indx_parm].parm_cat) + + ",\"type\":\""+ conf->type_desc(config_parms[indx_parm].parm_type) + "\""; + /* Add password_set flag for authentication parameters */ + if (parm_name == "webcontrol_authentication" || + parm_name == "webcontrol_user_authentication") { + webua->resp_page += ",\"password_set\":" + std::string(password_set ? "true" : "false"); + } + webua->resp_page += "}"; } } @@ -258,7 +507,7 @@ void cls_webu_json::movies_list() for (indx=0;indxwb_actions->params_cnt;indx++) { if (webu->wb_actions->params_array[indx].param_name == "movies") { if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Movies via webcontrol disabled"); + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Movies via webcontrol disabled"); webua->resp_page += "{\"count\" : 0} "; webua->resp_page += ",\"device_id\" : "; webua->resp_page += std::to_string(webua->cam->cfg->device_id); @@ -410,6 +659,24 @@ void cls_webu_json::status_vars(int indx_cam) webua->resp_page += ",\"user_pause\":\"" + cam->user_pause +"\""; + /* Add supportedControls for libcamera capability discovery */ + #ifdef HAVE_LIBCAM + if (cam->has_libcam()) { + webua->resp_page += ",\"supportedControls\":{"; + std::map caps = cam->get_libcam_capabilities(); + bool first = true; + for (const auto& [name, supported] : caps) { + if (!first) { + webua->resp_page += ","; + } + webua->resp_page += "\"" + name + "\":" + + (supported ? "true" : "false"); + first = false; + } + webua->resp_page += "}"; + } + #endif + webua->resp_page += "}"; } @@ -471,6 +738,2952 @@ void cls_webu_json::loghistory() } +/* + * Hot Reload API: Validate parameter exists and is hot-reloadable + * Returns true if parameter can be hot-reloaded + * Sets parm_index to the index in config_parms[] (-1 if not found) + */ +bool cls_webu_json::validate_hot_reload(const std::string &parm_name, int &parm_index) +{ + parm_index = 0; + while (config_parms[parm_index].parm_name != "") { + if (config_parms[parm_index].parm_name == parm_name) { + /* Check permission level */ + if (config_parms[parm_index].webui_level > app->cfg->webcontrol_parms) { + return false; + } + /* Check hot reload flag */ + return config_parms[parm_index].hot_reload; + } + parm_index++; + } + parm_index = -1; /* Not found */ + return false; +} + +/* + * Hot Reload Helper: Apply hot-reloadable parameter to a specific camera + * Uses lookup table for O(1) dispatch instead of if/else chain + */ +void cls_webu_json::apply_hot_reload_to_camera(cls_camera *cam, + const std::string &parm_name, const std::string &parm_val) +{ + auto it = hot_reload_map.find(parm_name); + if (it != hot_reload_map.end()) { + it->second(cam, parm_val); + } +} + +/* + * Hot Reload API: Apply parameter change to config + */ +void cls_webu_json::apply_hot_reload(int parm_index, const std::string &parm_val) +{ + std::string parm_name = config_parms[parm_index].parm_name; + + if (webua->device_id == 0) { + /* Update default config */ + app->cfg->edit_set(parm_name, parm_val); + app->conf_src->edit_set(parm_name, parm_val); + + /* Update all running cameras - currently unreachable from UI but kept for + * future "Apply to All Cameras" feature and external API clients */ + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->cfg->edit_set(parm_name, parm_val); + app->cam_list[indx]->conf_src->edit_set(parm_name, parm_val); + apply_hot_reload_to_camera(app->cam_list[indx], parm_name, parm_val); + } + } else if (webua->cam != nullptr) { + /* Update specific camera only */ + webua->cam->cfg->edit_set(parm_name, parm_val); + webua->cam->conf_src->edit_set(parm_name, parm_val); + apply_hot_reload_to_camera(webua->cam, parm_name, parm_val); + } + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Hot reload: %s = %s (camera %d)", + parm_name.c_str(), parm_val.c_str(), webua->device_id); +} + + +/* + * External API: Authentication status for HTTP Basic/Digest clients + * GET /0/api/auth/me + * + * This endpoint is used by external API clients (curl, scripts, automation tools) + * that authenticate via HTTP Basic/Digest. The React UI uses /0/api/auth/status instead. + */ +void cls_webu_json::api_auth_me() +{ + webua->resp_page = "{"; + + /* Check if authentication is configured */ + if (app->cfg->webcontrol_authentication != "") { + webua->resp_page += "\"authenticated\":true,"; + webua->resp_page += "\"auth_method\":\"digest\","; + + /* Include role from HTTP Basic/Digest auth */ + if (webua->auth_role != "") { + webua->resp_page += "\"role\":\"" + webua->auth_role + "\""; + } else { + /* Default to admin if role not determined */ + webua->resp_page += "\"role\":\"admin\""; + } + } else { + webua->resp_page += "\"authenticated\":false"; + } + + webua->resp_page += "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Login with session creation + * POST /0/api/auth/login + * Body: {username, password} + * Returns: {session_token, csrf_token, role, expires_in} + */ +void cls_webu_json::api_auth_login() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Only accept POST */ + if (webua->get_method() != WEBUI_METHOD_POST) { + webua->resp_page = "{\"error\":\"Method not allowed\"}"; + webua->resp_code = 405; + return; + } + + /* Parse JSON body for username/password */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + webua->resp_page = "{\"error\":\"Invalid JSON\"}"; + webua->resp_code = 400; + return; + } + + std::string username = parser.getString("username"); + std::string password = parser.getString("password"); + + if (username.empty() || password.empty()) { + webua->resp_page = "{\"error\":\"Missing username or password\"}"; + webua->resp_code = 400; + return; + } + + /* Validate credentials against config */ + std::string role = ""; + + /* Check admin credentials */ + std::string admin_auth = app->cfg->webcontrol_authentication; + if (!admin_auth.empty()) { + size_t colon_pos = admin_auth.find(':'); + if (colon_pos != std::string::npos) { + std::string admin_user = admin_auth.substr(0, colon_pos); + std::string stored_value = admin_auth.substr(colon_pos + 1); + + /* Verify username matches */ + if (username == admin_user) { + /* Check if stored value is bcrypt hash or plaintext */ + if (cls_webu_auth::is_bcrypt_hash(stored_value)) { + /* Bcrypt hash - verify password */ + if (cls_webu_auth::verify_password(password, stored_value)) { + role = "admin"; + } + } else { + /* Plaintext password (for initial setup compatibility) */ + if (password == stored_value) { + role = "admin"; + + /* Log warning about plaintext password */ + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, + "Plaintext admin password detected - " + "run motion-setup to hash credentials"); + } + } + } + } + } + + /* Check user credentials if admin didn't match */ + if (role.empty()) { + std::string user_auth = app->cfg->webcontrol_user_authentication; + if (!user_auth.empty()) { + size_t colon_pos = user_auth.find(':'); + if (colon_pos != std::string::npos) { + std::string user_user = user_auth.substr(0, colon_pos); + std::string stored_value = user_auth.substr(colon_pos + 1); + + /* Verify username matches */ + if (username == user_user) { + /* Check if stored value is bcrypt hash or plaintext */ + if (cls_webu_auth::is_bcrypt_hash(stored_value)) { + /* Bcrypt hash - verify password */ + if (cls_webu_auth::verify_password(password, stored_value)) { + role = "user"; + } + } else { + /* Plaintext password (for initial setup compatibility) */ + if (password == stored_value) { + role = "user"; + + /* Log warning about plaintext password */ + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, + "Plaintext viewer password detected - " + "run motion-setup to hash credentials"); + } + } + } + } + } + } + + if (role.empty()) { + /* Log failed attempt for rate limiting */ + webua->failauth_log(true, username); + + webua->resp_page = "{\"error\":\"Invalid credentials\"}"; + webua->resp_code = 401; + return; + } + + /* Create session */ + std::string session_token = webu->session_create(role, webua->clientip); + std::string csrf_token = webu->session_get_csrf(session_token); + + /* Return session info */ + webua->resp_page = "{"; + webua->resp_page += "\"session_token\":\"" + session_token + "\","; + webua->resp_page += "\"csrf_token\":\"" + csrf_token + "\","; + webua->resp_page += "\"role\":\"" + role + "\","; + webua->resp_page += "\"expires_in\":" + std::to_string(app->cfg->webcontrol_session_timeout); + webua->resp_page += "}"; +} + +/* + * React UI API: Logout (destroy session) + * POST /0/api/auth/logout + */ +void cls_webu_json::api_auth_logout() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (webua->get_method() != WEBUI_METHOD_POST) { + webua->resp_page = "{\"error\":\"Method not allowed\"}"; + webua->resp_code = 405; + return; + } + + /* Get session token from header */ + std::string session_token = webua->session_token; + + if (!session_token.empty()) { + webu->session_destroy(session_token); + } + + webua->resp_page = "{\"success\":true}"; +} + +/* + * React UI API: Get authentication status + * GET /0/api/auth/status + * Returns: {auth_required, authenticated, role?, csrf_token?} + */ +void cls_webu_json::api_auth_status() +{ + webua->resp_type = WEBUI_RESP_JSON; + webua->resp_page = "{"; + + /* Check if authentication is configured */ + bool auth_required = (app->cfg->webcontrol_authentication != ""); + + webua->resp_page += "\"auth_required\":" + std::string(auth_required ? "true" : "false"); + + if (!auth_required) { + /* No auth configured - full access with pseudo-session for CSRF protection */ + /* Create or reuse session for CSRF token even when auth not required */ + if (webua->session_token.empty()) { + /* No session yet - create pseudo-session for CSRF */ + std::string new_token = webu->session_create("admin", webua->clientip); + webua->resp_page += ",\"authenticated\":true"; + webua->resp_page += ",\"role\":\"admin\""; + webua->resp_page += ",\"session_token\":\"" + new_token + "\""; + webua->resp_page += ",\"csrf_token\":\"" + webu->session_get_csrf(new_token) + "\""; + } else { + /* Reuse existing session */ + std::string role = webu->session_validate(webua->session_token, webua->clientip); + if (!role.empty()) { + webua->resp_page += ",\"authenticated\":true"; + webua->resp_page += ",\"role\":\"" + role + "\""; + webua->resp_page += ",\"csrf_token\":\"" + webu->session_get_csrf(webua->session_token) + "\""; + } else { + /* Session expired - create new one */ + std::string new_token = webu->session_create("admin", webua->clientip); + webua->resp_page += ",\"authenticated\":true"; + webua->resp_page += ",\"role\":\"admin\""; + webua->resp_page += ",\"session_token\":\"" + new_token + "\""; + webua->resp_page += ",\"csrf_token\":\"" + webu->session_get_csrf(new_token) + "\""; + } + } + } else if (!webua->session_token.empty()) { + /* Session token provided - validate it */ + std::string role = webu->session_validate( + webua->session_token, webua->clientip); + + if (!role.empty()) { + webua->resp_page += ",\"authenticated\":true"; + webua->resp_page += ",\"role\":\"" + role + "\""; + webua->resp_page += ",\"csrf_token\":\"" + + webu->session_get_csrf(webua->session_token) + "\""; + } else { + webua->resp_page += ",\"authenticated\":false"; + } + } else if (!webua->auth_role.empty()) { + /* HTTP Basic/Digest auth for external API clients (curl, scripts, etc.) */ + webua->resp_page += ",\"authenticated\":true"; + webua->resp_page += ",\"role\":\"" + webua->auth_role + "\""; + webua->resp_page += ",\"csrf_token\":\"" + webu->csrf_token + "\""; + } else { + /* Auth required but no credentials */ + webua->resp_page += ",\"authenticated\":false"; + } + + webua->resp_page += "}"; +} + +/* + * React UI API: Media pictures list + * Returns list of snapshot images for a camera + */ +void cls_webu_json::api_media_pictures() +{ + vec_files flst, flst_count; + std::string sql, where_clause; + int offset = 0, limit = 100; + int64_t total_count = 0; + const char* date_filter = nullptr; + + if (webua->cam == nullptr) { + webua->bad_request(); + return; + } + + /* Parse query parameters */ + const char* offset_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "offset"); + const char* limit_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "limit"); + date_filter = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "date"); + + if (offset_str) offset = std::max(0, atoi(offset_str)); + if (limit_str) limit = std::min(std::max(1, atoi(limit_str)), 100); // Cap at 100 + + /* Build WHERE clause */ + where_clause = " where device_id = " + std::to_string(webua->cam->cfg->device_id); + where_clause += " and file_typ = 'pic'"; + if (date_filter && strlen(date_filter) == 8) { + where_clause += " and file_dtl = " + std::string(date_filter); + } + + /* Get total count - query just record_id for efficiency */ + sql = " select record_id from motion " + where_clause + ";"; + app->dbse->filelist_get(sql, flst_count); + total_count = flst_count.size(); + + /* Get paginated results */ + sql = " select * from motion "; + sql += where_clause; + sql += " order by file_dtl desc, file_tml desc"; + sql += " limit " + std::to_string(limit); + sql += " offset " + std::to_string(offset) + ";"; + + app->dbse->filelist_get(sql, flst); + + /* Build JSON response with pagination metadata */ + webua->resp_page = "{"; + webua->resp_page += "\"total_count\":" + std::to_string(total_count) + ","; + webua->resp_page += "\"offset\":" + std::to_string(offset) + ","; + webua->resp_page += "\"limit\":" + std::to_string(limit) + ","; + webua->resp_page += "\"date_filter\":"; + if (date_filter) { + webua->resp_page += "\"" + std::string(date_filter) + "\""; + } else { + webua->resp_page += "null"; + } + webua->resp_page += ",\"pictures\":["; + + for (size_t i = 0; i < flst.size(); i++) { + if (i > 0) webua->resp_page += ","; + webua->resp_page += "{"; + webua->resp_page += "\"id\":" + std::to_string(flst[i].record_id) + ","; + webua->resp_page += "\"filename\":\"" + escstr(flst[i].file_nm) + "\","; + webua->resp_page += "\"path\":\"" + escstr(flst[i].full_nm) + "\","; + webua->resp_page += "\"date\":\"" + std::to_string(flst[i].file_dtl) + "\","; + webua->resp_page += "\"time\":\"" + escstr(flst[i].file_tml) + "\","; + webua->resp_page += "\"size\":" + std::to_string(flst[i].file_sz); + webua->resp_page += "}"; + } + webua->resp_page += "]}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Delete a picture file + * DELETE /{camId}/api/media/picture/{id} + * Deletes both the file and database record + */ +void cls_webu_json::api_delete_picture() +{ + int indx; + std::string sql, full_path; + vec_files flst; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Check if delete action is enabled */ + for (indx=0; indxwb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "delete") { + if (webu->wb_actions->params_array[indx].param_value == "off") { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Delete action disabled"); + webua->resp_page = "{\"error\":\"Delete action is disabled\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + break; + } + } + + /* Get file ID from URI: uri_cmd4 contains the record ID */ + if (webua->uri_cmd4.empty()) { + webua->resp_page = "{\"error\":\"File ID required\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + int file_id = mtoi(webua->uri_cmd4); + if (file_id <= 0) { + webua->resp_page = "{\"error\":\"Invalid file ID\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Look up the file in database */ + sql = " select * from motion "; + sql += " where record_id = " + std::to_string(file_id); + sql += " and device_id = " + std::to_string(webua->cam->cfg->device_id); + sql += " and file_typ = 'pic'"; + app->dbse->filelist_get(sql, flst); + + if (flst.empty()) { + webua->resp_page = "{\"error\":\"File not found\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Security: Validate file path to prevent directory traversal */ + full_path = flst[0].full_nm; + if (full_path.find("..") != std::string::npos) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Path traversal attempt blocked: %s from %s"), + full_path.c_str(), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Invalid file path\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Delete the file from filesystem */ + if (remove(full_path.c_str()) != 0 && errno != ENOENT) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Failed to delete file: %s"), full_path.c_str()); + webua->resp_page = "{\"error\":\"Failed to delete file\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Delete from database */ + sql = "delete from motion where record_id = " + std::to_string(file_id); + app->dbse->exec_sql(sql); + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Deleted picture: %s (id=%d) by %s", + flst[0].file_nm.c_str(), file_id, webua->clientip.c_str()); + + webua->resp_page = "{\"success\":true,\"deleted_id\":" + std::to_string(file_id) + "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Delete a movie file + * DELETE /{camId}/api/media/movie/{id} + * Deletes both the file and database record + */ +void cls_webu_json::api_delete_movie() +{ + int indx; + std::string sql, full_path; + vec_files flst; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Check if delete action is enabled */ + for (indx=0; indxwb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "delete") { + if (webu->wb_actions->params_array[indx].param_value == "off") { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Delete action disabled"); + webua->resp_page = "{\"error\":\"Delete action is disabled\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + break; + } + } + + /* Get file ID from URI: uri_cmd4 contains the record ID */ + if (webua->uri_cmd4.empty()) { + webua->resp_page = "{\"error\":\"File ID required\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + int file_id = mtoi(webua->uri_cmd4); + if (file_id <= 0) { + webua->resp_page = "{\"error\":\"Invalid file ID\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Look up the file in database */ + sql = " select * from motion "; + sql += " where record_id = " + std::to_string(file_id); + sql += " and device_id = " + std::to_string(webua->cam->cfg->device_id); + sql += " and file_typ = 'movie'"; + app->dbse->filelist_get(sql, flst); + + if (flst.empty()) { + webua->resp_page = "{\"error\":\"File not found\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Security: Validate file path to prevent directory traversal */ + full_path = flst[0].full_nm; + if (full_path.find("..") != std::string::npos) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Path traversal attempt blocked: %s from %s"), + full_path.c_str(), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Invalid file path\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Delete the file from filesystem */ + if (remove(full_path.c_str()) != 0 && errno != ENOENT) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Failed to delete file: %s"), full_path.c_str()); + webua->resp_page = "{\"error\":\"Failed to delete file\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Delete associated thumbnail */ + std::string thumb_path = full_path + ".thumb.jpg"; + if (remove(thumb_path.c_str()) != 0 && errno != ENOENT) { + MOTION_LOG(NTC, TYPE_STREAM, SHOW_ERRNO, + _("Could not delete thumbnail: %s"), thumb_path.c_str()); + /* Non-fatal - continue with database deletion */ + } + + /* Delete from database */ + sql = "delete from motion where record_id = " + std::to_string(file_id); + app->dbse->exec_sql(sql); + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Deleted movie: %s (id=%d) by %s", + flst[0].file_nm.c_str(), file_id, webua->clientip.c_str()); + + webua->resp_page = "{\"success\":true,\"deleted_id\":" + std::to_string(file_id) + "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: List movies + * Returns list of movie files from database + */ +void cls_webu_json::api_media_movies() +{ + vec_files flst, flst_count; + std::string sql, where_clause, cam_id; + int offset = 0, limit = 100; + int64_t total_count = 0; + const char* date_filter = nullptr; + + if (webua->cam == nullptr) { + webua->bad_request(); + return; + } + + cam_id = std::to_string(webua->cam->cfg->device_id); + + /* Parse query parameters */ + const char* offset_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "offset"); + const char* limit_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "limit"); + date_filter = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "date"); + + if (offset_str) offset = std::max(0, atoi(offset_str)); + if (limit_str) limit = std::min(std::max(1, atoi(limit_str)), 100); // Cap at 100 + + /* Build WHERE clause */ + where_clause = " where device_id = " + cam_id; + where_clause += " and file_typ = 'movie'"; + if (date_filter && strlen(date_filter) == 8) { + where_clause += " and file_dtl = " + std::string(date_filter); + } + + /* Get total count - query just record_id for efficiency */ + sql = " select record_id from motion " + where_clause + ";"; + app->dbse->filelist_get(sql, flst_count); + total_count = flst_count.size(); + + /* Get paginated results */ + sql = " select * from motion "; + sql += where_clause; + sql += " order by file_dtl desc, file_tml desc"; + sql += " limit " + std::to_string(limit); + sql += " offset " + std::to_string(offset) + ";"; + + app->dbse->filelist_get(sql, flst); + + /* Build JSON response with pagination metadata */ + webua->resp_page = "{"; + webua->resp_page += "\"total_count\":" + std::to_string(total_count) + ","; + webua->resp_page += "\"offset\":" + std::to_string(offset) + ","; + webua->resp_page += "\"limit\":" + std::to_string(limit) + ","; + webua->resp_page += "\"date_filter\":"; + if (date_filter) { + webua->resp_page += "\"" + std::string(date_filter) + "\""; + } else { + webua->resp_page += "null"; + } + webua->resp_page += ",\"movies\":["; + + for (size_t i = 0; i < flst.size(); i++) { + if (i > 0) webua->resp_page += ","; + webua->resp_page += "{"; + webua->resp_page += "\"id\":" + std::to_string(flst[i].record_id) + ","; + webua->resp_page += "\"filename\":\"" + escstr(flst[i].file_nm) + "\","; + /* Return URL path for browser access, not filesystem path */ + webua->resp_page += "\"path\":\"/" + cam_id + "/movies/" + escstr(flst[i].file_nm) + "\","; + webua->resp_page += "\"date\":\"" + std::to_string(flst[i].file_dtl) + "\","; + webua->resp_page += "\"time\":\"" + escstr(flst[i].file_tml) + "\","; + webua->resp_page += "\"size\":" + std::to_string(flst[i].file_sz); + + /* Add thumbnail path if exists */ + std::string thumb_path = flst[i].full_nm + ".thumb.jpg"; + struct stat st; + if (stat(thumb_path.c_str(), &st) == 0) { + webua->resp_page += ",\"thumbnail\":\"/" + cam_id + "/movies/" + + escstr(flst[i].file_nm) + ".thumb.jpg\""; + } + + webua->resp_page += "}"; + } + webua->resp_page += "]}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Date summary + * Returns list of dates with counts for a media type + * GET /{camId}/api/media/dates?type=movie + */ +void cls_webu_json::api_media_dates() +{ + vec_files flst; + std::string sql, file_typ; + std::map date_counts; + int64_t total_count = 0; + const char* type_param; + + if (webua->cam == nullptr) { + webua->bad_request(); + return; + } + + /* Parse type parameter (required) */ + type_param = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "type"); + + if (!type_param || (strcmp(type_param, "pic") != 0 && strcmp(type_param, "movie") != 0)) { + webua->resp_page = "{\"error\":\"Invalid or missing 'type' parameter. Must be 'pic' or 'movie'\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + file_typ = type_param; + + /* Query all records for this type to build date summary */ + sql = " select record_id, file_dtl from motion "; + sql += " where device_id = " + std::to_string(webua->cam->cfg->device_id); + sql += " and file_typ = '" + file_typ + "'"; + sql += " order by file_dtl desc;"; + + app->dbse->filelist_get(sql, flst); + total_count = flst.size(); + + /* Group by date */ + for (size_t i = 0; i < flst.size(); i++) { + std::string date_str = std::to_string(flst[i].file_dtl); + date_counts[date_str]++; + } + + /* Build JSON response */ + webua->resp_page = "{"; + webua->resp_page += "\"type\":\"" + file_typ + "\","; + webua->resp_page += "\"total_count\":" + std::to_string(total_count) + ","; + webua->resp_page += "\"dates\":["; + + bool first = true; + for (const auto& pair : date_counts) { + if (!first) webua->resp_page += ","; + webua->resp_page += "{"; + webua->resp_page += "\"date\":\"" + pair.first + "\","; + webua->resp_page += "\"count\":" + std::to_string(pair.second); + webua->resp_page += "}"; + first = false; + } + + webua->resp_page += "]}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* Media file extension checking helpers */ +static bool is_media_extension(const std::string &ext) +{ + static const std::set media_exts = { + ".mp4", ".mkv", ".avi", ".webm", ".mov", + ".jpg", ".jpeg", ".png", ".gif", ".bmp" + }; + std::string lower_ext = ext; + std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(), ::tolower); + return media_exts.find(lower_ext) != media_exts.end(); +} + +static bool is_thumbnail(const std::string &filename) +{ + return filename.length() > 10 && + filename.substr(filename.length() - 10) == ".thumb.jpg"; +} + +static std::string get_file_extension(const std::string &filename) +{ + size_t dot_pos = filename.rfind('.'); + if (dot_pos == std::string::npos || dot_pos == 0) return ""; + return filename.substr(dot_pos); +} + +/* Validate path is safe (no traversal, within target_dir) */ +static bool validate_folder_path(const std::string &target_dir, const std::string &rel_path, + std::string &full_path) +{ + /* Check for path traversal attempts */ + if (rel_path.find("..") != std::string::npos) { + return false; + } + + /* Build full path */ + full_path = target_dir; + if (!full_path.empty() && full_path.back() != '/') { + full_path += '/'; + } + if (!rel_path.empty()) { + full_path += rel_path; + } + + /* Resolve symlinks and check real path is still under target_dir */ + char resolved[PATH_MAX]; + if (realpath(full_path.c_str(), resolved) == nullptr) { + /* Path doesn't exist - that's ok for empty folder case */ + return true; + } + + std::string real_path(resolved); + char target_resolved[PATH_MAX]; + if (realpath(target_dir.c_str(), target_resolved) == nullptr) { + return false; + } + std::string real_target(target_resolved); + + /* Ensure resolved path starts with target_dir */ + if (real_path.length() < real_target.length() || + real_path.substr(0, real_target.length()) != real_target) { + return false; + } + + /* Ensure it's either exactly target_dir or has a / separator after */ + if (real_path.length() > real_target.length() && + real_path[real_target.length()] != '/') { + return false; + } + + return true; +} + +/* + * React UI API: Folder-based media browsing + * GET /{camId}/api/media/folders?path=rel/path&offset=0&limit=100 + * Returns folders and media files in the specified directory + */ +void cls_webu_json::api_media_folders() +{ + vec_files flst; + std::string sql, target_dir, full_path; + int offset = 0, limit = 100; + const char* path_param = nullptr; + std::string rel_path; + + if (webua->cam == nullptr) { + webua->bad_request(); + return; + } + + /* Get target directory for this camera */ + target_dir = webua->cam->cfg->target_dir; + if (target_dir.empty()) { + webua->resp_page = "{\"error\":\"Target directory not configured\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Parse query parameters */ + path_param = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "path"); + const char* offset_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "offset"); + const char* limit_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "limit"); + + if (path_param) rel_path = path_param; + if (offset_str) offset = std::max(0, atoi(offset_str)); + if (limit_str) limit = std::min(std::max(1, atoi(limit_str)), 100); + + /* Validate and build full path */ + if (!validate_folder_path(target_dir, rel_path, full_path)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Path traversal attempt blocked: %s from %s"), + rel_path.c_str(), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Invalid path\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Open directory */ + DIR *dir = opendir(full_path.c_str()); + if (dir == nullptr) { + webua->resp_page = "{\"error\":\"Directory not found\"}"; + webua->resp_type = WEBUI_RESP_JSON; + return; + } + + /* Scan directory entries */ + struct dirent *entry; + std::vector> folders; /* name, path */ + std::vector media_files; + + while ((entry = readdir(dir)) != nullptr) { + std::string name = entry->d_name; + + /* Skip . and .. */ + if (name == "." || name == "..") continue; + + /* Skip hidden files */ + if (name[0] == '.') continue; + + std::string entry_path = full_path + "/" + name; + struct stat st; + if (stat(entry_path.c_str(), &st) != 0) continue; + + if (S_ISDIR(st.st_mode)) { + /* Directory - add to folders list */ + std::string folder_rel = rel_path.empty() ? name : rel_path + "/" + name; + folders.push_back({name, folder_rel}); + } else if (S_ISREG(st.st_mode)) { + /* Regular file - check if it's a media file (not thumbnail) */ + std::string ext = get_file_extension(name); + if (is_media_extension(ext) && !is_thumbnail(name)) { + media_files.push_back(name); + } + } + } + closedir(dir); + + /* Sort folders and files alphabetically */ + std::sort(folders.begin(), folders.end()); + std::sort(media_files.begin(), media_files.end()); + + /* Calculate folder statistics (file count, total size) */ + std::string cam_id = std::to_string(webua->cam->cfg->device_id); + + /* Build JSON response */ + webua->resp_page = "{"; + webua->resp_page += "\"path\":\"" + escstr(rel_path) + "\","; + + /* Parent path for navigation */ + if (rel_path.empty()) { + webua->resp_page += "\"parent\":null,"; + } else { + size_t last_slash = rel_path.rfind('/'); + std::string parent = (last_slash == std::string::npos) ? "" : rel_path.substr(0, last_slash); + webua->resp_page += "\"parent\":\"" + escstr(parent) + "\","; + } + + /* Folders */ + webua->resp_page += "\"folders\":["; + for (size_t i = 0; i < folders.size(); i++) { + if (i > 0) webua->resp_page += ","; + + /* Count files in this folder (from database) */ + std::string folder_path = full_path + "/" + folders[i].first; + int64_t file_count = 0; + int64_t total_size = 0; + + /* Count by scanning directory */ + DIR *subdir = opendir(folder_path.c_str()); + if (subdir != nullptr) { + struct dirent *subentry; + while ((subentry = readdir(subdir)) != nullptr) { + std::string subname = subentry->d_name; + if (subname == "." || subname == "..") continue; + std::string subpath = folder_path + "/" + subname; + struct stat sub_st; + if (stat(subpath.c_str(), &sub_st) == 0 && S_ISREG(sub_st.st_mode)) { + std::string ext = get_file_extension(subname); + if (is_media_extension(ext) && !is_thumbnail(subname)) { + file_count++; + total_size += sub_st.st_size; + } + } + } + closedir(subdir); + } + + webua->resp_page += "{"; + webua->resp_page += "\"name\":\"" + escstr(folders[i].first) + "\","; + webua->resp_page += "\"path\":\"" + escstr(folders[i].second) + "\","; + webua->resp_page += "\"file_count\":" + std::to_string(file_count) + ","; + webua->resp_page += "\"total_size\":" + std::to_string(total_size); + webua->resp_page += "}"; + } + webua->resp_page += "],"; + + /* Files with pagination */ + int total_files = (int)media_files.size(); + int start_idx = std::min(offset, total_files); + int end_idx = std::min(offset + limit, total_files); + + webua->resp_page += "\"files\":["; + for (int i = start_idx; i < end_idx; i++) { + if (i > start_idx) webua->resp_page += ","; + + std::string filename = media_files[i]; + std::string file_path = full_path + "/" + filename; + struct stat st; + stat(file_path.c_str(), &st); + + /* Determine file type */ + std::string ext = get_file_extension(filename); + std::string file_type = "movie"; + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || + ext == ".gif" || ext == ".bmp") { + file_type = "picture"; + } + + /* Look up in database for metadata */ + sql = " select * from motion "; + sql += " where device_id = " + cam_id; + sql += " and file_nm = '" + filename + "'"; + sql += " limit 1;"; + flst.clear(); + app->dbse->filelist_get(sql, flst); + + webua->resp_page += "{"; + + if (!flst.empty()) { + webua->resp_page += "\"id\":" + std::to_string(flst[0].record_id) + ","; + webua->resp_page += "\"date\":\"" + std::to_string(flst[0].file_dtl) + "\","; + webua->resp_page += "\"time\":\"" + escstr(flst[0].file_tml) + "\","; + } else { + webua->resp_page += "\"id\":0,"; + /* Extract date from filename if possible (common format: camera-YYYYMMDD...) */ + webua->resp_page += "\"date\":\"\","; + webua->resp_page += "\"time\":\"\","; + } + + webua->resp_page += "\"filename\":\"" + escstr(filename) + "\","; + + /* Build URL path for access */ + if (file_type == "movie") { + std::string url_path = "/" + cam_id + "/movies/"; + if (!rel_path.empty()) url_path += rel_path + "/"; + url_path += filename; + webua->resp_page += "\"path\":\"" + escstr(url_path) + "\","; + + /* Check for thumbnail */ + std::string thumb_file = file_path + ".thumb.jpg"; + struct stat thumb_st; + if (stat(thumb_file.c_str(), &thumb_st) == 0) { + webua->resp_page += "\"thumbnail\":\"" + escstr(url_path + ".thumb.jpg") + "\","; + } + } else { + /* Pictures use direct file path */ + webua->resp_page += "\"path\":\"" + escstr(file_path) + "\","; + } + + webua->resp_page += "\"type\":\"" + file_type + "\","; + webua->resp_page += "\"size\":" + std::to_string(st.st_size); + webua->resp_page += "}"; + } + webua->resp_page += "],"; + + webua->resp_page += "\"total_files\":" + std::to_string(total_files) + ","; + webua->resp_page += "\"offset\":" + std::to_string(offset) + ","; + webua->resp_page += "\"limit\":" + std::to_string(limit); + webua->resp_page += "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Delete all media files in a folder + * DELETE /{camId}/api/media/folders/files?path=rel/path + * Deletes media files only (not subfolders or non-media files) + * Also deletes associated thumbnails + */ +void cls_webu_json::api_delete_folder_files() +{ + std::string target_dir, full_path, sql; + const char* path_param = nullptr; + std::string rel_path; + int deleted_movies = 0, deleted_pictures = 0, deleted_thumbnails = 0; + std::vector errors; + + webua->resp_type = WEBUI_RESP_JSON; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + return; + } + + /* Require admin role */ + if (webua->auth_role != "admin") { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Delete folder files denied - requires admin role (from %s)"), + webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Admin access required\"}"; + return; + } + + /* Check if delete action is enabled */ + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "delete") { + if (webu->wb_actions->params_array[indx].param_value == "off") { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Delete action disabled"); + webua->resp_page = "{\"error\":\"Delete action is disabled\"}"; + return; + } + break; + } + } + + /* Get path parameter (required) */ + path_param = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "path"); + + if (path_param == nullptr) { + webua->resp_page = "{\"error\":\"Path parameter required\"}"; + return; + } + rel_path = path_param; + + /* Get target directory for this camera */ + target_dir = webua->cam->cfg->target_dir; + if (target_dir.empty()) { + webua->resp_page = "{\"error\":\"Target directory not configured\"}"; + return; + } + + /* Validate and build full path */ + if (!validate_folder_path(target_dir, rel_path, full_path)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("Path traversal attempt blocked: %s from %s"), + rel_path.c_str(), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Invalid path\"}"; + return; + } + + /* Open directory */ + DIR *dir = opendir(full_path.c_str()); + if (dir == nullptr) { + webua->resp_page = "{\"error\":\"Directory not found\"}"; + return; + } + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Delete all media files in folder '%s' requested by %s", + rel_path.c_str(), webua->clientip.c_str()); + + /* Collect media files to delete */ + struct dirent *entry; + std::vector files_to_delete; + std::vector thumbs_to_delete; + + while ((entry = readdir(dir)) != nullptr) { + std::string name = entry->d_name; + if (name == "." || name == "..") continue; + + std::string entry_path = full_path + "/" + name; + struct stat st; + if (stat(entry_path.c_str(), &st) != 0) continue; + + if (S_ISREG(st.st_mode)) { + std::string ext = get_file_extension(name); + if (is_thumbnail(name)) { + /* Track thumbnails separately - they'll be deleted with their movie */ + continue; + } else if (is_media_extension(ext)) { + files_to_delete.push_back(entry_path); + /* Check for associated thumbnail */ + std::string thumb_path = entry_path + ".thumb.jpg"; + struct stat thumb_st; + if (stat(thumb_path.c_str(), &thumb_st) == 0) { + thumbs_to_delete.push_back(thumb_path); + } + } + } + } + closedir(dir); + + std::string cam_id = std::to_string(webua->cam->cfg->device_id); + + /* Delete files */ + for (const auto& file_path : files_to_delete) { + std::string ext = get_file_extension(file_path); + bool is_movie = (ext == ".mp4" || ext == ".mkv" || ext == ".avi" || + ext == ".webm" || ext == ".mov"); + + if (remove(file_path.c_str()) == 0) { + if (is_movie) { + deleted_movies++; + } else { + deleted_pictures++; + } + + /* Delete from database */ + size_t last_slash = file_path.rfind('/'); + std::string filename = (last_slash == std::string::npos) ? + file_path : file_path.substr(last_slash + 1); + + sql = "delete from motion where device_id = " + cam_id + + " and file_nm = '" + filename + "'"; + app->dbse->exec_sql(sql); + } else { + errors.push_back("Failed to delete: " + file_path); + MOTION_LOG(ERR, TYPE_STREAM, SHOW_ERRNO, + _("Failed to delete file: %s"), file_path.c_str()); + } + } + + /* Delete thumbnails */ + for (const auto& thumb_path : thumbs_to_delete) { + if (remove(thumb_path.c_str()) == 0) { + deleted_thumbnails++; + } + } + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Deleted %d movies, %d pictures, %d thumbnails from '%s'", + deleted_movies, deleted_pictures, deleted_thumbnails, rel_path.c_str()); + + /* Build response */ + webua->resp_page = "{"; + webua->resp_page += "\"success\":true,"; + webua->resp_page += "\"deleted\":{"; + webua->resp_page += "\"movies\":" + std::to_string(deleted_movies) + ","; + webua->resp_page += "\"pictures\":" + std::to_string(deleted_pictures) + ","; + webua->resp_page += "\"thumbnails\":" + std::to_string(deleted_thumbnails); + webua->resp_page += "},"; + webua->resp_page += "\"errors\":["; + for (size_t i = 0; i < errors.size(); i++) { + if (i > 0) webua->resp_page += ","; + webua->resp_page += "\"" + escstr(errors[i]) + "\""; + } + webua->resp_page += "],"; + webua->resp_page += "\"path\":\"" + escstr(rel_path) + "\""; + webua->resp_page += "}"; +} + +/* + * React UI API: System temperature + * Returns CPU temperature (Raspberry Pi) + */ +void cls_webu_json::api_system_temperature() +{ + FILE *temp_file; + int temp_raw; + double temp_celsius; + + webua->resp_page = "{"; + + temp_file = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); + if (temp_file != nullptr) { + if (fscanf(temp_file, "%d", &temp_raw) == 1) { + temp_celsius = temp_raw / 1000.0; + webua->resp_page += "\"celsius\":" + std::to_string(temp_celsius) + ","; + webua->resp_page += "\"fahrenheit\":" + std::to_string(temp_celsius * 9.0 / 5.0 + 32.0); + } + fclose(temp_file); + } else { + webua->resp_page += "\"error\":\"Temperature not available\""; + } + + webua->resp_page += "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: System status + * Returns comprehensive system information (CPU temp, disk, memory, uptime) + */ +void cls_webu_json::api_system_status() +{ + FILE *file; + char buffer[256]; + int temp_raw; + double temp_celsius; + unsigned long uptime_sec, mem_total, mem_free, mem_available; + struct statvfs fs_stat; + + webua->resp_page = "{"; + + /* CPU Temperature */ + file = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); + if (file != nullptr) { + if (fscanf(file, "%d", &temp_raw) == 1) { + temp_celsius = temp_raw / 1000.0; + webua->resp_page += "\"temperature\":{"; + webua->resp_page += "\"celsius\":" + std::to_string(temp_celsius) + ","; + webua->resp_page += "\"fahrenheit\":" + std::to_string(temp_celsius * 9.0 / 5.0 + 32.0); + webua->resp_page += "},"; + } + fclose(file); + } + + /* System Uptime */ + file = fopen("/proc/uptime", "r"); + if (file != nullptr) { + if (fscanf(file, "%lu", &uptime_sec) == 1) { + webua->resp_page += "\"uptime\":{"; + webua->resp_page += "\"seconds\":" + std::to_string(uptime_sec) + ","; + webua->resp_page += "\"days\":" + std::to_string(uptime_sec / 86400) + ","; + webua->resp_page += "\"hours\":" + std::to_string((uptime_sec % 86400) / 3600); + webua->resp_page += "},"; + } + fclose(file); + } + + /* Memory Information */ + file = fopen("/proc/meminfo", "r"); + if (file != nullptr) { + mem_total = mem_free = mem_available = 0; + while (fgets(buffer, sizeof(buffer), file)) { + if (sscanf(buffer, "MemTotal: %lu kB", &mem_total) == 1) continue; + if (sscanf(buffer, "MemFree: %lu kB", &mem_free) == 1) continue; + if (sscanf(buffer, "MemAvailable: %lu kB", &mem_available) == 1) break; + } + fclose(file); + + if (mem_total > 0) { + unsigned long mem_used = mem_total - mem_available; + double mem_percent = (double)mem_used / mem_total * 100.0; + webua->resp_page += "\"memory\":{"; + webua->resp_page += "\"total\":" + std::to_string(mem_total * 1024) + ","; + webua->resp_page += "\"used\":" + std::to_string(mem_used * 1024) + ","; + webua->resp_page += "\"free\":" + std::to_string(mem_free * 1024) + ","; + webua->resp_page += "\"available\":" + std::to_string(mem_available * 1024) + ","; + webua->resp_page += "\"percent\":" + std::to_string(mem_percent); + webua->resp_page += "},"; + } + } + + /* Disk Usage (root filesystem) */ + if (statvfs("/", &fs_stat) == 0) { + unsigned long long total_bytes = (unsigned long long)fs_stat.f_blocks * fs_stat.f_frsize; + unsigned long long free_bytes = (unsigned long long)fs_stat.f_bfree * fs_stat.f_frsize; + unsigned long long avail_bytes = (unsigned long long)fs_stat.f_bavail * fs_stat.f_frsize; + unsigned long long used_bytes = total_bytes - free_bytes; + double disk_percent = (double)used_bytes / total_bytes * 100.0; + + webua->resp_page += "\"disk\":{"; + webua->resp_page += "\"total\":" + std::to_string(total_bytes) + ","; + webua->resp_page += "\"used\":" + std::to_string(used_bytes) + ","; + webua->resp_page += "\"free\":" + std::to_string(free_bytes) + ","; + webua->resp_page += "\"available\":" + std::to_string(avail_bytes) + ","; + webua->resp_page += "\"percent\":" + std::to_string(disk_percent); + webua->resp_page += "},"; + } + + /* Device Model (Raspberry Pi) */ + file = fopen("/proc/device-tree/model", "r"); + if (file != nullptr) { + if (fgets(buffer, sizeof(buffer), file)) { + /* Remove trailing newline/null */ + size_t len = strlen(buffer); + while (len > 0 && (buffer[len-1] == '\n' || buffer[len-1] == '\0' || buffer[len-1] == '\r')) { + buffer[--len] = '\0'; + } + webua->resp_page += "\"device_model\":\"" + escstr(buffer) + "\","; + + /* Detect Pi generation */ + if (strstr(buffer, "Pi 5") != nullptr) { + webua->resp_page += "\"pi_generation\":5,"; + } else if (strstr(buffer, "Pi 4") != nullptr) { + webua->resp_page += "\"pi_generation\":4,"; + } else if (strstr(buffer, "Pi 3") != nullptr) { + webua->resp_page += "\"pi_generation\":3,"; + } else { + webua->resp_page += "\"pi_generation\":0,"; + } + } + fclose(file); + } + + /* Hardware Encoder Availability */ + { + const AVCodec *codec_check; + webua->resp_page += "\"hardware_encoders\":{"; + + /* Check for V4L2 M2M H.264 encoder (Pi 4 only) */ + codec_check = avcodec_find_encoder_by_name("h264_v4l2m2m"); + webua->resp_page += "\"h264_v4l2m2m\":" + std::string(codec_check ? "true" : "false"); + + webua->resp_page += "},"; + } + + /* Webcontrol Actions Status */ + webua->resp_page += "\"actions\":{"; + + bool service_enabled = false; + bool power_enabled = false; + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "service" && + webu->wb_actions->params_array[indx].param_value == "on") { + service_enabled = true; + } + if (webu->wb_actions->params_array[indx].param_name == "power" && + webu->wb_actions->params_array[indx].param_value == "on") { + power_enabled = true; + } + } + + webua->resp_page += "\"service\":" + std::string(service_enabled ? "true" : "false"); + webua->resp_page += ",\"power\":" + std::string(power_enabled ? "true" : "false"); + webua->resp_page += "},"; + + /* Motion Version */ + webua->resp_page += "\"version\":\"" + escstr(VERSION) + "\""; + + /* Camera Status (includes FPS for each camera) */ + webua->resp_page += ",\"status\":{"; + webua->resp_page += "\"count\":" + std::to_string(app->cam_cnt); + for (int indx_cam = 0; indx_cam < app->cam_cnt; indx_cam++) { + webua->resp_page += ",\"cam" + + std::to_string(app->cam_list[indx_cam]->cfg->device_id) + "\":"; + status_vars(indx_cam); + } + webua->resp_page += "}"; + + webua->resp_page += "}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: System reboot + * POST /0/api/system/reboot + * Requires CSRF token and authentication + */ +void cls_webu_json::api_system_reboot() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for reboot from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + return; + } + + /* Check if power control is enabled via webcontrol_actions */ + bool power_enabled = false; + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "power") { + if (webu->wb_actions->params_array[indx].param_value == "on") { + power_enabled = true; + } + break; + } + } + + if (!power_enabled) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Reboot request denied - power control disabled (from %s)", webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Power control is disabled\"}"; + return; + } + + /* Log the reboot request */ + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "System reboot requested by %s", webua->clientip.c_str()); + + /* Schedule reboot with 2-second delay to allow HTTP response to complete */ + std::thread([]() { + sleep(2); + /* Try reboot commands in sequence (like MotionEye) */ + if (system("sudo /sbin/reboot") != 0) { + if (system("sudo /sbin/shutdown -r now") != 0) { + if (system("sudo /usr/bin/systemctl reboot") != 0) { + system("sudo /sbin/init 6"); + } + } + } + }).detach(); + + webua->resp_page = "{\"success\":true,\"operation\":\"reboot\",\"message\":\"System will reboot in 2 seconds\"}"; +} + +/* + * React UI API: System shutdown + * POST /0/api/system/shutdown + * Requires CSRF token and authentication + */ +void cls_webu_json::api_system_shutdown() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for shutdown from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + return; + } + + /* Check if power control is enabled via webcontrol_actions */ + bool power_enabled = false; + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "power") { + if (webu->wb_actions->params_array[indx].param_value == "on") { + power_enabled = true; + } + break; + } + } + + if (!power_enabled) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Shutdown request denied - power control disabled (from %s)", webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Power control is disabled\"}"; + return; + } + + /* Log the shutdown request */ + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "System shutdown requested by %s", webua->clientip.c_str()); + + /* Schedule shutdown with 2-second delay to allow HTTP response to complete */ + std::thread([]() { + sleep(2); + /* Try shutdown commands in sequence (like MotionEye) */ + if (system("sudo /sbin/poweroff") != 0) { + if (system("sudo /sbin/shutdown -h now") != 0) { + if (system("sudo /usr/bin/systemctl poweroff") != 0) { + system("sudo /sbin/init 0"); + } + } + } + }).detach(); + + webua->resp_page = "{\"success\":true,\"operation\":\"shutdown\",\"message\":\"System will shut down in 2 seconds\"}"; +} + +/* + * React UI API: Restart Motion service + * POST /0/api/system/service-restart + * Requires CSRF token and authentication + */ +void cls_webu_json::api_system_service_restart() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for service restart from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + return; + } + + /* Check if service control is enabled via webcontrol_actions */ + bool service_enabled = false; + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == "service") { + if (webu->wb_actions->params_array[indx].param_value == "on") { + service_enabled = true; + } + break; + } + } + + if (!service_enabled) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Service restart request denied - service control disabled (from %s)", webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"Service control is disabled\"}"; + return; + } + + /* Log the restart request */ + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Motion service restart requested by %s", webua->clientip.c_str()); + + /* Schedule restart with 2-second delay to allow HTTP response to complete */ + std::thread([]() { + sleep(2); + system("sudo /usr/bin/systemctl restart motion"); + }).detach(); + + webua->resp_page = "{\"success\":true,\"operation\":\"service-restart\",\"message\":\"Motion service will restart in 2 seconds\"}"; +} + +/* + * React UI API: Cameras list + * Returns list of configured cameras + */ +void cls_webu_json::api_cameras() +{ + int indx_cam; + std::string strid; + cls_camera *cam; + + webua->resp_page = "{\"cameras\":["; + + for (indx_cam=0; indx_camcam_cnt; indx_cam++) { + cam = app->cam_list[indx_cam]; + strid = std::to_string(cam->cfg->device_id); + + if (indx_cam > 0) { + webua->resp_page += ","; + } + + webua->resp_page += "{"; + webua->resp_page += "\"id\":" + strid + ","; + + if (cam->cfg->device_name == "") { + webua->resp_page += "\"name\":\"camera " + strid + "\","; + } else { + webua->resp_page += "\"name\":\"" + escstr(cam->cfg->device_name) + "\","; + } + + webua->resp_page += "\"url\":\"" + webua->hostfull + "/" + strid + "/\""; + webua->resp_page += "}"; + } + + webua->resp_page += "]}"; + webua->resp_type = WEBUI_RESP_JSON; +} + +/* + * React UI API: Configuration + * Returns full Motion configuration including parameters and categories + * Includes CSRF token for React UI authentication + */ +void cls_webu_json::api_config() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Add CSRF token at the start of the response */ + webua->resp_page = "{\"csrf_token\":\"" + webu->csrf_token + "\""; + + /* Add version - config() normally starts with { so we skip it */ + webua->resp_page += ",\"version\" : \"" VERSION "\""; + + /* Add cameras list */ + webua->resp_page += ",\"cameras\" : "; + cameras_list(); + + /* Add configuration parameters */ + webua->resp_page += ",\"configuration\" : "; + parms_all(); + + /* Add categories */ + webua->resp_page += ",\"categories\" : "; + categories_list(); + + webua->resp_page += "}"; +} + +/* + * React UI API: Batch Configuration Update + * PATCH /0/api/config with JSON body containing multiple parameters + * Returns detailed results for each parameter change + */ +void cls_webu_json::api_config_patch() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for PATCH from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + /* Parse JSON body */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("JSON parse error: %s"), parser.getError().c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid JSON: " + + parser.getError() + "\"}"; + return; + } + + /* Get config for this camera/device */ + cls_config *cfg; + if (webua->cam != nullptr) { + cfg = webua->cam->cfg; + } else { + cfg = app->cfg; + } + + /* Start response */ + webua->resp_page = "{\"status\":\"ok\",\"applied\":["; + bool first_item = true; + int success_count = 0; + int error_count = 0; + + /* Process each parameter */ + pthread_mutex_lock(&app->mutex_post); + for (const auto& kv : parser.getAll()) { + std::string parm_name = kv.first; + std::string parm_val = parser.getString(parm_name); + std::string old_val; + int parm_index = -1; + bool applied = false; + bool hot_reload = false; + bool unchanged = false; + std::string error_msg; + + /* Auto-hash authentication passwords if not already hashed */ + if (parm_name == "webcontrol_authentication" || + parm_name == "webcontrol_user_authentication") { + + size_t colon_pos = parm_val.find(':'); + if (colon_pos != std::string::npos) { + std::string username = parm_val.substr(0, colon_pos); + std::string password = parm_val.substr(colon_pos + 1); + + /* If password is not already a bcrypt hash, hash it */ + if (!cls_webu_auth::is_bcrypt_hash(password)) { + std::string hashed = cls_webu_auth::hash_password(password); + if (!hashed.empty()) { + parm_val = username + ":" + hashed; + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Auto-hashed password for %s", parm_name.c_str()); + } else { + /* Hash failed - log error but allow plaintext */ + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, + "Failed to hash password for %s - saving plaintext", parm_name.c_str()); + } + } + } + } + + /* SECURITY: Reject SQL parameter modifications */ + if (parm_name.substr(0, 4) == "sql_") { + error_msg = "SQL parameters cannot be modified via web interface (security restriction)"; + error_count++; + } + /* SECURITY: Allow initial authentication setup regardless of webcontrol_parms + * This enables first-run configuration without requiring webcontrol_parms 3 + * Exception only applies when BOTH auth parameters are empty (fresh install) + * Once any auth is configured, normal permission levels apply */ + else if ((parm_name == "webcontrol_authentication" || + parm_name == "webcontrol_user_authentication") && + cfg->webcontrol_authentication == "" && + cfg->webcontrol_user_authentication == "") { + /* Initial setup exception - find parameter without permission check */ + parm_index = 0; + while (config_parms[parm_index].parm_name != "") { + if (config_parms[parm_index].parm_name == parm_name) { + break; + } + parm_index++; + } + + if (config_parms[parm_index].parm_name == "") { + /* Parameter not found */ + parm_index = -1; + error_msg = "Unknown parameter"; + error_count++; + } else { + /* Parameter exists - get current value */ + cfg->edit_get(parm_name, old_val, config_parms[parm_index].parm_cat); + + /* Check if value actually changed */ + if (old_val == parm_val) { + unchanged = true; + hot_reload = config_parms[parm_index].hot_reload; + success_count++; + } else { + /* Authentication parameters require restart */ + cfg->edit_set(parm_name, parm_val); + applied = true; + hot_reload = false; + success_count++; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Initial setup: %s configured (restart required)", + parm_name.c_str()); + } + } + } + /* Check if parameter exists */ + else { + validate_hot_reload(parm_name, parm_index); + + if (parm_index < 0) { + /* Parameter doesn't exist */ + error_msg = "Unknown parameter"; + error_count++; + } else if (config_parms[parm_index].webui_level > app->cfg->webcontrol_parms) { + /* Permission level too low */ + error_msg = "Insufficient permissions (requires webcontrol_parms " + + std::to_string(config_parms[parm_index].webui_level) + ")"; + error_count++; + } else { + /* Parameter exists - get current value */ + cfg->edit_get(parm_name, old_val, config_parms[parm_index].parm_cat); + + /* Check if value actually changed */ + if (old_val == parm_val) { + unchanged = true; + hot_reload = config_parms[parm_index].hot_reload; + success_count++; + } else { + /* Check if hot-reloadable */ + if (config_parms[parm_index].hot_reload) { + /* Apply immediately */ + apply_hot_reload(parm_index, parm_val); + applied = true; + hot_reload = true; + success_count++; + } else { + /* Save to config - requires restart to take effect */ + cfg->edit_set(parm_name, parm_val); + + /* Also update source config for restart persistence */ + if (webua->cam != nullptr) { + webua->cam->conf_src->edit_set(parm_name, parm_val); + } else { + app->conf_src->edit_set(parm_name, parm_val); + } + + applied = true; + hot_reload = false; + success_count++; + } + } + } + } + + /* Add this parameter to response */ + if (!first_item) { + webua->resp_page += ","; + } + first_item = false; + + webua->resp_page += "{\"param\":\"" + parm_name + "\""; + webua->resp_page += ",\"old\":\"" + escstr(old_val) + "\""; + webua->resp_page += ",\"new\":\"" + escstr(parm_val) + "\""; + + if (unchanged) { + webua->resp_page += ",\"unchanged\":true"; + } else if (applied) { + webua->resp_page += ",\"hot_reload\":" + std::string(hot_reload ? "true" : "false"); + } + + if (!error_msg.empty()) { + webua->resp_page += ",\"error\":\"" + escstr(error_msg) + "\""; + } + + webua->resp_page += "}"; + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page += "]"; + webua->resp_page += ",\"summary\":{"; + webua->resp_page += "\"total\":" + std::to_string(success_count + error_count); + webua->resp_page += ",\"success\":" + std::to_string(success_count); + webua->resp_page += ",\"errors\":" + std::to_string(error_count); + webua->resp_page += "}}"; +} + +/* + * React UI API: Get mask information + * GET /{camId}/api/mask/{type} + * type = "motion" or "privacy" + */ +void cls_webu_json::api_mask_get() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + return; + } + + std::string type = webua->uri_cmd3; + if (type != "motion" && type != "privacy") { + webua->resp_page = "{\"error\":\"Invalid mask type. Use 'motion' or 'privacy'\"}"; + return; + } + + /* Get current mask path from config */ + std::string mask_path; + if (type == "motion") { + mask_path = webua->cam->cfg->mask_file; + } else { + mask_path = webua->cam->cfg->mask_privacy; + } + + webua->resp_page = "{"; + webua->resp_page += "\"type\":\"" + type + "\""; + + if (mask_path.empty()) { + webua->resp_page += ",\"exists\":false"; + webua->resp_page += ",\"path\":\"\""; + } else { + /* Check if file exists and get dimensions */ + FILE *f = myfopen(mask_path.c_str(), "rbe"); + if (f != nullptr) { + char line[256]; + int w = 0, h = 0; + + /* Skip magic number P5 */ + if (fgets(line, sizeof(line), f)) { + /* Skip comments */ + do { + if (!fgets(line, sizeof(line), f)) break; + } while (line[0] == '#'); + + /* Parse dimensions */ + sscanf(line, "%d %d", &w, &h); + } + myfclose(f); + + webua->resp_page += ",\"exists\":true"; + webua->resp_page += ",\"path\":\"" + escstr(mask_path) + "\""; + webua->resp_page += ",\"width\":" + std::to_string(w); + webua->resp_page += ",\"height\":" + std::to_string(h); + } else { + webua->resp_page += ",\"exists\":false"; + webua->resp_page += ",\"path\":\"" + escstr(mask_path) + "\""; + webua->resp_page += ",\"error\":\"File not accessible\""; + } + } + + webua->resp_page += "}"; +} + +/* + * React UI API: Save mask from polygon data + * POST /{camId}/api/mask/{type} + * Request body: {"polygons":[[{x,y},...]], "width":W, "height":H, "invert":bool} + */ +void cls_webu_json::api_mask_post() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + return; + } + + std::string type = webua->uri_cmd3; + if (type != "motion" && type != "privacy") { + webua->resp_page = "{\"error\":\"Invalid mask type. Use 'motion' or 'privacy'\"}"; + return; + } + + /* Validate CSRF (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + return; + } + + /* Parse JSON request body */ + std::string body = webua->raw_body; + + /* Extract dimensions - default to camera size */ + int img_width = webua->cam->imgs.width; + int img_height = webua->cam->imgs.height; + bool invert = false; + + /* Parse width/height from body if present */ + size_t pos = body.find("\"width\":"); + if (pos != std::string::npos) { + img_width = atoi(body.c_str() + pos + 8); + } + pos = body.find("\"height\":"); + if (pos != std::string::npos) { + img_height = atoi(body.c_str() + pos + 9); + } + pos = body.find("\"invert\":"); + if (pos != std::string::npos) { + invert = (body.substr(pos + 9, 4) == "true"); + } + + /* Validate dimensions match camera */ + if (img_width != webua->cam->imgs.width || img_height != webua->cam->imgs.height) { + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, + "Mask dimensions %dx%d differ from camera %dx%d, will be resized on load", + img_width, img_height, webua->cam->imgs.width, webua->cam->imgs.height); + } + + /* Allocate bitmap */ + u_char default_val = invert ? 255 : 0; /* 255=detect, 0=mask */ + u_char fill_val = invert ? 0 : 255; + std::vector bitmap(img_width * img_height, default_val); + + /* Parse polygons array */ + /* Format: "polygons":[[[x,y],[x,y],...],[[x,y],...]] */ + pos = body.find("\"polygons\":"); + if (pos != std::string::npos) { + size_t start = body.find('[', pos); + if (start != std::string::npos) { + start++; /* Skip outer [ */ + + while (start < body.length() && body[start] != ']') { + /* Skip whitespace */ + while (start < body.length() && + (body[start] == ' ' || body[start] == '\n' || body[start] == ',')) { + start++; + } + + if (body[start] == '[') { + /* Parse one polygon */ + std::vector> polygon; + start++; /* Skip [ */ + + while (start < body.length() && body[start] != ']') { + /* Skip to { or [ */ + while (start < body.length() && + body[start] != '{' && body[start] != '[' && body[start] != ']') { + start++; + } + if (body[start] == ']') break; + + /* Parse point {x:N, y:N} or [x,y] */ + int x = 0, y = 0; + if (body[start] == '{') { + /* Object format */ + size_t xpos = body.find("\"x\":", start); + size_t ypos = body.find("\"y\":", start); + if (xpos != std::string::npos && ypos != std::string::npos) { + x = atoi(body.c_str() + xpos + 4); + y = atoi(body.c_str() + ypos + 4); + } + start = body.find('}', start) + 1; + } else if (body[start] == '[') { + /* Array format [x,y] */ + start++; + x = atoi(body.c_str() + start); + size_t comma = body.find(',', start); + if (comma != std::string::npos) { + y = atoi(body.c_str() + comma + 1); + } + start = body.find(']', start) + 1; + } + + polygon.push_back({x, y}); + } + start++; /* Skip ] */ + + /* Fill polygon */ + if (polygon.size() >= 3) { + fill_polygon(bitmap.data(), img_width, img_height, polygon, fill_val); + } + } else { + break; + } + } + } + } + + /* Generate mask path */ + std::string mask_path = build_mask_path(webua->cam, type); + + /* Write PGM file */ + FILE *f = myfopen(mask_path.c_str(), "wbe"); + if (f == nullptr) { + MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, + "Cannot write mask file: %s", mask_path.c_str()); + webua->resp_page = "{\"error\":\"Cannot write mask file\"}"; + return; + } + + /* Write PGM P5 header */ + fprintf(f, "P5\n"); + fprintf(f, "# Motion mask - type: %s\n", type.c_str()); + fprintf(f, "%d %d\n", img_width, img_height); + fprintf(f, "255\n"); + + /* Write bitmap data */ + if (fwrite(bitmap.data(), 1, bitmap.size(), f) != bitmap.size()) { + MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, + "Failed writing mask data to: %s", mask_path.c_str()); + myfclose(f); + webua->resp_page = "{\"error\":\"Failed writing mask data\"}"; + return; + } + + myfclose(f); + + /* Update config parameter */ + pthread_mutex_lock(&app->mutex_post); + if (type == "motion") { + webua->cam->cfg->mask_file = mask_path; + app->cfg->edit_set("mask_file", mask_path); + } else { + webua->cam->cfg->mask_privacy = mask_path; + app->cfg->edit_set("mask_privacy", mask_path); + } + pthread_mutex_unlock(&app->mutex_post); + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Mask saved: %s (type=%s, %dx%d, polygons parsed)", + mask_path.c_str(), type.c_str(), img_width, img_height); + + webua->resp_page = "{"; + webua->resp_page += "\"success\":true"; + webua->resp_page += ",\"path\":\"" + escstr(mask_path) + "\""; + webua->resp_page += ",\"width\":" + std::to_string(img_width); + webua->resp_page += ",\"height\":" + std::to_string(img_height); + webua->resp_page += ",\"message\":\"Mask saved. Reload camera to apply.\""; + webua->resp_page += "}"; +} + +/* + * React UI API: Delete mask file + * DELETE /{camId}/api/mask/{type} + */ +void cls_webu_json::api_mask_delete() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (webua->cam == nullptr) { + webua->resp_page = "{\"error\":\"Camera not specified\"}"; + return; + } + + std::string type = webua->uri_cmd3; + if (type != "motion" && type != "privacy") { + webua->resp_page = "{\"error\":\"Invalid mask type. Use 'motion' or 'privacy'\"}"; + return; + } + + /* Validate CSRF (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + return; + } + + /* Get current mask path */ + std::string mask_path; + if (type == "motion") { + mask_path = webua->cam->cfg->mask_file; + } else { + mask_path = webua->cam->cfg->mask_privacy; + } + + bool file_deleted = false; + if (!mask_path.empty()) { + /* Security: Validate path doesn't contain traversal */ + if (mask_path.find("..") != std::string::npos) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + "Path traversal attempt blocked: %s", mask_path.c_str()); + webua->resp_page = "{\"error\":\"Invalid path\"}"; + return; + } + + /* Delete file */ + if (remove(mask_path.c_str()) == 0) { + file_deleted = true; + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "Deleted mask file: %s", mask_path.c_str()); + } else if (errno != ENOENT) { + MOTION_LOG(WRN, TYPE_ALL, SHOW_ERRNO, + "Failed to delete mask file: %s", mask_path.c_str()); + } + } + + /* Clear config parameter */ + pthread_mutex_lock(&app->mutex_post); + if (type == "motion") { + webua->cam->cfg->mask_file = ""; + app->cfg->edit_set("mask_file", ""); + } else { + webua->cam->cfg->mask_privacy = ""; + app->cfg->edit_set("mask_privacy", ""); + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{"; + webua->resp_page += "\"success\":true"; + webua->resp_page += ",\"deleted\":" + std::string(file_deleted ? "true" : "false"); + webua->resp_page += ",\"message\":\"Mask removed. Reload camera to apply.\""; + webua->resp_page += "}"; +} + +/* + * Configuration Profiles API: List all profiles for a camera + * GET /0/api/profiles?camera_id=X + */ +void cls_webu_json::api_profiles_list() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Get camera_id from query params (default to 0) */ + int camera_id = 0; + const char* cam_id_str = MHD_lookup_connection_value( + webua->connection, MHD_GET_ARGUMENT_KIND, "camera_id"); + if (cam_id_str != nullptr) { + camera_id = atoi(cam_id_str); + } + + /* Get profiles from database */ + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\",\"profiles\":[]}"; + return; + } + + std::vector profiles = app->profiles->list_profiles(camera_id); + + /* Build JSON response */ + webua->resp_page = "{\"status\":\"ok\",\"profiles\":["; + bool first = true; + for (const auto &prof : profiles) { + if (!first) { + webua->resp_page += ","; + } + first = false; + + webua->resp_page += "{"; + webua->resp_page += "\"profile_id\":" + std::to_string(prof.profile_id) + ","; + webua->resp_page += "\"camera_id\":" + std::to_string(prof.camera_id) + ","; + webua->resp_page += "\"name\":\"" + escstr(prof.name) + "\","; + webua->resp_page += "\"description\":\"" + escstr(prof.description) + "\","; + webua->resp_page += "\"is_default\":" + std::string(prof.is_default ? "true" : "false") + ","; + webua->resp_page += "\"created_at\":" + std::to_string((int64_t)prof.created_at) + ","; + webua->resp_page += "\"updated_at\":" + std::to_string((int64_t)prof.updated_at) + ","; + webua->resp_page += "\"param_count\":" + std::to_string(prof.param_count); + webua->resp_page += "}"; + } + webua->resp_page += "]}"; +} + +/* + * Configuration Profiles API: Get specific profile with parameters + * GET /0/api/profiles/{id} + */ +void cls_webu_json::api_profiles_get() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Parse profile_id from URI */ + int profile_id = atoi(webua->uri_cmd3.c_str()); + if (profile_id <= 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid profile ID\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Get profile info */ + ctx_profile_info info; + if (!app->profiles->get_profile_info(profile_id, info)) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile not found\"}"; + return; + } + + /* Load profile parameters */ + std::map params; + if (app->profiles->load_profile(profile_id, params) != 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Failed to load profile parameters\"}"; + return; + } + + /* Build JSON response with metadata + params */ + webua->resp_page = "{\"status\":\"ok\","; + webua->resp_page += "\"profile_id\":" + std::to_string(info.profile_id) + ","; + webua->resp_page += "\"camera_id\":" + std::to_string(info.camera_id) + ","; + webua->resp_page += "\"name\":\"" + escstr(info.name) + "\","; + webua->resp_page += "\"description\":\"" + escstr(info.description) + "\","; + webua->resp_page += "\"is_default\":" + std::string(info.is_default ? "true" : "false") + ","; + webua->resp_page += "\"created_at\":" + std::to_string((int64_t)info.created_at) + ","; + webua->resp_page += "\"updated_at\":" + std::to_string((int64_t)info.updated_at) + ","; + webua->resp_page += "\"params\":{"; + + bool first = true; + for (const auto &kv : params) { + if (!first) { + webua->resp_page += ","; + } + first = false; + webua->resp_page += "\"" + escstr(kv.first) + "\":\"" + escstr(kv.second) + "\""; + } + webua->resp_page += "}}"; +} + +/* + * Configuration Profiles API: Create new profile + * POST /0/api/profiles + * Body: {name, description?, camera_id, snapshot_current?, params?} + */ +void cls_webu_json::api_profiles_create() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for profile create from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Parse JSON body */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("JSON parse error: %s"), parser.getError().c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid JSON: " + + parser.getError() + "\"}"; + return; + } + + /* Extract required fields */ + std::string name = parser.getString("name"); + if (name.empty()) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile name is required\"}"; + return; + } + + std::string description = parser.getString("description", ""); + int camera_id = (int)parser.getNumber("camera_id", 0); + bool snapshot_current = parser.getBool("snapshot_current", false); + + /* Get parameters */ + std::map params; + + if (snapshot_current) { + /* Snapshot current configuration */ + cls_config *cfg; + if (webua->cam != nullptr) { + cfg = webua->cam->cfg; + } else { + cfg = app->cfg; + } + params = app->profiles->snapshot_config(cfg); + } else { + /* Use params from request body (TODO: parse nested params object) */ + /* For now, just create empty profile - params can be added via update */ + } + + /* Create profile */ + pthread_mutex_lock(&app->mutex_post); + int profile_id = app->profiles->create_profile(camera_id, name, description, params); + pthread_mutex_unlock(&app->mutex_post); + + if (profile_id < 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Failed to create profile\"}"; + return; + } + + webua->resp_page = "{\"status\":\"ok\",\"profile_id\":" + std::to_string(profile_id) + "}"; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + _("Profile created: id=%d, name='%s', camera=%d"), profile_id, name.c_str(), camera_id); +} + +/* + * Configuration Profiles API: Update profile parameters + * PATCH /0/api/profiles/{id} + * Body: {params: {...}} + */ +void cls_webu_json::api_profiles_update() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for profile update from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Parse profile_id from URI */ + int profile_id = atoi(webua->uri_cmd3.c_str()); + if (profile_id <= 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid profile ID\"}"; + return; + } + + /* Parse JSON body */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("JSON parse error: %s"), parser.getError().c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid JSON: " + + parser.getError() + "\"}"; + return; + } + + /* Extract params (for now, simple key-value pairs) */ + std::map params; + for (const auto &kv : parser.getAll()) { + params[kv.first] = parser.getString(kv.first); + } + + /* Update profile */ + pthread_mutex_lock(&app->mutex_post); + int retcd = app->profiles->update_profile(profile_id, params); + pthread_mutex_unlock(&app->mutex_post); + + if (retcd < 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Failed to update profile\"}"; + return; + } + + webua->resp_page = "{\"status\":\"ok\"}"; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + _("Profile updated: id=%d"), profile_id); +} + +/* + * Configuration Profiles API: Delete profile + * DELETE /0/api/profiles/{id} + */ +void cls_webu_json::api_profiles_delete() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for profile delete from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Parse profile_id from URI */ + int profile_id = atoi(webua->uri_cmd3.c_str()); + if (profile_id <= 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid profile ID\"}"; + return; + } + + /* Delete profile */ + pthread_mutex_lock(&app->mutex_post); + int retcd = app->profiles->delete_profile(profile_id); + pthread_mutex_unlock(&app->mutex_post); + + if (retcd < 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Failed to delete profile\"}"; + return; + } + + webua->resp_page = "{\"status\":\"ok\"}"; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + _("Profile deleted: id=%d"), profile_id); +} + +/* + * Configuration Profiles API: Apply profile to camera configuration + * POST /0/api/profiles/{id}/apply + */ +void cls_webu_json::api_profiles_apply() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for profile apply from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Parse profile_id from URI */ + int profile_id = atoi(webua->uri_cmd3.c_str()); + if (profile_id <= 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid profile ID\"}"; + return; + } + + /* Get config for this camera/device */ + cls_config *cfg; + if (webua->cam != nullptr) { + cfg = webua->cam->cfg; + } else { + cfg = app->cfg; + } + + /* Apply profile */ + pthread_mutex_lock(&app->mutex_post); + std::vector needs_restart = app->profiles->apply_profile(cfg, profile_id); + pthread_mutex_unlock(&app->mutex_post); + + /* Build response with restart requirements */ + webua->resp_page = "{\"status\":\"ok\",\"requires_restart\":["; + bool first = true; + for (const auto ¶m : needs_restart) { + if (!first) { + webua->resp_page += ","; + } + first = false; + webua->resp_page += "\"" + escstr(param) + "\""; + } + webua->resp_page += "]}"; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + _("Profile applied: id=%d, restart_required=%s"), + profile_id, needs_restart.empty() ? "no" : "yes"); +} + +/* + * Configuration Profiles API: Set profile as default for camera + * POST /0/api/profiles/{id}/default + */ +void cls_webu_json::api_profiles_set_default() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Validate CSRF token (supports both session and global tokens) */ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed for set default from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"CSRF validation failed\"}"; + return; + } + + if (!app->profiles || !app->profiles->enabled) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Profile system not available\"}"; + return; + } + + /* Parse profile_id from URI */ + int profile_id = atoi(webua->uri_cmd3.c_str()); + if (profile_id <= 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid profile ID\"}"; + return; + } + + /* Set as default */ + pthread_mutex_lock(&app->mutex_post); + int retcd = app->profiles->set_default_profile(profile_id); + pthread_mutex_unlock(&app->mutex_post); + + if (retcd < 0) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Failed to set default profile\"}"; + return; + } + + webua->resp_page = "{\"status\":\"ok\"}"; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + _("Default profile set: id=%d"), profile_id); +} + +/* + * CSRF validation helper for POST endpoints + * Gets token from X-CSRF-Token header and validates against session or global token + * Returns true if valid, false otherwise (also sets error response) + */ +bool cls_webu_json::validate_csrf() +{ + const char* csrf_token = MHD_lookup_connection_value( + webua->connection, MHD_HEADER_KIND, "X-CSRF-Token"); + if (!webu->csrf_validate_request(csrf_token ? std::string(csrf_token) : "", webua->session_token)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("CSRF token validation failed from %s"), webua->clientip.c_str()); + webua->resp_page = "{\"error\":\"CSRF validation failed\"}"; + webua->resp_code = 403; + return false; + } + return true; +} + +/* + * Check if an action is enabled in webcontrol_actions + * Returns true if action is enabled (or not explicitly disabled), false if disabled + */ +bool cls_webu_json::check_action_permission(const std::string &action_name) +{ + for (int indx = 0; indx < webu->wb_actions->params_cnt; indx++) { + if (webu->wb_actions->params_array[indx].param_name == action_name) { + if (webu->wb_actions->params_array[indx].param_value == "off") { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, + "%s action disabled", action_name.c_str()); + webua->resp_page = "{\"error\":\"" + action_name + " action is disabled\"}"; + return false; + } + break; + } + } + return true; +} + +/* + * Camera action API: Write configuration to file + * POST /0/api/config/write + * Saves current configuration parameters to file + */ +void cls_webu_json::api_config_write() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("config_write")) { + return; + } + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Config write requested by %s", webua->clientip.c_str()); + + pthread_mutex_lock(&app->mutex_post); + app->conf_src->parms_write(); + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: Restart camera(s) + * POST /{camId}/api/camera/restart + * If camId=0, restart all cameras; otherwise restart specific camera + */ +void cls_webu_json::api_camera_restart() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("restart")) { + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Restarting all cameras")); + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->handler_stop = false; + app->cam_list[indx]->restart = true; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Restarting camera %d"), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->handler_stop = false; + app->cam_list[webua->camindx]->restart = true; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: Take snapshot + * POST /{camId}/api/camera/snapshot + * If camId=0, snapshot all cameras; otherwise snapshot specific camera + */ +void cls_webu_json::api_camera_snapshot() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("snapshot")) { + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Snapshot requested for all cameras")); + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->action_snapshot = true; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Snapshot requested for camera %d"), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->action_snapshot = true; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: Pause/unpause detection + * POST /{camId}/api/camera/pause + * Body: {"action": "on"|"off"|"schedule"} + * If camId=0, applies to all cameras; otherwise specific camera + */ +void cls_webu_json::api_camera_pause() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("pause")) { + return; + } + + /* Parse JSON body for action */ + std::string action = "on"; /* Default to pause on */ + if (!webua->raw_body.empty()) { + JsonParser parser; + if (parser.parse(webua->raw_body)) { + std::string parsed_action = parser.getString("action"); + if (!parsed_action.empty()) { + action = parsed_action; + } + } + } + + /* Validate action value */ + if (action != "on" && action != "off" && action != "schedule") { + webua->resp_page = "{\"error\":\"Invalid action. Use 'on', 'off', or 'schedule'\"}"; + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Pause %s requested for all cameras"), action.c_str()); + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->user_pause = action; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Pause %s requested for camera %d"), action.c_str(), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->user_pause = action; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\",\"action\":\"" + action + "\"}"; +} + +/* + * Camera action API: Stop camera(s) + * POST /{camId}/api/camera/stop + * If camId=0, stop all cameras; otherwise stop specific camera + */ +void cls_webu_json::api_camera_stop() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("stop")) { + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + for (int indx = 0; indx < app->cam_cnt; indx++) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Stopping camera %d"), + app->cam_list[indx]->cfg->device_id); + app->cam_list[indx]->restart = false; + app->cam_list[indx]->event_stop = true; + app->cam_list[indx]->event_user = false; + app->cam_list[indx]->handler_stop = true; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Stopping camera %d"), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->restart = false; + app->cam_list[webua->camindx]->event_stop = true; + app->cam_list[webua->camindx]->event_user = false; + app->cam_list[webua->camindx]->handler_stop = true; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: Trigger event start + * POST /{camId}/api/camera/event/start + * If camId=0, trigger for all cameras; otherwise specific camera + */ +void cls_webu_json::api_camera_event_start() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("event")) { + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Event start triggered for all cameras")); + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->event_user = true; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Event start triggered for camera %d"), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->event_user = true; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: Trigger event end + * POST /{camId}/api/camera/event/end + * If camId=0, trigger for all cameras; otherwise specific camera + */ +void cls_webu_json::api_camera_event_end() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("event")) { + return; + } + + pthread_mutex_lock(&app->mutex_post); + if (webua->device_id == 0) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Event end triggered for all cameras")); + for (int indx = 0; indx < app->cam_cnt; indx++) { + app->cam_list[indx]->event_stop = true; + } + } else { + if (webua->camindx >= 0 && webua->camindx < app->cam_cnt) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("Event end triggered for camera %d"), + app->cam_list[webua->camindx]->cfg->device_id); + app->cam_list[webua->camindx]->event_stop = true; + } else { + pthread_mutex_unlock(&app->mutex_post); + webua->resp_page = "{\"error\":\"Invalid camera ID\"}"; + return; + } + } + pthread_mutex_unlock(&app->mutex_post); + + webua->resp_page = "{\"status\":\"ok\"}"; +} + +/* + * Camera action API: PTZ control + * POST /{camId}/api/camera/ptz + * Body: {"action": "pan_left"|"pan_right"|"tilt_up"|"tilt_down"|"zoom_in"|"zoom_out"} + * Requires specific camera (camId != 0) + */ +void cls_webu_json::api_camera_ptz() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (!check_action_permission("ptz")) { + return; + } + + /* PTZ requires a specific camera */ + if (webua->camindx < 0 || webua->camindx >= app->cam_cnt) { + webua->resp_page = "{\"error\":\"PTZ requires a specific camera ID\"}"; + return; + } + + /* Parse JSON body for action */ + if (webua->raw_body.empty()) { + webua->resp_page = "{\"error\":\"Missing request body with action\"}"; + return; + } + + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + webua->resp_page = "{\"error\":\"Invalid JSON: " + parser.getError() + "\"}"; + return; + } + + std::string action = parser.getString("action"); + if (action.empty()) { + webua->resp_page = "{\"error\":\"Missing 'action' field\"}"; + return; + } + + cls_camera *cam = app->cam_list[webua->camindx]; + std::string ptz_cmd; + + /* Map action to PTZ command */ + if (action == "pan_left" && !cam->cfg->ptz_pan_left.empty()) { + ptz_cmd = cam->cfg->ptz_pan_left; + } else if (action == "pan_right" && !cam->cfg->ptz_pan_right.empty()) { + ptz_cmd = cam->cfg->ptz_pan_right; + } else if (action == "tilt_up" && !cam->cfg->ptz_tilt_up.empty()) { + ptz_cmd = cam->cfg->ptz_tilt_up; + } else if (action == "tilt_down" && !cam->cfg->ptz_tilt_down.empty()) { + ptz_cmd = cam->cfg->ptz_tilt_down; + } else if (action == "zoom_in" && !cam->cfg->ptz_zoom_in.empty()) { + ptz_cmd = cam->cfg->ptz_zoom_in; + } else if (action == "zoom_out" && !cam->cfg->ptz_zoom_out.empty()) { + ptz_cmd = cam->cfg->ptz_zoom_out; + } else { + webua->resp_page = "{\"error\":\"Invalid or unconfigured PTZ action: " + action + "\"}"; + return; + } + + pthread_mutex_lock(&app->mutex_post); + cam->frame_skip = cam->cfg->ptz_wait; + util_exec_command(cam, ptz_cmd); + pthread_mutex_unlock(&app->mutex_post); + + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, + _("PTZ %s executed for camera %d"), action.c_str(), cam->cfg->device_id); + + webua->resp_page = "{\"status\":\"ok\",\"action\":\"" + action + "\"}"; +} + void cls_webu_json::main() { pthread_mutex_lock(&app->mutex_post); diff --git a/src/webu_json.hpp b/src/webu_json.hpp index 4092e365..28ae6cf4 100644 --- a/src/webu_json.hpp +++ b/src/webu_json.hpp @@ -14,7 +14,16 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_json.hpp - JSON REST API Interface + * + * Header file defining the JSON REST API class for configuration + * management, camera control, status queries, and profile operations + * between the React frontend and Motion backend. + * + */ #ifndef _INCLUDE_WEBU_JSON_HPP_ #define _INCLUDE_WEBU_JSON_HPP_ @@ -23,6 +32,50 @@ cls_webu_json(cls_webu_ans *p_webua); ~cls_webu_json(); void main(); + + /* React UI API endpoints */ + void api_auth_me(); + void api_auth_login(); + void api_auth_logout(); + void api_auth_status(); + void api_media_pictures(); + void api_media_movies(); + void api_media_dates(); + void api_media_folders(); /* GET /{camId}/api/media/folders */ + void api_delete_picture(); + void api_delete_movie(); + void api_delete_folder_files(); /* DELETE /{camId}/api/media/folders/files */ + void api_system_temperature(); + void api_system_status(); + void api_system_reboot(); /* POST /0/api/system/reboot */ + void api_system_shutdown(); /* POST /0/api/system/shutdown */ + void api_system_service_restart(); /* POST /0/api/system/service-restart */ + void api_cameras(); + void api_config(); + void api_config_patch(); /* Batch config update via PATCH */ + void api_mask_get(); /* GET /{camId}/api/mask/{type} */ + void api_mask_post(); /* POST /{camId}/api/mask/{type} */ + void api_mask_delete(); /* DELETE /{camId}/api/mask/{type} */ + + /* Configuration Profile API endpoints */ + void api_profiles_list(); /* GET /0/api/profiles?camera_id=X */ + void api_profiles_get(); /* GET /0/api/profiles/{id} */ + void api_profiles_create(); /* POST /0/api/profiles */ + void api_profiles_update(); /* PATCH /0/api/profiles/{id} */ + void api_profiles_delete(); /* DELETE /0/api/profiles/{id} */ + void api_profiles_apply(); /* POST /0/api/profiles/{id}/apply */ + void api_profiles_set_default(); /* POST /0/api/profiles/{id}/default */ + + /* Camera action API endpoints (JSON replacements for legacy POST) */ + void api_config_write(); /* POST /0/api/config/write */ + void api_camera_restart(); /* POST /{camId}/api/camera/restart */ + void api_camera_snapshot(); /* POST /{camId}/api/camera/snapshot */ + void api_camera_pause(); /* POST /{camId}/api/camera/pause */ + void api_camera_stop(); /* POST /{camId}/api/camera/stop */ + void api_camera_event_start(); /* POST /{camId}/api/camera/event/start */ + void api_camera_event_end(); /* POST /{camId}/api/camera/event/end */ + void api_camera_ptz(); /* POST /{camId}/api/camera/ptz */ + private: cls_motapp *app; cls_webu *webu; @@ -39,6 +92,21 @@ void status(); void loghistory(); std::string escstr(std::string invar); + void parms_item_detail(cls_config *conf, std::string pNm); + + /* Hot reload helpers */ + bool validate_hot_reload(const std::string &parm_name, int &parm_index); + void apply_hot_reload_to_camera(cls_camera *cam, + const std::string &parm_name, const std::string &parm_val); + void apply_hot_reload(int parm_index, const std::string &parm_val); + void build_response(bool success, const std::string &parm_name, + const std::string &old_val, const std::string &new_val, + bool hot_reload); + + /* CSRF validation helper for POST endpoints */ + bool validate_csrf(); + /* webcontrol_actions permission check helper */ + bool check_action_permission(const std::string &action_name); }; #endif /* _INCLUDE_WEBU_JSON_HPP_ */ diff --git a/src/webu_mpegts.cpp b/src/webu_mpegts.cpp index 1c685092..c2b55859 100644 --- a/src/webu_mpegts.cpp +++ b/src/webu_mpegts.cpp @@ -14,7 +14,16 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_mpegts.cpp - MPEG-TS Streaming Implementation + * + * This module provides H.264 video streaming in MPEG Transport Stream + * format over HTTP, delivering lower-latency video streams compared to + * MJPEG for compatible browsers and applications. + * + */ #include "motion.hpp" #include "util.hpp" @@ -30,7 +39,13 @@ /****** Callback functions for MHD ****************************************/ -static int webu_mpegts_avio_buf(void *opaque, myuint *buf, int buf_size) +#ifdef FF_API_AVIO_WRITE_NONCONST +/* FFmpeg 6.x and earlier - write_packet callback uses non-const uint8_t* */ +static int webu_mpegts_avio_buf(void *opaque, uint8_t *buf, int buf_size) +#else +/* FFmpeg 7.0+ - write_packet callback uses const uint8_t* */ +static int webu_mpegts_avio_buf(void *opaque, const uint8_t *buf, int buf_size) +#endif { cls_webu_mpegts *webu_mpegts; webu_mpegts =(cls_webu_mpegts *)opaque; @@ -84,7 +99,7 @@ int cls_webu_mpegts::pic_send(unsigned char *img) retcd = avcodec_send_frame(ctx_codec, picture); if (retcd < 0 ) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO , _("Error sending frame for encoding:%s"), errstr); av_frame_free(&picture); picture = NULL; @@ -111,7 +126,7 @@ int cls_webu_mpegts::pic_get() } if (retcd < 0 ) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("Error receiving encoded packet video:%s"), errstr); //Packet is freed upon failure of encoding return -1; @@ -122,7 +137,7 @@ int cls_webu_mpegts::pic_get() retcd = av_interleaved_write_frame(fmtctx, pkt); if (retcd < 0 ) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("Error while writing video frame. %s"), errstr); return -1; } @@ -221,13 +236,19 @@ int cls_webu_mpegts::getimg() return 0; } -int cls_webu_mpegts::avio_buf(myuint *buf, int buf_size) +#ifdef FF_API_AVIO_WRITE_NONCONST +/* FFmpeg 6.x and earlier - write_packet callback uses non-const uint8_t* */ +int cls_webu_mpegts::avio_buf(uint8_t *buf, int buf_size) +#else +/* FFmpeg 7.0+ - write_packet callback uses const uint8_t* */ +int cls_webu_mpegts::avio_buf(const uint8_t *buf, int buf_size) +#endif { if (webus->resp_size < (size_t)buf_size + webus->resp_used) { webus->resp_size = (size_t)buf_size + webus->resp_used; webus->resp_image = (unsigned char*)realloc( webus->resp_image, webus->resp_size); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("resp_image reallocated %d %d %d") ,webus->resp_size ,webus->resp_used @@ -355,7 +376,7 @@ int cls_webu_mpegts::open_mpegts() retcd = avcodec_open2(ctx_codec, codec, &opts); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("Failed to open codec context for %dx%d transport stream: %s") , img_w, img_h, errstr); av_dict_free(&opts); @@ -365,7 +386,7 @@ int cls_webu_mpegts::open_mpegts() retcd = avcodec_parameters_from_context(strm->codecpar, ctx_codec); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("Failed to copy decoder parameters!: %s"), errstr); av_dict_free(&opts); return -1; @@ -387,7 +408,7 @@ int cls_webu_mpegts::open_mpegts() retcd = avformat_write_header(fmtctx, &opts); if (retcd < 0) { av_strerror(retcd, errstr, sizeof(errstr)); - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO ,_("Failed to write header!: %s"), errstr); av_dict_free(&opts); return -1; @@ -414,7 +435,7 @@ mhdrslt cls_webu_mpegts::main() } if (open_mpegts() < 0 ) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Unable to open mpegts")); + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Unable to open mpegts")); return MHD_NO; } @@ -423,7 +444,7 @@ mhdrslt cls_webu_mpegts::main() response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 4096 ,&webu_mpegts_response, this, NULL); if (!response) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); return MHD_NO; } diff --git a/src/webu_mpegts.hpp b/src/webu_mpegts.hpp index 49c9556d..9a444895 100644 --- a/src/webu_mpegts.hpp +++ b/src/webu_mpegts.hpp @@ -14,7 +14,16 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_mpegts.hpp - MPEG-TS Streaming Interface + * + * Header file defining the MPEG Transport Stream class for H.264 video + * streaming over HTTP, providing lower-latency streams compared to MJPEG + * for compatible browsers and applications. + * + */ #ifndef _INCLUDE_WEBU_MPEGTS_HPP_ #define _INCLUDE_WEBU_MPEGTS_HPP_ @@ -23,7 +32,11 @@ public: cls_webu_mpegts(cls_webu_ans *p_webua, cls_webu_stream *p_webus); ~cls_webu_mpegts(); - int avio_buf(myuint *buf, int buf_size); + #ifdef FF_API_AVIO_WRITE_NONCONST + int avio_buf(uint8_t *buf, int buf_size); + #else + int avio_buf(const uint8_t *buf, int buf_size); + #endif ssize_t response(char *buf, size_t max); mhdrslt main(); diff --git a/src/webu_post.cpp b/src/webu_post.cpp deleted file mode 100644 index 1c5005ba..00000000 --- a/src/webu_post.cpp +++ /dev/null @@ -1,942 +0,0 @@ -/* - * This file is part of Motion. - * - * Motion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Motion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Motion. If not, see . - * -*/ - -#include "motion.hpp" -#include "util.hpp" -#include "camera.hpp" -#include "allcam.hpp" -#include "sound.hpp" -#include "dbse.hpp" -#include "conf.hpp" -#include "logger.hpp" -#include "webu.hpp" -#include "webu_ans.hpp" -#include "webu_html.hpp" -#include "webu_post.hpp" - -/**************Callback functions for MHD **********************/ - -mhdrslt webup_iterate_post (void *ptr, enum MHD_ValueKind kind - , const char *key, const char *filename, const char *content_type - , const char *transfer_encoding, const char *data, uint64_t off, size_t datasz) -{ - (void) kind; - (void) filename; - (void) content_type; - (void) transfer_encoding; - (void) off; - cls_webu_post *webu_post; - - webu_post = (cls_webu_post *)ptr; - return webu_post->iterate_post(key, data, datasz); -} - -/**************Class methods**********************/ - -/* Process the add camera action */ -void cls_webu_post::cam_add() -{ - int indx, maxcnt; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "camera_add") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Camera add action disabled"); - return; - } else { - break; - } - } - } - - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Adding camera."); - - maxcnt = 100; - - app->cam_add = true; - indx = 0; - while ((app->cam_add == true) && (indx < maxcnt)) { - SLEEP(0, 50000000) - indx++; - } - - if (indx == maxcnt) { - app->cam_add = false; - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO, "Error adding camera. Timed out"); - return; - } - - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "New camera added."); - -} - -/* Process the delete camera action */ -void cls_webu_post::cam_delete() -{ - int indx, maxcnt; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "camera_delete") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Camera delete action disabled"); - return; - } else { - break; - } - } - } - - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Deleting camera."); - - app->cam_delete = webua->camindx; - - maxcnt = 100; - indx = 0; - while ((app->cam_delete != -1) && (indx < maxcnt)) { - SLEEP(0, 50000000) - indx++; - } - if (indx == maxcnt) { - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO, "Error stopping camera. Timed out shutting down"); - app->cam_delete = -1; - return; - } - webua->cam = nullptr; - webua->camindx = -1; -} - -/* Get the command, device_id and camera index from the post data */ -void cls_webu_post::parse_cmd() -{ - int indx; - - post_cmd = ""; - webua->camindx = -1; - webua->device_id = -1; - - for (indx = 0; indx < post_sz; indx++) { - if (mystreq(post_info[indx].key_nm, "command")) { - post_cmd = post_info[indx].key_val; - } - if (mystreq(post_info[indx].key_nm, "camid")) { - webua->device_id = atoi(post_info[indx].key_val); - } - - MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO ,"key: %s value: %s " - , post_info[indx].key_nm - , post_info[indx].key_val - ); - } - - if (post_cmd == "") { - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO - , "Invalid post request. No command"); - return; - } - if (webua->device_id == -1) { - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO - , "Invalid post request. No camera id provided"); - return; - } - - if (webua->device_id != 0) { - for (indx=0; indxcam_cnt; indx++) { - if (app->cam_list[indx]->cfg->device_id == webua->device_id) { - webua->camindx = indx; - break; - } - } - if (webua->camindx == -1) { - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO - , "Invalid request. Device id %d not found" - , webua->device_id); - webua->device_id = -1; - return; - } - } -} - -/* Process the event end action */ -void cls_webu_post::action_eventend() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "event") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Event end action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->event_stop = true; - } - } else { - app->cam_list[webua->camindx]->event_stop = true; - } - -} - -/* Process the event start action */ -void cls_webu_post::action_eventstart() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "event") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Event start action disabled"); - return; - } else { - break; - } - } - } - - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->event_user = true; - } - } else { - app->cam_list[webua->camindx]->event_user = true; - } - -} - -/* Process the snapshot action */ -void cls_webu_post::action_snapshot() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "snapshot") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Snapshot action disabled"); - return; - } else { - break; - } - } - } - - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->action_snapshot = true; - } - } else { - app->cam_list[webua->camindx]->action_snapshot = true; - } - -} - -void cls_webu_post::action_pause_on() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "pause") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Pause action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->user_pause = "on"; - } - } else { - app->cam_list[webua->camindx]->user_pause = "on"; - } - -} - -void cls_webu_post::action_pause_off() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "pause") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Pause action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->user_pause = "off"; - } - } else { - app->cam_list[webua->camindx]->user_pause = "off"; - } - -} - -void cls_webu_post::action_pause_schedule() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "pause") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Pause action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->user_pause = "schedule"; - } - } else { - app->cam_list[webua->camindx]->user_pause = "schedule"; - } - -} - -/* Process the restart action */ -void cls_webu_post::action_restart() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "restart") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Restart action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Restarting all cameras")); - for (indx=0; indxcam_cnt; indx++) { - app->cam_list[indx]->handler_stop = false; - app->cam_list[indx]->restart = true; - } - } else { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Restarting camera %d") - , app->cam_list[webua->camindx]->cfg->device_id); - app->cam_list[webua->camindx]->handler_stop = false; - app->cam_list[webua->camindx]->restart = true; - } -} - -/* Process the stop action */ -void cls_webu_post::action_stop() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "stop") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Stop action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Stopping cam %d") - , app->cam_list[indx]->cfg->device_id); - app->cam_list[indx]->restart = false; - app->cam_list[indx]->event_stop = true; - app->cam_list[indx]->event_user = false; - app->cam_list[indx]->handler_stop = true; - } - } else { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Stopping cam %d") - , app->cam_list[webua->camindx]->cfg->device_id); - app->cam_list[webua->camindx]->restart = false; - app->cam_list[webua->camindx]->event_stop = true; - app->cam_list[webua->camindx]->event_user = false; - app->cam_list[webua->camindx]->handler_stop = true; - } - -} - -/* Process the action_user */ -void cls_webu_post::action_user() -{ - int indx, indx2; - cls_camera *cam; - std::string tmp; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "action_user") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "User action disabled"); - return; - } else { - break; - } - } - } - - if (webua->device_id == 0) { - for (indx=0; indxcam_cnt; indx++) { - cam = app->cam_list[indx]; - cam->action_user[0] = '\0'; - for (indx2 = 0; indx2 < post_sz; indx2++) { - if (mystreq(post_info[indx2].key_nm, "user")) { - tmp = std::string(post_info[indx2].key_val); - } - } - for (indx2 = 0; indx2<(int)tmp.length(); indx2++) { - if (isalnum(tmp.at((uint)indx2)) == false) { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Invalid character included in action user \"%c\"") - , tmp.at((uint)indx2)); - return; - } - } - snprintf(cam->action_user, 40, "%s", tmp.c_str()); - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Executing user action on cam %d") - , cam->cfg->device_id); - util_exec_command(cam, cam->cfg->on_action_user.c_str(), NULL); - } - } else { - cam = app->cam_list[webua->camindx]; - cam->action_user[0] = '\0'; - for (indx2 = 0; indx2 < post_sz; indx2++) { - if (mystreq(post_info[indx2].key_nm, "user")) { - tmp = std::string(post_info[indx2].key_val); - } - } - for (indx2 = 0; indx2<(int)tmp.length(); indx2++) { - if (isalnum(tmp.at((uint)indx2)) == false) { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Invalid character included in action user \"%c\"") - , tmp.at((uint)indx2)); - return; - } - } - snprintf(cam->action_user, 40, "%s", tmp.c_str()); - - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("Executing user action on cam %d") - , cam->cfg->device_id); - util_exec_command(cam, cam->cfg->on_action_user.c_str(), NULL); - } - -} - -/* Process the write config action */ -void cls_webu_post::write_config() -{ - int indx; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "config_write") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Config write action disabled"); - return; - } else { - break; - } - } - } - - app->conf_src->parms_write(); - -} - -void cls_webu_post::config_set(int indx_parm, std::string parm_vl) -{ - std::string parm_nm, parm_vl_dflt, parm_vl_dev; - PARM_CAT parm_ct; - int indx; - - parm_nm = config_parms[indx_parm].parm_name; - parm_ct = config_parms[indx_parm].parm_cat; - - if (webua->device_id == 0) { - app->conf_src->edit_get(parm_nm, parm_vl_dflt, parm_ct); - if (parm_vl == parm_vl_dflt) { - return; - } - if (parm_ct == PARM_CAT_00) { - app->conf_src->edit_set(parm_nm, parm_vl); - config_restart_set("log",0); - } else if (parm_ct == PARM_CAT_13) { - app->conf_src->edit_set(parm_nm, parm_vl); - config_restart_set("webu",0); - } else if (parm_ct == PARM_CAT_15) { - app->conf_src->edit_set(parm_nm, parm_vl); - config_restart_set("dbse",0); - } else { - app->conf_src->edit_set(parm_nm, parm_vl); - if (parm_ct == PARM_CAT_14) { - app->cfg->edit_set(parm_nm, parm_vl); - app->allcam->all_sizes.reset = true; - } - for (indx=0;indxcam_cnt;indx++){ - if (app->cam_list[indx]->handler_running == false) { - app->cfg->edit_set(parm_nm, parm_vl); - } else { - app->cam_list[indx]->conf_src->edit_get( - parm_nm, parm_vl_dev, parm_ct); - if (parm_vl_dev == parm_vl_dflt) { - app->cam_list[indx]->conf_src->edit_set( - parm_nm, parm_vl); - config_restart_set("cam",indx); - } - } - } - for (indx=0;indxsnd_cnt;indx++) { - app->snd_list[indx]->conf_src->edit_get( - parm_nm, parm_vl_dev, parm_ct); - if (parm_vl_dev == parm_vl_dflt) { - app->snd_list[indx]->conf_src->edit_set( - parm_nm, parm_vl); - config_restart_set("snd",indx); - } - } - } - } else { - if ((parm_ct == PARM_CAT_00) || - (parm_ct == PARM_CAT_13) || - (parm_ct == PARM_CAT_15)) { - return; - } - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Config edit set. %s:%s" - ,parm_nm.c_str(), parm_vl.c_str()); - app->cam_list[webua->camindx]->conf_src->edit_set( - parm_nm, parm_vl); - if (app->cam_list[webua->camindx]->handler_running == true) { - config_restart_set("cam", webua->camindx); - } else { - app->cam_list[webua->camindx]->cfg->edit_set( - parm_nm, parm_vl); - } - } - -} - -void cls_webu_post::config_restart_set(std::string p_type, int p_indx) -{ - int indx; - - for (indx=0; indxcam_cnt; indx++) { - itm_res.comp_type ="cam"; - itm_res.comp_indx = indx; - restart_list.push_back(itm_res); - } - for (indx = 0; indxsnd_cnt; indx++) { - itm_res.comp_type ="snd"; - itm_res.comp_indx = indx; - restart_list.push_back(itm_res); - } - -} -/* Process the configuration parameters */ -void cls_webu_post::config() -{ - int indx, indx2; - std::string tmpname; - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "config") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "Config save action disabled"); - return; - } else { - break; - } - } - } - - config_restart_reset(); - - for (indx = 0; indx < post_sz; indx++) { - if (mystrne(post_info[indx].key_nm, "command") && - mystrne(post_info[indx].key_nm, "camid")) { - - tmpname = post_info[indx].key_nm; - indx2=0; - while (config_parms_depr[indx2].parm_name != "") { - if (config_parms_depr[indx2].parm_name == tmpname) { - tmpname = config_parms_depr[indx2].newname; - break; - } - indx2++; - } - - /* Ignore any requests for parms above webcontrol_parms level. */ - indx2=0; - while (config_parms[indx2].parm_name != "") { - if ((config_parms[indx2].webui_level > app->conf_src->webcontrol_parms) || - (config_parms[indx2].webui_level == PARM_LEVEL_NEVER) ) { - indx2++; - continue; - } - if (tmpname == config_parms[indx2].parm_name) { - break; - } - indx2++; - } - - if (config_parms[indx2].parm_name != "") { - config_set(indx2, post_info[indx].key_val); - } - } - } - - for (indx = 0; indx < restart_list.size(); indx++) { - if (restart_list[indx].restart == true) { - if (restart_list[indx].comp_type == "log") { - motlog->restart = true; - MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, - "Restart request for log"); - } else if (restart_list[indx].comp_type == "webu") { - app->webu->restart = true; - MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, - "Restart request for webcontrol"); - } else if (restart_list[indx].comp_type == "dbse") { - app->dbse->restart = true; - MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, - "Restart request for database"); - } else if (restart_list[indx].comp_type == "cam") { - app->cam_list[restart_list[indx].comp_indx]->restart = true; - MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, - "Restart request for camera %d" - , app->cam_list[restart_list[indx].comp_indx]->cfg->device_id); - } else if (restart_list[indx].comp_type == "snd") { - app->snd_list[restart_list[indx].comp_indx]->restart = true; - MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, - "Restart request for sound %d" - , app->cam_list[restart_list[indx].comp_indx]->cfg->device_id); - } else { - MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO, "Bad programming"); - } - } - } - -} - -/* Process the ptz action */ -void cls_webu_post::ptz() -{ - cls_camera *cam; - int indx; - - if (webua->camindx == -1) { - return; - } - - for (indx=0;indxwb_actions->params_cnt;indx++) { - if (webu->wb_actions->params_array[indx].param_name == "ptz") { - if (webu->wb_actions->params_array[indx].param_value == "off") { - MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, "PTZ actions disabled"); - return; - } else { - break; - } - } - } - - cam = app->cam_list[webua->camindx]; - - if ((post_cmd == "pan_left") && - (cam->cfg->ptz_pan_left != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_pan_left.c_str(), NULL); - - } else if ((post_cmd == "pan_right") && - (cam->cfg->ptz_pan_right != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_pan_right.c_str(), NULL); - - } else if ((post_cmd == "tilt_up") && - (cam->cfg->ptz_tilt_up != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_tilt_up.c_str(), NULL); - - } else if ((post_cmd == "tilt_down") && - (cam->cfg->ptz_tilt_down != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_tilt_down.c_str(), NULL); - - } else if ((post_cmd == "zoom_in") && - (cam->cfg->ptz_zoom_in != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_zoom_in.c_str(), NULL); - - } else if ((post_cmd == "zoom_out") && - (cam->cfg->ptz_zoom_out != "")) { - cam->frame_skip = cam->cfg->ptz_wait; - util_exec_command(cam, cam->cfg->ptz_zoom_out.c_str(), NULL); - - } else { - return; - } - -} - -/* Process the actions from the webcontrol that the user requested */ -void cls_webu_post::process_actions() -{ - parse_cmd(); - - if ((post_cmd == "") || (webua->device_id == -1)) { - return; - } - - if (post_cmd == "eventend") { - action_eventend(); - - } else if (post_cmd == "eventstart") { - action_eventstart(); - - } else if (post_cmd == "snapshot") { - action_snapshot(); - - } else if (post_cmd == "pause") { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("pause action deprecated. Use pause_on")); - action_pause_on(); - - } else if (post_cmd == "unpause") { - MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO - , _("unpause action deprecated. Use pause_off")); - action_pause_off(); - - } else if (post_cmd == "pause_on") { - action_pause_on(); - - } else if (post_cmd == "pause_off") { - action_pause_off(); - - } else if (post_cmd == "pause_schedule") { - action_pause_schedule(); - - } else if (post_cmd == "restart") { - action_restart(); - - } else if (post_cmd == "stop") { - action_stop(); - - } else if (post_cmd == "config_write") { - write_config(); - - } else if (post_cmd == "camera_add") { - cam_add(); - - } else if (post_cmd == "camera_delete") { - cam_delete(); - - } else if (post_cmd == "config") { - config(); - - } else if (post_cmd == "action_user") { - action_user(); - - } else if ( - (post_cmd == "pan_left") || - (post_cmd == "pan_right") || - (post_cmd == "tilt_up") || - (post_cmd == "tilt_down") || - (post_cmd == "zoom_in") || - (post_cmd == "zoom_out")) { - ptz(); - - } else { - MOTPLS_LOG(INF, TYPE_STREAM, NO_ERRNO - , _("Invalid action requested: command: >%s< camindx : >%d< ") - , post_cmd.c_str(), webua->camindx); - } - -} - -/*Append more data on to an existing entry in the post info structure */ -void cls_webu_post::iterate_post_append(int indx - , const char *data, size_t datasz) -{ - post_info[indx].key_val = (char*)realloc( - post_info[indx].key_val - , post_info[indx].key_sz + datasz + 1); - - memset(post_info[indx].key_val + - post_info[indx].key_sz, 0, datasz + 1); - - if (datasz > 0) { - memcpy(post_info[indx].key_val + - post_info[indx].key_sz, data, datasz); - } - - post_info[indx].key_sz += datasz; -} - -/*Create new entry in the post info structure */ -void cls_webu_post::iterate_post_new(const char *key - , const char *data, size_t datasz) -{ - int retcd; - - post_sz++; - if (post_sz == 1) { - post_info = (ctx_key *)malloc(sizeof(ctx_key)); - } else { - post_info = (ctx_key *)realloc(post_info - , (uint)post_sz * sizeof(ctx_key)); - } - - post_info[post_sz-1].key_nm = (char*)malloc(strlen(key)+1); - retcd = snprintf(post_info[post_sz-1].key_nm, strlen(key)+1, "%s", key); - - post_info[post_sz-1].key_val = (char*)malloc(datasz+1); - memset(post_info[post_sz-1].key_val,0,datasz+1); - if (datasz > 0) { - memcpy(post_info[post_sz-1].key_val, data, datasz); - } - - post_info[post_sz-1].key_sz = datasz; - - if (retcd < 0) { - MOTPLS_LOG(INF, TYPE_STREAM, NO_ERRNO, _("Error processing post data")); - } -} - -mhdrslt cls_webu_post::iterate_post (const char *key, const char *data, size_t datasz) -{ - int indx; - - for (indx=0; indx < post_sz; indx++) { - if (mystreq(post_info[indx].key_nm, key)) { - break; - } - } - if (indx < post_sz) { - iterate_post_append(indx, data, datasz); - } else { - iterate_post_new(key, data, datasz); - } - - return MHD_YES; -} - -mhdrslt cls_webu_post::processor_init() -{ - post_processor = MHD_create_post_processor (webua->connection - , WEBUI_POST_BFRSZ, webup_iterate_post, (void *)this); - if (post_processor == NULL) { - return MHD_NO; - } - return MHD_YES; -} - -mhdrslt cls_webu_post::processor_start(const char *upload_data, size_t *upload_data_size) -{ - mhdrslt retcd; - - if (*upload_data_size != 0) { - retcd = MHD_post_process (post_processor, upload_data, *upload_data_size); - *upload_data_size = 0; - } else { - pthread_mutex_lock(&app->mutex_post); - process_actions(); - pthread_mutex_unlock(&app->mutex_post); - /* Send updated page back to user */ - webu_html = new cls_webu_html(webua); - webu_html->main(); - delete webu_html; - retcd = MHD_YES; - } - return retcd; -} - -cls_webu_post::cls_webu_post(cls_webu_ans *p_webua) -{ - app = p_webua->app; - webu = p_webua->webu; - webua = p_webua; - - post_processor = nullptr; - post_info = nullptr; - post_sz = 0; - -} - -cls_webu_post::~cls_webu_post() -{ - int indx; - - if (post_processor != nullptr) { - MHD_destroy_post_processor (post_processor); - } - - for (indx = 0; indx. - * -*/ - -#ifndef _INCLUDE_WEBU_POST_HPP_ -#define _INCLUDE_WEBU_POST_HPP_ - struct ctx_restart_item { - std::string comp_type; - bool restart; - int comp_indx; - }; - - class cls_webu_post { - public: - cls_webu_post(cls_webu_ans *webua); - ~cls_webu_post(); - - mhdrslt iterate_post (const char *key, const char *data, size_t datasz); - mhdrslt processor_init(); - mhdrslt processor_start(const char *upload_data, size_t *upload_data_size); - - private: - cls_motapp *app; - cls_webu *webu; - cls_webu_ans *webua; - cls_webu_html *webu_html; - - std::string post_cmd; - int post_sz; /* The number of entries in the post info */ - ctx_key *post_info; /* Structure of the entries provided from the post data */ - struct MHD_PostProcessor *post_processor; /* Processor for handling Post method connections */ - std::vector restart_list; - - void cam_add(); - void cam_delete(); - void parse_cmd(); - void iterate_post_append(int indx, const char *data, size_t datasz); - void iterate_post_new(const char *key, const char *data, size_t datasz); - void process_actions(); - void action_eventend(); - void action_eventstart(); - void action_snapshot(); - void action_pause_on(); - void action_pause_off(); - void action_pause_schedule(); - void action_restart(); - void action_stop(); - void action_user(); - void write_config(); - void config_set(int indx_parm, std::string parm_val); - void config_restart_set(std::string p_type, int p_indx); - void config_restart_reset(); - void config(); - void ptz(); - - }; - -#endif /* _INCLUDE_WEBU_POST_HPP_ */ diff --git a/src/webu_stream.cpp b/src/webu_stream.cpp index c65b11da..5051908d 100644 --- a/src/webu_stream.cpp +++ b/src/webu_stream.cpp @@ -14,7 +14,16 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_stream.cpp - MJPEG Streaming Implementation + * + * This module provides real-time MJPEG video streaming over HTTP, delivering + * live camera feeds to web browsers with minimal latency using multipart + * content responses. + * + */ #include "motion.hpp" #include "util.hpp" @@ -152,7 +161,7 @@ bool cls_webu_stream::all_ready() indx1++; } if (p_cam->passflag == false) { - MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO + MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO , "Camera %d not ready", p_cam->cfg->device_id); return false; } @@ -160,7 +169,7 @@ bool cls_webu_stream::all_ready() } if ((webua->app->allcam->all_sizes.dst_h == 0) || (webua->app->allcam->all_sizes.dst_w == 0)) { - MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO, "All cameras not ready"); + MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO, "All cameras not ready"); return false; } @@ -481,30 +490,6 @@ void cls_webu_stream::static_one_img() } -bool cls_webu_stream::valid_request() -{ - if (check_finish()) { - return false; - } - - pthread_mutex_lock(&app->mutex_camlst); - if (webua->device_id < 0) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO - , _("Invalid camera specified: %s"), webua->url.c_str()); - pthread_mutex_unlock(&app->mutex_camlst); - return false; - } - if ((webua->device_id > 0) && (webua->cam == NULL)) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO - , _("Invalid camera specified: %s"), webua->url.c_str()); - pthread_mutex_unlock(&app->mutex_camlst); - return false; - } - pthread_mutex_unlock(&app->mutex_camlst); - - return true; -} - /* Increment the transport stream counters */ void cls_webu_stream::ts_cnct() { @@ -600,7 +585,7 @@ mhdrslt cls_webu_stream::stream_mjpeg() response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 1024 , &webu_mjpeg_response, (void *)this, NULL); if (response == NULL) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); return MHD_NO; } @@ -630,7 +615,7 @@ mhdrslt cls_webu_stream::stream_static() int indx; if (resp_used == 0) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Could not get image to stream.")); + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Could not get image to stream.")); return MHD_NO; } @@ -638,7 +623,7 @@ mhdrslt cls_webu_stream::stream_static() resp_size,(void *)resp_image , MHD_RESPMEM_MUST_COPY); if (response == NULL) { - MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); return MHD_NO; } @@ -661,13 +646,12 @@ mhdrslt cls_webu_stream::stream_static() } /* Entry point for answering stream*/ -mhdrslt cls_webu_stream::main() +void cls_webu_stream::main() { - mhdrslt retcd; + mhdrslt retcd = MHD_NO; - if (valid_request() == false) { - webua->bad_request(); - return MHD_NO; + if (check_finish()) { + return; } set_cnct_type(); @@ -703,13 +687,12 @@ mhdrslt cls_webu_stream::main() if (retcd == MHD_NO) { mydelete(webu_mpegts); } + } - } else { + if (retcd == MHD_NO) { webua->bad_request(); - retcd = MHD_NO; } - return retcd; } cls_webu_stream::cls_webu_stream(cls_webu_ans *p_webua) diff --git a/src/webu_stream.hpp b/src/webu_stream.hpp index f5679727..eaa8f0b7 100644 --- a/src/webu_stream.hpp +++ b/src/webu_stream.hpp @@ -14,7 +14,16 @@ * You should have received a copy of the GNU General Public License * along with Motion. If not, see . * -*/ + */ + +/* + * webu_stream.hpp - MJPEG Streaming Interface + * + * Header file defining the MJPEG streaming class for delivering real-time + * video streams over HTTP using multipart content responses for live + * camera feeds in web browsers. + * + */ #ifndef _INCLUDE_WEBU_STREAM_HPP_ #define _INCLUDE_WEBU_STREAM_HPP_ @@ -28,7 +37,7 @@ size_t resp_used; /* The amount of the response page used */ u_char *resp_image; /* Response image to provide to user */ - mhdrslt main(); + void main(); ssize_t mjpeg_response (char *buf, size_t max); bool check_finish(); void delay(); @@ -53,7 +62,6 @@ mhdrslt stream_static(); mhdrslt stream_mjpeg(); - bool valid_request(); void all_cnct(); void jpg_cnct(); void ts_cnct(); From 9a1a88b1797699fec4e0c63282d165097e0f6132 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sat, 24 Jan 2026 01:04:31 -0600 Subject: [PATCH 02/10] fix(build): Remove static samplepage.html from AC_CONFIG_FILES The file data/webcontrol/samplepage.html is a static HTML file that doesn't require autoconf processing. Automake was looking for samplepage.html.in template file which doesn't exist and isn't needed. This fixes the autoreconf error: configure.ac:739: error: required file 'data/webcontrol/samplepage.html.in' not found --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index 06b6a4dd..c3faf0d9 100644 --- a/configure.ac +++ b/configure.ac @@ -746,7 +746,6 @@ AC_CONFIG_FILES([ data/sound1-dist.conf data/motion-dist.conf data/motion-dist.service - data/webcontrol/samplepage.html ]) AC_OUTPUT From eb05645045a529c332b1924560c0b54397927b46 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sat, 24 Jan 2026 01:10:13 -0600 Subject: [PATCH 03/10] fix(build): Restore missing doc/motion.png The file was referenced in Makefile.am but missing from the working tree, causing build failure. Restored from git history (commit 756b22b). Fixes make error: No rule to make target 'doc/motion.png', needed by 'all-am' --- doc/motion.png | Bin 0 -> 1532 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/motion.png diff --git a/doc/motion.png b/doc/motion.png new file mode 100644 index 0000000000000000000000000000000000000000..8ba283cd69eb2d0961fe718ed558b222d6cd8076 GIT binary patch literal 1532 zcmeAS@N?(olHy`uVBq!ia0vp^+91rq3?#2LH-2JZU}gyL32`lL+i>Xkm0Nf2MI^N_ zFl**kPW$lj^PH7ucOAZL>XPRXQjuD*&OK(5fM(d_g}XE@(pPW4(lz^7Waiw>`>t== zd3?#n(+gMbK6mMM!<1d@qV}qWaZ_fkYwBNO?b#@;2D0@(jRDY#Q7{?;WQTw=_Xby> zW`&X>fAC>eQA&`%ha0x`8%C+w^x1t zR{#C%xtx0|_j_8jGc*1F{pM>A z_m^ZP7zPi$iEgJbb$t?Smy}WG2EIx)k(nm9QD0;4aQ*mp%I%`Pz(QxjtuBdKkajwnw{rG1sJBnVvd*d*x=`x+*s5 z+u5W`{e|6aE803b&h$5~2wQYq@xYY{4VB-nwLHu$Tx9mA0`NiF4Gu9oIUhev()?TrB<9jkMbZS$Qf-C1lE#}>!Gdz5ng(q=soEBk=L@6S$|*Uz+! zzOlI2u+Qqb{JCi9lqFksy8E1Sel-8a7H=i(<)OZ;Q;I-;IwvR>E%1bmz-c1M|!8 zChyp!+dSd(C+-_tmf8PjzSmme_ Date: Sat, 24 Jan 2026 22:53:37 -0600 Subject: [PATCH 04/10] feat: Add multi-camera type UI support (V4L2/LIBCAM/NETCAM) Adds full UI support for all three Motion camera backends, allowing users to configure USB cameras (V4L2), Raspberry Pi cameras (LIBCAM), and IP cameras (NETCAM) from a unified React interface. Backend (C++): - Add camera_type field to status API (libcam/v4l2/netcam/unknown) - Add camera_device field (device path or URL) - Expose V4L2 controls array via new public accessor - Expose NETCAM connection status and dual-stream indicator Frontend (React/TypeScript): - Add useCameraInfo hook for feature detection - Add CameraSourceSettings component (type badge, device info) - Add V4L2Settings component (dynamic USB camera controls) - Add NetcamSettings component (IP camera configuration) - Update Settings page with conditional rendering - Update MovieSettings to show passthrough only for NETCAM Architecture: Hook-based feature detection with discriminated unions for type safety. No breaking changes - fully backward compatible. Files: 4 created, 7 modified (167 insertions, 17 deletions) Co-Authored-By: Claude Sonnet 4.5 --- frontend/.gitignore | 5 + frontend/package.json | 7 +- frontend/src/api/types.ts | 27 ++- .../settings/CameraSourceSettings.tsx | 134 ++++++++++++ .../src/components/settings/MovieSettings.tsx | 18 +- .../components/settings/NetcamSettings.tsx | 172 ++++++++++++++++ .../src/components/settings/V4L2Settings.tsx | 193 ++++++++++++++++++ frontend/src/hooks/useCameraInfo.ts | 117 +++++++++++ frontend/src/pages/Settings.tsx | 52 ++++- src/video_v4l2.hpp | 1 + src/webu_json.cpp | 74 +++++++ 11 files changed, 783 insertions(+), 17 deletions(-) create mode 100644 frontend/src/components/settings/CameraSourceSettings.tsx create mode 100644 frontend/src/components/settings/NetcamSettings.tsx create mode 100644 frontend/src/components/settings/V4L2Settings.tsx create mode 100644 frontend/src/hooks/useCameraInfo.ts diff --git a/frontend/.gitignore b/frontend/.gitignore index a547bf36..def33c61 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -22,3 +22,8 @@ dist-ssr *.njsproj *.sln *.sw? + +# E2E tests (local only) +e2e/ +playwright-report/ +test-results/ diff --git a/frontend/package.json b/frontend/package.json index 63cc46ac..0e96fa5a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,11 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test:e2e": "playwright test --config=e2e/playwright.config.ts", + "test:e2e:ui": "playwright test --config=e2e/playwright.config.ts --ui", + "test:e2e:headed": "playwright test --config=e2e/playwright.config.ts --headed", + "test:e2e:report": "playwright show-report e2e/playwright-report" }, "dependencies": { "@hookform/resolvers": "^5.2.2", @@ -20,6 +24,7 @@ "zustand": "^5.0.9" }, "devDependencies": { + "@playwright/test": "^1.50.0", "@eslint/js": "^9.39.1", "@types/node": "^24.10.1", "@types/react": "^19.2.5", diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index e12c1363..efdb1a75 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -132,6 +132,25 @@ export interface TemperatureResponse { fahrenheit: number; } +// Camera type discriminator +export type CameraType = 'libcam' | 'v4l2' | 'netcam' | 'unknown'; + +// V4L2 control structure (matches backend ctx_v4l2ctrl_item) +export interface V4L2Control { + name: string; + id: string; + type: 'integer' | 'boolean' | 'menu'; + min: number; + max: number; + default: number; + current: number; + step?: number; + menuItems?: Array<{ value: number; label: string }>; +} + +// NETCAM connection status +export type NetcamConnectionStatus = 'connected' | 'reading' | 'not_connected' | 'reconnecting' | 'unknown'; + // Per-camera status from /0/api/system/status export interface CameraStatus { name: string; @@ -146,7 +165,13 @@ export interface CameraStatus { detecting: boolean; pause: boolean; user_pause: string; - supportedControls?: CameraCapabilities; + // Type-specific fields + camera_type: CameraType; + camera_device: string; + supportedControls?: CameraCapabilities; // libcam only + v4l2_controls?: V4L2Control[]; // v4l2 only + netcam_status?: NetcamConnectionStatus; // netcam only + has_high_stream?: boolean; // netcam only } // Status section with dynamic camera keys diff --git a/frontend/src/components/settings/CameraSourceSettings.tsx b/frontend/src/components/settings/CameraSourceSettings.tsx new file mode 100644 index 00000000..79907b13 --- /dev/null +++ b/frontend/src/components/settings/CameraSourceSettings.tsx @@ -0,0 +1,134 @@ +import { FormSection } from '@/components/form'; +import { useCameraInfo } from '@/hooks/useCameraInfo'; +import type { CameraType } from '@/api/types'; + +export interface CameraSourceSettingsProps { + cameraId: number; + config: Record; + onChange: (param: string, value: string | number | boolean) => void; +} + +/** + * Camera Source Settings Component + * + * Displays the active camera type and device identifier. + * Shows a badge indicating the camera backend (libcam/v4l2/netcam/unknown). + * + * @param cameraId - Camera ID + * @param config - Camera configuration + * @param onChange - Configuration change handler + */ +export function CameraSourceSettings({ cameraId, config, onChange }: CameraSourceSettingsProps) { + const { cameraType, cameraDevice, isConnected } = useCameraInfo(cameraId); + + return ( + +
+ {/* Camera Type Badge */} +
+ Type: + +
+ + {/* Device/URL Info */} +
+ Device: + {cameraDevice ? ( +
+ + {cameraDevice} + +
+ ) : ( + Not configured + )} +
+ + {/* Connection Status */} +
+ Status: + +
+ + {/* Setup guidance for unknown camera type */} + {cameraType === 'unknown' && ( +
+

+ No camera configured +

+

+ Configure a camera by setting one of the following: +

+
    +
  • + libcam_device - Raspberry Pi camera +
  • +
  • + v4l2_device - USB webcam (e.g., /dev/video0) +
  • +
  • + netcam_url - IP camera (RTSP/HTTP URL) +
  • +
+
+ )} +
+
+ ); +} + +/** + * Camera Type Badge Component + * + * Visual indicator for camera backend type + */ +function CameraTypeBadge({ type }: { type: CameraType }) { + const badgeStyles = { + libcam: 'bg-purple-500/20 text-purple-300 border-purple-500/30', + v4l2: 'bg-blue-500/20 text-blue-300 border-blue-500/30', + netcam: 'bg-green-500/20 text-green-300 border-green-500/30', + unknown: 'bg-gray-500/20 text-gray-400 border-gray-500/30', + }; + + const labels = { + libcam: 'libcamera (Pi Camera)', + v4l2: 'V4L2 (USB Camera)', + netcam: 'Network Camera (IP)', + unknown: 'Not Configured', + }; + + return ( + + {labels[type]} + + ); +} + +/** + * Connection Status Badge Component + * + * Shows whether camera is currently connected + */ +function ConnectionStatusBadge({ isConnected }: { isConnected: boolean }) { + return ( + + + {isConnected ? 'Connected' : 'Disconnected'} + + ); +} diff --git a/frontend/src/components/settings/MovieSettings.tsx b/frontend/src/components/settings/MovieSettings.tsx index c805feb9..1845eaa5 100644 --- a/frontend/src/components/settings/MovieSettings.tsx +++ b/frontend/src/components/settings/MovieSettings.tsx @@ -13,9 +13,10 @@ export interface MovieSettingsProps { config: Record; onChange: (param: string, value: string | number | boolean) => void; getError?: (param: string) => string | undefined; + showPassthrough?: boolean; } -export function MovieSettings({ config, onChange, getError }: MovieSettingsProps) { +export function MovieSettings({ config, onChange, getError, showPassthrough = true }: MovieSettingsProps) { const { data: deviceInfo } = useDeviceInfo(); const getValue = (param: string, defaultValue: string | number | boolean = '') => { @@ -221,12 +222,15 @@ export function MovieSettings({ config, onChange, getError }: MovieSettingsProps error={getError?.('movie_max_time')} /> - onChange('movie_passthrough', val)} - helpText="Copy codec without re-encoding. Reduces CPU but may cause compatibility issues." - /> + {/* Passthrough mode - only available for NETCAM cameras */} + {showPassthrough && ( + onChange('movie_passthrough', val)} + helpText="Copy codec without re-encoding (NETCAM only). Reduces CPU but may cause compatibility issues." + /> + )}

Format Code Reference

diff --git a/frontend/src/components/settings/NetcamSettings.tsx b/frontend/src/components/settings/NetcamSettings.tsx new file mode 100644 index 00000000..800f01d2 --- /dev/null +++ b/frontend/src/components/settings/NetcamSettings.tsx @@ -0,0 +1,172 @@ +import { FormSection, FormInput } from '@/components/form'; +import type { NetcamConnectionStatus } from '@/api/types'; + +export interface NetcamSettingsProps { + config: Record; + onChange: (param: string, value: string | number | boolean) => void; + connectionStatus?: NetcamConnectionStatus; + hasDualStream?: boolean; + getError?: (param: string) => string | undefined; +} + +/** + * Network Camera Settings Component + * + * Configuration for IP cameras via FFmpeg (RTSP, HTTP, HTTPS, file://). + * Supports authentication, custom FFmpeg parameters, and dual-stream configuration. + * + * @param config - Camera configuration + * @param onChange - Configuration change handler + * @param connectionStatus - Current NETCAM connection state + * @param hasDualStream - Whether high-resolution stream is configured + * @param getError - Error message getter + */ +export function NetcamSettings({ + config, + onChange, + connectionStatus, + hasDualStream, + getError, +}: NetcamSettingsProps) { + const getValue = (param: string, defaultValue: string | number | boolean = '') => { + return config[param]?.value ?? defaultValue; + }; + + return ( + +
+ {/* Connection Status Indicator */} + {connectionStatus && ( +
+ Connection: + +
+ )} + + {/* Primary Stream URL */} + onChange('netcam_url', val)} + placeholder="rtsp://192.168.1.100:554/stream" + helpText="RTSP, HTTP, HTTPS, or file:// URL for the camera stream" + error={getError?.('netcam_url')} + /> + + {/* Authentication */} + onChange('netcam_userpass', val)} + type="password" + placeholder="username:password" + helpText="Leave empty if camera doesn't require authentication" + error={getError?.('netcam_userpass')} + /> + + {/* FFmpeg Parameters */} + onChange('netcam_params', val)} + placeholder="-rtsp_transport tcp" + helpText="Advanced: Custom FFmpeg input options (e.g., -rtsp_transport tcp)" + error={getError?.('netcam_params')} + /> + + {/* Dual Stream Configuration */} + {hasDualStream && ( + <> +
+

+ High Resolution Stream +

+

+ Optional secondary stream for higher quality recordings while using lower + resolution for motion detection. +

+
+ + onChange('netcam_high_url', val)} + placeholder="rtsp://192.168.1.100:554/stream1" + helpText="Optional: Higher resolution stream for recordings" + error={getError?.('netcam_high_url')} + /> + + onChange('netcam_high_params', val)} + placeholder="-rtsp_transport tcp" + helpText="FFmpeg parameters for high-resolution stream" + error={getError?.('netcam_high_params')} + /> + + )} + + {/* Information Box */} +
+

+ Supported Protocols +

+
    +
  • + RTSP: rtsp:// - Most IP cameras +
  • +
  • + HTTP: http:// - MJPEG streams +
  • +
  • + HTTPS: https:// - Secure streams +
  • +
  • + File: file:// - Local video files +
  • +
+
+
+
+ ); +} + +/** + * NETCAM Status Badge Component + * + * Visual indicator for network camera connection state + */ +function NetcamStatusBadge({ status }: { status: NetcamConnectionStatus }) { + const styles: Record = { + connected: { color: 'bg-green-500/20 text-green-300 border-green-500/30', text: 'Connected' }, + reading: { color: 'bg-blue-500/20 text-blue-300 border-blue-500/30', text: 'Reading' }, + not_connected: { color: 'bg-red-500/20 text-red-300 border-red-500/30', text: 'Not Connected' }, + reconnecting: { color: 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30', text: 'Reconnecting' }, + unknown: { color: 'bg-gray-500/20 text-gray-400 border-gray-500/30', text: 'Unknown' }, + }; + + const { color, text } = styles[status] || styles.unknown; + + return ( + + + {text} + + ); +} diff --git a/frontend/src/components/settings/V4L2Settings.tsx b/frontend/src/components/settings/V4L2Settings.tsx new file mode 100644 index 00000000..467a971c --- /dev/null +++ b/frontend/src/components/settings/V4L2Settings.tsx @@ -0,0 +1,193 @@ +import { FormSection, FormSlider, FormToggle, FormSelect } from '@/components/form'; +import type { V4L2Control } from '@/api/types'; + +export interface V4L2SettingsProps { + config: Record; + onChange: (param: string, value: string | number | boolean) => void; + controls?: V4L2Control[]; + getError?: (param: string) => string | undefined; +} + +/** + * V4L2 Settings Component + * + * Renders runtime controls for USB cameras (V4L2 devices). + * Controls are discovered dynamically from the camera hardware. + * + * Common controls: brightness, contrast, saturation, gain, exposure, + * white balance, focus, sharpness, etc. (device-dependent) + * + * @param config - Camera configuration + * @param onChange - Configuration change handler + * @param controls - Array of V4L2 controls from device + * @param getError - Error message getter + */ +export function V4L2Settings({ config, onChange, controls, getError }: V4L2SettingsProps) { + if (!controls || controls.length === 0) { + return ( + +

+ No controls available for this camera. +

+
+ ); + } + + // Group controls by category (heuristic-based grouping) + const groupedControls = groupV4L2Controls(controls); + + return ( + + {Object.entries(groupedControls).map(([group, groupControls]) => ( +
+ {group !== 'Other' && ( +

{group}

+ )} + {groupControls.map((control) => ( + onChange(`v4l2_${control.id}`, val)} + error={getError?.(`v4l2_${control.id}`)} + /> + ))} +
+ ))} +
+ ); +} + +/** + * V4L2 Control Input Component + * + * Renders appropriate form control based on V4L2 control type + */ +function V4L2ControlInput({ + control, + value, + onChange, + error, +}: { + control: V4L2Control; + value: number | boolean; + onChange: (value: number | boolean) => void; + error?: string; +}) { + switch (control.type) { + case 'boolean': + return ( + + ); + + case 'menu': + return ( + onChange(Number(v))} + options={ + control.menuItems?.map((item) => ({ + value: String(item.value), + label: item.label, + })) ?? [] + } + helpText={`Default: ${control.default}`} + error={error} + /> + ); + + case 'integer': + default: + return ( + + ); + } +} + +/** + * Get control value from config with fallback to current value + */ +function getControlValue( + config: Record, + control: V4L2Control +): number | boolean { + const paramKey = `v4l2_${control.id}`; + const configValue = config[paramKey]?.value; + + if (configValue !== undefined) { + return control.type === 'boolean' ? Boolean(configValue) : Number(configValue); + } + + // Fallback to current value from device + return control.type === 'boolean' ? Boolean(control.current) : control.current; +} + +/** + * Group V4L2 controls by category (heuristic-based) + * + * Groups controls into logical categories for better UX. + * Falls back to "Other" for unrecognized controls. + */ +function groupV4L2Controls(controls: V4L2Control[]): Record { + const groups: Record = { + 'Image Quality': [], + 'Exposure & Gain': [], + 'White Balance': [], + 'Focus': [], + 'Other': [], + }; + + const categoryKeywords: Record = { + 'Image Quality': ['brightness', 'contrast', 'saturation', 'hue', 'sharpness', 'gamma'], + 'Exposure & Gain': ['exposure', 'gain', 'iso', 'backlight'], + 'White Balance': ['white', 'balance', 'color', 'colour', 'temperature'], + 'Focus': ['focus', 'zoom', 'pan', 'tilt', 'lens'], + }; + + for (const control of controls) { + const name = control.name.toLowerCase(); + let categorized = false; + + for (const [category, keywords] of Object.entries(categoryKeywords)) { + if (keywords.some((keyword) => name.includes(keyword))) { + groups[category].push(control); + categorized = true; + break; + } + } + + if (!categorized) { + groups['Other'].push(control); + } + } + + // Remove empty groups + return Object.fromEntries( + Object.entries(groups).filter(([_, groupControls]) => groupControls.length > 0) + ); +} diff --git a/frontend/src/hooks/useCameraInfo.ts b/frontend/src/hooks/useCameraInfo.ts new file mode 100644 index 00000000..dc928644 --- /dev/null +++ b/frontend/src/hooks/useCameraInfo.ts @@ -0,0 +1,117 @@ +import { useMemo } from 'react'; +import { useSystemStatus } from '@/api/queries'; +import type { + CameraType, + CameraCapabilities, + V4L2Control, + NetcamConnectionStatus, +} from '@/api/types'; + +export interface CameraInfo { + isLoading: boolean; + cameraType: CameraType; + cameraDevice: string; + isConnected: boolean; + features: { + hasLibcamControls: boolean; + hasV4L2Controls: boolean; + hasNetcamConfig: boolean; + hasDualStream: boolean; + supportsPassthrough: boolean; + }; + // Type-specific data + libcamCapabilities?: CameraCapabilities; + v4l2Controls?: V4L2Control[]; + netcamStatus?: NetcamConnectionStatus; +} + +/** + * Central camera info hook with feature detection + * + * Returns camera type, device info, connection status, and feature flags + * for conditional rendering of type-specific settings components. + * + * @param cameraId - Camera ID (1-based) + * @returns CameraInfo with type detection and feature flags + * + * @example + * ```tsx + * const cameraInfo = useCameraInfo(1); + * + * if (cameraInfo.features.hasLibcamControls) { + * return ; + * } + * + * if (cameraInfo.features.hasV4L2Controls) { + * return ; + * } + * ``` + */ +export function useCameraInfo(cameraId: number): CameraInfo { + const { data: status, isLoading } = useSystemStatus(); + + return useMemo(() => { + // Return loading state if data not yet fetched + if (isLoading || !status?.status) { + return { + isLoading: true, + cameraType: 'unknown', + cameraDevice: '', + isConnected: false, + features: { + hasLibcamControls: false, + hasV4L2Controls: false, + hasNetcamConfig: false, + hasDualStream: false, + supportsPassthrough: false, + }, + }; + } + + // Get camera status from response + const camKey = `cam${cameraId}`; + const cam = status.status[camKey]; + + // If camera not found, return unknown state + if (!cam) { + return { + isLoading: false, + cameraType: 'unknown', + cameraDevice: '', + isConnected: false, + features: { + hasLibcamControls: false, + hasV4L2Controls: false, + hasNetcamConfig: false, + hasDualStream: false, + supportsPassthrough: false, + }, + }; + } + + const cameraType = cam.camera_type ?? 'unknown'; + + return { + isLoading: false, + cameraType, + cameraDevice: cam.camera_device ?? '', + isConnected: !cam.lost_connection, + features: { + // libcam: Has supportedControls map + hasLibcamControls: cameraType === 'libcam', + // v4l2: Has v4l2_controls array + hasV4L2Controls: cameraType === 'v4l2', + // netcam: Has netcam_url and related config + hasNetcamConfig: cameraType === 'netcam', + // netcam dual stream: has_high_stream is true + hasDualStream: cameraType === 'netcam' && cam.has_high_stream === true, + // netcam passthrough: only netcam supports movie passthrough + supportsPassthrough: cameraType === 'netcam', + }, + // Type-specific data (conditionally included) + ...(cameraType === 'libcam' && { libcamCapabilities: cam.supportedControls }), + ...(cameraType === 'v4l2' && { v4l2Controls: cam.v4l2_controls }), + ...(cameraType === 'netcam' && { netcamStatus: cam.netcam_status }), + }; + }, [status, cameraId, isLoading]); +} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 0eae6042..b5ebb26c 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -10,7 +10,10 @@ import { useBatchUpdateConfig, useSystemStatus } from '@/api/queries' import { validateConfigParam } from '@/lib/validation' import { SystemSettings } from '@/components/settings/SystemSettings' import { DeviceSettings } from '@/components/settings/DeviceSettings' +import { CameraSourceSettings } from '@/components/settings/CameraSourceSettings' import { LibcameraSettings } from '@/components/settings/LibcameraSettings' +import { V4L2Settings } from '@/components/settings/V4L2Settings' +import { NetcamSettings } from '@/components/settings/NetcamSettings' import { OverlaySettings } from '@/components/settings/OverlaySettings' import { StreamSettings } from '@/components/settings/StreamSettings' import { MotionSettings } from '@/components/settings/MotionSettings' @@ -26,6 +29,7 @@ import { UploadSettings } from '@/components/settings/UploadSettings' import { ConfigurationPresets } from '@/components/ConfigurationPresets' import { useAuthContext } from '@/contexts/AuthContext' import { useCameraCapabilities } from '@/hooks/useCameraCapabilities' +import { useCameraInfo } from '@/hooks/useCameraInfo' interface ConfigParam { value: string | number | boolean @@ -84,6 +88,9 @@ export function Settings() { // Fetch camera capabilities for conditional UI rendering (e.g., autofocus controls) const { data: capabilities } = useCameraCapabilities(Number(selectedCamera)) + // Fetch camera info for multi-camera type support + const cameraInfo = useCameraInfo(Number(selectedCamera)) + // Clear changes and errors when camera selection changes // This prevents race conditions where settings from one camera could be // applied to another if the user switches cameras before saving @@ -418,27 +425,56 @@ export function Settings() {
- {/* 2. Device Settings */} - - {/* 3. libcamera Controls */} - + )} + + {cameraInfo.features.hasV4L2Controls && ( + + )} + + {cameraInfo.features.hasNetcamConfig && ( + + )} + + {/* 4. Device Settings */} + - {/* 4. Movie Settings */} + {/* 5. Movie Settings */} {/* 5. Video Streaming */} diff --git a/src/video_v4l2.hpp b/src/video_v4l2.hpp index 2ae3fbb2..896bb023 100644 --- a/src/video_v4l2.hpp +++ b/src/video_v4l2.hpp @@ -62,6 +62,7 @@ class cls_v4l2cam { ~cls_v4l2cam(); int next(ctx_image_data *img_data); void noimage(); + vec_v4l2ctrl get_device_ctrls() const { return device_ctrls; } private: cls_camera *cam; cls_convert *convert; diff --git a/src/webu_json.cpp b/src/webu_json.cpp index 9ba782ed..dfea979b 100644 --- a/src/webu_json.cpp +++ b/src/webu_json.cpp @@ -677,6 +677,80 @@ void cls_webu_json::status_vars(int indx_cam) } #endif + /* Add camera_type field */ + std::string type_str; + switch (cam->camera_type) { + case CAMERA_TYPE_LIBCAM: type_str = "libcam"; break; + case CAMERA_TYPE_V4L2: type_str = "v4l2"; break; + case CAMERA_TYPE_NETCAM: type_str = "netcam"; break; + default: type_str = "unknown"; break; + } + webua->resp_page += ",\"camera_type\":\"" + type_str + "\""; + + /* Add camera_device identifier */ + std::string device_str = ""; + if (cam->camera_type == CAMERA_TYPE_V4L2) { + device_str = cam->cfg->v4l2_device; + } else if (cam->camera_type == CAMERA_TYPE_NETCAM) { + device_str = cam->cfg->netcam_url; + } else if (cam->camera_type == CAMERA_TYPE_LIBCAM) { + device_str = cam->cfg->libcam_device; + } + webua->resp_page += ",\"camera_device\":\"" + escstr(device_str) + "\""; + + /* Add V4L2 controls array (if V4L2 camera) */ + #ifdef HAVE_V4L2 + if (cam->camera_type == CAMERA_TYPE_V4L2 && cam->v4l2cam != nullptr) { + vec_v4l2ctrl controls = cam->v4l2cam->get_device_ctrls(); + webua->resp_page += ",\"v4l2_controls\":["; + bool first_ctrl = true; + for (const auto& ctrl : controls) { + if (ctrl.ctrl_menuitem) continue; // Skip menu items + if (!first_ctrl) webua->resp_page += ","; + webua->resp_page += "{"; + webua->resp_page += "\"name\":\"" + escstr(ctrl.ctrl_name) + "\""; + webua->resp_page += ",\"id\":\"" + escstr(ctrl.ctrl_iddesc) + "\""; + // Map V4L2 control type to string + std::string ctrl_type_str; + if (ctrl.ctrl_type == V4L2_CTRL_TYPE_BOOLEAN) { + ctrl_type_str = "boolean"; + } else if (ctrl.ctrl_type == V4L2_CTRL_TYPE_MENU) { + ctrl_type_str = "menu"; + } else { + ctrl_type_str = "integer"; + } + webua->resp_page += ",\"type\":\"" + ctrl_type_str + "\""; + webua->resp_page += ",\"min\":" + std::to_string(ctrl.ctrl_minimum); + webua->resp_page += ",\"max\":" + std::to_string(ctrl.ctrl_maximum); + webua->resp_page += ",\"default\":" + std::to_string(ctrl.ctrl_default); + webua->resp_page += ",\"current\":" + std::to_string(ctrl.ctrl_currval); + webua->resp_page += "}"; + first_ctrl = false; + } + webua->resp_page += "]"; + } + #endif + + /* Add NETCAM status and high stream indicator (if NETCAM) */ + if (cam->camera_type == CAMERA_TYPE_NETCAM && cam->netcam != nullptr) { + std::string netcam_status_str; + switch (cam->netcam->status) { + case NETCAM_CONNECTED: netcam_status_str = "connected"; break; + case NETCAM_READINGIMAGE: netcam_status_str = "reading"; break; + case NETCAM_NOTCONNECTED: netcam_status_str = "not_connected"; break; + case NETCAM_RECONNECTING: netcam_status_str = "reconnecting"; break; + default: netcam_status_str = "unknown"; break; + } + webua->resp_page += ",\"netcam_status\":\"" + netcam_status_str + "\""; + + // Check if high resolution stream is configured + if (cam->netcam_high != nullptr) { + webua->resp_page += ",\"has_high_stream\":true"; + } else { + webua->resp_page += ",\"has_high_stream\":false"; + } + } + webua->resp_page += "}"; } From 00519c067ba0452677a0974ca6a5edf5154518e4 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sat, 24 Jan 2026 22:56:13 -0600 Subject: [PATCH 05/10] fix(build): Add missing netcam.hpp and video_v4l2.hpp includes Required for NETCAM_STATUS enum and cls_netcam class access. Co-Authored-By: Claude Sonnet 4.5 --- src/webu_json.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webu_json.cpp b/src/webu_json.cpp index dfea979b..5bed19d2 100644 --- a/src/webu_json.cpp +++ b/src/webu_json.cpp @@ -37,6 +37,8 @@ #include "webu_json.hpp" #include "dbse.hpp" #include "libcam.hpp" +#include "netcam.hpp" +#include "video_v4l2.hpp" #include "json_parse.hpp" #include #include From f9f64143d3a4d346c5da5433f7046a26f583816b Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sun, 25 Jan 2026 20:50:04 -0600 Subject: [PATCH 06/10] refactor: improve camera type checking with accessor methods Add encapsulation methods to cls_camera for safer camera type checking and device access. This improves code maintainability by avoiding direct pointer access from web API handlers. Backend changes: - Add has_v4l2(), get_v4l2_controls() for V4L2 camera access - Add has_netcam(), has_netcam_high() for netcam stream checking - Update webu_json.cpp to use new accessor methods Frontend changes: - Remove unused config/onChange props from CameraSourceSettings - Fix TypeScript type assertion in useCameraInfo hook Co-Authored-By: Claude Sonnet 4.5 --- ...oard-D701LIV_.js => Dashboard-D7oUR9hz.js} | 2 +- .../{Media-6m4Qsu8Y.js => Media-4gl6Y8Bi.js} | 2 +- data/webui/assets/Settings-Ca5UNPDy.js | 22 --------------- data/webui/assets/Settings-JwhcLbnw.js | 22 +++++++++++++++ data/webui/assets/index-BK6lPNRb.css | 1 - data/webui/assets/index-CVdKRgH6.css | 1 + .../{index-DCzq8GhF.js => index-tiawrtsp.js} | 4 +-- ...SfEkB.js => parameterMappings-BmLxmuw_.js} | 2 +- data/webui/index.html | 4 +-- .../settings/CameraSourceSettings.tsx | 6 +---- frontend/src/hooks/useCameraInfo.ts | 2 +- frontend/src/pages/Settings.tsx | 2 -- src/camera.cpp | 27 +++++++++++++++++++ src/camera.hpp | 14 ++++++++++ src/webu_json.cpp | 8 +++--- 15 files changed, 77 insertions(+), 42 deletions(-) rename data/webui/assets/{Dashboard-D701LIV_.js => Dashboard-D7oUR9hz.js} (99%) rename data/webui/assets/{Media-6m4Qsu8Y.js => Media-4gl6Y8Bi.js} (98%) delete mode 100644 data/webui/assets/Settings-Ca5UNPDy.js create mode 100644 data/webui/assets/Settings-JwhcLbnw.js delete mode 100644 data/webui/assets/index-BK6lPNRb.css create mode 100644 data/webui/assets/index-CVdKRgH6.css rename data/webui/assets/{index-DCzq8GhF.js => index-tiawrtsp.js} (99%) rename data/webui/assets/{parameterMappings-CUuSfEkB.js => parameterMappings-BmLxmuw_.js} (99%) diff --git a/data/webui/assets/Dashboard-D701LIV_.js b/data/webui/assets/Dashboard-D7oUR9hz.js similarity index 99% rename from data/webui/assets/Dashboard-D701LIV_.js rename to data/webui/assets/Dashboard-D7oUR9hz.js index 17b0c33b..1c47508c 100644 --- a/data/webui/assets/Dashboard-D701LIV_.js +++ b/data/webui/assets/Dashboard-D7oUR9hz.js @@ -1 +1 @@ -import{r as a,j as e,u as W,a as O,t as Y,b as q,c as Q,d as G,e as H,f as z,g as V}from"./index-DCzq8GhF.js";import{u as U,p as $,a as K,C as J,F as y,b as L,c as F,A as X,d as Z,e as B}from"./parameterMappings-CUuSfEkB.js";function ee({isOpen:n,onClose:i,sheetRef:o,closeThreshold:m=.3}){const[c,d]=a.useState(!1),[g,b]=a.useState(0),v=a.useRef(0),h=a.useRef(0),r=a.useRef(0),l=a.useRef(0),j=a.useRef(0),x=a.useCallback(u=>{d(!0),v.current=u,h.current=u,j.current=u,l.current=Date.now(),r.current=0},[]),p=a.useCallback(u=>{if(!c)return;const k=Date.now(),T=k-l.current,s=u-j.current;T>0&&(r.current=s/T),l.current=k,j.current=u,h.current=u;const f=Math.max(0,u-v.current);b(f)},[c]),N=a.useCallback(()=>{if(!c)return;d(!1);const u=o.current?.offsetHeight||400;(g>u*m||r.current>.5)&&i(),b(0),r.current=0},[c,g,i,m,o]),w=a.useCallback(u=>{const k=u.touches[0],T=o.current?.getBoundingClientRect().top||0;k.clientY-T<=60&&x(k.clientY)},[x,o]),M=a.useCallback(u=>{c&&(u.preventDefault(),p(u.touches[0].clientY))},[c,p]),t=a.useCallback(()=>{N()},[N]),C=a.useCallback(u=>{const k=o.current?.getBoundingClientRect().top||0;if(u.clientY-k<=60){x(u.clientY);const s=_=>{p(_.clientY)},f=()=>{N(),document.removeEventListener("mousemove",s),document.removeEventListener("mouseup",f)};document.addEventListener("mousemove",s),document.addEventListener("mouseup",f)}},[x,p,N,o]),S=n?c?`translateY(${g}px)`:"translateY(0)":"translateY(100%)";return{handlers:{onTouchStart:w,onTouchMove:M,onTouchEnd:t,onMouseDown:C},style:{transform:S},state:{isDragging:c,dragOffset:g}}}function A({isOpen:n,onClose:i,title:o,children:m,headerRight:c}){const d=a.useRef(null),g=a.useRef(null),b=a.useRef(null),{handlers:v,style:h}=ee({isOpen:n,onClose:i,sheetRef:d});a.useEffect(()=>{if(!n)return;const l=x=>{x.key==="Escape"&&i()},j=x=>{if(x.key!=="Tab"||!d.current)return;const p=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),N=p[0],w=p[p.length-1];N&&(x.shiftKey?document.activeElement===N&&(x.preventDefault(),w?.focus()):document.activeElement===w&&(x.preventDefault(),N?.focus()))};return document.addEventListener("keydown",l),document.addEventListener("keydown",j),()=>{document.removeEventListener("keydown",l),document.removeEventListener("keydown",j)}},[n,i]),a.useEffect(()=>(n?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[n]),a.useEffect(()=>{n?(b.current=document.activeElement,setTimeout(()=>{if(d.current){const l=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');l.length>0&&l[0].focus()}},100)):b.current&&b.current.focus()},[n]);const r=a.useCallback(l=>{l.target===l.currentTarget&&i()},[i]);return n?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"fixed inset-0 z-[100]",onClick:r,"aria-hidden":"true"}),e.jsxs("div",{ref:d,className:"fixed bottom-0 left-0 right-0 z-[101] bg-surface/95 backdrop-blur-sm rounded-t-2xl shadow-2xl transition-transform duration-300 ease-out border-t border-surface-elevated",style:{maxHeight:"45vh",transform:h.transform},role:"dialog","aria-modal":"true","aria-label":o,...v,children:[e.jsx("div",{className:"flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none",children:e.jsx("div",{className:"w-12 h-1.5 bg-gray-500 rounded-full"})}),e.jsxs("div",{className:"flex items-center justify-between px-4 pb-3 border-b border-surface-elevated",children:[e.jsx("h2",{className:"text-lg font-semibold",children:o}),e.jsxs("div",{className:"flex items-center gap-3",children:[c,e.jsx("button",{type:"button",onClick:i,className:"p-2 hover:bg-surface-elevated rounded-full transition-colors","aria-label":"Close",children:e.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})]}),e.jsx("div",{ref:g,className:"overflow-y-auto overscroll-contain px-4 py-4",style:{maxHeight:"calc(45vh - 80px)"},children:m})]})]}):null}function E({title:n,defaultOpen:i=!0,children:o}){const[m,c]=a.useState(i);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full py-2 text-left",onClick:()=>c(!m),children:[e.jsx("span",{className:"font-medium text-sm",children:n}),e.jsx("svg",{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&e.jsx("div",{className:"pt-2",children:o})]})}function D({cameraId:n,config:i}){const{mutate:o,isPending:m}=W(),[c,d]=a.useState(null),[g,b]=a.useState({}),v=a.useRef({}),{data:h}=U(n);a.useEffect(()=>()=>{Object.values(v.current).forEach(clearTimeout)},[]),a.useEffect(()=>{b({})},[i]);const r=a.useCallback((t,C="")=>t in g?g[t]:i[t]?.value??C,[i,g]);a.useEffect(()=>{Object.values(v.current).forEach(clearTimeout),v.current={}},[n]);const l=a.useCallback((t,C)=>{b(u=>({...u,[t]:C})),v.current[t]&&clearTimeout(v.current[t]);const S=n;v.current[t]=setTimeout(()=>{o({camId:S,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},300)},[n,o]),j=a.useCallback((t,C)=>{b(S=>({...S,[t]:C})),o({camId:n,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},[n,o]),x=Number(r("width",640)),p=Number(r("height",480)),N=Number(r("threshold",1500)),w=$(N,x,p),M=a.useCallback(t=>{const C=K(t,x,p);l("threshold",C)},[x,p,l]);return e.jsxs("div",{className:"space-y-2",children:[e.jsx(J,{cameraId:n,readOnly:!0}),e.jsxs(E,{title:"Stream",defaultOpen:!1,children:[e.jsx(y,{label:"Quality",value:Number(r("stream_quality",50)),onChange:t=>l("stream_quality",t),min:1,max:100,unit:"%",helpText:"JPEG compression quality"}),e.jsx(y,{label:"Max Framerate",value:Number(r("stream_maxrate",15)),onChange:t=>l("stream_maxrate",t),min:1,max:30,unit:" fps",helpText:"Maximum stream framerate"})]}),e.jsxs(E,{title:"Image",defaultOpen:!1,children:[e.jsx(y,{label:"Brightness",value:Number(r("libcam_brightness",0)),onChange:t=>l("libcam_brightness",t),min:-1,max:1,step:.1,helpText:"Brightness adjustment"}),e.jsx(y,{label:"Contrast",value:Number(r("libcam_contrast",1)),onChange:t=>l("libcam_contrast",t),min:0,max:32,step:.5,helpText:"Contrast adjustment"}),e.jsx(y,{label:"Gain (ISO)",value:Number(r("libcam_gain",1)),onChange:t=>l("libcam_gain",t),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)"}),e.jsx(L,{label:"Auto White Balance",value:!!r("libcam_awb_enable",!0),onChange:t=>j("libcam_awb_enable",t),helpText:"Enable automatic white balance"}),!!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"AWB Mode",value:String(r("libcam_awb_mode",0)),onChange:t=>j("libcam_awb_mode",Number(t)),options:X.map(t=>({value:String(t.value),label:t.label})),helpText:"White balance mode"}),h?.AwbLocked!==!1&&e.jsx(L,{label:"Lock AWB",value:!!r("libcam_awb_locked",!1),onChange:t=>j("libcam_awb_locked",t),helpText:"Lock white balance settings"})]}),!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[h?.ColourTemperature!==!1&&e.jsx(y,{label:"Color Temperature",value:Number(r("libcam_colour_temp",0)),onChange:t=>l("libcam_colour_temp",t),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)"}),e.jsx(y,{label:"Red Gain",value:Number(r("libcam_colour_gain_r",1)),onChange:t=>l("libcam_colour_gain_r",t),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)"}),e.jsx(y,{label:"Blue Gain",value:Number(r("libcam_colour_gain_b",1)),onChange:t=>l("libcam_colour_gain_b",t),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)"}),h?.ColourTemperature===!1&&e.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[e.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),h?.AfMode&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"Autofocus Mode",value:String(r("libcam_af_mode",0)),onChange:t=>j("libcam_af_mode",Number(t)),options:Z.map(t=>({value:String(t.value),label:t.label})),helpText:"Focus control mode"}),Number(r("libcam_af_mode",0))===0&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),!h?.AfMode&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),e.jsxs(E,{title:"Detection",defaultOpen:!1,children:[e.jsx(y,{label:"Threshold",value:w,onChange:M,min:0,max:20,step:.1,unit:"%",helpText:"Motion sensitivity (higher = less sensitive)"}),e.jsx(y,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:t=>l("noise_level",t),min:1,max:255,helpText:"Noise tolerance"}),e.jsx(L,{label:"Auto-tune Noise",value:!!r("noise_tune",!1),onChange:t=>j("noise_tune",t),helpText:"Automatically adjust noise level"})]}),m&&e.jsx("div",{className:"text-center text-sm text-gray-400 py-2",children:"Applying..."}),c&&!m&&e.jsx("div",{className:"text-center text-sm text-green-400 py-2",children:"Applied!"})]})}function R({cameraId:n}){const[i,o]=a.useState(!1),m=c=>{c.stopPropagation();const d=document.querySelector(`[data-camera-id="${n}"]`);d&&(i?document.exitFullscreen&&document.exitFullscreen():d.requestFullscreen&&d.requestFullscreen())};return a.useEffect(()=>{const c=()=>{o(!!document.fullscreenElement)};return document.addEventListener("fullscreenchange",c),()=>{document.removeEventListener("fullscreenchange",c)}},[]),e.jsx("button",{type:"button",onClick:m,className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Toggle fullscreen",title:"Toggle fullscreen",children:e.jsx("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:i?e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"}):e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"})})})}function I({cameraId:n,onClick:i}){return e.jsx("button",{type:"button",onClick:o=>{o.stopPropagation(),i(n)},className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Quick settings",title:"Quick settings",children:e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function P({cameraId:n}){const[i,o]=a.useState(!1),{addToast:m}=O(),c=async d=>{if(d.stopPropagation(),!i){o(!0);try{await Y(n),m("Snapshot captured","success")}catch(g){m(g instanceof Error?g.message:"Failed to capture snapshot","error")}finally{o(!1)}}};return e.jsx("button",{type:"button",onClick:c,disabled:i,className:"p-1.5 hover:bg-surface rounded-full transition-colors disabled:opacity-50","aria-label":"Take snapshot",title:"Take snapshot",children:i?e.jsxs("svg",{className:"w-5 h-5 text-gray-400 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}):e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 13a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function ae(){const{data:n,isLoading:i,error:o}=q(),{role:m,isAuthenticated:c,authRequired:d}=Q(),{data:g}=G({enabled:!d||c}),[b,v]=a.useState(!1),[h,r]=a.useState(null),[l,j]=a.useState({}),x=s=>g?.find(f=>f.id===s)?.fps??0,p=s=>l[s]??0,N=s=>f=>{j(_=>({..._,[s]:f}))},{data:w}=H({queryKey:["config"],queryFn:async()=>{const s=await z("/0/api/config");return s.csrf_token&&V(s.csrf_token),s},enabled:b,staleTime:3e4}),M=s=>{r(s),v(!0)},t=()=>{v(!1)},C=n?.find(s=>s.id===h),S=C?`Quick Settings - ${C.name}`:"Quick Settings",u=a.useMemo(()=>{if(!w||!h)return{};const s=w.configuration?.default||{},f=w.configuration?.[`cam${h}`]||{};return{...s,...f}},[w,h]);if(i)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsx("div",{className:"flex flex-col items-center gap-6",children:[1].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 animate-pulse w-full max-w-4xl",children:[e.jsx("div",{className:"h-6 bg-surface rounded w-1/3 mb-4"}),e.jsx("div",{className:"aspect-video bg-surface rounded"})]},s))})]});if(o)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-danger/10 border border-danger rounded-lg p-4 max-w-2xl mx-auto",children:[e.jsxs("p",{className:"text-danger",children:["Failed to load cameras: ",o instanceof Error?o.message:"Unknown error"]}),e.jsx("button",{className:"mt-2 text-sm text-primary hover:underline",onClick:()=>window.location.reload(),children:"Retry"})]})]});if(!n||n.length===0)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[e.jsx("svg",{className:"w-16 h-16 mx-auto text-gray-600 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"})}),e.jsx("p",{className:"text-gray-400 text-lg",children:"No cameras configured"}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:"Add cameras in Motion's configuration file"})]})]});const k=n.length;if(k===1){const s=n[0],f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("div",{className:"max-w-5xl mx-auto",children:e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}const T=()=>k===2?"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6":k<=4?"grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6":"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6";return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsxs("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:["Cameras (",k,")"]}),e.jsx("div",{className:T(),children:n.map(s=>{const f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium text-sm sm:text-base",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]},s.id)})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}export{ae as Dashboard}; +import{r as a,j as e,u as W,a as O,t as Y,b as q,c as Q,d as G,e as H,f as z,g as V}from"./index-tiawrtsp.js";import{u as U,p as $,a as K,C as J,F as y,b as L,c as F,A as X,d as Z,e as B}from"./parameterMappings-BmLxmuw_.js";function ee({isOpen:n,onClose:i,sheetRef:o,closeThreshold:m=.3}){const[c,d]=a.useState(!1),[g,b]=a.useState(0),v=a.useRef(0),h=a.useRef(0),r=a.useRef(0),l=a.useRef(0),j=a.useRef(0),x=a.useCallback(u=>{d(!0),v.current=u,h.current=u,j.current=u,l.current=Date.now(),r.current=0},[]),p=a.useCallback(u=>{if(!c)return;const k=Date.now(),T=k-l.current,s=u-j.current;T>0&&(r.current=s/T),l.current=k,j.current=u,h.current=u;const f=Math.max(0,u-v.current);b(f)},[c]),N=a.useCallback(()=>{if(!c)return;d(!1);const u=o.current?.offsetHeight||400;(g>u*m||r.current>.5)&&i(),b(0),r.current=0},[c,g,i,m,o]),w=a.useCallback(u=>{const k=u.touches[0],T=o.current?.getBoundingClientRect().top||0;k.clientY-T<=60&&x(k.clientY)},[x,o]),M=a.useCallback(u=>{c&&(u.preventDefault(),p(u.touches[0].clientY))},[c,p]),t=a.useCallback(()=>{N()},[N]),C=a.useCallback(u=>{const k=o.current?.getBoundingClientRect().top||0;if(u.clientY-k<=60){x(u.clientY);const s=_=>{p(_.clientY)},f=()=>{N(),document.removeEventListener("mousemove",s),document.removeEventListener("mouseup",f)};document.addEventListener("mousemove",s),document.addEventListener("mouseup",f)}},[x,p,N,o]),S=n?c?`translateY(${g}px)`:"translateY(0)":"translateY(100%)";return{handlers:{onTouchStart:w,onTouchMove:M,onTouchEnd:t,onMouseDown:C},style:{transform:S},state:{isDragging:c,dragOffset:g}}}function A({isOpen:n,onClose:i,title:o,children:m,headerRight:c}){const d=a.useRef(null),g=a.useRef(null),b=a.useRef(null),{handlers:v,style:h}=ee({isOpen:n,onClose:i,sheetRef:d});a.useEffect(()=>{if(!n)return;const l=x=>{x.key==="Escape"&&i()},j=x=>{if(x.key!=="Tab"||!d.current)return;const p=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),N=p[0],w=p[p.length-1];N&&(x.shiftKey?document.activeElement===N&&(x.preventDefault(),w?.focus()):document.activeElement===w&&(x.preventDefault(),N?.focus()))};return document.addEventListener("keydown",l),document.addEventListener("keydown",j),()=>{document.removeEventListener("keydown",l),document.removeEventListener("keydown",j)}},[n,i]),a.useEffect(()=>(n?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[n]),a.useEffect(()=>{n?(b.current=document.activeElement,setTimeout(()=>{if(d.current){const l=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');l.length>0&&l[0].focus()}},100)):b.current&&b.current.focus()},[n]);const r=a.useCallback(l=>{l.target===l.currentTarget&&i()},[i]);return n?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"fixed inset-0 z-[100]",onClick:r,"aria-hidden":"true"}),e.jsxs("div",{ref:d,className:"fixed bottom-0 left-0 right-0 z-[101] bg-surface/95 backdrop-blur-sm rounded-t-2xl shadow-2xl transition-transform duration-300 ease-out border-t border-surface-elevated",style:{maxHeight:"45vh",transform:h.transform},role:"dialog","aria-modal":"true","aria-label":o,...v,children:[e.jsx("div",{className:"flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none",children:e.jsx("div",{className:"w-12 h-1.5 bg-gray-500 rounded-full"})}),e.jsxs("div",{className:"flex items-center justify-between px-4 pb-3 border-b border-surface-elevated",children:[e.jsx("h2",{className:"text-lg font-semibold",children:o}),e.jsxs("div",{className:"flex items-center gap-3",children:[c,e.jsx("button",{type:"button",onClick:i,className:"p-2 hover:bg-surface-elevated rounded-full transition-colors","aria-label":"Close",children:e.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})]}),e.jsx("div",{ref:g,className:"overflow-y-auto overscroll-contain px-4 py-4",style:{maxHeight:"calc(45vh - 80px)"},children:m})]})]}):null}function E({title:n,defaultOpen:i=!0,children:o}){const[m,c]=a.useState(i);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full py-2 text-left",onClick:()=>c(!m),children:[e.jsx("span",{className:"font-medium text-sm",children:n}),e.jsx("svg",{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&e.jsx("div",{className:"pt-2",children:o})]})}function D({cameraId:n,config:i}){const{mutate:o,isPending:m}=W(),[c,d]=a.useState(null),[g,b]=a.useState({}),v=a.useRef({}),{data:h}=U(n);a.useEffect(()=>()=>{Object.values(v.current).forEach(clearTimeout)},[]),a.useEffect(()=>{b({})},[i]);const r=a.useCallback((t,C="")=>t in g?g[t]:i[t]?.value??C,[i,g]);a.useEffect(()=>{Object.values(v.current).forEach(clearTimeout),v.current={}},[n]);const l=a.useCallback((t,C)=>{b(u=>({...u,[t]:C})),v.current[t]&&clearTimeout(v.current[t]);const S=n;v.current[t]=setTimeout(()=>{o({camId:S,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},300)},[n,o]),j=a.useCallback((t,C)=>{b(S=>({...S,[t]:C})),o({camId:n,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},[n,o]),x=Number(r("width",640)),p=Number(r("height",480)),N=Number(r("threshold",1500)),w=$(N,x,p),M=a.useCallback(t=>{const C=K(t,x,p);l("threshold",C)},[x,p,l]);return e.jsxs("div",{className:"space-y-2",children:[e.jsx(J,{cameraId:n,readOnly:!0}),e.jsxs(E,{title:"Stream",defaultOpen:!1,children:[e.jsx(y,{label:"Quality",value:Number(r("stream_quality",50)),onChange:t=>l("stream_quality",t),min:1,max:100,unit:"%",helpText:"JPEG compression quality"}),e.jsx(y,{label:"Max Framerate",value:Number(r("stream_maxrate",15)),onChange:t=>l("stream_maxrate",t),min:1,max:30,unit:" fps",helpText:"Maximum stream framerate"})]}),e.jsxs(E,{title:"Image",defaultOpen:!1,children:[e.jsx(y,{label:"Brightness",value:Number(r("libcam_brightness",0)),onChange:t=>l("libcam_brightness",t),min:-1,max:1,step:.1,helpText:"Brightness adjustment"}),e.jsx(y,{label:"Contrast",value:Number(r("libcam_contrast",1)),onChange:t=>l("libcam_contrast",t),min:0,max:32,step:.5,helpText:"Contrast adjustment"}),e.jsx(y,{label:"Gain (ISO)",value:Number(r("libcam_gain",1)),onChange:t=>l("libcam_gain",t),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)"}),e.jsx(L,{label:"Auto White Balance",value:!!r("libcam_awb_enable",!0),onChange:t=>j("libcam_awb_enable",t),helpText:"Enable automatic white balance"}),!!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"AWB Mode",value:String(r("libcam_awb_mode",0)),onChange:t=>j("libcam_awb_mode",Number(t)),options:X.map(t=>({value:String(t.value),label:t.label})),helpText:"White balance mode"}),h?.AwbLocked!==!1&&e.jsx(L,{label:"Lock AWB",value:!!r("libcam_awb_locked",!1),onChange:t=>j("libcam_awb_locked",t),helpText:"Lock white balance settings"})]}),!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[h?.ColourTemperature!==!1&&e.jsx(y,{label:"Color Temperature",value:Number(r("libcam_colour_temp",0)),onChange:t=>l("libcam_colour_temp",t),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)"}),e.jsx(y,{label:"Red Gain",value:Number(r("libcam_colour_gain_r",1)),onChange:t=>l("libcam_colour_gain_r",t),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)"}),e.jsx(y,{label:"Blue Gain",value:Number(r("libcam_colour_gain_b",1)),onChange:t=>l("libcam_colour_gain_b",t),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)"}),h?.ColourTemperature===!1&&e.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[e.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),h?.AfMode&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"Autofocus Mode",value:String(r("libcam_af_mode",0)),onChange:t=>j("libcam_af_mode",Number(t)),options:Z.map(t=>({value:String(t.value),label:t.label})),helpText:"Focus control mode"}),Number(r("libcam_af_mode",0))===0&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),!h?.AfMode&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),e.jsxs(E,{title:"Detection",defaultOpen:!1,children:[e.jsx(y,{label:"Threshold",value:w,onChange:M,min:0,max:20,step:.1,unit:"%",helpText:"Motion sensitivity (higher = less sensitive)"}),e.jsx(y,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:t=>l("noise_level",t),min:1,max:255,helpText:"Noise tolerance"}),e.jsx(L,{label:"Auto-tune Noise",value:!!r("noise_tune",!1),onChange:t=>j("noise_tune",t),helpText:"Automatically adjust noise level"})]}),m&&e.jsx("div",{className:"text-center text-sm text-gray-400 py-2",children:"Applying..."}),c&&!m&&e.jsx("div",{className:"text-center text-sm text-green-400 py-2",children:"Applied!"})]})}function R({cameraId:n}){const[i,o]=a.useState(!1),m=c=>{c.stopPropagation();const d=document.querySelector(`[data-camera-id="${n}"]`);d&&(i?document.exitFullscreen&&document.exitFullscreen():d.requestFullscreen&&d.requestFullscreen())};return a.useEffect(()=>{const c=()=>{o(!!document.fullscreenElement)};return document.addEventListener("fullscreenchange",c),()=>{document.removeEventListener("fullscreenchange",c)}},[]),e.jsx("button",{type:"button",onClick:m,className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Toggle fullscreen",title:"Toggle fullscreen",children:e.jsx("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:i?e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"}):e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"})})})}function I({cameraId:n,onClick:i}){return e.jsx("button",{type:"button",onClick:o=>{o.stopPropagation(),i(n)},className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Quick settings",title:"Quick settings",children:e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function P({cameraId:n}){const[i,o]=a.useState(!1),{addToast:m}=O(),c=async d=>{if(d.stopPropagation(),!i){o(!0);try{await Y(n),m("Snapshot captured","success")}catch(g){m(g instanceof Error?g.message:"Failed to capture snapshot","error")}finally{o(!1)}}};return e.jsx("button",{type:"button",onClick:c,disabled:i,className:"p-1.5 hover:bg-surface rounded-full transition-colors disabled:opacity-50","aria-label":"Take snapshot",title:"Take snapshot",children:i?e.jsxs("svg",{className:"w-5 h-5 text-gray-400 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}):e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 13a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function ae(){const{data:n,isLoading:i,error:o}=q(),{role:m,isAuthenticated:c,authRequired:d}=Q(),{data:g}=G({enabled:!d||c}),[b,v]=a.useState(!1),[h,r]=a.useState(null),[l,j]=a.useState({}),x=s=>g?.find(f=>f.id===s)?.fps??0,p=s=>l[s]??0,N=s=>f=>{j(_=>({..._,[s]:f}))},{data:w}=H({queryKey:["config"],queryFn:async()=>{const s=await z("/0/api/config");return s.csrf_token&&V(s.csrf_token),s},enabled:b,staleTime:3e4}),M=s=>{r(s),v(!0)},t=()=>{v(!1)},C=n?.find(s=>s.id===h),S=C?`Quick Settings - ${C.name}`:"Quick Settings",u=a.useMemo(()=>{if(!w||!h)return{};const s=w.configuration?.default||{},f=w.configuration?.[`cam${h}`]||{};return{...s,...f}},[w,h]);if(i)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsx("div",{className:"flex flex-col items-center gap-6",children:[1].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 animate-pulse w-full max-w-4xl",children:[e.jsx("div",{className:"h-6 bg-surface rounded w-1/3 mb-4"}),e.jsx("div",{className:"aspect-video bg-surface rounded"})]},s))})]});if(o)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-danger/10 border border-danger rounded-lg p-4 max-w-2xl mx-auto",children:[e.jsxs("p",{className:"text-danger",children:["Failed to load cameras: ",o instanceof Error?o.message:"Unknown error"]}),e.jsx("button",{className:"mt-2 text-sm text-primary hover:underline",onClick:()=>window.location.reload(),children:"Retry"})]})]});if(!n||n.length===0)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[e.jsx("svg",{className:"w-16 h-16 mx-auto text-gray-600 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"})}),e.jsx("p",{className:"text-gray-400 text-lg",children:"No cameras configured"}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:"Add cameras in Motion's configuration file"})]})]});const k=n.length;if(k===1){const s=n[0],f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("div",{className:"max-w-5xl mx-auto",children:e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}const T=()=>k===2?"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6":k<=4?"grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6":"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6";return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsxs("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:["Cameras (",k,")"]}),e.jsx("div",{className:T(),children:n.map(s=>{const f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium text-sm sm:text-base",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]},s.id)})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}export{ae as Dashboard}; diff --git a/data/webui/assets/Media-6m4Qsu8Y.js b/data/webui/assets/Media-4gl6Y8Bi.js similarity index 98% rename from data/webui/assets/Media-6m4Qsu8Y.js rename to data/webui/assets/Media-4gl6Y8Bi.js index 69431ac7..b7990dce 100644 --- a/data/webui/assets/Media-6m4Qsu8Y.js +++ b/data/webui/assets/Media-4gl6Y8Bi.js @@ -1 +1 @@ -import{j as e,a as ie,c as ne,i as re,r as n,q as R,b as oe,s as ce,v as de,w as ue,x as me,y as xe,z as pe,l as he}from"./index-DCzq8GhF.js";function O({offset:a,limit:c,total:r,onPageChange:f,context:l}){const v=Math.floor(a/c)+1,i=Math.ceil(r/c),b=r===0?0:a+1,t=Math.min(a+c,r),x=a>0,d=a+c1&&e.jsxs("div",{className:"flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>f(Math.max(0,a-c)),disabled:!x,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Previous page",children:"◀ Previous"}),e.jsxs("span",{className:"px-3 py-1 text-sm text-gray-400",children:["Page ",v," of ",i]}),e.jsx("button",{onClick:()=>f(a+c),disabled:!d,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Next page",children:"Next ▶"})]})]})}function k(a){const c=he();if(!c)return a;const r=a.includes("?")?"&":"?";return`${a}${r}token=${encodeURIComponent(c)}`}const g=100;function fe(a,c){if(!a||a.length!==8)return null;const r=parseInt(a.substring(0,4),10),f=parseInt(a.substring(4,6),10)-1,l=parseInt(a.substring(6,8),10);if(isNaN(r)||isNaN(f)||isNaN(l))return null;let v=0,i=0,b=0;if(c){const x=c.split(":");x.length>=2&&(v=parseInt(x[0],10)||0,i=parseInt(x[1],10)||0,b=parseInt(x[2],10)||0)}const t=new Date(r,f,l,v,i,b);return isNaN(t.getTime())?null:t}function be(){const{addToast:a}=ie(),{role:c}=ne(),r=re(),f=c==="admin",[l,v]=n.useState(1),[i,b]=n.useState("pictures"),[t,x]=n.useState("all"),[d,P]=n.useState(""),[Y,y]=n.useState(0),[o,w]=n.useState(null),[p,C]=n.useState(null),[j,A]=n.useState(null),N=Y*g;n.useEffect(()=>{y(0)},[l,i,d]),n.useEffect(()=>{t==="all"&&P("")},[t]),n.useEffect(()=>{t==="all"&&(r.invalidateQueries({queryKey:R.movies(l)}),r.invalidateQueries({queryKey:R.pictures(l)}))},[t,l,r]),n.useEffect(()=>{t==="folders"&&r.invalidateQueries({predicate:s=>Array.isArray(s.queryKey)&&s.queryKey[0]==="media-folders"&&s.queryKey[1]===l})},[t,d,l,r]);const{data:Z}=oe(),{data:B,isLoading:J}=ce(l,N,g,null,{enabled:t==="all"&&i==="pictures"}),{data:E,isLoading:X}=de(l,N,g,null,{enabled:t==="all"&&i==="movies"}),{data:u,isLoading:ee}=ue(l,d,N,g,{enabled:t==="folders"}),$=me(),L=xe(),F=pe(),T=t==="folders"?ee:i==="pictures"?J:X,q=t==="folders"?u?.files??[]:i==="pictures"?B?.pictures??[]:E?.movies??[],D=t==="folders"?u?.total_files??0:i==="pictures"?B?.total_count??0:E?.total_count??0,I=s=>s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:s<1024*1024*1024?`${(s/(1024*1024)).toFixed(1)} MB`:`${(s/(1024*1024*1024)).toFixed(1)} GB`,K=(s,m)=>{const h=fe(s,m);return h?h.toLocaleDateString()+" "+h.toLocaleTimeString():"Unknown date"},se=n.useCallback((s,m)=>{m.stopPropagation(),C(s)},[]),te=n.useCallback(async()=>{if(p)try{const s="type"in p?p.type:i;s==="picture"||s==="pictures"?await $.mutateAsync({camId:l,pictureId:p.id}):await L.mutateAsync({camId:l,movieId:p.id}),a(`${s==="picture"||s==="pictures"?"Picture":"Movie"} deleted`,"success"),C(null),o?.id===p.id&&w(null)}catch{a("Failed to delete file","error")}},[p,i,l,$,L,a,o]),V=n.useCallback(()=>{C(null)},[]),W=n.useCallback((s,m)=>{A({path:s,fileCount:m})},[]),ae=n.useCallback(async()=>{if(j)try{const s=await F.mutateAsync({camId:l,path:j.path});a(`Deleted ${s.deleted.movies} movies, ${s.deleted.pictures} pictures`,"success"),A(null)}catch{a("Failed to delete folder contents","error")}},[j,l,F,a]),U=n.useCallback(()=>{A(null)},[]),z=n.useCallback(s=>{P(s),y(0)},[]),le=n.useCallback(()=>{u?.parent!==null&&(P(u?.parent??""),y(0))},[u]),S=$.isPending||L.isPending,_=F.isPending,G=d?d.split("/"):[];return e.jsxs("div",{className:"p-6",children:[e.jsx("div",{className:"flex items-center justify-between mb-6",children:e.jsx("h2",{className:"text-3xl font-bold",children:"Media"})}),e.jsxs("div",{className:"flex flex-wrap gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"camera-select",className:"block text-sm font-medium mb-2",children:"Camera"}),e.jsx("select",{id:"camera-select",value:l,onChange:s=>v(parseInt(s.target.value)),className:"px-3 py-2 bg-surface border border-surface-elevated rounded-lg",children:Z?.map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"Type"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>b("pictures"),className:`px-4 py-2 rounded-lg transition-colors ${i==="pictures"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Pictures"}),e.jsx("button",{onClick:()=>b("movies"),className:`px-4 py-2 rounded-lg transition-colors ${i==="movies"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Movies"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"View"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>x("all"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="all"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"All"}),e.jsx("button",{onClick:()=>x("folders"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="folders"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Folders"})]})]})]}),t==="folders"&&e.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3",children:[e.jsx("svg",{className:"w-5 h-5 text-gray-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"})}),e.jsx("span",{className:"text-sm font-medium text-gray-300",children:"Browse Folders"})]}),e.jsxs("div",{className:"flex items-center gap-1 text-sm flex-wrap",children:[e.jsx("button",{onClick:()=>z(""),className:"hover:text-primary px-1",children:"Root"}),G.map((s,m)=>{const h=G.slice(0,m+1).join("/");return e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("button",{onClick:()=>z(h),className:"hover:text-primary px-1",children:s})]},m)})]}),u?.parent!==null&&e.jsxs("button",{onClick:le,className:"mt-2 flex items-center gap-2 text-sm text-gray-400 hover:text-white",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 19l-7-7 7-7"})}),"Up to parent folder"]})]}),t==="folders"&&u&&u.folders.length>0&&e.jsx("div",{className:"mb-6 grid gap-2 md:grid-cols-2 lg:grid-cols-4",children:u.folders.map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 hover:ring-2 hover:ring-primary cursor-pointer transition-all group",children:[e.jsx("button",{onClick:()=>z(s.path),className:"w-full text-left",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("svg",{className:"w-8 h-8 text-yellow-500 flex-shrink-0",fill:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{d:"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"font-medium truncate",children:s.name}),e.jsxs("p",{className:"text-xs text-gray-400",children:[s.file_count," files - ",I(s.total_size)]})]})]})}),f&&s.file_count>0&&e.jsx("button",{onClick:m=>{m.stopPropagation(),W(s.path,s.file_count)},className:"mt-2 w-full px-2 py-1 text-xs bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded opacity-0 group-hover:opacity-100 transition-opacity",title:`Delete all ${s.file_count} media files in this folder`,children:"Delete All Media"})]},s.path))}),t==="folders"&&f&&d&&u&&u.total_files>0&&e.jsx("div",{className:"mb-4 flex justify-end",children:e.jsxs("button",{onClick:()=>W(d,u.total_files),className:"px-3 py-1.5 text-sm bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded-lg flex items-center gap-2",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})}),"Delete All Media in This Folder (",u.total_files,")"]})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),T?e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:[1,2,3,4,5,6,7,8].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg animate-pulse",children:[e.jsx("div",{className:"aspect-video bg-surface rounded-t-lg"}),e.jsxs("div",{className:"p-3",children:[e.jsx("div",{className:"h-4 bg-surface rounded w-3/4 mb-2"}),e.jsx("div",{className:"h-3 bg-surface rounded w-1/2"})]})]},s))}):q.length===0?e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center",children:[e.jsx("p",{className:"text-gray-400",children:t==="folders"?"No media files in this folder":`No ${i} found`}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:t==="folders"?"Navigate to a folder with media files":i==="pictures"?"Motion detection snapshots will appear here":"Recorded videos will appear here"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:q.map(s=>{const m="type"in s?s.type:i==="pictures"?"picture":"movie",h=s.thumbnail||void 0;return e.jsxs("button",{className:"bg-surface-elevated rounded-lg overflow-hidden cursor-pointer hover:ring-2 hover:ring-primary focus:ring-2 focus:ring-primary focus:outline-none transition-all group relative text-left w-full",onClick:()=>w(s),"aria-label":`View ${s.filename}`,children:[e.jsx("button",{onClick:M=>se(s,M),className:"absolute top-2 right-2 z-10 p-1.5 bg-red-600/80 hover:bg-red-600 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity",title:"Delete","aria-label":`Delete ${s.filename}`,children:e.jsx("svg",{className:"w-4 h-4 text-white",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})})}),e.jsxs("div",{className:"aspect-video bg-surface flex items-center justify-center relative overflow-hidden",children:[m==="picture"?e.jsx("img",{src:k(s.path),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy"}):h?e.jsx("img",{src:k(h),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy",onError:M=>{M.currentTarget.style.display="none";const H=M.currentTarget.parentElement;if(H){const Q=H.querySelector(".fallback-icon");Q&&(Q.style.display="flex")}}}):null,e.jsx("div",{className:`fallback-icon text-gray-400 absolute inset-0 flex items-center justify-center ${h?"hidden":""}`,children:e.jsxs("svg",{className:"w-16 h-16",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]})})]}),e.jsxs("div",{className:"p-3",children:[e.jsx("p",{className:"text-sm font-medium truncate",children:s.filename}),e.jsxs("div",{className:"flex justify-between text-xs text-gray-400 mt-1",children:[e.jsx("span",{children:I(s.size)}),e.jsx("span",{children:s.date?K(s.date,s.time):""})]})]})]},`${t}-${d}-${s.id}`)})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4",onClick:()=>w(null),children:e.jsxs("div",{className:"max-w-6xl w-full bg-surface-elevated rounded-lg overflow-hidden",onClick:s=>s.stopPropagation(),children:[e.jsxs("div",{className:"p-4 border-b border-surface flex justify-between items-center",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:o.filename}),e.jsxs("p",{className:"text-sm text-gray-400",children:[I(o.size)," ",o.date&&`- ${K(o.date,o.time)}`]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("a",{href:k(o.path),download:o.filename,className:"px-3 py-1 bg-primary hover:bg-primary-hover rounded text-sm",onClick:s=>s.stopPropagation(),children:"Download"}),e.jsx("button",{onClick:s=>{s.stopPropagation(),C(o)},className:"px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm",children:"Delete"}),e.jsx("button",{onClick:()=>w(null),className:"px-3 py-1 bg-surface hover:bg-surface-elevated rounded text-sm",children:"Close"})]})]}),e.jsx("div",{className:"p-4",children:("type"in o?o.type:i)==="picture"?e.jsx("img",{src:k(o.path),alt:o.filename,className:"w-full h-auto max-h-[70vh] object-contain"}):e.jsx("video",{src:k(o.path),controls:!0,className:"w-full h-auto max-h-[70vh]",autoPlay:!0,children:"Your browser does not support video playback."})})]})}),p&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:V,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2",children:"Delete File?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsx("span",{className:"font-medium text-white",children:p.filename}),"? This action cannot be undone."]}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:V,disabled:S,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:te,disabled:S,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:S?"Deleting...":"Delete"})]})]})})}),j&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:U,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2 text-red-400",children:"Delete All Media Files?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsxs("span",{className:"font-medium text-white",children:["all ",j.fileCount," media files"]})," in folder ",e.jsx("span",{className:"font-mono text-primary",children:j.path||"root"}),"?"]}),e.jsx("p",{className:"text-sm text-yellow-500 mb-4",children:"This will delete movies, pictures, and their thumbnails. Subfolders will NOT be deleted. This action cannot be undone."}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:U,disabled:_,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:ae,disabled:_,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:_?"Deleting...":"Delete All"})]})]})})})]})}export{be as Media}; +import{j as e,a as ie,c as ne,k as re,r as n,q as R,b as oe,s as ce,v as de,w as ue,x as me,y as xe,z as pe,m as he}from"./index-tiawrtsp.js";function O({offset:a,limit:c,total:r,onPageChange:f,context:l}){const v=Math.floor(a/c)+1,i=Math.ceil(r/c),b=r===0?0:a+1,t=Math.min(a+c,r),x=a>0,d=a+c1&&e.jsxs("div",{className:"flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>f(Math.max(0,a-c)),disabled:!x,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Previous page",children:"◀ Previous"}),e.jsxs("span",{className:"px-3 py-1 text-sm text-gray-400",children:["Page ",v," of ",i]}),e.jsx("button",{onClick:()=>f(a+c),disabled:!d,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Next page",children:"Next ▶"})]})]})}function k(a){const c=he();if(!c)return a;const r=a.includes("?")?"&":"?";return`${a}${r}token=${encodeURIComponent(c)}`}const g=100;function fe(a,c){if(!a||a.length!==8)return null;const r=parseInt(a.substring(0,4),10),f=parseInt(a.substring(4,6),10)-1,l=parseInt(a.substring(6,8),10);if(isNaN(r)||isNaN(f)||isNaN(l))return null;let v=0,i=0,b=0;if(c){const x=c.split(":");x.length>=2&&(v=parseInt(x[0],10)||0,i=parseInt(x[1],10)||0,b=parseInt(x[2],10)||0)}const t=new Date(r,f,l,v,i,b);return isNaN(t.getTime())?null:t}function be(){const{addToast:a}=ie(),{role:c}=ne(),r=re(),f=c==="admin",[l,v]=n.useState(1),[i,b]=n.useState("pictures"),[t,x]=n.useState("all"),[d,P]=n.useState(""),[Y,y]=n.useState(0),[o,w]=n.useState(null),[p,C]=n.useState(null),[j,A]=n.useState(null),N=Y*g;n.useEffect(()=>{y(0)},[l,i,d]),n.useEffect(()=>{t==="all"&&P("")},[t]),n.useEffect(()=>{t==="all"&&(r.invalidateQueries({queryKey:R.movies(l)}),r.invalidateQueries({queryKey:R.pictures(l)}))},[t,l,r]),n.useEffect(()=>{t==="folders"&&r.invalidateQueries({predicate:s=>Array.isArray(s.queryKey)&&s.queryKey[0]==="media-folders"&&s.queryKey[1]===l})},[t,d,l,r]);const{data:Z}=oe(),{data:B,isLoading:J}=ce(l,N,g,null,{enabled:t==="all"&&i==="pictures"}),{data:E,isLoading:X}=de(l,N,g,null,{enabled:t==="all"&&i==="movies"}),{data:u,isLoading:ee}=ue(l,d,N,g,{enabled:t==="folders"}),$=me(),L=xe(),F=pe(),T=t==="folders"?ee:i==="pictures"?J:X,q=t==="folders"?u?.files??[]:i==="pictures"?B?.pictures??[]:E?.movies??[],D=t==="folders"?u?.total_files??0:i==="pictures"?B?.total_count??0:E?.total_count??0,I=s=>s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:s<1024*1024*1024?`${(s/(1024*1024)).toFixed(1)} MB`:`${(s/(1024*1024*1024)).toFixed(1)} GB`,K=(s,m)=>{const h=fe(s,m);return h?h.toLocaleDateString()+" "+h.toLocaleTimeString():"Unknown date"},se=n.useCallback((s,m)=>{m.stopPropagation(),C(s)},[]),te=n.useCallback(async()=>{if(p)try{const s="type"in p?p.type:i;s==="picture"||s==="pictures"?await $.mutateAsync({camId:l,pictureId:p.id}):await L.mutateAsync({camId:l,movieId:p.id}),a(`${s==="picture"||s==="pictures"?"Picture":"Movie"} deleted`,"success"),C(null),o?.id===p.id&&w(null)}catch{a("Failed to delete file","error")}},[p,i,l,$,L,a,o]),V=n.useCallback(()=>{C(null)},[]),W=n.useCallback((s,m)=>{A({path:s,fileCount:m})},[]),ae=n.useCallback(async()=>{if(j)try{const s=await F.mutateAsync({camId:l,path:j.path});a(`Deleted ${s.deleted.movies} movies, ${s.deleted.pictures} pictures`,"success"),A(null)}catch{a("Failed to delete folder contents","error")}},[j,l,F,a]),U=n.useCallback(()=>{A(null)},[]),z=n.useCallback(s=>{P(s),y(0)},[]),le=n.useCallback(()=>{u?.parent!==null&&(P(u?.parent??""),y(0))},[u]),S=$.isPending||L.isPending,_=F.isPending,G=d?d.split("/"):[];return e.jsxs("div",{className:"p-6",children:[e.jsx("div",{className:"flex items-center justify-between mb-6",children:e.jsx("h2",{className:"text-3xl font-bold",children:"Media"})}),e.jsxs("div",{className:"flex flex-wrap gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"camera-select",className:"block text-sm font-medium mb-2",children:"Camera"}),e.jsx("select",{id:"camera-select",value:l,onChange:s=>v(parseInt(s.target.value)),className:"px-3 py-2 bg-surface border border-surface-elevated rounded-lg",children:Z?.map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"Type"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>b("pictures"),className:`px-4 py-2 rounded-lg transition-colors ${i==="pictures"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Pictures"}),e.jsx("button",{onClick:()=>b("movies"),className:`px-4 py-2 rounded-lg transition-colors ${i==="movies"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Movies"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"View"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>x("all"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="all"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"All"}),e.jsx("button",{onClick:()=>x("folders"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="folders"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Folders"})]})]})]}),t==="folders"&&e.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3",children:[e.jsx("svg",{className:"w-5 h-5 text-gray-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"})}),e.jsx("span",{className:"text-sm font-medium text-gray-300",children:"Browse Folders"})]}),e.jsxs("div",{className:"flex items-center gap-1 text-sm flex-wrap",children:[e.jsx("button",{onClick:()=>z(""),className:"hover:text-primary px-1",children:"Root"}),G.map((s,m)=>{const h=G.slice(0,m+1).join("/");return e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("button",{onClick:()=>z(h),className:"hover:text-primary px-1",children:s})]},m)})]}),u?.parent!==null&&e.jsxs("button",{onClick:le,className:"mt-2 flex items-center gap-2 text-sm text-gray-400 hover:text-white",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 19l-7-7 7-7"})}),"Up to parent folder"]})]}),t==="folders"&&u&&u.folders.length>0&&e.jsx("div",{className:"mb-6 grid gap-2 md:grid-cols-2 lg:grid-cols-4",children:u.folders.map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 hover:ring-2 hover:ring-primary cursor-pointer transition-all group",children:[e.jsx("button",{onClick:()=>z(s.path),className:"w-full text-left",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("svg",{className:"w-8 h-8 text-yellow-500 flex-shrink-0",fill:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{d:"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"font-medium truncate",children:s.name}),e.jsxs("p",{className:"text-xs text-gray-400",children:[s.file_count," files - ",I(s.total_size)]})]})]})}),f&&s.file_count>0&&e.jsx("button",{onClick:m=>{m.stopPropagation(),W(s.path,s.file_count)},className:"mt-2 w-full px-2 py-1 text-xs bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded opacity-0 group-hover:opacity-100 transition-opacity",title:`Delete all ${s.file_count} media files in this folder`,children:"Delete All Media"})]},s.path))}),t==="folders"&&f&&d&&u&&u.total_files>0&&e.jsx("div",{className:"mb-4 flex justify-end",children:e.jsxs("button",{onClick:()=>W(d,u.total_files),className:"px-3 py-1.5 text-sm bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded-lg flex items-center gap-2",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})}),"Delete All Media in This Folder (",u.total_files,")"]})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),T?e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:[1,2,3,4,5,6,7,8].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg animate-pulse",children:[e.jsx("div",{className:"aspect-video bg-surface rounded-t-lg"}),e.jsxs("div",{className:"p-3",children:[e.jsx("div",{className:"h-4 bg-surface rounded w-3/4 mb-2"}),e.jsx("div",{className:"h-3 bg-surface rounded w-1/2"})]})]},s))}):q.length===0?e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center",children:[e.jsx("p",{className:"text-gray-400",children:t==="folders"?"No media files in this folder":`No ${i} found`}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:t==="folders"?"Navigate to a folder with media files":i==="pictures"?"Motion detection snapshots will appear here":"Recorded videos will appear here"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:q.map(s=>{const m="type"in s?s.type:i==="pictures"?"picture":"movie",h=s.thumbnail||void 0;return e.jsxs("button",{className:"bg-surface-elevated rounded-lg overflow-hidden cursor-pointer hover:ring-2 hover:ring-primary focus:ring-2 focus:ring-primary focus:outline-none transition-all group relative text-left w-full",onClick:()=>w(s),"aria-label":`View ${s.filename}`,children:[e.jsx("button",{onClick:M=>se(s,M),className:"absolute top-2 right-2 z-10 p-1.5 bg-red-600/80 hover:bg-red-600 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity",title:"Delete","aria-label":`Delete ${s.filename}`,children:e.jsx("svg",{className:"w-4 h-4 text-white",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})})}),e.jsxs("div",{className:"aspect-video bg-surface flex items-center justify-center relative overflow-hidden",children:[m==="picture"?e.jsx("img",{src:k(s.path),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy"}):h?e.jsx("img",{src:k(h),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy",onError:M=>{M.currentTarget.style.display="none";const H=M.currentTarget.parentElement;if(H){const Q=H.querySelector(".fallback-icon");Q&&(Q.style.display="flex")}}}):null,e.jsx("div",{className:`fallback-icon text-gray-400 absolute inset-0 flex items-center justify-center ${h?"hidden":""}`,children:e.jsxs("svg",{className:"w-16 h-16",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]})})]}),e.jsxs("div",{className:"p-3",children:[e.jsx("p",{className:"text-sm font-medium truncate",children:s.filename}),e.jsxs("div",{className:"flex justify-between text-xs text-gray-400 mt-1",children:[e.jsx("span",{children:I(s.size)}),e.jsx("span",{children:s.date?K(s.date,s.time):""})]})]})]},`${t}-${d}-${s.id}`)})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4",onClick:()=>w(null),children:e.jsxs("div",{className:"max-w-6xl w-full bg-surface-elevated rounded-lg overflow-hidden",onClick:s=>s.stopPropagation(),children:[e.jsxs("div",{className:"p-4 border-b border-surface flex justify-between items-center",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:o.filename}),e.jsxs("p",{className:"text-sm text-gray-400",children:[I(o.size)," ",o.date&&`- ${K(o.date,o.time)}`]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("a",{href:k(o.path),download:o.filename,className:"px-3 py-1 bg-primary hover:bg-primary-hover rounded text-sm",onClick:s=>s.stopPropagation(),children:"Download"}),e.jsx("button",{onClick:s=>{s.stopPropagation(),C(o)},className:"px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm",children:"Delete"}),e.jsx("button",{onClick:()=>w(null),className:"px-3 py-1 bg-surface hover:bg-surface-elevated rounded text-sm",children:"Close"})]})]}),e.jsx("div",{className:"p-4",children:("type"in o?o.type:i)==="picture"?e.jsx("img",{src:k(o.path),alt:o.filename,className:"w-full h-auto max-h-[70vh] object-contain"}):e.jsx("video",{src:k(o.path),controls:!0,className:"w-full h-auto max-h-[70vh]",autoPlay:!0,children:"Your browser does not support video playback."})})]})}),p&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:V,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2",children:"Delete File?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsx("span",{className:"font-medium text-white",children:p.filename}),"? This action cannot be undone."]}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:V,disabled:S,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:te,disabled:S,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:S?"Deleting...":"Delete"})]})]})})}),j&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:U,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2 text-red-400",children:"Delete All Media Files?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsxs("span",{className:"font-medium text-white",children:["all ",j.fileCount," media files"]})," in folder ",e.jsx("span",{className:"font-mono text-primary",children:j.path||"root"}),"?"]}),e.jsx("p",{className:"text-sm text-yellow-500 mb-4",children:"This will delete movies, pictures, and their thumbnails. Subfolders will NOT be deleted. This action cannot be undone."}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:U,disabled:_,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:ae,disabled:_,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:_?"Deleting...":"Delete All"})]})]})})})]})}export{be as Media}; diff --git a/data/webui/assets/Settings-Ca5UNPDy.js b/data/webui/assets/Settings-Ca5UNPDy.js deleted file mode 100644 index dde79aee..00000000 --- a/data/webui/assets/Settings-Ca5UNPDy.js +++ /dev/null @@ -1,22 +0,0 @@ -import{r as x,j as s,h as Ce,a as Be,e as qe,i as Rt,k as rt,l as fn,m as gn,f as Ft,c as bn,u as xn,n as vn,o as _n,g as yn}from"./index-DCzq8GhF.js";import{c as A,b as B,R as st,f as ot,F as T,g as wn,h as jn,A as Sn,d as Nn,i as kn,j as Cn,m as at,k as it,l as Tn,p as Pn,L as zn,a as $n,n as Mn,o as On,q as In,r as te,s as Ie,E as An,t as En,M as ct,u as Zn,C as Dn,v as Rn,w as Fn}from"./parameterMappings-CUuSfEkB.js";function S({label:e,value:t,onChange:n,type:r="text",placeholder:o,disabled:a=!1,required:i=!1,helpText:c,error:l,min:u,max:d,step:h,originalValue:f,showVisibilityToggle:p}){const[v,b]=x.useState(!1),w=oe=>{n(oe.target.value)},g=!!l,k=f!==void 0&&String(t)!==String(f),V=r==="password",q=p??V,O=V&&v?"text":r;return s.jsxs("div",{className:"mb-4",children:[s.jsxs("label",{className:"block text-sm font-medium mb-1",children:[e,i&&s.jsx("span",{className:"text-red-500 ml-1",children:"*"}),k&&s.jsx("span",{className:"ml-2 text-xs text-yellow-400",children:"(modified)"})]}),s.jsxs("div",{className:"relative",children:[s.jsx("input",{type:O,value:t,onChange:w,placeholder:o,disabled:a,required:i,min:u,max:d,step:h,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${q?"pr-10":""} ${g?"border-red-500 focus:ring-red-500":k?"border-yellow-500/50 focus:ring-yellow-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":g,"aria-describedby":g?`${e}-error`:void 0}),q&&s.jsx("button",{type:"button",onClick:()=>b(!v),className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-200 transition-colors","aria-label":v?"Hide password":"Show password",children:v?s.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"})}):s.jsxs("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})]})})]}),g&&s.jsx("p",{id:`${e}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:l}),c&&!g&&s.jsx("p",{className:"mt-1 text-sm text-gray-400",children:c})]})}function E({title:e,description:t,children:n,collapsible:r=!1,defaultOpen:o=!0}){const[a,i]=x.useState(o),c=()=>{r&&i(!a)};return s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-6 mb-6",children:[s.jsxs("div",{className:`flex items-center justify-between ${r?"cursor-pointer":""}`,onClick:c,children:[s.jsxs("div",{children:[s.jsx("h3",{className:"text-lg font-semibold",children:e}),t&&s.jsx("p",{className:"text-sm text-gray-400 mt-1",children:t})]}),r&&s.jsx("svg",{className:`w-5 h-5 transition-transform ${a?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),a&&s.jsx("div",{className:"mt-4",children:n})]})}function m(e,t,n){function r(c,l){if(c._zod||Object.defineProperty(c,"_zod",{value:{def:l,constr:i,traits:new Set},enumerable:!1}),c._zod.traits.has(e))return;c._zod.traits.add(e),t(c,l);const u=i.prototype,d=Object.keys(u);for(let h=0;hn?.Parent&&c instanceof n.Parent?!0:c?._zod?.traits?.has(e)}),Object.defineProperty(i,"name",{value:e}),i}class le extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class Ut extends Error{constructor(t){super(`Encountered unidirectional transform during encode: ${t}`),this.name="ZodEncodeError"}}const Un={};function re(e){return Un}function Ue(e,t){return typeof t=="bigint"?t.toString():t}function Je(e){return e==null}function Ge(e){const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length;return e.slice(t,n)}function Ln(e,t){const n=(e.toString().split(".")[1]||"").length,r=t.toString();let o=(r.split(".")[1]||"").length;if(o===0&&/\d?e-\d?/.test(r)){const l=r.match(/\d?e-(\d?)/);l?.[1]&&(o=Number.parseInt(l[1]))}const a=n>o?n:o,i=Number.parseInt(e.toFixed(a).replace(".","")),c=Number.parseInt(t.toFixed(a).replace(".",""));return i%c/10**a}const lt=Symbol("evaluating");function P(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==lt)return r===void 0&&(r=lt,r=n()),r},set(o){Object.defineProperty(e,t,{value:o})},configurable:!0})}function Hn(...e){const t={};for(const n of e){const r=Object.getOwnPropertyDescriptors(n);Object.assign(t,r)}return Object.defineProperties({},t)}function Vn(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const Lt="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function ut(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Le(e){if(ut(e)===!1)return!1;const t=e.constructor;if(t===void 0||typeof t!="function")return!0;const n=t.prototype;return!(ut(n)===!1||Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")===!1)}function Ht(e){return Le(e)?{...e}:Array.isArray(e)?[...e]:e}function Ke(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Yn(e,t,n){const r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function _(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}const Wn={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ce(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var r;return(r=n).path??(r.path=[]),n.path.unshift(e),n})}function ve(e){return typeof e=="string"?e:e?.message}function se(e,t,n){const r={...e,path:e.path??[]};if(!e.message){const o=ve(e.inst?._zod.def?.error?.(e))??ve(t?.error?.(e))??ve(n.customError?.(e))??ve(n.localeError?.(e))??"Invalid input";r.message=o}return delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function Qe(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function pe(...e){const[t,n,r]=e;return typeof t=="string"?{message:t,code:"custom",input:n,inst:r}:{...t}}const Vt=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),e.message=JSON.stringify(t,Ue,2),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},Yt=m("$ZodError",Vt),Wt=m("$ZodError",Vt,{Parent:Error});function qn(e,t=n=>n.message){const n={},r=[];for(const o of e.issues)o.path.length>0?(n[o.path[0]]=n[o.path[0]]||[],n[o.path[0]].push(t(o))):r.push(t(o));return{formErrors:r,fieldErrors:n}}function Jn(e,t=n=>n.message){const n={_errors:[]},r=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(i=>r({issues:i}));else if(a.code==="invalid_key")r({issues:a.issues});else if(a.code==="invalid_element")r({issues:a.issues});else if(a.path.length===0)n._errors.push(t(a));else{let i=n,c=0;for(;c(t,n,r,o)=>{const a=r?Object.assign(r,{async:!1}):{async:!1},i=t._zod.run({value:n,issues:[]},a);if(i instanceof Promise)throw new le;if(i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>se(l,a,re())));throw Lt(c,o?.callee),c}return i.value},et=e=>async(t,n,r,o)=>{const a=r?Object.assign(r,{async:!0}):{async:!0};let i=t._zod.run({value:n,issues:[]},a);if(i instanceof Promise&&(i=await i),i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>se(l,a,re())));throw Lt(c,o?.callee),c}return i.value},Te=e=>(t,n,r)=>{const o=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise)throw new le;return a.issues.length?{success:!1,error:new(e??Yt)(a.issues.map(i=>se(i,o,re())))}:{success:!0,data:a.value}},Gn=Te(Wt),Pe=e=>async(t,n,r)=>{const o=r?Object.assign(r,{async:!0}):{async:!0};let a=t._zod.run({value:n,issues:[]},o);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(i=>se(i,o,re())))}:{success:!0,data:a.value}},Kn=Pe(Wt),Qn=e=>(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Xe(e)(t,n,o)},Xn=e=>(t,n,r)=>Xe(e)(t,n,r),er=e=>async(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return et(e)(t,n,o)},tr=e=>async(t,n,r)=>et(e)(t,n,r),nr=e=>(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Te(e)(t,n,o)},rr=e=>(t,n,r)=>Te(e)(t,n,r),sr=e=>async(t,n,r)=>{const o=r?Object.assign(r,{direction:"backward"}):{direction:"backward"};return Pe(e)(t,n,o)},or=e=>async(t,n,r)=>Pe(e)(t,n,r),ar=/^[cC][^\s-]{8,}$/,ir=/^[0-9a-z]+$/,cr=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,lr=/^[0-9a-vA-V]{20}$/,ur=/^[A-Za-z0-9]{27}$/,dr=/^[a-zA-Z0-9_-]{21}$/,mr=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,hr=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,dt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,pr=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,fr="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function gr(){return new RegExp(fr,"u")}const br=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,xr=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,vr=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,_r=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,yr=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,Bt=/^[A-Za-z0-9_-]*$/,wr=/^\+(?:[0-9]){6,14}[0-9]$/,qt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",jr=new RegExp(`^${qt}$`);function Jt(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function Sr(e){return new RegExp(`^${Jt(e)}$`)}function Nr(e){const t=Jt({precision:e.precision}),n=["Z"];e.local&&n.push(""),e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const r=`${t}(?:${n.join("|")})`;return new RegExp(`^${qt}T(?:${r})$`)}const kr=e=>{const t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},Cr=/^-?\d+$/,Tr=/^-?\d+(?:\.\d+)?/,Pr=/^[^A-Z]*$/,zr=/^[^a-z]*$/,H=m("$ZodCheck",(e,t)=>{var n;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),Gt={number:"number",bigint:"bigint",object:"date"},Kt=m("$ZodCheckLessThan",(e,t)=>{H.init(e,t);const n=Gt[typeof t.value];e._zod.onattach.push(r=>{const o=r._zod.bag,a=(t.inclusive?o.maximum:o.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?r.value<=t.value:r.value{H.init(e,t);const n=Gt[typeof t.value];e._zod.onattach.push(r=>{const o=r._zod.bag,a=(t.inclusive?o.minimum:o.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>a&&(t.inclusive?o.minimum=t.value:o.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:"too_small",minimum:t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),$r=m("$ZodCheckMultipleOf",(e,t)=>{H.init(e,t),e._zod.onattach.push(n=>{var r;(r=n._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof n.value=="bigint"?n.value%t.value===BigInt(0):Ln(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),Mr=m("$ZodCheckNumberFormat",(e,t)=>{H.init(e,t),t.format=t.format||"float64";const n=t.format?.includes("int"),r=n?"int":"number",[o,a]=Wn[t.format];e._zod.onattach.push(i=>{const c=i._zod.bag;c.format=t.format,c.minimum=o,c.maximum=a,n&&(c.pattern=Cr)}),e._zod.check=i=>{const c=i.value;if(n){if(!Number.isInteger(c)){i.issues.push({expected:r,format:t.format,code:"invalid_type",continue:!1,input:c,inst:e});return}if(!Number.isSafeInteger(c)){c>0?i.issues.push({input:c,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort}):i.issues.push({input:c,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort});return}}ca&&i.issues.push({origin:"number",input:c,code:"too_big",maximum:a,inst:e})}}),Or=m("$ZodCheckMaxLength",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const o=r.value;if(o.length<=t.maximum)return;const i=Qe(o);r.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),Ir=m("$ZodCheckMinLength",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>o&&(r._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{const o=r.value;if(o.length>=t.minimum)return;const i=Qe(o);r.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),Ar=m("$ZodCheckLengthEquals",(e,t)=>{var n;H.init(e,t),(n=e._zod.def).when??(n.when=r=>{const o=r.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(r=>{const o=r._zod.bag;o.minimum=t.length,o.maximum=t.length,o.length=t.length}),e._zod.check=r=>{const o=r.value,a=o.length;if(a===t.length)return;const i=Qe(o),c=a>t.length;r.issues.push({origin:i,...c?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),ze=m("$ZodCheckStringFormat",(e,t)=>{var n,r;H.init(e,t),e._zod.onattach.push(o=>{const a=o._zod.bag;a.format=t.format,t.pattern&&(a.patterns??(a.patterns=new Set),a.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=o=>{t.pattern.lastIndex=0,!t.pattern.test(o.value)&&o.issues.push({origin:"string",code:"invalid_format",format:t.format,input:o.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),Er=m("$ZodCheckRegex",(e,t)=>{ze.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:"string",code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),Zr=m("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=Pr),ze.init(e,t)}),Dr=m("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=zr),ze.init(e,t)}),Rr=m("$ZodCheckIncludes",(e,t)=>{H.init(e,t);const n=Ke(t.includes),r=new RegExp(typeof t.position=="number"?`^.{${t.position}}${n}`:n);t.pattern=r,e._zod.onattach.push(o=>{const a=o._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(r)}),e._zod.check=o=>{o.value.includes(t.includes,t.position)||o.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:o.value,inst:e,continue:!t.abort})}}),Fr=m("$ZodCheckStartsWith",(e,t)=>{H.init(e,t);const n=new RegExp(`^${Ke(t.prefix)}.*`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const o=r._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(n)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),Ur=m("$ZodCheckEndsWith",(e,t)=>{H.init(e,t);const n=new RegExp(`.*${Ke(t.suffix)}$`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const o=r._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(n)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),Lr=m("$ZodCheckOverwrite",(e,t)=>{H.init(e,t),e._zod.check=n=>{n.value=t.tx(n.value)}}),Hr={major:4,minor:2,patch:1},Z=m("$ZodType",(e,t)=>{var n;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=Hr;const r=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&r.unshift(e);for(const o of r)for(const a of o._zod.onattach)a(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{const o=(i,c,l)=>{let u=ce(i),d;for(const h of c){if(h._zod.def.when){if(!h._zod.def.when(i))continue}else if(u)continue;const f=i.issues.length,p=h._zod.check(i);if(p instanceof Promise&&l?.async===!1)throw new le;if(d||p instanceof Promise)d=(d??Promise.resolve()).then(async()=>{await p,i.issues.length!==f&&(u||(u=ce(i,f)))});else{if(i.issues.length===f)continue;u||(u=ce(i,f))}}return d?d.then(()=>i):i},a=(i,c,l)=>{if(ce(i))return i.aborted=!0,i;const u=o(c,r,l);if(u instanceof Promise){if(l.async===!1)throw new le;return u.then(d=>e._zod.parse(d,l))}return e._zod.parse(u,l)};e._zod.run=(i,c)=>{if(c.skipChecks)return e._zod.parse(i,c);if(c.direction==="backward"){const u=e._zod.parse({value:i.value,issues:[]},{...c,skipChecks:!0});return u instanceof Promise?u.then(d=>a(d,i,c)):a(u,i,c)}const l=e._zod.parse(i,c);if(l instanceof Promise){if(c.async===!1)throw new le;return l.then(u=>o(u,r,c))}return o(l,r,c)}}e["~standard"]={validate:o=>{try{const a=Gn(e,o);return a.success?{value:a.data}:{issues:a.error?.issues}}catch{return Kn(e,o).then(i=>i.success?{value:i.data}:{issues:i.error?.issues})}},vendor:"zod",version:1}}),tt=m("$ZodString",(e,t)=>{Z.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??kr(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value=="string"||n.issues.push({expected:"string",code:"invalid_type",input:n.value,inst:e}),n}}),z=m("$ZodStringFormat",(e,t)=>{ze.init(e,t),tt.init(e,t)}),Vr=m("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=hr),z.init(e,t)}),Yr=m("$ZodUUID",(e,t)=>{if(t.version){const r={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(r===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=dt(r))}else t.pattern??(t.pattern=dt());z.init(e,t)}),Wr=m("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=pr),z.init(e,t)}),Br=m("$ZodURL",(e,t)=>{z.init(e,t),e._zod.check=n=>{try{const r=n.value.trim(),o=new URL(r);t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(o.hostname)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:t.hostname.source,input:n.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(o.protocol.endsWith(":")?o.protocol.slice(0,-1):o.protocol)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:n.value,inst:e,continue:!t.abort})),t.normalize?n.value=o.href:n.value=r;return}catch{n.issues.push({code:"invalid_format",format:"url",input:n.value,inst:e,continue:!t.abort})}}}),qr=m("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=gr()),z.init(e,t)}),Jr=m("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=dr),z.init(e,t)}),Gr=m("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=ar),z.init(e,t)}),Kr=m("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=ir),z.init(e,t)}),Qr=m("$ZodULID",(e,t)=>{t.pattern??(t.pattern=cr),z.init(e,t)}),Xr=m("$ZodXID",(e,t)=>{t.pattern??(t.pattern=lr),z.init(e,t)}),es=m("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=ur),z.init(e,t)}),ts=m("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=Nr(t)),z.init(e,t)}),ns=m("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=jr),z.init(e,t)}),rs=m("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=Sr(t)),z.init(e,t)}),ss=m("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=mr),z.init(e,t)}),os=m("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=br),z.init(e,t),e._zod.bag.format="ipv4"}),as=m("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=xr),z.init(e,t),e._zod.bag.format="ipv6",e._zod.check=n=>{try{new URL(`http://[${n.value}]`)}catch{n.issues.push({code:"invalid_format",format:"ipv6",input:n.value,inst:e,continue:!t.abort})}}}),is=m("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=vr),z.init(e,t)}),cs=m("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=_r),z.init(e,t),e._zod.check=n=>{const r=n.value.split("/");try{if(r.length!==2)throw new Error;const[o,a]=r;if(!a)throw new Error;const i=Number(a);if(`${i}`!==a)throw new Error;if(i<0||i>128)throw new Error;new URL(`http://[${o}]`)}catch{n.issues.push({code:"invalid_format",format:"cidrv6",input:n.value,inst:e,continue:!t.abort})}}});function Xt(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const ls=m("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=yr),z.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=n=>{Xt(n.value)||n.issues.push({code:"invalid_format",format:"base64",input:n.value,inst:e,continue:!t.abort})}});function us(e){if(!Bt.test(e))return!1;const t=e.replace(/[-_]/g,r=>r==="-"?"+":"/"),n=t.padEnd(Math.ceil(t.length/4)*4,"=");return Xt(n)}const ds=m("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=Bt),z.init(e,t),e._zod.bag.contentEncoding="base64url",e._zod.check=n=>{us(n.value)||n.issues.push({code:"invalid_format",format:"base64url",input:n.value,inst:e,continue:!t.abort})}}),ms=m("$ZodE164",(e,t)=>{t.pattern??(t.pattern=wr),z.init(e,t)});function hs(e,t=null){try{const n=e.split(".");if(n.length!==3)return!1;const[r]=n;if(!r)return!1;const o=JSON.parse(atob(r));return!("typ"in o&&o?.typ!=="JWT"||!o.alg||t&&(!("alg"in o)||o.alg!==t))}catch{return!1}}const ps=m("$ZodJWT",(e,t)=>{z.init(e,t),e._zod.check=n=>{hs(n.value,t.alg)||n.issues.push({code:"invalid_format",format:"jwt",input:n.value,inst:e,continue:!t.abort})}}),en=m("$ZodNumber",(e,t)=>{Z.init(e,t),e._zod.pattern=e._zod.bag.pattern??Tr,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}const o=n.value;if(typeof o=="number"&&!Number.isNaN(o)&&Number.isFinite(o))return n;const a=typeof o=="number"?Number.isNaN(o)?"NaN":Number.isFinite(o)?void 0:"Infinity":void 0;return n.issues.push({expected:"number",code:"invalid_type",input:o,inst:e,...a?{received:a}:{}}),n}}),fs=m("$ZodNumberFormat",(e,t)=>{Mr.init(e,t),en.init(e,t)});function mt(e,t,n){e.issues.length&&t.issues.push(...Bn(n,e.issues)),t.value[n]=e.value}const gs=m("$ZodArray",(e,t)=>{Z.init(e,t),e._zod.parse=(n,r)=>{const o=n.value;if(!Array.isArray(o))return n.issues.push({expected:"array",code:"invalid_type",input:o,inst:e}),n;n.value=Array(o.length);const a=[];for(let i=0;imt(u,n,i))):mt(l,n,i)}return a.length?Promise.all(a).then(()=>n):n}});function ht(e,t,n,r){for(const a of e)if(a.issues.length===0)return t.value=a.value,t;const o=e.filter(a=>!ce(a));return o.length===1?(t.value=o[0].value,o[0]):(t.issues.push({code:"invalid_union",input:t.value,inst:n,errors:e.map(a=>a.issues.map(i=>se(i,r,re())))}),t)}const bs=m("$ZodUnion",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.options.some(o=>o._zod.optin==="optional")?"optional":void 0),P(e._zod,"optout",()=>t.options.some(o=>o._zod.optout==="optional")?"optional":void 0),P(e._zod,"values",()=>{if(t.options.every(o=>o._zod.values))return new Set(t.options.flatMap(o=>Array.from(o._zod.values)))}),P(e._zod,"pattern",()=>{if(t.options.every(o=>o._zod.pattern)){const o=t.options.map(a=>a._zod.pattern);return new RegExp(`^(${o.map(a=>Ge(a.source)).join("|")})$`)}});const n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(o,a)=>{if(n)return r(o,a);let i=!1;const c=[];for(const l of t.options){const u=l._zod.run({value:o.value,issues:[]},a);if(u instanceof Promise)c.push(u),i=!0;else{if(u.issues.length===0)return u;c.push(u)}}return i?Promise.all(c).then(l=>ht(l,o,e,a)):ht(c,o,e,a)}}),xs=m("$ZodIntersection",(e,t)=>{Z.init(e,t),e._zod.parse=(n,r)=>{const o=n.value,a=t.left._zod.run({value:o,issues:[]},r),i=t.right._zod.run({value:o,issues:[]},r);return a instanceof Promise||i instanceof Promise?Promise.all([a,i]).then(([l,u])=>pt(n,l,u)):pt(n,a,i)}});function He(e,t){if(e===t)return{valid:!0,data:e};if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Le(e)&&Le(t)){const n=Object.keys(t),r=Object.keys(e).filter(a=>n.indexOf(a)!==-1),o={...e,...t};for(const a of r){const i=He(e[a],t[a]);if(!i.valid)return{valid:!1,mergeErrorPath:[a,...i.mergeErrorPath]};o[a]=i.data}return{valid:!0,data:o}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};const n=[];for(let r=0;r{Z.init(e,t),e._zod.parse=(n,r)=>{if(r.direction==="backward")throw new Ut(e.constructor.name);const o=t.transform(n.value,n);if(r.async)return(o instanceof Promise?o:Promise.resolve(o)).then(i=>(n.value=i,n));if(o instanceof Promise)throw new le;return n.value=o,n}});function ft(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const _s=m("$ZodOptional",(e,t)=>{Z.init(e,t),e._zod.optin="optional",e._zod.optout="optional",P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),P(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)})?$`):void 0}),e._zod.parse=(n,r)=>{if(t.innerType._zod.optin==="optional"){const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>ft(a,n.value)):ft(o,n.value)}return n.value===void 0?n:t.innerType._zod.run(n,r)}}),ys=m("$ZodNullable",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)}|null)$`):void 0}),P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(n,r)=>n.value===null?n:t.innerType._zod.run(n,r)}),ws=m("$ZodDefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);if(n.value===void 0)return n.value=t.defaultValue,n;const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>gt(a,t)):gt(o,t)}});function gt(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}const js=m("$ZodPrefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>(r.direction==="backward"||n.value===void 0&&(n.value=t.defaultValue),t.innerType._zod.run(n,r))}),Ss=m("$ZodNonOptional",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>{const n=t.innerType._zod.values;return n?new Set([...n].filter(r=>r!==void 0)):void 0}),e._zod.parse=(n,r)=>{const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>bt(a,e)):bt(o,e)}});function bt(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}const Ns=m("$ZodCatch",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(a=>(n.value=a.value,a.issues.length&&(n.value=t.catchValue({...n,error:{issues:a.issues.map(i=>se(i,r,re()))},input:n.value}),n.issues=[]),n)):(n.value=o.value,o.issues.length&&(n.value=t.catchValue({...n,error:{issues:o.issues.map(a=>se(a,r,re()))},input:n.value}),n.issues=[]),n)}}),ks=m("$ZodPipe",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>t.in._zod.values),P(e._zod,"optin",()=>t.in._zod.optin),P(e._zod,"optout",()=>t.out._zod.optout),P(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(n,r)=>{if(r.direction==="backward"){const a=t.out._zod.run(n,r);return a instanceof Promise?a.then(i=>_e(i,t.in,r)):_e(a,t.in,r)}const o=t.in._zod.run(n,r);return o instanceof Promise?o.then(a=>_e(a,t.out,r)):_e(o,t.out,r)}});function _e(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}const Cs=m("$ZodReadonly",(e,t)=>{Z.init(e,t),P(e._zod,"propValues",()=>t.innerType._zod.propValues),P(e._zod,"values",()=>t.innerType._zod.values),P(e._zod,"optin",()=>t.innerType?._zod?.optin),P(e._zod,"optout",()=>t.innerType?._zod?.optout),e._zod.parse=(n,r)=>{if(r.direction==="backward")return t.innerType._zod.run(n,r);const o=t.innerType._zod.run(n,r);return o instanceof Promise?o.then(xt):xt(o)}});function xt(e){return e.value=Object.freeze(e.value),e}const Ts=m("$ZodCustom",(e,t)=>{H.init(e,t),Z.init(e,t),e._zod.parse=(n,r)=>n,e._zod.check=n=>{const r=n.value,o=t.fn(r);if(o instanceof Promise)return o.then(a=>vt(a,n,r,e));vt(o,n,r,e)}});function vt(e,t,n,r){if(!e){const o={code:"custom",input:n,inst:r,path:[...r._zod.def.path??[]],continue:!r._zod.def.abort};r._zod.def.params&&(o.params=r._zod.def.params),t.issues.push(pe(o))}}var _t;class Ps{constructor(){this._map=new WeakMap,this._idmap=new Map}add(t,...n){const r=n[0];if(this._map.set(t,r),r&&typeof r=="object"&&"id"in r){if(this._idmap.has(r.id))throw new Error(`ID ${r.id} already exists in the registry`);this._idmap.set(r.id,t)}return this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(t){const n=this._map.get(t);return n&&typeof n=="object"&&"id"in n&&this._idmap.delete(n.id),this._map.delete(t),this}get(t){const n=t._zod.parent;if(n){const r={...this.get(n)??{}};delete r.id;const o={...r,...this._map.get(t)};return Object.keys(o).length?o:void 0}return this._map.get(t)}has(t){return this._map.has(t)}}function zs(){return new Ps}(_t=globalThis).__zod_globalRegistry??(_t.__zod_globalRegistry=zs());const he=globalThis.__zod_globalRegistry;function $s(e,t){return new e({type:"string",..._(t)})}function Ms(e,t){return new e({type:"string",format:"email",check:"string_format",abort:!1,..._(t)})}function yt(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,..._(t)})}function Os(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,..._(t)})}function Is(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",..._(t)})}function As(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",..._(t)})}function Es(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",..._(t)})}function Zs(e,t){return new e({type:"string",format:"url",check:"string_format",abort:!1,..._(t)})}function Ds(e,t){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,..._(t)})}function Rs(e,t){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,..._(t)})}function Fs(e,t){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,..._(t)})}function Us(e,t){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,..._(t)})}function Ls(e,t){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,..._(t)})}function Hs(e,t){return new e({type:"string",format:"xid",check:"string_format",abort:!1,..._(t)})}function Vs(e,t){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,..._(t)})}function Ys(e,t){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,..._(t)})}function Ws(e,t){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,..._(t)})}function Bs(e,t){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,..._(t)})}function qs(e,t){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,..._(t)})}function Js(e,t){return new e({type:"string",format:"base64",check:"string_format",abort:!1,..._(t)})}function Gs(e,t){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,..._(t)})}function Ks(e,t){return new e({type:"string",format:"e164",check:"string_format",abort:!1,..._(t)})}function Qs(e,t){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,..._(t)})}function Xs(e,t){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,..._(t)})}function eo(e,t){return new e({type:"string",format:"date",check:"string_format",..._(t)})}function to(e,t){return new e({type:"string",format:"time",check:"string_format",precision:null,..._(t)})}function no(e,t){return new e({type:"string",format:"duration",check:"string_format",..._(t)})}function ro(e,t){return new e({type:"number",coerce:!0,checks:[],..._(t)})}function so(e,t){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",..._(t)})}function wt(e,t){return new Kt({check:"less_than",..._(t),value:e,inclusive:!1})}function Ae(e,t){return new Kt({check:"less_than",..._(t),value:e,inclusive:!0})}function jt(e,t){return new Qt({check:"greater_than",..._(t),value:e,inclusive:!1})}function Ee(e,t){return new Qt({check:"greater_than",..._(t),value:e,inclusive:!0})}function St(e,t){return new $r({check:"multiple_of",..._(t),value:e})}function tn(e,t){return new Or({check:"max_length",..._(t),maximum:e})}function Ne(e,t){return new Ir({check:"min_length",..._(t),minimum:e})}function nn(e,t){return new Ar({check:"length_equals",..._(t),length:e})}function oo(e,t){return new Er({check:"string_format",format:"regex",..._(t),pattern:e})}function ao(e){return new Zr({check:"string_format",format:"lowercase",..._(e)})}function io(e){return new Dr({check:"string_format",format:"uppercase",..._(e)})}function co(e,t){return new Rr({check:"string_format",format:"includes",..._(t),includes:e})}function lo(e,t){return new Fr({check:"string_format",format:"starts_with",..._(t),prefix:e})}function uo(e,t){return new Ur({check:"string_format",format:"ends_with",..._(t),suffix:e})}function ue(e){return new Lr({check:"overwrite",tx:e})}function mo(e){return ue(t=>t.normalize(e))}function ho(){return ue(e=>e.trim())}function po(){return ue(e=>e.toLowerCase())}function fo(){return ue(e=>e.toUpperCase())}function go(){return ue(e=>Vn(e))}function bo(e,t,n){return new e({type:"array",element:t,..._(n)})}function xo(e,t,n){return new e({type:"custom",check:"custom",fn:t,..._(n)})}function vo(e){const t=_o(n=>(n.addIssue=r=>{if(typeof r=="string")n.issues.push(pe(r,n.value,t._zod.def));else{const o=r;o.fatal&&(o.continue=!1),o.code??(o.code="custom"),o.input??(o.input=n.value),o.inst??(o.inst=t),o.continue??(o.continue=!t._zod.def.abort),n.issues.push(pe(o))}},e(n.value,n)));return t}function _o(e,t){const n=new H({check:"custom",..._(t)});return n._zod.check=e,n}function rn(e){let t=e?.target??"draft-2020-12";return t==="draft-4"&&(t="draft-04"),t==="draft-7"&&(t="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??he,target:t,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref",reused:e?.reused??"inline",external:e?.external??void 0}}function R(e,t,n={path:[],schemaPath:[]}){var r;const o=e._zod.def,a=t.seen.get(e);if(a)return a.count++,n.schemaPath.includes(e)&&(a.cycle=n.path),a.schema;const i={schema:{},count:1,cycle:void 0,path:n.path};t.seen.set(e,i);const c=e._zod.toJSONSchema?.();if(c)i.schema=c;else{const d={...n,schemaPath:[...n.schemaPath,e],path:n.path},h=e._zod.parent;if(h)i.ref=h,R(h,t,d),t.seen.get(h).isParent=!0;else if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,d);else{const f=i.schema,p=t.processors[o.type];if(!p)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${o.type}`);p(e,t,f,d)}}const l=t.metadataRegistry.get(e);return l&&Object.assign(i.schema,l),t.io==="input"&&D(e)&&(delete i.schema.examples,delete i.schema.default),t.io==="input"&&i.schema._prefault&&((r=i.schema).default??(r.default=i.schema._prefault)),delete i.schema._prefault,t.seen.get(e).schema}function sn(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const r=a=>{const i=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const d=e.external.registry.get(a[0])?.id,h=e.external.uri??(p=>p);if(d)return{ref:h(d)};const f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${h("__shared")}#/${i}/${f}`}}if(a[1]===n)return{ref:"#"};const l=`#/${i}/`,u=a[1].schema.id??`__schema${e.counter++}`;return{defId:u,ref:l+u}},o=a=>{if(a[1].schema.$ref)return;const i=a[1],{ref:c,defId:l}=r(a);i.def={...i.schema},l&&(i.defId=l);const u=i.schema;for(const d in u)delete u[d];u.$ref=c};if(e.cycles==="throw")for(const a of e.seen.entries()){const i=a[1];if(i.cycle)throw new Error(`Cycle detected: #/${i.cycle?.join("/")}/ - -Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const a of e.seen.entries()){const i=a[1];if(t===a[0]){o(a);continue}if(e.external){const l=e.external.registry.get(a[0])?.id;if(t!==a[0]&&l){o(a);continue}}if(e.metadataRegistry.get(a[0])?.id){o(a);continue}if(i.cycle){o(a);continue}if(i.count>1&&e.reused==="ref"){o(a);continue}}}function on(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const r=i=>{const c=e.seen.get(i),l=c.def??c.schema,u={...l};if(c.ref===null)return;const d=c.ref;if(c.ref=null,d){r(d);const h=e.seen.get(d).schema;h.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(l.allOf=l.allOf??[],l.allOf.push(h)):(Object.assign(l,h),Object.assign(l,u))}c.isParent||e.override({zodSchema:i,jsonSchema:l,path:c.path??[]})};for(const i of[...e.seen.entries()].reverse())r(i[0]);const o={};if(e.target==="draft-2020-12"?o.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?o.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?o.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){const i=e.external.registry.get(t)?.id;if(!i)throw new Error("Schema is missing an `id` property");o.$id=e.external.uri(i)}Object.assign(o,n.def??n.schema);const a=e.external?.defs??{};for(const i of e.seen.entries()){const c=i[1];c.def&&c.defId&&(a[c.defId]=c.def)}e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?o.$defs=a:o.definitions=a);try{const i=JSON.parse(JSON.stringify(o));return Object.defineProperty(i,"~standard",{value:{...t["~standard"],jsonSchema:{input:ke(t,"input"),output:ke(t,"output")}},enumerable:!1,writable:!1}),i}catch{throw new Error("Error converting schema to JSON.")}}function D(e,t){const n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);const r=e._zod.def;if(r.type==="transform")return!0;if(r.type==="array")return D(r.element,n);if(r.type==="set")return D(r.valueType,n);if(r.type==="lazy")return D(r.getter(),n);if(r.type==="promise"||r.type==="optional"||r.type==="nonoptional"||r.type==="nullable"||r.type==="readonly"||r.type==="default"||r.type==="prefault")return D(r.innerType,n);if(r.type==="intersection")return D(r.left,n)||D(r.right,n);if(r.type==="record"||r.type==="map")return D(r.keyType,n)||D(r.valueType,n);if(r.type==="pipe")return D(r.in,n)||D(r.out,n);if(r.type==="object"){for(const o in r.shape)if(D(r.shape[o],n))return!0;return!1}if(r.type==="union"){for(const o of r.options)if(D(o,n))return!0;return!1}if(r.type==="tuple"){for(const o of r.items)if(D(o,n))return!0;return!!(r.rest&&D(r.rest,n))}return!1}const yo=(e,t={})=>n=>{const r=rn({...n,processors:t});return R(e,r),sn(r,e),on(r,e)},ke=(e,t)=>n=>{const{libraryOptions:r,target:o}=n??{},a=rn({...r??{},target:o,io:t,processors:{}});return R(e,a),sn(a,e),on(a,e)},wo={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},jo=(e,t,n,r)=>{const o=n;o.type="string";const{minimum:a,maximum:i,format:c,patterns:l,contentEncoding:u}=e._zod.bag;if(typeof a=="number"&&(o.minLength=a),typeof i=="number"&&(o.maxLength=i),c&&(o.format=wo[c]??c,o.format===""&&delete o.format),u&&(o.contentEncoding=u),l&&l.size>0){const d=[...l];d.length===1?o.pattern=d[0].source:d.length>1&&(o.allOf=[...d.map(h=>({...t.target==="draft-07"||t.target==="draft-04"||t.target==="openapi-3.0"?{type:"string"}:{},pattern:h.source}))])}},So=(e,t,n,r)=>{const o=n,{minimum:a,maximum:i,format:c,multipleOf:l,exclusiveMaximum:u,exclusiveMinimum:d}=e._zod.bag;typeof c=="string"&&c.includes("int")?o.type="integer":o.type="number",typeof d=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.minimum=d,o.exclusiveMinimum=!0):o.exclusiveMinimum=d),typeof a=="number"&&(o.minimum=a,typeof d=="number"&&t.target!=="draft-04"&&(d>=a?delete o.minimum:delete o.exclusiveMinimum)),typeof u=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.maximum=u,o.exclusiveMaximum=!0):o.exclusiveMaximum=u),typeof i=="number"&&(o.maximum=i,typeof u=="number"&&t.target!=="draft-04"&&(u<=i?delete o.maximum:delete o.exclusiveMaximum)),typeof l=="number"&&(o.multipleOf=l)},No=(e,t,n,r)=>{if(t.unrepresentable==="throw")throw new Error("Custom types cannot be represented in JSON Schema")},ko=(e,t,n,r)=>{if(t.unrepresentable==="throw")throw new Error("Transforms cannot be represented in JSON Schema")},Co=(e,t,n,r)=>{const o=n,a=e._zod.def,{minimum:i,maximum:c}=e._zod.bag;typeof i=="number"&&(o.minItems=i),typeof c=="number"&&(o.maxItems=c),o.type="array",o.items=R(a.element,t,{...r,path:[...r.path,"items"]})},To=(e,t,n,r)=>{const o=e._zod.def,a=o.inclusive===!1,i=o.options.map((c,l)=>R(c,t,{...r,path:[...r.path,a?"oneOf":"anyOf",l]}));a?n.oneOf=i:n.anyOf=i},Po=(e,t,n,r)=>{const o=e._zod.def,a=R(o.left,t,{...r,path:[...r.path,"allOf",0]}),i=R(o.right,t,{...r,path:[...r.path,"allOf",1]}),c=u=>"allOf"in u&&Object.keys(u).length===1,l=[...c(a)?a.allOf:[a],...c(i)?i.allOf:[i]];n.allOf=l},zo=(e,t,n,r)=>{const o=e._zod.def,a=R(o.innerType,t,r),i=t.seen.get(e);t.target==="openapi-3.0"?(i.ref=o.innerType,n.nullable=!0):n.anyOf=[a,{type:"null"}]},$o=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType},Mo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,n.default=JSON.parse(JSON.stringify(o.defaultValue))},Oo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,t.io==="input"&&(n._prefault=JSON.parse(JSON.stringify(o.defaultValue)))},Io=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType;let i;try{i=o.catchValue(void 0)}catch{throw new Error("Dynamic catch values are not supported in JSON Schema")}n.default=i},Ao=(e,t,n,r)=>{const o=e._zod.def,a=t.io==="input"?o.in._zod.def.type==="transform"?o.out:o.in:o.out;R(a,t,r);const i=t.seen.get(e);i.ref=a},Eo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType,n.readOnly=!0},Zo=(e,t,n,r)=>{const o=e._zod.def;R(o.innerType,t,r);const a=t.seen.get(e);a.ref=o.innerType},Do=m("ZodISODateTime",(e,t)=>{ts.init(e,t),$.init(e,t)});function Ro(e){return Xs(Do,e)}const Fo=m("ZodISODate",(e,t)=>{ns.init(e,t),$.init(e,t)});function Uo(e){return eo(Fo,e)}const Lo=m("ZodISOTime",(e,t)=>{rs.init(e,t),$.init(e,t)});function Ho(e){return to(Lo,e)}const Vo=m("ZodISODuration",(e,t)=>{ss.init(e,t),$.init(e,t)});function Yo(e){return no(Vo,e)}const Wo=(e,t)=>{Yt.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:n=>Jn(e,n)},flatten:{value:n=>qn(e,n)},addIssue:{value:n=>{e.issues.push(n),e.message=JSON.stringify(e.issues,Ue,2)}},addIssues:{value:n=>{e.issues.push(...n),e.message=JSON.stringify(e.issues,Ue,2)}},isEmpty:{get(){return e.issues.length===0}}})},J=m("ZodError",Wo,{Parent:Error}),Bo=Xe(J),qo=et(J),Jo=Te(J),Go=Pe(J),Ko=Qn(J),Qo=Xn(J),Xo=er(J),ea=tr(J),ta=nr(J),na=rr(J),ra=sr(J),sa=or(J),F=m("ZodType",(e,t)=>(Z.init(e,t),Object.assign(e["~standard"],{jsonSchema:{input:ke(e,"input"),output:ke(e,"output")}}),e.toJSONSchema=yo(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{value:t}),e.check=(...n)=>e.clone(Hn(t,{checks:[...t.checks??[],...n.map(r=>typeof r=="function"?{_zod:{check:r,def:{check:"custom"},onattach:[]}}:r)]})),e.clone=(n,r)=>Yn(e,n,r),e.brand=()=>e,e.register=((n,r)=>(n.add(e,r),e)),e.parse=(n,r)=>Bo(e,n,r,{callee:e.parse}),e.safeParse=(n,r)=>Jo(e,n,r),e.parseAsync=async(n,r)=>qo(e,n,r,{callee:e.parseAsync}),e.safeParseAsync=async(n,r)=>Go(e,n,r),e.spa=e.safeParseAsync,e.encode=(n,r)=>Ko(e,n,r),e.decode=(n,r)=>Qo(e,n,r),e.encodeAsync=async(n,r)=>Xo(e,n,r),e.decodeAsync=async(n,r)=>ea(e,n,r),e.safeEncode=(n,r)=>ta(e,n,r),e.safeDecode=(n,r)=>na(e,n,r),e.safeEncodeAsync=async(n,r)=>ra(e,n,r),e.safeDecodeAsync=async(n,r)=>sa(e,n,r),e.refine=(n,r)=>e.check(Wa(n,r)),e.superRefine=n=>e.check(Ba(n)),e.overwrite=n=>e.check(ue(n)),e.optional=()=>Ct(e),e.nullable=()=>Tt(e),e.nullish=()=>Ct(Tt(e)),e.nonoptional=n=>Ra(e,n),e.array=()=>Na(e),e.or=n=>Ca([e,n]),e.and=n=>Pa(e,n),e.transform=n=>Pt(e,$a(n)),e.default=n=>Aa(e,n),e.prefault=n=>Za(e,n),e.catch=n=>Ua(e,n),e.pipe=n=>Pt(e,n),e.readonly=()=>Va(e),e.describe=n=>{const r=e.clone();return he.add(r,{description:n}),r},Object.defineProperty(e,"description",{get(){return he.get(e)?.description},configurable:!0}),e.meta=(...n)=>{if(n.length===0)return he.get(e);const r=e.clone();return he.add(r,n[0]),r},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),an=m("_ZodString",(e,t)=>{tt.init(e,t),F.init(e,t),e._zod.processJSONSchema=(r,o,a)=>jo(e,r,o);const n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...r)=>e.check(oo(...r)),e.includes=(...r)=>e.check(co(...r)),e.startsWith=(...r)=>e.check(lo(...r)),e.endsWith=(...r)=>e.check(uo(...r)),e.min=(...r)=>e.check(Ne(...r)),e.max=(...r)=>e.check(tn(...r)),e.length=(...r)=>e.check(nn(...r)),e.nonempty=(...r)=>e.check(Ne(1,...r)),e.lowercase=r=>e.check(ao(r)),e.uppercase=r=>e.check(io(r)),e.trim=()=>e.check(ho()),e.normalize=(...r)=>e.check(mo(...r)),e.toLowerCase=()=>e.check(po()),e.toUpperCase=()=>e.check(fo()),e.slugify=()=>e.check(go())}),oa=m("ZodString",(e,t)=>{tt.init(e,t),an.init(e,t),e.email=n=>e.check(Ms(aa,n)),e.url=n=>e.check(Zs(ia,n)),e.jwt=n=>e.check(Qs(wa,n)),e.emoji=n=>e.check(Ds(ca,n)),e.guid=n=>e.check(yt(Nt,n)),e.uuid=n=>e.check(Os(ye,n)),e.uuidv4=n=>e.check(Is(ye,n)),e.uuidv6=n=>e.check(As(ye,n)),e.uuidv7=n=>e.check(Es(ye,n)),e.nanoid=n=>e.check(Rs(la,n)),e.guid=n=>e.check(yt(Nt,n)),e.cuid=n=>e.check(Fs(ua,n)),e.cuid2=n=>e.check(Us(da,n)),e.ulid=n=>e.check(Ls(ma,n)),e.base64=n=>e.check(Js(va,n)),e.base64url=n=>e.check(Gs(_a,n)),e.xid=n=>e.check(Hs(ha,n)),e.ksuid=n=>e.check(Vs(pa,n)),e.ipv4=n=>e.check(Ys(fa,n)),e.ipv6=n=>e.check(Ws(ga,n)),e.cidrv4=n=>e.check(Bs(ba,n)),e.cidrv6=n=>e.check(qs(xa,n)),e.e164=n=>e.check(Ks(ya,n)),e.datetime=n=>e.check(Ro(n)),e.date=n=>e.check(Uo(n)),e.time=n=>e.check(Ho(n)),e.duration=n=>e.check(Yo(n))});function ge(e){return $s(oa,e)}const $=m("ZodStringFormat",(e,t)=>{z.init(e,t),an.init(e,t)}),aa=m("ZodEmail",(e,t)=>{Wr.init(e,t),$.init(e,t)}),Nt=m("ZodGUID",(e,t)=>{Vr.init(e,t),$.init(e,t)}),ye=m("ZodUUID",(e,t)=>{Yr.init(e,t),$.init(e,t)}),ia=m("ZodURL",(e,t)=>{Br.init(e,t),$.init(e,t)}),ca=m("ZodEmoji",(e,t)=>{qr.init(e,t),$.init(e,t)}),la=m("ZodNanoID",(e,t)=>{Jr.init(e,t),$.init(e,t)}),ua=m("ZodCUID",(e,t)=>{Gr.init(e,t),$.init(e,t)}),da=m("ZodCUID2",(e,t)=>{Kr.init(e,t),$.init(e,t)}),ma=m("ZodULID",(e,t)=>{Qr.init(e,t),$.init(e,t)}),ha=m("ZodXID",(e,t)=>{Xr.init(e,t),$.init(e,t)}),pa=m("ZodKSUID",(e,t)=>{es.init(e,t),$.init(e,t)}),fa=m("ZodIPv4",(e,t)=>{os.init(e,t),$.init(e,t)}),ga=m("ZodIPv6",(e,t)=>{as.init(e,t),$.init(e,t)}),ba=m("ZodCIDRv4",(e,t)=>{is.init(e,t),$.init(e,t)}),xa=m("ZodCIDRv6",(e,t)=>{cs.init(e,t),$.init(e,t)}),va=m("ZodBase64",(e,t)=>{ls.init(e,t),$.init(e,t)}),_a=m("ZodBase64URL",(e,t)=>{ds.init(e,t),$.init(e,t)}),ya=m("ZodE164",(e,t)=>{ms.init(e,t),$.init(e,t)}),wa=m("ZodJWT",(e,t)=>{ps.init(e,t),$.init(e,t)}),cn=m("ZodNumber",(e,t)=>{en.init(e,t),F.init(e,t),e._zod.processJSONSchema=(r,o,a)=>So(e,r,o),e.gt=(r,o)=>e.check(jt(r,o)),e.gte=(r,o)=>e.check(Ee(r,o)),e.min=(r,o)=>e.check(Ee(r,o)),e.lt=(r,o)=>e.check(wt(r,o)),e.lte=(r,o)=>e.check(Ae(r,o)),e.max=(r,o)=>e.check(Ae(r,o)),e.int=r=>e.check(kt(r)),e.safe=r=>e.check(kt(r)),e.positive=r=>e.check(jt(0,r)),e.nonnegative=r=>e.check(Ee(0,r)),e.negative=r=>e.check(wt(0,r)),e.nonpositive=r=>e.check(Ae(0,r)),e.multipleOf=(r,o)=>e.check(St(r,o)),e.step=(r,o)=>e.check(St(r,o)),e.finite=()=>e;const n=e._zod.bag;e.minValue=Math.max(n.minimum??Number.NEGATIVE_INFINITY,n.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null,e.maxValue=Math.min(n.maximum??Number.POSITIVE_INFINITY,n.exclusiveMaximum??Number.POSITIVE_INFINITY)??null,e.isInt=(n.format??"").includes("int")||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null}),ja=m("ZodNumberFormat",(e,t)=>{fs.init(e,t),cn.init(e,t)});function kt(e){return so(ja,e)}const Sa=m("ZodArray",(e,t)=>{gs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Co(e,n,r,o),e.element=t.element,e.min=(n,r)=>e.check(Ne(n,r)),e.nonempty=n=>e.check(Ne(1,n)),e.max=(n,r)=>e.check(tn(n,r)),e.length=(n,r)=>e.check(nn(n,r)),e.unwrap=()=>e.element});function Na(e,t){return bo(Sa,e,t)}const ka=m("ZodUnion",(e,t)=>{bs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>To(e,n,r,o),e.options=t.options});function Ca(e,t){return new ka({type:"union",options:e,..._(t)})}const Ta=m("ZodIntersection",(e,t)=>{xs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Po(e,n,r,o)});function Pa(e,t){return new Ta({type:"intersection",left:e,right:t})}const za=m("ZodTransform",(e,t)=>{vs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>ko(e,n),e._zod.parse=(n,r)=>{if(r.direction==="backward")throw new Ut(e.constructor.name);n.addIssue=a=>{if(typeof a=="string")n.issues.push(pe(a,n.value,t));else{const i=a;i.fatal&&(i.continue=!1),i.code??(i.code="custom"),i.input??(i.input=n.value),i.inst??(i.inst=e),n.issues.push(pe(i))}};const o=t.transform(n.value,n);return o instanceof Promise?o.then(a=>(n.value=a,n)):(n.value=o,n)}});function $a(e){return new za({type:"transform",transform:e})}const Ma=m("ZodOptional",(e,t)=>{_s.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Zo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Ma({type:"optional",innerType:e})}const Oa=m("ZodNullable",(e,t)=>{ys.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>zo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Tt(e){return new Oa({type:"nullable",innerType:e})}const Ia=m("ZodDefault",(e,t)=>{ws.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Mo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Aa(e,t){return new Ia({type:"default",innerType:e,get defaultValue(){return typeof t=="function"?t():Ht(t)}})}const Ea=m("ZodPrefault",(e,t)=>{js.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Oo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Za(e,t){return new Ea({type:"prefault",innerType:e,get defaultValue(){return typeof t=="function"?t():Ht(t)}})}const Da=m("ZodNonOptional",(e,t)=>{Ss.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>$o(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Ra(e,t){return new Da({type:"nonoptional",innerType:e,..._(t)})}const Fa=m("ZodCatch",(e,t)=>{Ns.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Io(e,n,r,o),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Ua(e,t){return new Fa({type:"catch",innerType:e,catchValue:typeof t=="function"?t:()=>t})}const La=m("ZodPipe",(e,t)=>{ks.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Ao(e,n,r,o),e.in=t.in,e.out=t.out});function Pt(e,t){return new La({type:"pipe",in:e,out:t})}const Ha=m("ZodReadonly",(e,t)=>{Cs.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>Eo(e,n,r,o),e.unwrap=()=>e._zod.def.innerType});function Va(e){return new Ha({type:"readonly",innerType:e})}const Ya=m("ZodCustom",(e,t)=>{Ts.init(e,t),F.init(e,t),e._zod.processJSONSchema=(n,r,o)=>No(e,n)});function Wa(e,t={}){return xo(Ya,e,t)}function Ba(e){return vo(e)}function Q(e){return ro(cn,e)}const qa=/^[A-Za-z0-9\-_+ ]*$/,Ja=/^([A-Za-z0-9 ()/._-]|%\d*[CYmdHMSqvQtwhf$]|%\d*\{[a-z_]+\})*$/,Ga=/^[A-Za-z0-9 ()/._-]*$/,Ka=/^[A-Za-z0-9 _+.@^~<>,-]*$/;function nt(e){return e.includes("..")||e.includes("~/")}const zt=ge().max(64,"Name must be 64 characters or less").regex(qa,"Name can only contain letters, numbers, hyphens, underscores, plus signs, and spaces"),Ze=ge().max(255,"Filename must be 255 characters or less").regex(Ja,"Filename contains invalid characters. Use letters, numbers, underscores, hyphens, strftime codes (%Y, %m, %d), and Motion tokens (%{movienbr}, %v, etc.)").refine(e=>!nt(e),{message:"Filename cannot contain directory traversal sequences (.. or ~/)"}),$t=ge().max(4096,"Path must be 4096 characters or less").regex(Ga,"Path contains invalid characters").refine(e=>!nt(e),{message:"Path cannot contain directory traversal sequences (.. or ~/)"});ge().max(255,"Email must be 255 characters or less").regex(Ka,"Email contains invalid characters");const Mt=Q().int("Framerate must be a whole number").min(1,"Framerate must be at least 1").max(100,"Framerate cannot exceed 100"),Ot=Q().int("Quality must be a whole number").min(1,"Quality must be at least 1%").max(100,"Quality cannot exceed 100%"),Qa=Q().int("Width must be a whole number").min(160,"Width must be at least 160 pixels").max(4096,"Width cannot exceed 4096 pixels"),Xa=Q().int("Height must be a whole number").min(120,"Height must be at least 120 pixels").max(2160,"Height cannot exceed 2160 pixels"),It=Q().int("Port must be a whole number").min(1,"Port must be at least 1").max(65535,"Port cannot exceed 65535"),ei=Q().int("Threshold must be a whole number").min(1,"Threshold must be at least 1").max(2147483647,"Threshold is too large"),ti=Q().int("Noise level must be a whole number").min(0,"Noise level must be at least 0").max(255,"Noise level cannot exceed 255"),ni=Q().int("Log level must be a whole number").min(1,"Log level must be at least 1").max(9,"Log level cannot exceed 9"),ri=Q().int("Device ID must be a whole number").min(1,"Device ID must be at least 1").max(999,"Device ID cannot exceed 999"),si=Q().int("Must be a whole number").min(0,"Must be 0 or greater");ge().transform(e=>{const t=e.toLowerCase();return t==="on"||t==="true"||t==="1"});function oi(e,t){const r={device_name:zt,camera_name:zt,device_id:ri,target_dir:$t,snapshot_filename:Ze,picture_filename:Ze,movie_filename:Ze,log_file:$t,framerate:Mt,stream_maxrate:Mt,width:Qa,height:Xa,stream_quality:Ot,picture_quality:Ot,stream_port:It,webcontrol_port:It,threshold:ei,noise_level:ti,minimum_motion_frames:si,log_level:ni}[e];if(!r)return typeof t=="string"&&nt(t)?{success:!1,error:"Value cannot contain directory traversal sequences (.. or ~/)"}:{success:!0};const o=r.safeParse(t);return o.success?{success:!0}:{success:!1,error:o.error.issues[0]?.message??"Invalid value"}}async function ai(){return await Ce("/0/api/system/reboot",{})}async function ii(){return await Ce("/0/api/system/shutdown",{})}async function ci(){return await Ce("/0/api/system/service-restart",{})}function li({config:e,onChange:t,getError:n,originalConfig:r,systemStatus:o}){const{addToast:a}=Be(),i=o?.actions?.service??!1,c=o?.actions?.power??!1,l=(b,w="")=>e[b]?.value??w,u=(b,w="")=>r?.[b]?.value??w,d=b=>e[b]?.password_set===!0,h=b=>r?.[b]?.password_set===!0,f=async()=>{if(window.confirm("Are you sure you want to reboot the Pi? The system will restart and be unavailable for about a minute."))try{await ai(),a("Rebooting... The system will be back online shortly.","info")}catch(b){a(b.message||"Failed to reboot. Power control may be disabled in config.","error")}},p=async()=>{if(window.confirm("Are you sure you want to shutdown the Pi? You will need to physically power it back on."))try{await ii(),a("Shutting down... The system will power off.","warning")}catch(b){a(b.message||"Failed to shutdown. Power control may be disabled in config.","error")}},v=async()=>{if(window.confirm("Are you sure you want to restart the Motion service? Active streams will be interrupted briefly."))try{await ci(),a("Restarting Motion... Streams will resume shortly.","info")}catch(b){a(b.message||"Failed to restart service. Service control may be disabled in config.","error")}};return s.jsxs(s.Fragment,{children:[s.jsx(E,{title:"Device Controls",description:"Service and system power management",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"flex flex-col gap-4",children:[s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"Service Control"}),s.jsx("button",{onClick:v,disabled:!i,className:`px-4 py-2 rounded-lg text-sm transition-colors ${i?"bg-blue-600/20 text-blue-300 hover:bg-blue-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:i?void 0:"Enable with webcontrol_actions service=on",children:"Restart Motion"}),!i&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions service=on"})," to enable"]})]}),s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"System Power"}),s.jsxs("div",{className:"flex gap-3",children:[s.jsx("button",{onClick:f,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-yellow-600/20 text-yellow-300 hover:bg-yellow-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Restart Pi"}),s.jsx("button",{onClick:p,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-red-600/20 text-red-300 hover:bg-red-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Shutdown Pi"})]}),!c&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions power=on"})," to enable"]})]})]})}),s.jsxs(E,{title:"Authentication",description:"Web interface and stream access credentials",collapsible:!0,defaultOpen:!0,children:[s.jsxs("div",{className:"mb-6",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Web Interface"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Credentials for logging into this web interface. Format: username:password"}),u("webcontrol_authentication","")===""&&u("webcontrol_user_authentication","")===""&&s.jsx("div",{className:"mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg",children:s.jsxs("div",{className:"flex items-start gap-2",children:[s.jsx("svg",{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})}),s.jsxs("div",{className:"flex-1",children:[s.jsx("p",{className:"text-sm font-medium text-blue-300 mb-1",children:"Initial Setup Available"}),s.jsxs("p",{className:"text-xs text-blue-300/80",children:["Configure authentication now to secure your Motion installation. During initial setup, you can set credentials without changing"," ",s.jsx("code",{className:"text-xs bg-surface px-1 rounded",children:"webcontrol_parms"})," in the config file. Once authentication is configured, it will require restart to apply."]})]})]})}),s.jsxs("div",{className:"mb-4",children:[s.jsx("label",{className:"block text-sm font-medium mb-1 text-gray-300",children:"Admin Username"}),s.jsx("input",{type:"text",value:"admin",disabled:!0,className:`w-full px-3 py-2 bg-surface-elevated border border-gray-700 rounded-lg - text-gray-500 cursor-not-allowed`}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Admin username is fixed for security"})]}),s.jsx(S,{label:"Admin Password",value:String(l("webcontrol_authentication","")).split(":")[1]||"",onChange:b=>{const w=String(l("webcontrol_authentication","")).split(":")[0]||"admin";t("webcontrol_authentication",`${w}:${b}`)},type:"password",placeholder:d("webcontrol_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_authentication")?"Password is configured. Enter a new password to change it.":"Administrator password (click eye icon to reveal)",originalValue:h("webcontrol_authentication")?"[set]":""}),s.jsx(S,{label:"Viewer Username",value:String(l("webcontrol_user_authentication","")).split(":")[0]||"",onChange:b=>{const w=String(l("webcontrol_user_authentication","")).split(":")[1]||"";t("webcontrol_user_authentication",b?`${b}:${w}`:"")},helpText:"View-only username (can view streams but not change settings)",error:n?.("webcontrol_user_authentication"),originalValue:String(u("webcontrol_user_authentication","")).split(":")[0]||"",showVisibilityToggle:!1}),s.jsx(S,{label:"Viewer Password",value:String(l("webcontrol_user_authentication","")).split(":")[1]||"",onChange:b=>{const w=String(l("webcontrol_user_authentication","")).split(":")[0]||"";t("webcontrol_user_authentication",w?`${w}:${b}`:"")},type:"password",placeholder:d("webcontrol_user_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_user_authentication")?"Password is configured. Enter a new password to change it.":"View-only password (click eye icon to reveal)",originalValue:h("webcontrol_user_authentication")?"[set]":""})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Direct Stream Access"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Authentication for direct stream URLs (embedded in websites, VLC, home automation)"}),s.jsx(A,{label:"Authentication Mode",value:String(l("webcontrol_auth_method","0")),onChange:b=>t("webcontrol_auth_method",b),options:[{value:"0",label:"None - No authentication required"},{value:"1",label:"Basic - Simple username/password (use with HTTPS)"},{value:"2",label:"Digest - Secure hash-based (recommended)"}],helpText:"Controls authentication for direct stream access and external API clients. The web UI uses session-based login instead.",error:n?.("webcontrol_auth_method")})]})]}),s.jsxs(E,{title:"Daemon",description:"Motion process settings",collapsible:!0,defaultOpen:!1,children:[s.jsx(B,{label:"Run as Daemon",value:l("daemon",!1),onChange:b=>t("daemon",b),helpText:"Run Motion in background mode"}),s.jsx(S,{label:"PID File",value:String(l("pid_file","")),onChange:b=>t("pid_file",b),helpText:"Path to process ID file. Leave empty to let systemd manage the PID.",error:n?.("pid_file"),originalValue:String(u("pid_file",""))}),s.jsx(S,{label:"Log File",value:String(l("log_file","")),onChange:b=>t("log_file",b),helpText:"Path to log file. Leave empty to use journald (view with: journalctl -u motion).",error:n?.("log_file"),originalValue:String(u("log_file",""))}),s.jsx(A,{label:"Log Level",value:String(l("log_level","6")),onChange:b=>t("log_level",b),options:[{value:"1",label:"Emergency"},{value:"2",label:"Alert"},{value:"3",label:"Critical"},{value:"4",label:"Error"},{value:"5",label:"Warning"},{value:"6",label:"Notice"},{value:"7",label:"Info"},{value:"8",label:"Debug"},{value:"9",label:"All"}],helpText:"Verbosity level for logging",error:n?.("log_level")})]}),s.jsxs(E,{title:"Web Server",description:"API server configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(S,{label:"Port",value:String(l("webcontrol_port","8080")),onChange:b=>t("webcontrol_port",b),type:"number",helpText:"Primary web server port",error:n?.("webcontrol_port"),originalValue:String(u("webcontrol_port","8080"))}),s.jsx(B,{label:"Localhost Only",value:l("webcontrol_localhost",!1),onChange:b=>t("webcontrol_localhost",b),helpText:"Restrict access to localhost only (127.0.0.1)"}),s.jsx(B,{label:"TLS/HTTPS",value:l("webcontrol_tls",!1),onChange:b=>t("webcontrol_tls",b),helpText:"Enable HTTPS encryption"}),l("webcontrol_tls",!1)&&s.jsxs(s.Fragment,{children:[s.jsx(S,{label:"TLS Certificate",value:String(l("webcontrol_cert","")),onChange:b=>t("webcontrol_cert",b),helpText:"Path to TLS certificate file (.crt or .pem)",error:n?.("webcontrol_cert")}),s.jsx(S,{label:"TLS Private Key",value:String(l("webcontrol_key","")),onChange:b=>t("webcontrol_key",b),helpText:"Path to TLS private key file (.key or .pem)",error:n?.("webcontrol_key")})]})]})]})}function ui({config:e,onChange:t,getError:n}){const r=(h,f="")=>e[h]?.value??f,o=Number(r("width",640)),a=Number(r("height",480)),i=ot(o,a),c=st.some(h=>h.width===o&&h.height===a),l=h=>{if(h==="custom")return;const{width:f,height:p}=jn(h);t("width",f),t("height",p)},u=h=>{t("width",Number(h))},d=h=>{t("height",Number(h))};return s.jsxs(E,{title:"Device Settings",description:"Basic camera configuration and identification",collapsible:!0,defaultOpen:!1,children:[s.jsx(S,{label:"Camera Name",value:String(r("device_name","")),onChange:h=>t("device_name",h),placeholder:"My Camera",helpText:"Friendly name for this camera",error:n?.("device_name")}),s.jsx(A,{label:"Resolution",value:c?i:"custom",onChange:l,options:[...st.map(h=>({value:ot(h.width,h.height),label:h.label})),{value:"custom",label:"Custom"}],helpText:"Video resolution (width x height)"}),!c&&s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(S,{label:"Width",value:String(o),onChange:u,type:"number",helpText:"Custom width in pixels",error:n?.("width")}),s.jsx(S,{label:"Height",value:String(a),onChange:d,type:"number",helpText:"Custom height in pixels",error:n?.("height")})]}),s.jsx(T,{label:"Framerate",value:Number(r("framerate",15)),onChange:h=>t("framerate",h),min:2,max:30,unit:" fps",helpText:"Frames per second (higher uses more CPU)",error:n?.("framerate")}),s.jsx(A,{label:"Rotation",value:String(r("rotate",0)),onChange:h=>t("rotate",Number(h)),options:wn.map(h=>({value:String(h.value),label:h.label})),helpText:"Rotate camera image"})]})}function di({config:e,onChange:t,getError:n,capabilities:r,originalConfig:o}){const a=(l,u="")=>e[l]?.value??u,i=(l,u="")=>o?.[l]?.value??u,c=!!a("libcam_awb_enable",!1);return s.jsxs(E,{title:"libcamera Controls",description:"Raspberry Pi camera controls (libcamera only)",collapsible:!0,defaultOpen:!1,children:[s.jsx(T,{label:"Brightness",value:Number(a("libcam_brightness",0)),onChange:l=>t("libcam_brightness",l),min:-1,max:1,step:.1,helpText:"Brightness adjustment (-1.0 to 1.0)",error:n?.("libcam_brightness")}),s.jsx(T,{label:"Contrast",value:Number(a("libcam_contrast",1)),onChange:l=>t("libcam_contrast",l),min:0,max:32,step:.5,helpText:"Contrast adjustment (0.0 to 32.0)",error:n?.("libcam_contrast")}),s.jsx(T,{label:"Gain (ISO)",value:Number(a("libcam_gain",1)),onChange:l=>t("libcam_gain",l),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)",error:n?.("libcam_gain")}),s.jsx(B,{label:"Auto White Balance",value:c,onChange:l=>t("libcam_awb_enable",l),helpText:"Enable automatic white balance"}),c&&s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"AWB Mode",value:String(a("libcam_awb_mode",0)),onChange:l=>t("libcam_awb_mode",Number(l)),options:Sn.map(l=>({value:String(l.value),label:l.label})),helpText:"White balance mode"}),r?.AwbLocked!==!1&&s.jsx(B,{label:"Lock AWB",value:!!a("libcam_awb_locked",!1),onChange:l=>t("libcam_awb_locked",l),helpText:"Lock white balance settings"})]}),!c&&s.jsxs(s.Fragment,{children:[r?.ColourTemperature!==!1&&s.jsx(T,{label:"Color Temperature",value:Number(a("libcam_colour_temp",0)),onChange:l=>t("libcam_colour_temp",l),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)",error:n?.("libcam_colour_temp")}),s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(T,{label:"Red Gain",value:Number(a("libcam_colour_gain_r",1)),onChange:l=>t("libcam_colour_gain_r",l),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)",error:n?.("libcam_colour_gain_r")}),s.jsx(T,{label:"Blue Gain",value:Number(a("libcam_colour_gain_b",1)),onChange:l=>t("libcam_colour_gain_b",l),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)",error:n?.("libcam_colour_gain_b")})]}),r?.ColourTemperature===!1&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),r?.AfMode?s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"Autofocus Mode",value:String(a("libcam_af_mode",0)),onChange:l=>t("libcam_af_mode",Number(l)),options:Nn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus control mode"}),Number(a("libcam_af_mode",0))===0&&r?.LensPosition&&s.jsx(T,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),Number(a("libcam_af_mode",0))>0&&s.jsxs(s.Fragment,{children:[r?.AfRange&&s.jsx(A,{label:"Autofocus Range",value:String(a("libcam_af_range",0)),onChange:l=>t("libcam_af_range",Number(l)),options:kn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus range preference"}),r?.AfSpeed&&s.jsx(A,{label:"Autofocus Speed",value:String(a("libcam_af_speed",0)),onChange:l=>t("libcam_af_speed",Number(l)),options:Cn.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus adjustment speed"})]})]}):r!==void 0?s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Autofocus:"})," Not supported on this camera.",r?.LensPosition?s.jsx("span",{children:" Manual focus (lens position) is available."}):s.jsx("span",{children:" This camera has fixed focus."})]}):null,!r?.AfMode&&r?.LensPosition&&s.jsx(T,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),s.jsx(S,{label:"Buffer Count",value:String(a("libcam_buffer_count",4)),onChange:l=>t("libcam_buffer_count",Number(l)),type:"number",helpText:"Frame buffers for capture (2-8). Higher values reduce frame drops under load but use more memory. Default 4 works for most setups; increase to 6-8 if seeing drops at high framerates.",error:n?.("libcam_buffer_count"),originalValue:String(i("libcam_buffer_count",4))})]})}function mi({config:e,onChange:t,getError:n}){const r=(g,k="")=>e[g]?.value??k,o=String(r("text_left","")),a=String(r("text_right","")),i=at(o),c=at(a),[l,u]=x.useState(i==="custom"?o:""),[d,h]=x.useState(c==="custom"?a:""),f=g=>{g==="custom"?t("text_left",l):t("text_left",it(g))},p=g=>{g==="custom"?t("text_right",d):t("text_right",it(g))},v=g=>{u(g),t("text_left",g)},b=g=>{h(g),t("text_right",g)},w=[{value:"disabled",label:"Disabled"},{value:"camera-name",label:"Camera Name"},{value:"timestamp",label:"Timestamp"},{value:"custom",label:"Custom Text"}];return s.jsxs(E,{title:"Text Overlay",description:"Add text overlays to video frames",collapsible:!0,defaultOpen:!1,children:[s.jsx(A,{label:"Left Text",value:i,onChange:f,options:w,helpText:"Text displayed in top-left corner"}),i==="custom"&&s.jsx(S,{label:"Custom Left Text",value:l,onChange:v,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_left")}),s.jsx(A,{label:"Right Text",value:c,onChange:p,options:w,helpText:"Text displayed in top-right corner"}),c==="custom"&&s.jsx(S,{label:"Custom Right Text",value:d,onChange:b,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_right")}),s.jsx(T,{label:"Text Scale",value:Number(r("text_scale",1)),onChange:g=>t("text_scale",g),min:1,max:10,unit:"x",helpText:"Text size multiplier (1-10)",error:n?.("text_scale")})]})}const hi=[{value:"100",label:"Full (100%)"},{value:"75",label:"High (75%)"},{value:"50",label:"Medium (50%)"},{value:"25",label:"Low (25%)"},{value:"10",label:"Minimal (10%)"}];function pi({config:e,onChange:t,getError:n}){const r=(a,i="")=>e[a]?.value??i,o=!r("stream_localhost",!1);return s.jsxs(E,{title:"Video Streaming",description:"Live MJPEG stream configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(B,{label:"Enable Video Streaming",value:o,onChange:a=>t("stream_localhost",!a),helpText:"Enable/disable live MJPEG streaming. When disabled, stream is only accessible from localhost."}),o&&s.jsxs(s.Fragment,{children:[s.jsx(A,{label:"Streaming Resolution",value:String(r("stream_preview_scale",100)),onChange:a=>t("stream_preview_scale",Number(a)),options:hi,helpText:"Scale stream as percentage of source resolution. Lower = less bandwidth and CPU."}),s.jsx(T,{label:"Stream Quality",value:Number(r("stream_quality",50)),onChange:a=>t("stream_quality",a),min:1,max:100,unit:"%",helpText:"JPEG compression quality (1-100). Higher = better quality, more bandwidth.",error:n?.("stream_quality")}),s.jsx(T,{label:"Stream Max Framerate",value:Number(r("stream_maxrate",15)),onChange:a=>t("stream_maxrate",a),min:1,max:30,unit:" fps",helpText:"Maximum frames per second (lower = less bandwidth and CPU)",error:n?.("stream_maxrate")}),s.jsx(B,{label:"Show Motion Boxes",value:!!r("stream_motion",!1),onChange:a=>t("stream_motion",a),helpText:"Display motion detection boxes in stream"}),s.jsx(A,{label:"Direct Stream Access Security",value:String(r("webcontrol_auth_method",0)),onChange:a=>t("webcontrol_auth_method",Number(a)),options:Tn.map(a=>({value:String(a.value),label:a.label})),helpText:"Authentication when streams are accessed directly (embedded in other websites, VLC, home automation). None = open access on trusted networks only. Basic = use with HTTPS. Digest = recommended."}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Stream URL:"})," ",s.jsx("code",{children:"http://[hostname]:[port]/[cam]/mjpg/stream"})]}),s.jsxs("p",{className:"mt-1",children:[s.jsx("strong",{children:"Note:"})," Streaming resolution scales the output to reduce bandwidth and CPU usage. Server-side resizing is always performed by Motion."]})]})]})]})}function fi({config:e,onChange:t,getError:n}){const r=(d,h="")=>e[d]?.value??h,o=Number(r("width",640)),a=Number(r("height",480)),i=Number(r("threshold",1500)),c=Pn(i,o,a),l=d=>{const h=Number(d),f=$n(h,o,a);t("threshold",f)},u=[{value:"",label:"Off"},{value:"EedDl",label:"Light"},{value:"EedDl",label:"Medium (default)"},{value:"EedDl",label:"Heavy"}];return s.jsx(E,{title:"Motion Detection",description:"Configure motion detection sensitivity and behavior",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(T,{label:"Threshold",value:c,onChange:d=>l(String(d)),min:0,max:20,step:.1,unit:"%",helpText:`Percentage of frame that must change (${i} pixels at ${o}x${a}). Higher = less sensitive.`,error:n?.("threshold")}),s.jsx(S,{label:"Threshold Maximum",value:String(r("threshold_maximum",0)),onChange:d=>t("threshold_maximum",Number(d)),type:"number",helpText:"Maximum threshold for auto-tuning (0 = disabled)",error:n?.("threshold_maximum")}),s.jsx(B,{label:"Auto-tune Threshold",value:r("threshold_tune",!1),onChange:d=>t("threshold_tune",d),helpText:"Automatically adjust threshold based on noise levels"}),s.jsx(B,{label:"Auto-tune Noise Level",value:r("noise_tune",!1),onChange:d=>t("noise_tune",d),helpText:"Automatically determine optimal noise level"}),s.jsx(T,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:d=>t("noise_level",d),min:1,max:255,helpText:"Noise tolerance (1-255). Lower values detect smaller motions.",error:n?.("noise_level")}),s.jsx(T,{label:"Light Switch Detection",value:Number(r("lightswitch_percent",0)),onChange:d=>t("lightswitch_percent",d),min:0,max:100,unit:"%",helpText:"Ignore sudden brightness changes (0 = disabled). Prevents false triggers from lights turning on/off.",error:n?.("lightswitch_percent")}),s.jsx(A,{label:"Despeckle Filter",value:String(r("despeckle_filter","")),onChange:d=>t("despeckle_filter",d),options:u,helpText:"Remove noise speckles from motion detection"}),s.jsx(T,{label:"Smart Mask Speed",value:Number(r("smart_mask_speed",0)),onChange:d=>t("smart_mask_speed",d),min:0,max:10,helpText:"Auto-mask static areas (0 = disabled, 1-10 = speed). Higher values adapt faster to static objects.",error:n?.("smart_mask_speed")}),s.jsx(A,{label:"Locate Motion Mode",value:String(r("locate_motion_mode","off")),onChange:d=>t("locate_motion_mode",d),options:zn,helpText:"Draw box around motion area. 'Preview' = stream only, 'On' = saved images, 'Both' = both."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Event Timing"}),s.jsx(S,{label:"Event Gap (seconds)",value:String(r("event_gap",60)),onChange:d=>t("event_gap",Number(d)),type:"number",min:"0",helpText:"Seconds of no motion before ending an event. Prevents splitting continuous motion into multiple events.",error:n?.("event_gap")}),s.jsx(S,{label:"Pre-Capture (frames)",value:String(r("pre_capture",0)),onChange:d=>t("pre_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture before motion detected. Uses CPU/memory to buffer frames.",error:n?.("pre_capture")}),s.jsx(S,{label:"Post-Capture (frames)",value:String(r("post_capture",0)),onChange:d=>t("post_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture after motion stops",error:n?.("post_capture")}),s.jsx(S,{label:"Minimum Motion Frames",value:String(r("minimum_motion_frames",1)),onChange:d=>t("minimum_motion_frames",Number(d)),type:"number",min:"1",helpText:"Consecutive frames with motion required to trigger event. Filters brief false positives.",error:n?.("minimum_motion_frames")})]})]})})}function gi({config:e,onChange:t,getError:n}){const r=(f,p="")=>e[f]?.value??p,o=String(r("picture_output","off")),a=Number(r("snapshot_interval",0)),i=Mn(o,a),[c,l]=x.useState(i),u=f=>{l(f);const p=On(f);t("picture_output",p.picture_output),p.snapshot_interval!==void 0&&t("snapshot_interval",p.snapshot_interval)},d=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered (all frames)"},{value:"motion-triggered-one",label:"Motion Triggered (first frame only)"},{value:"best",label:"Best Quality Frame"},{value:"center",label:"Center Frame"},{value:"interval-snapshots",label:"Interval Snapshots"},{value:"manual",label:"Manual Only"}],h=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(E,{title:"Picture Settings",description:"Configure picture capture and snapshots",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(A,{label:"Capture Mode",value:c,onChange:u,options:d,helpText:"When to capture still images during motion events"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Current settings:"})," picture_output=",o,a>0&&`, snapshot_interval=${a}s`]}),c==="motion-triggered"&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"text-xs text-yellow-300 bg-yellow-900/30 border border-yellow-700 p-3 rounded",children:[s.jsx("strong",{children:"Warning:"})," This mode captures every frame during motion. At 15fps, continuous motion can generate 900+ pictures per minute. Configure limits below to prevent runaway capture."]}),s.jsx(S,{label:"Max Pictures Per Event",value:String(r("picture_max_per_event",0)),onChange:f=>t("picture_max_per_event",Number(f)),type:"number",min:"0",max:"100000",helpText:"Maximum pictures per motion event (0 = unlimited)",error:n?.("picture_max_per_event")}),s.jsx(S,{label:"Min Interval Between Pictures (ms)",value:String(r("picture_min_interval",0)),onChange:f=>t("picture_min_interval",Number(f)),type:"number",min:"0",max:"60000",helpText:"Minimum milliseconds between captures (0 = no limit). 1000ms = 1 picture/second.",error:n?.("picture_min_interval")})]}),c==="interval-snapshots"&&s.jsx(S,{label:"Snapshot Interval (seconds)",value:String(r("snapshot_interval",60)),onChange:f=>t("snapshot_interval",Number(f)),type:"number",min:"1",helpText:"Seconds between snapshots (independent of motion)",error:n?.("snapshot_interval")}),s.jsx(T,{label:"Picture Quality",value:Number(r("picture_quality",75)),onChange:f=>t("picture_quality",f),min:1,max:100,unit:"%",helpText:"JPEG quality (1-100). Higher = better quality, larger files.",error:n?.("picture_quality")}),s.jsx(S,{label:"Picture Filename Pattern",value:String(r("picture_filename","%Y%m%d%H%M%S-%q")),onChange:f=>t("picture_filename",f),helpText:`Format codes: ${h}`,error:n?.("picture_filename")}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S-%q"})," → ",s.jsx("code",{children:"2025-01-29/143022-05.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/143022.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/143022.jpg"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S-%q"})," → ",s.jsx("code",{children:"20250129143022-05.jpg"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",h]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]})]})})}function bi(){return qe({queryKey:["deviceInfo"],queryFn:async()=>{const e=await fetch("/0/api/system/status");if(!e.ok)throw new Error("Failed to fetch device info");return e.json()},staleTime:6e4,retry:1})}function At(e){return e?.pi_generation===5}function xi(e){return e?.pi_generation===4}function de(e){return e?.hardware_encoders?.h264_v4l2m2m===!0}function vi(e,t=70){return(e?.temperature?.celsius??0)>t}function _i({config:e,onChange:t,getError:n}){const{data:r}=bi(),o=(g,k="")=>e[g]?.value??k,a=o("movie_output",!1),i=o("movie_output_motion",!1),c=o("emulate_motion",!1),l=In(a,i,c),[u,d]=x.useState(l),h=g=>{d(g);const k=En(g);t("movie_output",k.movie_output),k.movie_output_motion!==void 0&&t("movie_output_motion",k.movie_output_motion),k.emulate_motion!==void 0&&t("emulate_motion",k.emulate_motion)},f=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered"},{value:"continuous",label:"Continuous Recording"}],p=["%Y - Year","%m - Month","%d - Day","%H - Hour","%M - Minute","%S - Second","%v - Event number","%$ - Camera name"].join(", "),v=String(o("movie_container","mp4")),b=()=>!r||de(r)?ct:ct.filter(g=>!te(g.value)),w=()=>!(o("movie_passthrough",!1)||te(v)||v==="webm");return s.jsx(E,{title:"Movie Settings",description:"Configure video recording settings",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(A,{label:"Recording Mode",value:u,onChange:h,options:f,helpText:"When to record video. Motion Triggered = only during events, Continuous = always record."}),s.jsx(T,{label:"Movie Quality",value:Number(o("movie_quality",75)),onChange:g=>t("movie_quality",g),min:1,max:100,unit:"%",helpText:"Video encoding quality (1-100). Higher = better quality, larger files, more CPU.",error:n?.("movie_quality")}),s.jsx(S,{label:"Movie Filename Pattern",value:String(o("movie_filename","%Y%m%d%H%M%S")),onChange:g=>t("movie_filename",g),helpText:`Format codes: ${p}`,error:n?.("movie_filename")}),s.jsx(A,{label:"Container Format",value:String(o("movie_container","mp4")),onChange:g=>t("movie_container",g),options:b(),helpText:"Video container format. Hardware encoding requires v4l2m2m support."}),te(v)&&de(r)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Active:"})," Using h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%."]}),r&&!de(r)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Not Available:"})," This device does not have a hardware H.264 encoder.",At(r)&&" Pi 5 does not include a hardware encoder."," ","Hardware encoding options (h264_v4l2m2m) are hidden. Using software encoding (~40-70% CPU)."]}),te(v)&&!r&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding:"})," Uses h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%. Only available on devices with v4l2m2m support."]}),Ie(v)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"High CPU Warning:"})," H.265/HEVC software encoding uses 80-100% CPU on Raspberry Pi. Not recommended for continuous recording. Consider H.264 for better performance."]}),de(r)&&!te(v)&&!o("movie_passthrough",!1)&&!v.includes("webm")&&!Ie(v)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Available:"}),' This device has a hardware H.264 encoder. Select "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" to reduce CPU from ~40-70% to ~10%.']}),!r&&!te(v)&&!o("movie_passthrough",!1)&&!v.includes("webm")&&!Ie(v)&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-2",children:[s.jsx("strong",{children:"Tip:"}),' If your device has hardware encoding support (e.g., Raspberry Pi 4), consider selecting "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" for ~10% CPU instead of ~40-70% with software encoding.']}),v==="webm"&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"WebM Format:"})," Uses VP8 codec, optimized for web streaming. Encoder preset setting does not apply to VP8."]}),w()&&s.jsx(A,{label:"Encoder Preset",value:String(o("movie_encoder_preset","medium")),onChange:g=>t("movie_encoder_preset",g),options:An.map(g=>({value:g.value,label:g.label})),helpText:"Tradeoff between CPU usage and video quality. Lower presets use less CPU but produce lower quality video. Requires restart to take effect."}),s.jsx(S,{label:"Max Duration (seconds)",value:String(o("movie_max_time",0)),onChange:g=>t("movie_max_time",Number(g)),type:"number",min:"0",helpText:"Maximum movie length (0 = unlimited). Splits long events into multiple files.",error:n?.("movie_max_time")}),s.jsx(B,{label:"Passthrough Mode",value:o("movie_passthrough",!1),onChange:g=>t("movie_passthrough",g),helpText:"Copy codec without re-encoding. Reduces CPU but may cause compatibility issues."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"2025-01-29/143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%v-%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/42-143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%v"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/42.mkv"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S"})," → ",s.jsx("code",{children:"20250129143022.mkv"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",p]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]}),u==="continuous"&&At(r)&&!o("movie_passthrough",!1)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Pi 5 CPU Warning:"})," Pi 5 does not have a hardware H.264 encoder. Continuous recording uses software encoding (~35-60% CPU constant).",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsx("li",{children:'Use encoder preset "Ultrafast" to reduce CPU by ~30%'}),s.jsx("li",{children:"Add active cooling (fan) to prevent thermal throttling"}),s.jsx("li",{children:"Enable passthrough if source is already H.264"})]})]}),u==="continuous"&&xi(r)&&de(r)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording on Pi 4:"})," Camera will record 24/7.",te(v)?s.jsx("span",{children:" Using hardware encoder - expect ~10% CPU usage."}):o("movie_passthrough",!1)?s.jsx("span",{children:" Passthrough mode enabled - expect ~5-10% CPU usage."}):s.jsx("span",{children:" Consider using hardware encoder (MKV/MP4 H.264 Hardware) for ~10% CPU instead of ~40-70%."})]}),u==="continuous"&&!r&&s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording:"})," Camera will record 24/7 regardless of motion. Expected CPU usage on Raspberry Pi:",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 4 with hardware encoder:"})," ~10% CPU"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 5 or Pi 4 software encoding:"})," ~35-60% CPU depending on preset"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Passthrough mode:"})," ~5-10% CPU (if source is H.264)"]})]})]}),vi(r)&&s.jsxs("div",{className:"text-xs text-red-400 bg-red-950/30 p-3 rounded",children:[s.jsx("strong",{children:"High Temperature Warning:"})," Device is running at ",r?.temperature?.celsius.toFixed(1),"°C. Consider reducing encoding quality or adding active cooling."]})]})})}function yi({config:e,onChange:t,getError:n,originalConfig:r}){const o=(c,l="")=>e[c]?.value??l,a=(c,l="")=>r?.[c]?.value??l,i=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(E,{title:"Storage",description:"Base directory and periodic snapshot settings for this camera",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(S,{label:"Base Storage Directory",value:String(o("target_dir","/var/lib/motion")),onChange:c=>t("target_dir",c),helpText:"Root directory for ALL camera files. Picture and movie filename patterns (configured in their sections) create paths relative to this directory.",error:n?.("target_dir"),originalValue:String(a("target_dir","/var/lib/motion"))}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Filename Patterns"}),s.jsx(S,{label:"Snapshot Filename",value:String(o("snapshot_filename","%Y%m%d%H%M%S-snapshot")),onChange:c=>t("snapshot_filename",c),helpText:"Format for periodic snapshot filenames (strftime syntax)",error:n?.("snapshot_filename")}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Example:"})," ",s.jsx("code",{children:"%Y%m%d%H%M%S-snapshot"})," → ",s.jsx("code",{children:"20250129143022-snapshot.jpg"})]}),s.jsxs("p",{children:[s.jsx("strong",{children:"With subdirs:"})," ",s.jsx("code",{children:"%$/%Y-%m-%d/snapshot-%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/snapshot-143022.jpg"})]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-1",children:[s.jsxs("p",{children:["Available codes: ",i]}),s.jsxs("p",{className:"mt-2 text-blue-200",children:[s.jsx("strong",{children:"How it works:"})," The Base Storage Directory above sets where files go. Picture and Movie sections set filename patterns (which can include subdirectories like ",s.jsx("code",{children:"%Y-%m-%d/"}),")."]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"File Cleanup (Future)"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("p",{children:"Automatic file retention and cleanup based on age/size will be available in a future update."}),s.jsxs("p",{className:"mt-2",children:["For now, use ",s.jsx("code",{children:"cleandir_params"})," in the Motion configuration file or manual cleanup scripts."]})]})]}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"💡 Network Storage:"})," For network shares (NFS, SMB), ensure target_dir points to a mounted directory. Test write permissions before starting recording."]})]})})}const X=["sun","mon","tue","wed","thu","fri","sat"],Et={sun:{full:"Sunday",short:"Sun"},mon:{full:"Monday",short:"Mon"},tue:{full:"Tuesday",short:"Tue"},wed:{full:"Wednesday",short:"Wed"},thu:{full:"Thursday",short:"Thu"},fri:{full:"Friday",short:"Fri"},sat:{full:"Saturday",short:"Sat"}},ne=96,fe=15;function ln(){return{sun:new Set,mon:new Set,tue:new Set,wed:new Set,thu:new Set,fri:new Set,sat:new Set}}function wi(e){const t=new Map,n=e.trim().split(/\s+/);for(const r of n){const o=r.indexOf("=");if(o===-1)continue;const a=r.slice(0,o).toLowerCase(),i=r.slice(o+1);t.has(a)||t.set(a,[]),t.get(a).push(i)}return t}function ji(e){if(e.length!==9||e[4]!=="-")return[];const t=parseInt(e.slice(0,2),10),n=parseInt(e.slice(2,4),10),r=parseInt(e.slice(5,7),10),o=parseInt(e.slice(7,9),10);if(isNaN(t)||isNaN(n)||isNaN(r)||isNaN(o)||t<0||t>23||n<0||n>59||r<0||r>23||o<0||o>59)return[];const a=t*4+Math.floor(n/fe),i=r*4+Math.floor(o/fe),c=[];for(let l=a;l<=i&&lc-l),n=[];let r=t[0],o=t[0];for(let c=1;cZt(e[i],e.sun))&&e.sun.size>0){const i=we(Array.from(e.sun));for(const c of i)r.push(`sun-sat=${je(c)}`);return r.join(" ")}if(["mon","tue","wed","thu","fri"].every(i=>Zt(e[i],e.mon))&&e.mon.size>0){const i=we(Array.from(e.mon));for(const c of i)r.push(`mon-fri=${je(c)}`);for(const c of["sat","sun"])if(e[c].size>0){const l=we(Array.from(e[c]));for(const u of l)r.push(`${c}=${je(u)}`)}return r.join(" ")}for(const i of X){if(e[i].size===0)continue;const c=we(Array.from(e[i]));for(const l of c)r.push(`${i}=${je(l)}`)}return r.join(" ")}function Zt(e,t){if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0}function Ni(e,t){const n=Ye(e),r=Ve(t),o=(a,i)=>{const c=a===0?12:a>12?a-12:a,l=a<12?"am":"pm",u=i===0?"":`:${String(i).padStart(2,"0")}`;return`${c}${u}${l}`};return`${o(n.hour,n.min)} - ${o(r.hour,r.min)}`}function ki(e){return X.every(t=>e[t].size===0)}function Ci(e){const t=X.filter(r=>e[r].size>0);return t.length===0?"No time ranges selected":t.length===7?"All days configured":t.map(r=>r.charAt(0).toUpperCase()+r.slice(1,3)).join(", ")}function Ti({value:e,onChange:t}){const{schedule:n,defaultOn:r,action:o}=x.useMemo(()=>Si(e),[e]),a=x.useCallback(p=>{const v=De(p,r,o);t(v)},[t,r,o]),i=x.useCallback(p=>{const v=Re(n);v[p].size===ne?v[p]=new Set:v[p]=new Set(Array.from({length:ne},(b,w)=>w)),a(v)},[n,a]),c=x.useCallback((p,v,b,w)=>{const g=Re(n),k=new Set(n[p]),[V,q]=v<=b?[v,b]:[b,v];for(let O=V;O<=q;O++)w?k.add(O):k.delete(O);g[p]=k,a(g)},[n,a]),l=x.useCallback(p=>{const v=Re(n);v[p]=new Set,a(v)},[n,a]),u=x.useCallback(()=>{a(ln())},[a]),d=x.useCallback(p=>{const v=De(n,p,o);t(v)},[n,o,t]),h=x.useCallback(p=>{const v=De(n,r,p);t(v)},[n,r,t]),f=x.useCallback(p=>{t(p)},[t]);return{schedule:n,defaultOn:r,action:o,updateSchedule:a,toggleDay:i,setRange:c,clearDay:l,clearAll:u,setDefaultOn:d,setAction:h,applyPreset:f}}function Re(e){return{sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)}}const Pi=x.memo(function({isSelected:t,isInDragRange:n,isDragSelect:r,isHourBoundary:o,onPointerDown:a,onPointerEnter:i}){let c;n?c=r?"bg-primary/70":"bg-surface-hover":t?c="bg-primary":c="bg-surface";const l=o?"border-t border-gray-700/50":"";return s.jsx("div",{className:`h-[6px] ${c} ${l} cursor-pointer transition-colors duration-75`,onPointerDown:a,onPointerEnter:i})}),zi=x.memo(function({day:t,schedule:n,dragState:r,onPointerDown:o,onPointerMove:a}){const i=x.useMemo(()=>{if(!r.isDragging||r.startDay!==t)return null;const l=r.startIndex,u=r.currentIndex;return{from:Math.min(l,u),to:Math.max(l,u)}},[r.isDragging,r.startDay,r.startIndex,r.currentIndex,t]),c=x.useMemo(()=>Array.from({length:ne},(l,u)=>({isSelected:n.has(u),isInDragRange:i!==null&&u>=i.from&&u<=i.to,isHourBoundary:u%4===0})),[n,i]);return s.jsx("div",{className:"flex flex-col",children:c.map((l,u)=>s.jsx(Pi,{index:u,isSelected:l.isSelected,isInDragRange:l.isInDragRange,isDragSelect:r.selectMode,isHourBoundary:l.isHourBoundary,onPointerDown:()=>o(u),onPointerEnter:()=>a(u)},u))})}),$i=[0,2,4,6,8,10,12,14,16,18,20,22];function Mi(e){return e===0?"12a":e===12?"12p":e<12?`${e}a`:`${e-12}p`}const Oi=x.memo(function(){return s.jsx("div",{className:"flex flex-col pr-1 text-xs text-gray-500 select-none",children:$i.map(t=>s.jsx("div",{className:"flex items-start justify-end",style:{height:"48px"},children:s.jsx("span",{className:"-mt-1.5",children:Mi(t)})},t))})}),Se={isDragging:!1,startDay:null,startIndex:null,currentIndex:null,selectMode:!0};function Ii({schedule:e,onScheduleChange:t,disabled:n=!1}){const[r,o]=x.useState(Se),a=x.useRef(null),i=x.useCallback((f,p)=>{if(n)return;const v=e[f].has(p);o({isDragging:!0,startDay:f,startIndex:p,currentIndex:p,selectMode:!v})},[e,n]),c=x.useCallback((f,p)=>{!r.isDragging||f!==r.startDay||o(v=>({...v,currentIndex:p}))},[r.isDragging,r.startDay]),l=x.useCallback(()=>{if(!r.isDragging||r.startDay===null||r.startIndex===null||r.currentIndex===null){o(Se);return}const f={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)},p=new Set(e[r.startDay]),[v,b]=r.startIndex<=r.currentIndex?[r.startIndex,r.currentIndex]:[r.currentIndex,r.startIndex];for(let w=v;w<=b;w++)r.selectMode?p.add(w):p.delete(w);f[r.startDay]=p,t(f),o(Se)},[r,e,t]);x.useEffect(()=>{const f=p=>{p.key==="Escape"&&r.isDragging&&o(Se)};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)},[r.isDragging]),x.useEffect(()=>(r.isDragging?(document.body.style.touchAction="none",document.body.style.userSelect="none"):(document.body.style.touchAction="",document.body.style.userSelect=""),()=>{document.body.style.touchAction="",document.body.style.userSelect=""}),[r.isDragging]);const u=x.useCallback(f=>{if(n)return;const p={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)};p[f].size===ne?p[f]=new Set:p[f]=new Set(Array.from({length:ne},(v,b)=>b)),t(p)},[e,t,n]),h=(()=>{if(!r.isDragging||r.startIndex===null||r.currentIndex===null)return null;const f=Math.min(r.startIndex,r.currentIndex),p=Math.max(r.startIndex,r.currentIndex);return Ni(f,p)})();return s.jsxs("div",{className:"select-none",children:[s.jsxs("div",{className:"flex mb-1",children:[s.jsx("div",{className:"w-8 shrink-0"}),s.jsx("div",{className:"grid grid-cols-7 gap-px flex-1",children:X.map(f=>{const p=e[f].size===ne,v=e[f].size>0;return s.jsxs("button",{type:"button",onClick:()=>u(f),disabled:n,className:`text-xs font-medium py-1 rounded-t transition-colors ${p?"bg-primary text-white":v?"bg-primary/30 text-primary":"bg-surface-elevated text-gray-400 hover:bg-surface-hover"} ${n?"cursor-not-allowed opacity-50":"cursor-pointer"}`,title:`Click to ${p?"clear":"select all"} ${Et[f].full}`,children:[s.jsx("span",{className:"hidden sm:inline",children:Et[f].short}),s.jsx("span",{className:"sm:hidden",children:f.charAt(0).toUpperCase()})]},f)})})]}),s.jsxs("div",{className:"flex",children:[s.jsx("div",{className:"w-8 shrink-0",children:s.jsx(Oi,{})}),s.jsx("div",{ref:a,className:`grid grid-cols-7 gap-px bg-surface-elevated flex-1 rounded ${n?"opacity-50":""}`,style:{touchAction:"none"},onPointerUp:l,onPointerLeave:l,onPointerCancel:l,children:X.map(f=>s.jsx(zi,{day:f,schedule:e[f],dragState:r,onPointerDown:p=>i(f,p),onPointerMove:p=>c(f,p)},f))})]}),h&&s.jsx("div",{className:"mt-2 text-center",children:s.jsxs("span",{className:"text-xs bg-surface-elevated px-2 py-1 rounded text-gray-300",children:[r.selectMode?"Selecting":"Deselecting",":"," ",s.jsx("span",{className:"text-white font-medium",children:h})]})}),s.jsx("div",{className:"mt-2 text-xs text-gray-500 text-center",children:"Click and drag to select time ranges. Click day header to toggle entire day."})]})}const Ai=[{label:"Business Hours",value:"default=true action=pause mon-fri=0900-1700",description:"Pause Mon-Fri 9am-5pm"},{label:"Night Watch",value:"default=false action=pause sun-sat=1800-2359 sun-sat=0000-0600",description:"Active 6pm-6am only"},{label:"Weekends Only",value:"default=false action=pause sat=0000-2359 sun=0000-2359",description:"Active Sat & Sun only"},{label:"Always On",value:"default=true action=pause",description:"No schedule restrictions"}],Ei=x.memo(function({defaultOn:t,action:n,onDefaultOnChange:r,onActionChange:o,onApplyPreset:a,onClearAll:i,disabled:c=!1}){return s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Detection Default"}),s.jsxs("select",{value:t?"on":"off",onChange:l=>r(l.target.value==="on"),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"on",children:"On by default"}),s.jsx("option",{value:"off",children:"Off by default"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:t?"Schedule defines when detection is paused":"Schedule defines when detection is active"})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Schedule Action"}),s.jsxs("select",{value:n,onChange:l=>o(l.target.value),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"pause",children:"Pause detection"}),s.jsx("option",{value:"stop",children:"Stop camera"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n==="pause"?"Camera runs but ignores motion":"Camera completely stops during schedule"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-2",children:"Quick Presets"}),s.jsxs("div",{className:"flex flex-wrap gap-2",children:[Ai.map(l=>s.jsx("button",{type:"button",onClick:()=>a(l.value),disabled:c,className:"px-3 py-1.5 text-xs bg-surface-elevated hover:bg-surface-hover border border-gray-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:l.description,children:l.label},l.label)),s.jsx("button",{type:"button",onClick:i,disabled:c,className:"px-3 py-1.5 text-xs bg-danger/20 hover:bg-danger/30 text-danger border border-danger/30 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:"Clear all time ranges",children:"Clear All"})]})]})]})});function Dt({value:e,onChange:t,helpText:n,error:r,disabled:o=!1}){const{schedule:a,defaultOn:i,action:c,updateSchedule:l,setDefaultOn:u,setAction:d,applyPreset:h,clearAll:f}=Ti({value:e,onChange:t}),p=ki(a),v=Ci(a);return s.jsxs("div",{className:"space-y-4",children:[s.jsx(Ei,{defaultOn:i,action:c,onDefaultOnChange:u,onActionChange:d,onApplyPreset:h,onClearAll:f,disabled:o}),s.jsx("div",{className:"border border-gray-700 rounded-lg p-4 bg-surface",children:s.jsx(Ii,{schedule:a,onScheduleChange:l,disabled:o})}),s.jsxs("div",{className:"flex items-center justify-between text-sm",children:[s.jsx("span",{className:"text-gray-400",children:p?s.jsx("span",{className:"text-yellow-400",children:"No time ranges configured"}):s.jsxs(s.Fragment,{children:["Schedule: ",s.jsx("span",{className:"text-white",children:v})]})}),i?s.jsx("span",{className:"text-xs text-gray-500",children:"Detection paused during selected times"}):s.jsx("span",{className:"text-xs text-gray-500",children:"Detection active during selected times"})]}),n&&!r&&s.jsx("p",{className:"text-sm text-gray-400",children:n}),r&&s.jsx("p",{className:"text-sm text-red-400",role:"alert",children:r}),s.jsxs("details",{className:"text-xs",children:[s.jsx("summary",{className:"cursor-pointer text-gray-500 hover:text-gray-400",children:"Show raw schedule format"}),s.jsx("code",{className:"block mt-2 p-2 bg-surface-elevated rounded text-gray-400 break-all",children:e||"(empty)"})]})]})}function Zi({config:e,onChange:t,getError:n}){const r=(d,h="")=>e[d]?.value??h,o=String(r("schedule_params","")),a=o.trim()!=="",i=d=>{d?t("schedule_params","default=true action=pause mon-fri=0900-1700"):t("schedule_params","")},c=String(r("picture_schedule_params","")),l=c.trim()!=="",u=d=>{d?t("picture_schedule_params","default=false action=pause mon-fri=0900-1700"):t("picture_schedule_params","")};return s.jsx(E,{title:"Schedules",description:"Configure when motion detection and continuous recording are active",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-6",children:[s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Motion Detection Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when motion detection is active or paused"}),s.jsx(B,{label:"Enable Motion Detection Schedule",value:a,onChange:i,helpText:"When enabled, motion detection follows the schedule below"}),a&&s.jsx(Dt,{value:o,onChange:d=>t("schedule_params",d),error:n?.("schedule_params")})]}),s.jsx("div",{className:"border-t border-gray-700"}),s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Continuous Recording Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when continuous picture capture (timelapse) is active"}),s.jsx(B,{label:"Enable Continuous Recording Schedule",value:l,onChange:u,helpText:"When enabled, continuous recording follows the schedule below"}),l&&s.jsx(Dt,{value:c,onChange:d=>t("picture_schedule_params",d),error:n?.("picture_schedule_params")})]})]})})}const We={gridColumns:2,gridRows:2,fitFramesVertically:!1,playbackFramerateFactor:1,playbackResolutionFactor:1,theme:"dark"};function Di(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{...We,...t}}catch(t){console.error("Failed to parse preferences:",t)}return We}function Ri(){const[e,t]=x.useState(Di),n=(r,o)=>{const a={...e,[r]:o};t(a),localStorage.setItem("motion-ui-preferences",JSON.stringify(a))};return s.jsx(E,{title:"UI Preferences",description:"User interface preferences (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Dashboard Layout"}),s.jsx(T,{label:"Grid Columns",value:e.gridColumns,onChange:r=>n("gridColumns",r),min:1,max:4,helpText:"Number of camera columns in dashboard grid (1-4)"}),s.jsx(T,{label:"Grid Rows",value:e.gridRows,onChange:r=>n("gridRows",r),min:1,max:4,helpText:"Number of camera rows in dashboard grid (1-4)"}),s.jsx(B,{label:"Fit Frames Vertically",value:e.fitFramesVertically,onChange:r=>n("fitFramesVertically",r),helpText:"Fit camera frames to viewport height instead of width"})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Playback Settings"}),s.jsx(T,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:r=>n("playbackFramerateFactor",r),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(T,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:r=>n("playbackResolutionFactor",r),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Appearance"}),s.jsx(A,{label:"Theme",value:e.theme,onChange:r=>n("theme",r),options:[{value:"dark",label:"Dark"},{value:"light",label:"Light (Coming Soon)"},{value:"auto",label:"Auto (System Preference)"}],helpText:"UI color theme",disabled:!0}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Light theme and auto theme switching will be available in a future update."]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("button",{onClick:()=>{localStorage.removeItem("motion-ui-preferences"),t(We)},className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors text-sm",children:"Reset to Defaults"}),s.jsx("p",{className:"text-xs text-gray-400 mt-2",children:"Clears all saved preferences and returns to default settings"})]})]})})}const Fe={playbackFramerateFactor:1,playbackResolutionFactor:1};function Fi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{playbackFramerateFactor:t.playbackFramerateFactor??Fe.playbackFramerateFactor,playbackResolutionFactor:t.playbackResolutionFactor??Fe.playbackResolutionFactor}}catch(t){console.error("Failed to parse preferences:",t)}return Fe}function Ui(){const[e,t]=x.useState(Fi),n=(r,o)=>{const a={...e,[r]:o};t(a);const i=localStorage.getItem("motion-ui-preferences");let c={};if(i)try{c=JSON.parse(i)}catch(l){console.error("Failed to parse existing preferences:",l)}localStorage.setItem("motion-ui-preferences",JSON.stringify({...c,...a}))};return s.jsx(E,{title:"Playback Settings",description:"Video playback preferences for this camera (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsx(T,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:r=>n("playbackFramerateFactor",r),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(T,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:r=>n("playbackResolutionFactor",r),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]})})}function Li({cameraId:e}){const{addToast:t}=Be(),n=Rt(),r=x.useRef(null),o=x.useRef(null),[a,i]=x.useState("motion"),[c,l]=x.useState("rectangle"),[u,d]=x.useState(!1),[h,f]=x.useState(null),[p,v]=x.useState(null),[b,w]=x.useState([]),[g,k]=x.useState([]),[V,q]=x.useState(!1),[O,oe]=x.useState(!1),{data:ee,isLoading:U}=qe({queryKey:["mask",e,a],queryFn:()=>Ft(`/${e}/api/mask/${a}`)}),[j,I]=x.useState({width:640,height:480}),M=rt({mutationFn:N=>Ce(`/${e}/api/mask/${a}`,N),onSuccess:()=>{t(`${a==="motion"?"Motion":"Privacy"} mask saved`,"success"),n.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to save mask","error")}}),Y=rt({mutationFn:()=>gn(`/${e}/api/mask/${a}`),onSuccess:()=>{t("Mask deleted","success"),w([]),n.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to delete mask","error")}}),K=x.useCallback(N=>{const y=r.current;if(!y)return{x:0,y:0};const C=y.getBoundingClientRect(),G=j.width/C.width,ae=j.height/C.height;return{x:Math.round((N.clientX-C.left)*G),y:Math.round((N.clientY-C.top)*ae)}},[j]),L=x.useCallback(()=>{const N=r.current;if(!N)return;const y=N.getContext("2d");if(y&&(y.clearRect(0,0,N.width,N.height),y.fillStyle="rgba(255, 0, 0, 0.4)",y.strokeStyle="rgba(255, 0, 0, 0.8)",y.lineWidth=2,b.forEach(C=>{C.length<3||(y.beginPath(),y.moveTo(C[0].x,C[0].y),C.slice(1).forEach(G=>y.lineTo(G.x,G.y)),y.closePath(),y.fill(),y.stroke())}),g.length>0&&(y.beginPath(),y.moveTo(g[0].x,g[0].y),g.slice(1).forEach(C=>y.lineTo(C.x,C.y)),p&&y.lineTo(p.x,p.y),y.stroke(),y.fillStyle="rgba(255, 255, 0, 0.8)",g.forEach(C=>{y.beginPath(),y.arc(C.x,C.y,4,0,Math.PI*2),y.fill()})),c==="rectangle"&&u&&h&&p)){y.fillStyle="rgba(255, 0, 0, 0.4)",y.strokeStyle="rgba(255, 0, 0, 0.8)";const C=Math.min(h.x,p.x),G=Math.min(h.y,p.y),ae=Math.abs(p.x-h.x),xe=Math.abs(p.y-h.y);y.fillRect(C,G,ae,xe),y.strokeRect(C,G,ae,xe)}},[b,g,p,h,u,c]);x.useEffect(()=>{L()},[L]);const $e=x.useCallback(N=>{const y=K(N);c==="rectangle"?(d(!0),f(y),v(y)):k(C=>[...C,y])},[c,K]),be=x.useCallback(N=>{const y=K(N);v(y)},[K]),W=x.useCallback(()=>{if(c==="rectangle"&&u&&h&&p){const N=Math.min(h.x,p.x),y=Math.min(h.y,p.y),C=Math.max(h.x,p.x),G=Math.max(h.y,p.y);if(C-N>5&&G-y>5){const ae=[{x:N,y},{x:C,y},{x:C,y:G},{x:N,y:G}];w(xe=>[...xe,ae])}}d(!1),f(null)},[c,u,h,p]),Me=x.useCallback(()=>{c==="polygon"&&g.length>=3&&(w(N=>[...N,g]),k([]))},[c,g]),Oe=x.useCallback(()=>{w([]),k([]),f(null),v(null),d(!1)},[]),un=x.useCallback(()=>{g.length>0?k(N=>N.slice(0,-1)):b.length>0&&w(N=>N.slice(0,-1))},[g.length,b.length]),dn=x.useCallback(()=>{if(b.length===0){t("Draw at least one mask area first","warning");return}M.mutate({polygons:b,width:j.width,height:j.height,invert:V})},[b,j,V,M,t]),mn=x.useCallback(()=>{window.confirm(`Delete the ${a} mask? This cannot be undone.`)&&Y.mutate()},[a,Y]),hn=x.useCallback(N=>{const y=N.currentTarget;I({width:y.naturalWidth,height:y.naturalHeight}),oe(!1)},[]),pn=x.useMemo(()=>{const N=fn(),y=`/${e}/mjpg/stream`;return N?`${y}?token=${encodeURIComponent(N)}`:y},[e]);return s.jsxs(E,{title:"Mask Editor",description:"Draw mask areas on the camera feed to define motion detection or privacy zones",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"flex gap-4 mb-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Mask Type"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>{i("motion"),Oe()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="motion"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Motion"}),s.jsx("button",{onClick:()=>{i("privacy"),Oe()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="privacy"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Privacy"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Draw Mode"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>l("rectangle"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="rectangle"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Rectangle"}),s.jsx("button",{onClick:()=>l("polygon"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="polygon"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Polygon"})]})]}),s.jsx("div",{className:"flex items-end",children:s.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[s.jsx("input",{type:"checkbox",checked:V,onChange:N=>q(N.target.checked),className:"rounded"}),"Invert (detect outside areas)"]})})]}),U?s.jsx("div",{className:"text-sm text-gray-400 mb-4",children:"Loading mask info..."}):ee?.exists?s.jsxs("div",{className:"text-sm text-green-500 mb-4",children:["Current mask: ",ee.path," (",ee.width,"x",ee.height,")"]}):s.jsxs("div",{className:"text-sm text-gray-400 mb-4",children:["No ",a," mask configured"]}),s.jsxs("div",{ref:o,className:"relative bg-black rounded-lg overflow-hidden mb-4",children:[O?s.jsx("div",{className:"aspect-video bg-surface flex items-center justify-center text-gray-400",children:s.jsxs("div",{className:"text-center",children:[s.jsx("p",{children:"Camera stream unavailable"}),s.jsx("p",{className:"text-xs mt-1",children:"Draw on blank canvas (640x480)"})]})}):s.jsx("img",{src:pn,alt:"Camera stream",onLoad:hn,onError:()=>oe(!0),className:"w-full h-auto",style:{display:"block"}}),s.jsx("canvas",{ref:r,width:j.width,height:j.height,onMouseDown:$e,onMouseMove:be,onMouseUp:W,onMouseLeave:W,onDoubleClick:Me,className:"absolute inset-0 w-full h-full cursor-crosshair",style:{touchAction:"none"}})]}),s.jsx("div",{className:"text-xs text-gray-400 mb-4",children:c==="rectangle"?s.jsxs("p",{children:["Click and drag to draw rectangles. Red areas will be ",a==="motion"?"ignored for motion detection":"blacked out for privacy","."]}):s.jsx("p",{children:"Click to add polygon points. Double-click to complete the polygon."})}),s.jsxs("div",{className:"flex gap-3 flex-wrap",children:[s.jsx("button",{onClick:un,disabled:b.length===0&&g.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Undo"}),s.jsx("button",{onClick:Oe,disabled:b.length===0&&g.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Clear All"}),s.jsx("button",{onClick:dn,disabled:M.isPending||b.length===0,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:M.isPending?"Saving...":"Save Mask"}),ee?.exists&&s.jsx("button",{onClick:mn,disabled:Y.isPending,className:"px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 rounded transition-colors disabled:opacity-50",children:Y.isPending?"Deleting...":"Delete Mask"})]}),b.length>0&&s.jsxs("div",{className:"text-xs text-gray-400 mt-3",children:[b.length," mask area",b.length!==1?"s":""," drawn"]})]})}const me={custom:{label:"Custom Command",description:"Enter your own shell command",command:""},webhook:{label:"Webhook (HTTP POST)",description:"Send HTTP POST to a URL",command:`curl -s -X POST -H "Content-Type: application/json" -d '{"camera":"%t","event":"%v","time":"%Y-%m-%d %T"}' "YOUR_WEBHOOK_URL"`},telegram:{label:"Telegram Bot",description:"Send message via Telegram Bot API",command:'curl -s -X POST "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage" -d "chat_id=YOUR_CHAT_ID&text=Motion detected on %t at %Y-%m-%d %T"'},email:{label:"Email (msmtp)",description:"Send email using msmtp",command:'echo -e "Subject: Motion Alert\\n\\nMotion detected on camera %t at %Y-%m-%d %T" | msmtp recipient@example.com'},pushover:{label:"Pushover",description:"Send push notification via Pushover",command:'curl -s -F "token=YOUR_APP_TOKEN" -F "user=YOUR_USER_KEY" -F "message=Motion on %t at %T" https://api.pushover.net/1/messages.json'}};function Hi({config:e,onChange:t,getError:n}){const[r,o]=x.useState("custom"),a=(c,l="")=>e[c]?.value??l,i=c=>{const l=me[r];l.command&&t(c,l.command)};return s.jsxs(E,{title:"Notifications & Scripts",description:"Configure commands that run on motion events. Use script hooks to send notifications via webhooks, Telegram, email, or custom scripts.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsx("h4",{className:"font-medium mb-2",children:"Available Variables"}),s.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-gray-400",children:[s.jsx("code",{children:"%f"})," ",s.jsx("span",{children:"Filename"}),s.jsx("code",{children:"%t"})," ",s.jsx("span",{children:"Camera name"}),s.jsx("code",{children:"%v"})," ",s.jsx("span",{children:"Event number"}),s.jsx("code",{children:"%Y"})," ",s.jsx("span",{children:"Year (4 digit)"}),s.jsx("code",{children:"%m"})," ",s.jsx("span",{children:"Month (01-12)"}),s.jsx("code",{children:"%d"})," ",s.jsx("span",{children:"Day (01-31)"}),s.jsx("code",{children:"%H"})," ",s.jsx("span",{children:"Hour (00-23)"}),s.jsx("code",{children:"%M"})," ",s.jsx("span",{children:"Minute (00-59)"}),s.jsx("code",{children:"%S"})," ",s.jsx("span",{children:"Second (00-59)"}),s.jsx("code",{children:"%T"})," ",s.jsx("span",{children:"Time HH:MM:SS"})]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-3",children:Object.keys(me).map(c=>s.jsx("button",{onClick:()=>o(c),className:`px-3 py-1.5 text-sm rounded transition-colors ${r===c?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:me[c].label},c))}),r!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-3",children:[s.jsx("p",{children:me[r].description}),s.jsx("pre",{className:"mt-2 p-2 bg-surface rounded text-xs overflow-x-auto whitespace-pre-wrap break-all",children:me[r].command})]}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>i("on_event_start"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Event Start"}),s.jsx("button",{onClick:()=>i("on_picture_save"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Picture Save"}),s.jsx("button",{onClick:()=>i("on_movie_end"),disabled:r==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Movie End"})]})]}),s.jsx(S,{label:"On Event Start",value:String(a("on_event_start","")),onChange:c=>t("on_event_start",c),placeholder:"Command to run when motion event starts",helpText:"Executed when a new motion event begins",error:n?.("on_event_start")}),s.jsx(S,{label:"On Event End",value:String(a("on_event_end","")),onChange:c=>t("on_event_end",c),placeholder:"Command to run when motion event ends",helpText:"Executed when motion event ends (after gap timeout)",error:n?.("on_event_end")}),s.jsx(S,{label:"On Motion Detected",value:String(a("on_motion_detected","")),onChange:c=>t("on_motion_detected",c),placeholder:"Command to run on each motion frame",helpText:"Executed on every frame with motion (can be frequent!)",error:n?.("on_motion_detected")}),s.jsx(S,{label:"On Picture Save",value:String(a("on_picture_save","")),onChange:c=>t("on_picture_save",c),placeholder:"Command to run when picture is saved",helpText:"Executed after a snapshot is saved (%f = filename)",error:n?.("on_picture_save")}),s.jsx(S,{label:"On Movie Start",value:String(a("on_movie_start","")),onChange:c=>t("on_movie_start",c),placeholder:"Command to run when recording starts",helpText:"Executed when video recording begins",error:n?.("on_movie_start")}),s.jsx(S,{label:"On Movie End",value:String(a("on_movie_end","")),onChange:c=>t("on_movie_end",c),placeholder:"Command to run when recording ends",helpText:"Executed when video recording is complete (%f = filename)",error:n?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Example: Send Picture via Telegram"}),s.jsx("pre",{className:"text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap",children:`# On Picture Save: -curl -F chat_id="YOUR_CHAT_ID" \\ - -F photo=@"%f" \\ - -F caption="Motion on %t at %T" \\ - "https://api.telegram.org/botYOUR_TOKEN/sendPhoto"`})]})]})}const ie={rclone:{label:"Rclone",description:"Universal cloud storage sync (supports 40+ providers)",pictureCmd:'rclone copy "%f" remote:motion/pictures/%Y%m%d/',movieCmd:'rclone copy "%f" remote:motion/movies/%Y%m%d/'},s3:{label:"AWS S3",description:"Amazon S3 or compatible storage (MinIO, DigitalOcean Spaces)",pictureCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/pictures/%Y%m%d/',movieCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/movies/%Y%m%d/'},gdrive:{label:"Google Drive",description:"Upload via gdrive CLI",pictureCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"',movieCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"'},dropbox:{label:"Dropbox",description:"Upload via Dropbox-Uploader script",pictureCmd:'dropbox_uploader.sh upload "%f" /motion/pictures/',movieCmd:'dropbox_uploader.sh upload "%f" /motion/movies/'},sftp:{label:"SFTP/SCP",description:"Upload to remote server via SSH",pictureCmd:'scp "%f" user@server:/backup/motion/pictures/',movieCmd:'scp "%f" user@server:/backup/motion/movies/'},custom:{label:"Custom",description:"Enter your own upload command",pictureCmd:"",movieCmd:""}};function Vi({config:e,onChange:t,getError:n}){const[r,o]=x.useState("rclone"),a=(u,d="")=>e[u]?.value??d,i=String(a("on_picture_save","")),c=String(a("on_movie_end","")),l=()=>{const u=ie[r];u.pictureCmd&&t("on_picture_save",u.pictureCmd),u.movieCmd&&t("on_movie_end",u.movieCmd)};return s.jsxs(E,{title:"Cloud Upload",description:"Configure automatic upload of pictures and videos to cloud storage. Uses the same event hooks as notifications.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsxs("p",{className:"text-gray-400 mb-2",children:["Cloud uploads are triggered by the ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_picture_save"})," and ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_movie_end"})," event hooks. Select a provider template below or enter a custom command."]}),s.jsxs("p",{className:"text-xs text-gray-500",children:[s.jsx("strong",{children:"Note:"})," You must install the required CLI tools (rclone, aws-cli, etc.) on your Pi before uploads will work."]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Cloud Provider Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:Object.keys(ie).map(u=>s.jsx("button",{onClick:()=>o(u),className:`px-3 py-1.5 text-sm rounded transition-colors ${r===u?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:ie[u].label},u))}),r!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-4",children:[s.jsx("p",{className:"mb-2",children:ie[r].description}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Picture:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[r].pictureCmd})]}),s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Movie:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[r].movieCmd})]})]})]}),s.jsx("button",{onClick:l,disabled:r==="custom",className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:"Apply Template"})]}),s.jsx(S,{label:"Picture Upload Command",value:i,onChange:u=>t("on_picture_save",u),placeholder:"Command to upload pictures",helpText:"Runs after each picture is saved. %f = filename, %Y%m%d = date",error:n?.("on_picture_save")}),s.jsx(S,{label:"Movie Upload Command",value:c,onChange:u=>t("on_movie_end",u),placeholder:"Command to upload videos",helpText:"Runs after each video recording completes. %f = filename",error:n?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Guides"}),s.jsxs("div",{className:"space-y-4 text-xs text-gray-400",children:[s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"Rclone Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install rclone -sudo apt install rclone - -# Configure a remote (interactive) -rclone config - -# Test upload -rclone copy /path/to/file remote:folder/`})]}),s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"AWS S3 Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install AWS CLI -sudo apt install awscli - -# Configure credentials -aws configure - -# Test upload -aws s3 cp /path/to/file s3://bucket/`})]})]})]})]})}function Bi(){const{role:e}=bn(),{addToast:t}=Be(),[n,r]=x.useState("0"),[o,a]=x.useState({}),[i,c]=x.useState({}),[l,u]=x.useState(!1),d=Rt(),{data:h,isLoading:f,error:p}=qe({queryKey:["config"],queryFn:async()=>{const j=await Ft("/0/api/config");return j.csrf_token&&yn(j.csrf_token),j}}),v=xn(),{data:b}=vn(),{data:w}=Zn(Number(n));x.useEffect(()=>{a({}),c({})},[n]);const g=x.useCallback((j,I)=>{a(Y=>({...Y,[j]:I}));const M=oi(j,String(I));c(Y=>{if(M.success){const{[j]:K,...L}=Y;return L}else return{...Y,[j]:M.error??"Invalid value"}})},[]),k=Object.keys(o).length>0,V=Object.keys(i).length>0,q=x.useMemo(()=>{if(!h)return{};const j=h.configuration.default||{};if(n==="0")return j;{const I=h.configuration[`cam${n}`]||{};return{...j,...I}}},[h,n]),O=x.useMemo(()=>{if(!h)return{};const j={...q};for(const[I,M]of Object.entries(o))j[I]?j[I]={...j[I],value:M}:j[I]={value:M,enabled:!0,category:0,type:"string"};return j},[h,q,o]),oe=async()=>{if(!k){t("No changes to save","info");return}if(V){t("Please fix validation errors before saving","error");return}u(!0);const j=parseInt(n,10);try{const I=await v.mutateAsync({camId:j,changes:o});await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]});const M=I?.summary,Y=I?.applied||[];if(!M)t(`Saved ${Object.keys(o).length} setting(s)`,"success"),a({});else{const K=Y.filter(L=>!L.error&&L.hot_reload===!1).map(L=>L.param);if(K.length>0){t(`Restarting camera to apply ${K.length} setting(s): ${K.join(", ")}...`,"info"),a({});const L=await _n(j);Rn(j),window.dispatchEvent(new CustomEvent(Fn,{detail:{cameraId:j}})),await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]}),L?t(`Applied ${K.length} setting(s). Camera restarted successfully.`,"success"):t("Settings saved. Camera is restarting - refresh page if stream doesn't recover.","warning")}else if(M.errors>0){const L=Y.filter(W=>W.error).map(W=>W.param),$e=Y.filter(W=>!W.error).map(W=>W.param),be={};for(const[W,Me]of Object.entries(o))$e.includes(W)||(be[W]=Me);a(be),M.success>0?t(`Saved ${M.success} setting(s). ${M.errors} failed: ${L.join(", ")}`,"warning"):t(`Failed to save settings: ${L.join(", ")}`,"error")}else t(`Successfully saved ${M.success} setting(s)`,"success"),a({})}}catch(I){console.error("Failed to save settings:",I),t("Failed to save settings. Check browser console for details.","error")}finally{u(!1)}},ee=()=>{a({}),c({}),t("Changes discarded","info")},U=j=>i[j];return e!=="admin"?s.jsx("div",{className:"p-4 sm:p-6",children:s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[s.jsx("svg",{className:"w-16 h-16 mx-auto text-yellow-500 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),s.jsx("h1",{className:"text-2xl font-bold mb-2",children:"Admin Access Required"}),s.jsx("p",{className:"text-gray-400",children:"You must be logged in as an administrator to access settings."})]})}):f?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsxs("div",{className:"animate-pulse",children:[s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"}),s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"})]})]}):p?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsx("div",{className:"bg-danger/10 border border-danger rounded-lg p-4",children:s.jsx("p",{className:"text-danger",children:"Failed to load configuration"})})]}):h?s.jsxs("div",{className:"p-6",children:[s.jsx("div",{className:"sticky top-[73px] z-40 -mx-6 px-6 py-3 bg-surface/95 backdrop-blur border-b border-gray-800 mb-6",children:s.jsxs("div",{className:"flex items-center justify-between",children:[s.jsxs("div",{className:"flex items-center gap-4",children:[s.jsx("h2",{className:"text-2xl font-bold",children:"Settings"}),s.jsx("div",{children:s.jsxs("select",{value:n,onChange:j=>r(j.target.value),className:"px-3 py-1.5 bg-surface-elevated border border-gray-700 rounded-lg text-sm",children:[s.jsx("option",{value:"0",children:"Global Settings"}),h.cameras&&Object.entries(h.cameras).map(([j,I])=>{if(j==="count"||typeof I=="number")return null;const M=I;return s.jsx("option",{value:String(M.id),children:M.name||`Camera ${M.id}`},M.id)})]})})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[k&&!V&&s.jsx("span",{className:"text-yellow-200 text-sm",children:"Unsaved changes"}),V&&s.jsx("span",{className:"text-red-200 text-sm",children:"Fix errors below"}),k&&s.jsx("button",{onClick:ee,disabled:l,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded-lg transition-colors disabled:opacity-50",children:"Discard"}),s.jsx("button",{onClick:oe,disabled:!k||l||V,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:l?"Saving...":k?"Save Changes":"Saved"})]})]})}),n==="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-6",children:s.jsx("p",{className:"text-sm text-blue-200",children:"Global settings apply to the Motion daemon and web server. To configure camera-specific settings, select a camera from the dropdown above."})}),s.jsx(li,{config:O,onChange:g,getError:U,originalConfig:q,systemStatus:b}),s.jsx(Ri,{}),s.jsx(E,{title:"About",description:"Motion version information",collapsible:!0,defaultOpen:!1,children:s.jsxs("p",{className:"text-sm text-gray-400",children:["Motion Version: ",h.version]})})]}),n!=="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-surface-elevated rounded-lg p-4 mb-6",children:s.jsx(Dn,{cameraId:Number(n),readOnly:!1})}),s.jsx(ui,{config:O,onChange:g,getError:U}),s.jsx(di,{config:O,onChange:g,getError:U,capabilities:w,originalConfig:q}),s.jsx(_i,{config:O,onChange:g,getError:U}),s.jsx(pi,{config:O,onChange:g,getError:U}),s.jsx(gi,{config:O,onChange:g,getError:U}),s.jsx(fi,{config:O,onChange:g,getError:U}),s.jsx(Li,{cameraId:parseInt(n,10)}),s.jsx(Zi,{config:O,onChange:g,getError:U}),s.jsx(yi,{config:O,onChange:g,getError:U,originalConfig:q}),s.jsx(mi,{config:O,onChange:g,getError:U}),s.jsx(Hi,{config:O,onChange:g,getError:U}),s.jsx(Vi,{config:O,onChange:g,getError:U}),s.jsx(Ui,{})]})]}):null}export{Bi as Settings}; diff --git a/data/webui/assets/Settings-JwhcLbnw.js b/data/webui/assets/Settings-JwhcLbnw.js new file mode 100644 index 00000000..7d588470 --- /dev/null +++ b/data/webui/assets/Settings-JwhcLbnw.js @@ -0,0 +1,22 @@ +import{r as x,j as s,h as Te,a as Ye,i as Zt,e as qe,k as Ft,l as nt,m as gr,n as xr,f as Lt,c as br,u as vr,o as _r,g as yr}from"./index-tiawrtsp.js";import{c as D,b as V,R as st,f as ot,F as C,g as wr,h as jr,A as Sr,d as Nr,i as kr,j as Cr,m as at,k as it,l as Tr,p as Pr,L as $r,a as zr,n as Or,o as Mr,q as Ir,r as re,s as Ie,E as Ar,t as Er,M as ct,u as Dr,C as Rr,v as Zr,w as Fr}from"./parameterMappings-BmLxmuw_.js";function j({label:e,value:t,onChange:r,type:n="text",placeholder:o,disabled:a=!1,required:i=!1,helpText:c,error:l,min:u,max:d,step:h,originalValue:f,showVisibilityToggle:p}){const[v,g]=x.useState(!1),S=R=>{r(R.target.value)},_=!!l,b=f!==void 0&&String(t)!==String(f),I=n==="password",G=p??I,H=I&&v?"text":n;return s.jsxs("div",{className:"mb-4",children:[s.jsxs("label",{className:"block text-sm font-medium mb-1",children:[e,i&&s.jsx("span",{className:"text-red-500 ml-1",children:"*"}),b&&s.jsx("span",{className:"ml-2 text-xs text-yellow-400",children:"(modified)"})]}),s.jsxs("div",{className:"relative",children:[s.jsx("input",{type:H,value:t,onChange:S,placeholder:o,disabled:a,required:i,min:u,max:d,step:h,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${G?"pr-10":""} ${_?"border-red-500 focus:ring-red-500":b?"border-yellow-500/50 focus:ring-yellow-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":_,"aria-describedby":_?`${e}-error`:void 0}),G&&s.jsx("button",{type:"button",onClick:()=>g(!v),className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-200 transition-colors","aria-label":v?"Hide password":"Show password",children:v?s.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"})}):s.jsxs("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})]})})]}),_&&s.jsx("p",{id:`${e}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:l}),c&&!_&&s.jsx("p",{className:"mt-1 text-sm text-gray-400",children:c})]})}function O({title:e,description:t,children:r,collapsible:n=!1,defaultOpen:o=!0}){const[a,i]=x.useState(o),c=()=>{n&&i(!a)};return s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-6 mb-6",children:[s.jsxs("div",{className:`flex items-center justify-between ${n?"cursor-pointer":""}`,onClick:c,children:[s.jsxs("div",{children:[s.jsx("h3",{className:"text-lg font-semibold",children:e}),t&&s.jsx("p",{className:"text-sm text-gray-400 mt-1",children:t})]}),n&&s.jsx("svg",{className:`w-5 h-5 transition-transform ${a?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),a&&s.jsx("div",{className:"mt-4",children:r})]})}function m(e,t,r){function n(c,l){if(c._zod||Object.defineProperty(c,"_zod",{value:{def:l,constr:i,traits:new Set},enumerable:!1}),c._zod.traits.has(e))return;c._zod.traits.add(e),t(c,l);const u=i.prototype,d=Object.keys(u);for(let h=0;hr?.Parent&&c instanceof r.Parent?!0:c?._zod?.traits?.has(e)}),Object.defineProperty(i,"name",{value:e}),i}class le extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class Ut extends Error{constructor(t){super(`Encountered unidirectional transform during encode: ${t}`),this.name="ZodEncodeError"}}const Lr={};function se(e){return Lr}function Le(e,t){return typeof t=="bigint"?t.toString():t}function Je(e){return e==null}function Ge(e){const t=e.startsWith("^")?1:0,r=e.endsWith("$")?e.length-1:e.length;return e.slice(t,r)}function Ur(e,t){const r=(e.toString().split(".")[1]||"").length,n=t.toString();let o=(n.split(".")[1]||"").length;if(o===0&&/\d?e-\d?/.test(n)){const l=n.match(/\d?e-(\d?)/);l?.[1]&&(o=Number.parseInt(l[1]))}const a=r>o?r:o,i=Number.parseInt(e.toFixed(a).replace(".","")),c=Number.parseInt(t.toFixed(a).replace(".",""));return i%c/10**a}const lt=Symbol("evaluating");function P(e,t,r){let n;Object.defineProperty(e,t,{get(){if(n!==lt)return n===void 0&&(n=lt,n=r()),n},set(o){Object.defineProperty(e,t,{value:o})},configurable:!0})}function Hr(...e){const t={};for(const r of e){const n=Object.getOwnPropertyDescriptors(r);Object.assign(t,n)}return Object.defineProperties({},t)}function Vr(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const Ht="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function ut(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ue(e){if(ut(e)===!1)return!1;const t=e.constructor;if(t===void 0||typeof t!="function")return!0;const r=t.prototype;return!(ut(r)===!1||Object.prototype.hasOwnProperty.call(r,"isPrototypeOf")===!1)}function Vt(e){return Ue(e)?{...e}:Array.isArray(e)?[...e]:e}function Ke(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Br(e,t,r){const n=new e._zod.constr(t??e._zod.def);return(!t||r?.parent)&&(n._zod.parent=e),n}function y(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}const Wr={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ce(e,t=0){if(e.aborted===!0)return!0;for(let r=t;r{var n;return(n=r).path??(n.path=[]),r.path.unshift(e),r})}function _e(e){return typeof e=="string"?e:e?.message}function oe(e,t,r){const n={...e,path:e.path??[]};if(!e.message){const o=_e(e.inst?._zod.def?.error?.(e))??_e(t?.error?.(e))??_e(r.customError?.(e))??_e(r.localeError?.(e))??"Invalid input";n.message=o}return delete n.inst,delete n.continue,t?.reportInput||delete n.input,n}function Qe(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function ge(...e){const[t,r,n]=e;return typeof t=="string"?{message:t,code:"custom",input:r,inst:n}:{...t}}const Bt=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),e.message=JSON.stringify(t,Le,2),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},Wt=m("$ZodError",Bt),Yt=m("$ZodError",Bt,{Parent:Error});function qr(e,t=r=>r.message){const r={},n=[];for(const o of e.issues)o.path.length>0?(r[o.path[0]]=r[o.path[0]]||[],r[o.path[0]].push(t(o))):n.push(t(o));return{formErrors:n,fieldErrors:r}}function Jr(e,t=r=>r.message){const r={_errors:[]},n=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(i=>n({issues:i}));else if(a.code==="invalid_key")n({issues:a.issues});else if(a.code==="invalid_element")n({issues:a.issues});else if(a.path.length===0)r._errors.push(t(a));else{let i=r,c=0;for(;c(t,r,n,o)=>{const a=n?Object.assign(n,{async:!1}):{async:!1},i=t._zod.run({value:r,issues:[]},a);if(i instanceof Promise)throw new le;if(i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>oe(l,a,se())));throw Ht(c,o?.callee),c}return i.value},et=e=>async(t,r,n,o)=>{const a=n?Object.assign(n,{async:!0}):{async:!0};let i=t._zod.run({value:r,issues:[]},a);if(i instanceof Promise&&(i=await i),i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>oe(l,a,se())));throw Ht(c,o?.callee),c}return i.value},Pe=e=>(t,r,n)=>{const o=n?{...n,async:!1}:{async:!1},a=t._zod.run({value:r,issues:[]},o);if(a instanceof Promise)throw new le;return a.issues.length?{success:!1,error:new(e??Wt)(a.issues.map(i=>oe(i,o,se())))}:{success:!0,data:a.value}},Gr=Pe(Yt),$e=e=>async(t,r,n)=>{const o=n?Object.assign(n,{async:!0}):{async:!0};let a=t._zod.run({value:r,issues:[]},o);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(i=>oe(i,o,se())))}:{success:!0,data:a.value}},Kr=$e(Yt),Qr=e=>(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return Xe(e)(t,r,o)},Xr=e=>(t,r,n)=>Xe(e)(t,r,n),en=e=>async(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return et(e)(t,r,o)},tn=e=>async(t,r,n)=>et(e)(t,r,n),rn=e=>(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return Pe(e)(t,r,o)},nn=e=>(t,r,n)=>Pe(e)(t,r,n),sn=e=>async(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return $e(e)(t,r,o)},on=e=>async(t,r,n)=>$e(e)(t,r,n),an=/^[cC][^\s-]{8,}$/,cn=/^[0-9a-z]+$/,ln=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,un=/^[0-9a-vA-V]{20}$/,dn=/^[A-Za-z0-9]{27}$/,mn=/^[a-zA-Z0-9_-]{21}$/,hn=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,pn=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,dt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,fn=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,gn="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function xn(){return new RegExp(gn,"u")}const bn=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,vn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,_n=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,yn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,wn=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,qt=/^[A-Za-z0-9_-]*$/,jn=/^\+(?:[0-9]){6,14}[0-9]$/,Jt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",Sn=new RegExp(`^${Jt}$`);function Gt(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function Nn(e){return new RegExp(`^${Gt(e)}$`)}function kn(e){const t=Gt({precision:e.precision}),r=["Z"];e.local&&r.push(""),e.offset&&r.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const n=`${t}(?:${r.join("|")})`;return new RegExp(`^${Jt}T(?:${n})$`)}const Cn=e=>{const t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},Tn=/^-?\d+$/,Pn=/^-?\d+(?:\.\d+)?/,$n=/^[^A-Z]*$/,zn=/^[^a-z]*$/,B=m("$ZodCheck",(e,t)=>{var r;e._zod??(e._zod={}),e._zod.def=t,(r=e._zod).onattach??(r.onattach=[])}),Kt={number:"number",bigint:"bigint",object:"date"},Qt=m("$ZodCheckLessThan",(e,t)=>{B.init(e,t);const r=Kt[typeof t.value];e._zod.onattach.push(n=>{const o=n._zod.bag,a=(t.inclusive?o.maximum:o.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?n.value<=t.value:n.value{B.init(e,t);const r=Kt[typeof t.value];e._zod.onattach.push(n=>{const o=n._zod.bag,a=(t.inclusive?o.minimum:o.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>a&&(t.inclusive?o.minimum=t.value:o.exclusiveMinimum=t.value)}),e._zod.check=n=>{(t.inclusive?n.value>=t.value:n.value>t.value)||n.issues.push({origin:r,code:"too_small",minimum:t.value,input:n.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),On=m("$ZodCheckMultipleOf",(e,t)=>{B.init(e,t),e._zod.onattach.push(r=>{var n;(n=r._zod.bag).multipleOf??(n.multipleOf=t.value)}),e._zod.check=r=>{if(typeof r.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof r.value=="bigint"?r.value%t.value===BigInt(0):Ur(r.value,t.value)===0)||r.issues.push({origin:typeof r.value,code:"not_multiple_of",divisor:t.value,input:r.value,inst:e,continue:!t.abort})}}),Mn=m("$ZodCheckNumberFormat",(e,t)=>{B.init(e,t),t.format=t.format||"float64";const r=t.format?.includes("int"),n=r?"int":"number",[o,a]=Wr[t.format];e._zod.onattach.push(i=>{const c=i._zod.bag;c.format=t.format,c.minimum=o,c.maximum=a,r&&(c.pattern=Tn)}),e._zod.check=i=>{const c=i.value;if(r){if(!Number.isInteger(c)){i.issues.push({expected:n,format:t.format,code:"invalid_type",continue:!1,input:c,inst:e});return}if(!Number.isSafeInteger(c)){c>0?i.issues.push({input:c,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}):i.issues.push({input:c,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort});return}}ca&&i.issues.push({origin:"number",input:c,code:"too_big",maximum:a,inst:e})}}),In=m("$ZodCheckMaxLength",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const o=n.value;if(o.length<=t.maximum)return;const i=Qe(o);n.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),An=m("$ZodCheckMinLength",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>o&&(n._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{const o=n.value;if(o.length>=t.minimum)return;const i=Qe(o);n.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),En=m("$ZodCheckLengthEquals",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag;o.minimum=t.length,o.maximum=t.length,o.length=t.length}),e._zod.check=n=>{const o=n.value,a=o.length;if(a===t.length)return;const i=Qe(o),c=a>t.length;n.issues.push({origin:i,...c?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:n.value,inst:e,continue:!t.abort})}}),ze=m("$ZodCheckStringFormat",(e,t)=>{var r,n;B.init(e,t),e._zod.onattach.push(o=>{const a=o._zod.bag;a.format=t.format,t.pattern&&(a.patterns??(a.patterns=new Set),a.patterns.add(t.pattern))}),t.pattern?(r=e._zod).check??(r.check=o=>{t.pattern.lastIndex=0,!t.pattern.test(o.value)&&o.issues.push({origin:"string",code:"invalid_format",format:t.format,input:o.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(n=e._zod).check??(n.check=()=>{})}),Dn=m("$ZodCheckRegex",(e,t)=>{ze.init(e,t),e._zod.check=r=>{t.pattern.lastIndex=0,!t.pattern.test(r.value)&&r.issues.push({origin:"string",code:"invalid_format",format:"regex",input:r.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),Rn=m("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=$n),ze.init(e,t)}),Zn=m("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=zn),ze.init(e,t)}),Fn=m("$ZodCheckIncludes",(e,t)=>{B.init(e,t);const r=Ke(t.includes),n=new RegExp(typeof t.position=="number"?`^.{${t.position}}${r}`:r);t.pattern=n,e._zod.onattach.push(o=>{const a=o._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(n)}),e._zod.check=o=>{o.value.includes(t.includes,t.position)||o.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:o.value,inst:e,continue:!t.abort})}}),Ln=m("$ZodCheckStartsWith",(e,t)=>{B.init(e,t);const r=new RegExp(`^${Ke(t.prefix)}.*`);t.pattern??(t.pattern=r),e._zod.onattach.push(n=>{const o=n._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=n=>{n.value.startsWith(t.prefix)||n.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:n.value,inst:e,continue:!t.abort})}}),Un=m("$ZodCheckEndsWith",(e,t)=>{B.init(e,t);const r=new RegExp(`.*${Ke(t.suffix)}$`);t.pattern??(t.pattern=r),e._zod.onattach.push(n=>{const o=n._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=n=>{n.value.endsWith(t.suffix)||n.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:n.value,inst:e,continue:!t.abort})}}),Hn=m("$ZodCheckOverwrite",(e,t)=>{B.init(e,t),e._zod.check=r=>{r.value=t.tx(r.value)}}),Vn={major:4,minor:2,patch:1},Z=m("$ZodType",(e,t)=>{var r;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=Vn;const n=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&n.unshift(e);for(const o of n)for(const a of o._zod.onattach)a(e);if(n.length===0)(r=e._zod).deferred??(r.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{const o=(i,c,l)=>{let u=ce(i),d;for(const h of c){if(h._zod.def.when){if(!h._zod.def.when(i))continue}else if(u)continue;const f=i.issues.length,p=h._zod.check(i);if(p instanceof Promise&&l?.async===!1)throw new le;if(d||p instanceof Promise)d=(d??Promise.resolve()).then(async()=>{await p,i.issues.length!==f&&(u||(u=ce(i,f)))});else{if(i.issues.length===f)continue;u||(u=ce(i,f))}}return d?d.then(()=>i):i},a=(i,c,l)=>{if(ce(i))return i.aborted=!0,i;const u=o(c,n,l);if(u instanceof Promise){if(l.async===!1)throw new le;return u.then(d=>e._zod.parse(d,l))}return e._zod.parse(u,l)};e._zod.run=(i,c)=>{if(c.skipChecks)return e._zod.parse(i,c);if(c.direction==="backward"){const u=e._zod.parse({value:i.value,issues:[]},{...c,skipChecks:!0});return u instanceof Promise?u.then(d=>a(d,i,c)):a(u,i,c)}const l=e._zod.parse(i,c);if(l instanceof Promise){if(c.async===!1)throw new le;return l.then(u=>o(u,n,c))}return o(l,n,c)}}e["~standard"]={validate:o=>{try{const a=Gr(e,o);return a.success?{value:a.data}:{issues:a.error?.issues}}catch{return Kr(e,o).then(i=>i.success?{value:i.data}:{issues:i.error?.issues})}},vendor:"zod",version:1}}),tt=m("$ZodString",(e,t)=>{Z.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??Cn(e._zod.bag),e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=String(r.value)}catch{}return typeof r.value=="string"||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),$=m("$ZodStringFormat",(e,t)=>{ze.init(e,t),tt.init(e,t)}),Bn=m("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=pn),$.init(e,t)}),Wn=m("$ZodUUID",(e,t)=>{if(t.version){const n={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(n===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=dt(n))}else t.pattern??(t.pattern=dt());$.init(e,t)}),Yn=m("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=fn),$.init(e,t)}),qn=m("$ZodURL",(e,t)=>{$.init(e,t),e._zod.check=r=>{try{const n=r.value.trim(),o=new URL(n);t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(o.hostname)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:t.hostname.source,input:r.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(o.protocol.endsWith(":")?o.protocol.slice(0,-1):o.protocol)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:r.value,inst:e,continue:!t.abort})),t.normalize?r.value=o.href:r.value=n;return}catch{r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!t.abort})}}}),Jn=m("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=xn()),$.init(e,t)}),Gn=m("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=mn),$.init(e,t)}),Kn=m("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=an),$.init(e,t)}),Qn=m("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=cn),$.init(e,t)}),Xn=m("$ZodULID",(e,t)=>{t.pattern??(t.pattern=ln),$.init(e,t)}),es=m("$ZodXID",(e,t)=>{t.pattern??(t.pattern=un),$.init(e,t)}),ts=m("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=dn),$.init(e,t)}),rs=m("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=kn(t)),$.init(e,t)}),ns=m("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=Sn),$.init(e,t)}),ss=m("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=Nn(t)),$.init(e,t)}),os=m("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=hn),$.init(e,t)}),as=m("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=bn),$.init(e,t),e._zod.bag.format="ipv4"}),is=m("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=vn),$.init(e,t),e._zod.bag.format="ipv6",e._zod.check=r=>{try{new URL(`http://[${r.value}]`)}catch{r.issues.push({code:"invalid_format",format:"ipv6",input:r.value,inst:e,continue:!t.abort})}}}),cs=m("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=_n),$.init(e,t)}),ls=m("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=yn),$.init(e,t),e._zod.check=r=>{const n=r.value.split("/");try{if(n.length!==2)throw new Error;const[o,a]=n;if(!a)throw new Error;const i=Number(a);if(`${i}`!==a)throw new Error;if(i<0||i>128)throw new Error;new URL(`http://[${o}]`)}catch{r.issues.push({code:"invalid_format",format:"cidrv6",input:r.value,inst:e,continue:!t.abort})}}});function er(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const us=m("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=wn),$.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=r=>{er(r.value)||r.issues.push({code:"invalid_format",format:"base64",input:r.value,inst:e,continue:!t.abort})}});function ds(e){if(!qt.test(e))return!1;const t=e.replace(/[-_]/g,n=>n==="-"?"+":"/"),r=t.padEnd(Math.ceil(t.length/4)*4,"=");return er(r)}const ms=m("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=qt),$.init(e,t),e._zod.bag.contentEncoding="base64url",e._zod.check=r=>{ds(r.value)||r.issues.push({code:"invalid_format",format:"base64url",input:r.value,inst:e,continue:!t.abort})}}),hs=m("$ZodE164",(e,t)=>{t.pattern??(t.pattern=jn),$.init(e,t)});function ps(e,t=null){try{const r=e.split(".");if(r.length!==3)return!1;const[n]=r;if(!n)return!1;const o=JSON.parse(atob(n));return!("typ"in o&&o?.typ!=="JWT"||!o.alg||t&&(!("alg"in o)||o.alg!==t))}catch{return!1}}const fs=m("$ZodJWT",(e,t)=>{$.init(e,t),e._zod.check=r=>{ps(r.value,t.alg)||r.issues.push({code:"invalid_format",format:"jwt",input:r.value,inst:e,continue:!t.abort})}}),tr=m("$ZodNumber",(e,t)=>{Z.init(e,t),e._zod.pattern=e._zod.bag.pattern??Pn,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=Number(r.value)}catch{}const o=r.value;if(typeof o=="number"&&!Number.isNaN(o)&&Number.isFinite(o))return r;const a=typeof o=="number"?Number.isNaN(o)?"NaN":Number.isFinite(o)?void 0:"Infinity":void 0;return r.issues.push({expected:"number",code:"invalid_type",input:o,inst:e,...a?{received:a}:{}}),r}}),gs=m("$ZodNumberFormat",(e,t)=>{Mn.init(e,t),tr.init(e,t)});function mt(e,t,r){e.issues.length&&t.issues.push(...Yr(r,e.issues)),t.value[r]=e.value}const xs=m("$ZodArray",(e,t)=>{Z.init(e,t),e._zod.parse=(r,n)=>{const o=r.value;if(!Array.isArray(o))return r.issues.push({expected:"array",code:"invalid_type",input:o,inst:e}),r;r.value=Array(o.length);const a=[];for(let i=0;imt(u,r,i))):mt(l,r,i)}return a.length?Promise.all(a).then(()=>r):r}});function ht(e,t,r,n){for(const a of e)if(a.issues.length===0)return t.value=a.value,t;const o=e.filter(a=>!ce(a));return o.length===1?(t.value=o[0].value,o[0]):(t.issues.push({code:"invalid_union",input:t.value,inst:r,errors:e.map(a=>a.issues.map(i=>oe(i,n,se())))}),t)}const bs=m("$ZodUnion",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.options.some(o=>o._zod.optin==="optional")?"optional":void 0),P(e._zod,"optout",()=>t.options.some(o=>o._zod.optout==="optional")?"optional":void 0),P(e._zod,"values",()=>{if(t.options.every(o=>o._zod.values))return new Set(t.options.flatMap(o=>Array.from(o._zod.values)))}),P(e._zod,"pattern",()=>{if(t.options.every(o=>o._zod.pattern)){const o=t.options.map(a=>a._zod.pattern);return new RegExp(`^(${o.map(a=>Ge(a.source)).join("|")})$`)}});const r=t.options.length===1,n=t.options[0]._zod.run;e._zod.parse=(o,a)=>{if(r)return n(o,a);let i=!1;const c=[];for(const l of t.options){const u=l._zod.run({value:o.value,issues:[]},a);if(u instanceof Promise)c.push(u),i=!0;else{if(u.issues.length===0)return u;c.push(u)}}return i?Promise.all(c).then(l=>ht(l,o,e,a)):ht(c,o,e,a)}}),vs=m("$ZodIntersection",(e,t)=>{Z.init(e,t),e._zod.parse=(r,n)=>{const o=r.value,a=t.left._zod.run({value:o,issues:[]},n),i=t.right._zod.run({value:o,issues:[]},n);return a instanceof Promise||i instanceof Promise?Promise.all([a,i]).then(([l,u])=>pt(r,l,u)):pt(r,a,i)}});function He(e,t){if(e===t)return{valid:!0,data:e};if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Ue(e)&&Ue(t)){const r=Object.keys(t),n=Object.keys(e).filter(a=>r.indexOf(a)!==-1),o={...e,...t};for(const a of n){const i=He(e[a],t[a]);if(!i.valid)return{valid:!1,mergeErrorPath:[a,...i.mergeErrorPath]};o[a]=i.data}return{valid:!0,data:o}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};const r=[];for(let n=0;n{Z.init(e,t),e._zod.parse=(r,n)=>{if(n.direction==="backward")throw new Ut(e.constructor.name);const o=t.transform(r.value,r);if(n.async)return(o instanceof Promise?o:Promise.resolve(o)).then(i=>(r.value=i,r));if(o instanceof Promise)throw new le;return r.value=o,r}});function ft(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const ys=m("$ZodOptional",(e,t)=>{Z.init(e,t),e._zod.optin="optional",e._zod.optout="optional",P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),P(e._zod,"pattern",()=>{const r=t.innerType._zod.pattern;return r?new RegExp(`^(${Ge(r.source)})?$`):void 0}),e._zod.parse=(r,n)=>{if(t.innerType._zod.optin==="optional"){const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>ft(a,r.value)):ft(o,r.value)}return r.value===void 0?r:t.innerType._zod.run(r,n)}}),ws=m("$ZodNullable",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"pattern",()=>{const r=t.innerType._zod.pattern;return r?new RegExp(`^(${Ge(r.source)}|null)$`):void 0}),P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(r,n)=>r.value===null?r:t.innerType._zod.run(r,n)}),js=m("$ZodDefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);if(r.value===void 0)return r.value=t.defaultValue,r;const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>gt(a,t)):gt(o,t)}});function gt(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}const Ss=m("$ZodPrefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>(n.direction==="backward"||r.value===void 0&&(r.value=t.defaultValue),t.innerType._zod.run(r,n))}),Ns=m("$ZodNonOptional",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>{const r=t.innerType._zod.values;return r?new Set([...r].filter(n=>n!==void 0)):void 0}),e._zod.parse=(r,n)=>{const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>xt(a,e)):xt(o,e)}});function xt(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}const ks=m("$ZodCatch",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>(r.value=a.value,a.issues.length&&(r.value=t.catchValue({...r,error:{issues:a.issues.map(i=>oe(i,n,se()))},input:r.value}),r.issues=[]),r)):(r.value=o.value,o.issues.length&&(r.value=t.catchValue({...r,error:{issues:o.issues.map(a=>oe(a,n,se()))},input:r.value}),r.issues=[]),r)}}),Cs=m("$ZodPipe",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>t.in._zod.values),P(e._zod,"optin",()=>t.in._zod.optin),P(e._zod,"optout",()=>t.out._zod.optout),P(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(r,n)=>{if(n.direction==="backward"){const a=t.out._zod.run(r,n);return a instanceof Promise?a.then(i=>ye(i,t.in,n)):ye(a,t.in,n)}const o=t.in._zod.run(r,n);return o instanceof Promise?o.then(a=>ye(a,t.out,n)):ye(o,t.out,n)}});function ye(e,t,r){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},r)}const Ts=m("$ZodReadonly",(e,t)=>{Z.init(e,t),P(e._zod,"propValues",()=>t.innerType._zod.propValues),P(e._zod,"values",()=>t.innerType._zod.values),P(e._zod,"optin",()=>t.innerType?._zod?.optin),P(e._zod,"optout",()=>t.innerType?._zod?.optout),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(bt):bt(o)}});function bt(e){return e.value=Object.freeze(e.value),e}const Ps=m("$ZodCustom",(e,t)=>{B.init(e,t),Z.init(e,t),e._zod.parse=(r,n)=>r,e._zod.check=r=>{const n=r.value,o=t.fn(n);if(o instanceof Promise)return o.then(a=>vt(a,r,n,e));vt(o,r,n,e)}});function vt(e,t,r,n){if(!e){const o={code:"custom",input:r,inst:n,path:[...n._zod.def.path??[]],continue:!n._zod.def.abort};n._zod.def.params&&(o.params=n._zod.def.params),t.issues.push(ge(o))}}var _t;class $s{constructor(){this._map=new WeakMap,this._idmap=new Map}add(t,...r){const n=r[0];if(this._map.set(t,n),n&&typeof n=="object"&&"id"in n){if(this._idmap.has(n.id))throw new Error(`ID ${n.id} already exists in the registry`);this._idmap.set(n.id,t)}return this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(t){const r=this._map.get(t);return r&&typeof r=="object"&&"id"in r&&this._idmap.delete(r.id),this._map.delete(t),this}get(t){const r=t._zod.parent;if(r){const n={...this.get(r)??{}};delete n.id;const o={...n,...this._map.get(t)};return Object.keys(o).length?o:void 0}return this._map.get(t)}has(t){return this._map.has(t)}}function zs(){return new $s}(_t=globalThis).__zod_globalRegistry??(_t.__zod_globalRegistry=zs());const fe=globalThis.__zod_globalRegistry;function Os(e,t){return new e({type:"string",...y(t)})}function Ms(e,t){return new e({type:"string",format:"email",check:"string_format",abort:!1,...y(t)})}function yt(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...y(t)})}function Is(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...y(t)})}function As(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...y(t)})}function Es(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...y(t)})}function Ds(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...y(t)})}function Rs(e,t){return new e({type:"string",format:"url",check:"string_format",abort:!1,...y(t)})}function Zs(e,t){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...y(t)})}function Fs(e,t){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...y(t)})}function Ls(e,t){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...y(t)})}function Us(e,t){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...y(t)})}function Hs(e,t){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...y(t)})}function Vs(e,t){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...y(t)})}function Bs(e,t){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...y(t)})}function Ws(e,t){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...y(t)})}function Ys(e,t){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...y(t)})}function qs(e,t){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...y(t)})}function Js(e,t){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...y(t)})}function Gs(e,t){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...y(t)})}function Ks(e,t){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...y(t)})}function Qs(e,t){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...y(t)})}function Xs(e,t){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...y(t)})}function eo(e,t){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...y(t)})}function to(e,t){return new e({type:"string",format:"date",check:"string_format",...y(t)})}function ro(e,t){return new e({type:"string",format:"time",check:"string_format",precision:null,...y(t)})}function no(e,t){return new e({type:"string",format:"duration",check:"string_format",...y(t)})}function so(e,t){return new e({type:"number",coerce:!0,checks:[],...y(t)})}function oo(e,t){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...y(t)})}function wt(e,t){return new Qt({check:"less_than",...y(t),value:e,inclusive:!1})}function Ae(e,t){return new Qt({check:"less_than",...y(t),value:e,inclusive:!0})}function jt(e,t){return new Xt({check:"greater_than",...y(t),value:e,inclusive:!1})}function Ee(e,t){return new Xt({check:"greater_than",...y(t),value:e,inclusive:!0})}function St(e,t){return new On({check:"multiple_of",...y(t),value:e})}function rr(e,t){return new In({check:"max_length",...y(t),maximum:e})}function ke(e,t){return new An({check:"min_length",...y(t),minimum:e})}function nr(e,t){return new En({check:"length_equals",...y(t),length:e})}function ao(e,t){return new Dn({check:"string_format",format:"regex",...y(t),pattern:e})}function io(e){return new Rn({check:"string_format",format:"lowercase",...y(e)})}function co(e){return new Zn({check:"string_format",format:"uppercase",...y(e)})}function lo(e,t){return new Fn({check:"string_format",format:"includes",...y(t),includes:e})}function uo(e,t){return new Ln({check:"string_format",format:"starts_with",...y(t),prefix:e})}function mo(e,t){return new Un({check:"string_format",format:"ends_with",...y(t),suffix:e})}function ue(e){return new Hn({check:"overwrite",tx:e})}function ho(e){return ue(t=>t.normalize(e))}function po(){return ue(e=>e.trim())}function fo(){return ue(e=>e.toLowerCase())}function go(){return ue(e=>e.toUpperCase())}function xo(){return ue(e=>Vr(e))}function bo(e,t,r){return new e({type:"array",element:t,...y(r)})}function vo(e,t,r){return new e({type:"custom",check:"custom",fn:t,...y(r)})}function _o(e){const t=yo(r=>(r.addIssue=n=>{if(typeof n=="string")r.issues.push(ge(n,r.value,t._zod.def));else{const o=n;o.fatal&&(o.continue=!1),o.code??(o.code="custom"),o.input??(o.input=r.value),o.inst??(o.inst=t),o.continue??(o.continue=!t._zod.def.abort),r.issues.push(ge(o))}},e(r.value,r)));return t}function yo(e,t){const r=new B({check:"custom",...y(t)});return r._zod.check=e,r}function sr(e){let t=e?.target??"draft-2020-12";return t==="draft-4"&&(t="draft-04"),t==="draft-7"&&(t="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??fe,target:t,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref",reused:e?.reused??"inline",external:e?.external??void 0}}function L(e,t,r={path:[],schemaPath:[]}){var n;const o=e._zod.def,a=t.seen.get(e);if(a)return a.count++,r.schemaPath.includes(e)&&(a.cycle=r.path),a.schema;const i={schema:{},count:1,cycle:void 0,path:r.path};t.seen.set(e,i);const c=e._zod.toJSONSchema?.();if(c)i.schema=c;else{const d={...r,schemaPath:[...r.schemaPath,e],path:r.path},h=e._zod.parent;if(h)i.ref=h,L(h,t,d),t.seen.get(h).isParent=!0;else if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,d);else{const f=i.schema,p=t.processors[o.type];if(!p)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${o.type}`);p(e,t,f,d)}}const l=t.metadataRegistry.get(e);return l&&Object.assign(i.schema,l),t.io==="input"&&F(e)&&(delete i.schema.examples,delete i.schema.default),t.io==="input"&&i.schema._prefault&&((n=i.schema).default??(n.default=i.schema._prefault)),delete i.schema._prefault,t.seen.get(e).schema}function or(e,t){const r=e.seen.get(t);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");const n=a=>{const i=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const d=e.external.registry.get(a[0])?.id,h=e.external.uri??(p=>p);if(d)return{ref:h(d)};const f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${h("__shared")}#/${i}/${f}`}}if(a[1]===r)return{ref:"#"};const l=`#/${i}/`,u=a[1].schema.id??`__schema${e.counter++}`;return{defId:u,ref:l+u}},o=a=>{if(a[1].schema.$ref)return;const i=a[1],{ref:c,defId:l}=n(a);i.def={...i.schema},l&&(i.defId=l);const u=i.schema;for(const d in u)delete u[d];u.$ref=c};if(e.cycles==="throw")for(const a of e.seen.entries()){const i=a[1];if(i.cycle)throw new Error(`Cycle detected: #/${i.cycle?.join("/")}/ + +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const a of e.seen.entries()){const i=a[1];if(t===a[0]){o(a);continue}if(e.external){const l=e.external.registry.get(a[0])?.id;if(t!==a[0]&&l){o(a);continue}}if(e.metadataRegistry.get(a[0])?.id){o(a);continue}if(i.cycle){o(a);continue}if(i.count>1&&e.reused==="ref"){o(a);continue}}}function ar(e,t){const r=e.seen.get(t);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");const n=i=>{const c=e.seen.get(i),l=c.def??c.schema,u={...l};if(c.ref===null)return;const d=c.ref;if(c.ref=null,d){n(d);const h=e.seen.get(d).schema;h.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(l.allOf=l.allOf??[],l.allOf.push(h)):(Object.assign(l,h),Object.assign(l,u))}c.isParent||e.override({zodSchema:i,jsonSchema:l,path:c.path??[]})};for(const i of[...e.seen.entries()].reverse())n(i[0]);const o={};if(e.target==="draft-2020-12"?o.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?o.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?o.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){const i=e.external.registry.get(t)?.id;if(!i)throw new Error("Schema is missing an `id` property");o.$id=e.external.uri(i)}Object.assign(o,r.def??r.schema);const a=e.external?.defs??{};for(const i of e.seen.entries()){const c=i[1];c.def&&c.defId&&(a[c.defId]=c.def)}e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?o.$defs=a:o.definitions=a);try{const i=JSON.parse(JSON.stringify(o));return Object.defineProperty(i,"~standard",{value:{...t["~standard"],jsonSchema:{input:Ce(t,"input"),output:Ce(t,"output")}},enumerable:!1,writable:!1}),i}catch{throw new Error("Error converting schema to JSON.")}}function F(e,t){const r=t??{seen:new Set};if(r.seen.has(e))return!1;r.seen.add(e);const n=e._zod.def;if(n.type==="transform")return!0;if(n.type==="array")return F(n.element,r);if(n.type==="set")return F(n.valueType,r);if(n.type==="lazy")return F(n.getter(),r);if(n.type==="promise"||n.type==="optional"||n.type==="nonoptional"||n.type==="nullable"||n.type==="readonly"||n.type==="default"||n.type==="prefault")return F(n.innerType,r);if(n.type==="intersection")return F(n.left,r)||F(n.right,r);if(n.type==="record"||n.type==="map")return F(n.keyType,r)||F(n.valueType,r);if(n.type==="pipe")return F(n.in,r)||F(n.out,r);if(n.type==="object"){for(const o in n.shape)if(F(n.shape[o],r))return!0;return!1}if(n.type==="union"){for(const o of n.options)if(F(o,r))return!0;return!1}if(n.type==="tuple"){for(const o of n.items)if(F(o,r))return!0;return!!(n.rest&&F(n.rest,r))}return!1}const wo=(e,t={})=>r=>{const n=sr({...r,processors:t});return L(e,n),or(n,e),ar(n,e)},Ce=(e,t)=>r=>{const{libraryOptions:n,target:o}=r??{},a=sr({...n??{},target:o,io:t,processors:{}});return L(e,a),or(a,e),ar(a,e)},jo={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},So=(e,t,r,n)=>{const o=r;o.type="string";const{minimum:a,maximum:i,format:c,patterns:l,contentEncoding:u}=e._zod.bag;if(typeof a=="number"&&(o.minLength=a),typeof i=="number"&&(o.maxLength=i),c&&(o.format=jo[c]??c,o.format===""&&delete o.format),u&&(o.contentEncoding=u),l&&l.size>0){const d=[...l];d.length===1?o.pattern=d[0].source:d.length>1&&(o.allOf=[...d.map(h=>({...t.target==="draft-07"||t.target==="draft-04"||t.target==="openapi-3.0"?{type:"string"}:{},pattern:h.source}))])}},No=(e,t,r,n)=>{const o=r,{minimum:a,maximum:i,format:c,multipleOf:l,exclusiveMaximum:u,exclusiveMinimum:d}=e._zod.bag;typeof c=="string"&&c.includes("int")?o.type="integer":o.type="number",typeof d=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.minimum=d,o.exclusiveMinimum=!0):o.exclusiveMinimum=d),typeof a=="number"&&(o.minimum=a,typeof d=="number"&&t.target!=="draft-04"&&(d>=a?delete o.minimum:delete o.exclusiveMinimum)),typeof u=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.maximum=u,o.exclusiveMaximum=!0):o.exclusiveMaximum=u),typeof i=="number"&&(o.maximum=i,typeof u=="number"&&t.target!=="draft-04"&&(u<=i?delete o.maximum:delete o.exclusiveMaximum)),typeof l=="number"&&(o.multipleOf=l)},ko=(e,t,r,n)=>{if(t.unrepresentable==="throw")throw new Error("Custom types cannot be represented in JSON Schema")},Co=(e,t,r,n)=>{if(t.unrepresentable==="throw")throw new Error("Transforms cannot be represented in JSON Schema")},To=(e,t,r,n)=>{const o=r,a=e._zod.def,{minimum:i,maximum:c}=e._zod.bag;typeof i=="number"&&(o.minItems=i),typeof c=="number"&&(o.maxItems=c),o.type="array",o.items=L(a.element,t,{...n,path:[...n.path,"items"]})},Po=(e,t,r,n)=>{const o=e._zod.def,a=o.inclusive===!1,i=o.options.map((c,l)=>L(c,t,{...n,path:[...n.path,a?"oneOf":"anyOf",l]}));a?r.oneOf=i:r.anyOf=i},$o=(e,t,r,n)=>{const o=e._zod.def,a=L(o.left,t,{...n,path:[...n.path,"allOf",0]}),i=L(o.right,t,{...n,path:[...n.path,"allOf",1]}),c=u=>"allOf"in u&&Object.keys(u).length===1,l=[...c(a)?a.allOf:[a],...c(i)?i.allOf:[i]];r.allOf=l},zo=(e,t,r,n)=>{const o=e._zod.def,a=L(o.innerType,t,n),i=t.seen.get(e);t.target==="openapi-3.0"?(i.ref=o.innerType,r.nullable=!0):r.anyOf=[a,{type:"null"}]},Oo=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType},Mo=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,r.default=JSON.parse(JSON.stringify(o.defaultValue))},Io=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,t.io==="input"&&(r._prefault=JSON.parse(JSON.stringify(o.defaultValue)))},Ao=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType;let i;try{i=o.catchValue(void 0)}catch{throw new Error("Dynamic catch values are not supported in JSON Schema")}r.default=i},Eo=(e,t,r,n)=>{const o=e._zod.def,a=t.io==="input"?o.in._zod.def.type==="transform"?o.out:o.in:o.out;L(a,t,n);const i=t.seen.get(e);i.ref=a},Do=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,r.readOnly=!0},Ro=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType},Zo=m("ZodISODateTime",(e,t)=>{rs.init(e,t),M.init(e,t)});function Fo(e){return eo(Zo,e)}const Lo=m("ZodISODate",(e,t)=>{ns.init(e,t),M.init(e,t)});function Uo(e){return to(Lo,e)}const Ho=m("ZodISOTime",(e,t)=>{ss.init(e,t),M.init(e,t)});function Vo(e){return ro(Ho,e)}const Bo=m("ZodISODuration",(e,t)=>{os.init(e,t),M.init(e,t)});function Wo(e){return no(Bo,e)}const Yo=(e,t)=>{Wt.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:r=>Jr(e,r)},flatten:{value:r=>qr(e,r)},addIssue:{value:r=>{e.issues.push(r),e.message=JSON.stringify(e.issues,Le,2)}},addIssues:{value:r=>{e.issues.push(...r),e.message=JSON.stringify(e.issues,Le,2)}},isEmpty:{get(){return e.issues.length===0}}})},J=m("ZodError",Yo,{Parent:Error}),qo=Xe(J),Jo=et(J),Go=Pe(J),Ko=$e(J),Qo=Qr(J),Xo=Xr(J),ea=en(J),ta=tn(J),ra=rn(J),na=nn(J),sa=sn(J),oa=on(J),U=m("ZodType",(e,t)=>(Z.init(e,t),Object.assign(e["~standard"],{jsonSchema:{input:Ce(e,"input"),output:Ce(e,"output")}}),e.toJSONSchema=wo(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{value:t}),e.check=(...r)=>e.clone(Hr(t,{checks:[...t.checks??[],...r.map(n=>typeof n=="function"?{_zod:{check:n,def:{check:"custom"},onattach:[]}}:n)]})),e.clone=(r,n)=>Br(e,r,n),e.brand=()=>e,e.register=((r,n)=>(r.add(e,n),e)),e.parse=(r,n)=>qo(e,r,n,{callee:e.parse}),e.safeParse=(r,n)=>Go(e,r,n),e.parseAsync=async(r,n)=>Jo(e,r,n,{callee:e.parseAsync}),e.safeParseAsync=async(r,n)=>Ko(e,r,n),e.spa=e.safeParseAsync,e.encode=(r,n)=>Qo(e,r,n),e.decode=(r,n)=>Xo(e,r,n),e.encodeAsync=async(r,n)=>ea(e,r,n),e.decodeAsync=async(r,n)=>ta(e,r,n),e.safeEncode=(r,n)=>ra(e,r,n),e.safeDecode=(r,n)=>na(e,r,n),e.safeEncodeAsync=async(r,n)=>sa(e,r,n),e.safeDecodeAsync=async(r,n)=>oa(e,r,n),e.refine=(r,n)=>e.check(Ya(r,n)),e.superRefine=r=>e.check(qa(r)),e.overwrite=r=>e.check(ue(r)),e.optional=()=>Ct(e),e.nullable=()=>Tt(e),e.nullish=()=>Ct(Tt(e)),e.nonoptional=r=>Fa(e,r),e.array=()=>ka(e),e.or=r=>Ta([e,r]),e.and=r=>$a(e,r),e.transform=r=>Pt(e,Oa(r)),e.default=r=>Ea(e,r),e.prefault=r=>Ra(e,r),e.catch=r=>Ua(e,r),e.pipe=r=>Pt(e,r),e.readonly=()=>Ba(e),e.describe=r=>{const n=e.clone();return fe.add(n,{description:r}),n},Object.defineProperty(e,"description",{get(){return fe.get(e)?.description},configurable:!0}),e.meta=(...r)=>{if(r.length===0)return fe.get(e);const n=e.clone();return fe.add(n,r[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),ir=m("_ZodString",(e,t)=>{tt.init(e,t),U.init(e,t),e._zod.processJSONSchema=(n,o,a)=>So(e,n,o);const r=e._zod.bag;e.format=r.format??null,e.minLength=r.minimum??null,e.maxLength=r.maximum??null,e.regex=(...n)=>e.check(ao(...n)),e.includes=(...n)=>e.check(lo(...n)),e.startsWith=(...n)=>e.check(uo(...n)),e.endsWith=(...n)=>e.check(mo(...n)),e.min=(...n)=>e.check(ke(...n)),e.max=(...n)=>e.check(rr(...n)),e.length=(...n)=>e.check(nr(...n)),e.nonempty=(...n)=>e.check(ke(1,...n)),e.lowercase=n=>e.check(io(n)),e.uppercase=n=>e.check(co(n)),e.trim=()=>e.check(po()),e.normalize=(...n)=>e.check(ho(...n)),e.toLowerCase=()=>e.check(fo()),e.toUpperCase=()=>e.check(go()),e.slugify=()=>e.check(xo())}),aa=m("ZodString",(e,t)=>{tt.init(e,t),ir.init(e,t),e.email=r=>e.check(Ms(ia,r)),e.url=r=>e.check(Rs(ca,r)),e.jwt=r=>e.check(Xs(ja,r)),e.emoji=r=>e.check(Zs(la,r)),e.guid=r=>e.check(yt(Nt,r)),e.uuid=r=>e.check(Is(we,r)),e.uuidv4=r=>e.check(As(we,r)),e.uuidv6=r=>e.check(Es(we,r)),e.uuidv7=r=>e.check(Ds(we,r)),e.nanoid=r=>e.check(Fs(ua,r)),e.guid=r=>e.check(yt(Nt,r)),e.cuid=r=>e.check(Ls(da,r)),e.cuid2=r=>e.check(Us(ma,r)),e.ulid=r=>e.check(Hs(ha,r)),e.base64=r=>e.check(Gs(_a,r)),e.base64url=r=>e.check(Ks(ya,r)),e.xid=r=>e.check(Vs(pa,r)),e.ksuid=r=>e.check(Bs(fa,r)),e.ipv4=r=>e.check(Ws(ga,r)),e.ipv6=r=>e.check(Ys(xa,r)),e.cidrv4=r=>e.check(qs(ba,r)),e.cidrv6=r=>e.check(Js(va,r)),e.e164=r=>e.check(Qs(wa,r)),e.datetime=r=>e.check(Fo(r)),e.date=r=>e.check(Uo(r)),e.time=r=>e.check(Vo(r)),e.duration=r=>e.check(Wo(r))});function be(e){return Os(aa,e)}const M=m("ZodStringFormat",(e,t)=>{$.init(e,t),ir.init(e,t)}),ia=m("ZodEmail",(e,t)=>{Yn.init(e,t),M.init(e,t)}),Nt=m("ZodGUID",(e,t)=>{Bn.init(e,t),M.init(e,t)}),we=m("ZodUUID",(e,t)=>{Wn.init(e,t),M.init(e,t)}),ca=m("ZodURL",(e,t)=>{qn.init(e,t),M.init(e,t)}),la=m("ZodEmoji",(e,t)=>{Jn.init(e,t),M.init(e,t)}),ua=m("ZodNanoID",(e,t)=>{Gn.init(e,t),M.init(e,t)}),da=m("ZodCUID",(e,t)=>{Kn.init(e,t),M.init(e,t)}),ma=m("ZodCUID2",(e,t)=>{Qn.init(e,t),M.init(e,t)}),ha=m("ZodULID",(e,t)=>{Xn.init(e,t),M.init(e,t)}),pa=m("ZodXID",(e,t)=>{es.init(e,t),M.init(e,t)}),fa=m("ZodKSUID",(e,t)=>{ts.init(e,t),M.init(e,t)}),ga=m("ZodIPv4",(e,t)=>{as.init(e,t),M.init(e,t)}),xa=m("ZodIPv6",(e,t)=>{is.init(e,t),M.init(e,t)}),ba=m("ZodCIDRv4",(e,t)=>{cs.init(e,t),M.init(e,t)}),va=m("ZodCIDRv6",(e,t)=>{ls.init(e,t),M.init(e,t)}),_a=m("ZodBase64",(e,t)=>{us.init(e,t),M.init(e,t)}),ya=m("ZodBase64URL",(e,t)=>{ms.init(e,t),M.init(e,t)}),wa=m("ZodE164",(e,t)=>{hs.init(e,t),M.init(e,t)}),ja=m("ZodJWT",(e,t)=>{fs.init(e,t),M.init(e,t)}),cr=m("ZodNumber",(e,t)=>{tr.init(e,t),U.init(e,t),e._zod.processJSONSchema=(n,o,a)=>No(e,n,o),e.gt=(n,o)=>e.check(jt(n,o)),e.gte=(n,o)=>e.check(Ee(n,o)),e.min=(n,o)=>e.check(Ee(n,o)),e.lt=(n,o)=>e.check(wt(n,o)),e.lte=(n,o)=>e.check(Ae(n,o)),e.max=(n,o)=>e.check(Ae(n,o)),e.int=n=>e.check(kt(n)),e.safe=n=>e.check(kt(n)),e.positive=n=>e.check(jt(0,n)),e.nonnegative=n=>e.check(Ee(0,n)),e.negative=n=>e.check(wt(0,n)),e.nonpositive=n=>e.check(Ae(0,n)),e.multipleOf=(n,o)=>e.check(St(n,o)),e.step=(n,o)=>e.check(St(n,o)),e.finite=()=>e;const r=e._zod.bag;e.minValue=Math.max(r.minimum??Number.NEGATIVE_INFINITY,r.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null,e.maxValue=Math.min(r.maximum??Number.POSITIVE_INFINITY,r.exclusiveMaximum??Number.POSITIVE_INFINITY)??null,e.isInt=(r.format??"").includes("int")||Number.isSafeInteger(r.multipleOf??.5),e.isFinite=!0,e.format=r.format??null}),Sa=m("ZodNumberFormat",(e,t)=>{gs.init(e,t),cr.init(e,t)});function kt(e){return oo(Sa,e)}const Na=m("ZodArray",(e,t)=>{xs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>To(e,r,n,o),e.element=t.element,e.min=(r,n)=>e.check(ke(r,n)),e.nonempty=r=>e.check(ke(1,r)),e.max=(r,n)=>e.check(rr(r,n)),e.length=(r,n)=>e.check(nr(r,n)),e.unwrap=()=>e.element});function ka(e,t){return bo(Na,e,t)}const Ca=m("ZodUnion",(e,t)=>{bs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Po(e,r,n,o),e.options=t.options});function Ta(e,t){return new Ca({type:"union",options:e,...y(t)})}const Pa=m("ZodIntersection",(e,t)=>{vs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>$o(e,r,n,o)});function $a(e,t){return new Pa({type:"intersection",left:e,right:t})}const za=m("ZodTransform",(e,t)=>{_s.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Co(e,r),e._zod.parse=(r,n)=>{if(n.direction==="backward")throw new Ut(e.constructor.name);r.addIssue=a=>{if(typeof a=="string")r.issues.push(ge(a,r.value,t));else{const i=a;i.fatal&&(i.continue=!1),i.code??(i.code="custom"),i.input??(i.input=r.value),i.inst??(i.inst=e),r.issues.push(ge(i))}};const o=t.transform(r.value,r);return o instanceof Promise?o.then(a=>(r.value=a,r)):(r.value=o,r)}});function Oa(e){return new za({type:"transform",transform:e})}const Ma=m("ZodOptional",(e,t)=>{ys.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Ro(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Ma({type:"optional",innerType:e})}const Ia=m("ZodNullable",(e,t)=>{ws.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>zo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Tt(e){return new Ia({type:"nullable",innerType:e})}const Aa=m("ZodDefault",(e,t)=>{js.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Mo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Ea(e,t){return new Aa({type:"default",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Da=m("ZodPrefault",(e,t)=>{Ss.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Io(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ra(e,t){return new Da({type:"prefault",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Za=m("ZodNonOptional",(e,t)=>{Ns.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Oo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Fa(e,t){return new Za({type:"nonoptional",innerType:e,...y(t)})}const La=m("ZodCatch",(e,t)=>{ks.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Ao(e,r,n,o),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Ua(e,t){return new La({type:"catch",innerType:e,catchValue:typeof t=="function"?t:()=>t})}const Ha=m("ZodPipe",(e,t)=>{Cs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Eo(e,r,n,o),e.in=t.in,e.out=t.out});function Pt(e,t){return new Ha({type:"pipe",in:e,out:t})}const Va=m("ZodReadonly",(e,t)=>{Ts.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Do(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ba(e){return new Va({type:"readonly",innerType:e})}const Wa=m("ZodCustom",(e,t)=>{Ps.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>ko(e,r)});function Ya(e,t={}){return vo(Wa,e,t)}function qa(e){return _o(e)}function Q(e){return so(cr,e)}const Ja=/^[A-Za-z0-9\-_+ ]*$/,Ga=/^([A-Za-z0-9 ()/._-]|%\d*[CYmdHMSqvQtwhf$]|%\d*\{[a-z_]+\})*$/,Ka=/^[A-Za-z0-9 ()/._-]*$/,Qa=/^[A-Za-z0-9 _+.@^~<>,-]*$/;function rt(e){return e.includes("..")||e.includes("~/")}const $t=be().max(64,"Name must be 64 characters or less").regex(Ja,"Name can only contain letters, numbers, hyphens, underscores, plus signs, and spaces"),De=be().max(255,"Filename must be 255 characters or less").regex(Ga,"Filename contains invalid characters. Use letters, numbers, underscores, hyphens, strftime codes (%Y, %m, %d), and Motion tokens (%{movienbr}, %v, etc.)").refine(e=>!rt(e),{message:"Filename cannot contain directory traversal sequences (.. or ~/)"}),zt=be().max(4096,"Path must be 4096 characters or less").regex(Ka,"Path contains invalid characters").refine(e=>!rt(e),{message:"Path cannot contain directory traversal sequences (.. or ~/)"});be().max(255,"Email must be 255 characters or less").regex(Qa,"Email contains invalid characters");const Ot=Q().int("Framerate must be a whole number").min(1,"Framerate must be at least 1").max(100,"Framerate cannot exceed 100"),Mt=Q().int("Quality must be a whole number").min(1,"Quality must be at least 1%").max(100,"Quality cannot exceed 100%"),Xa=Q().int("Width must be a whole number").min(160,"Width must be at least 160 pixels").max(4096,"Width cannot exceed 4096 pixels"),ei=Q().int("Height must be a whole number").min(120,"Height must be at least 120 pixels").max(2160,"Height cannot exceed 2160 pixels"),It=Q().int("Port must be a whole number").min(1,"Port must be at least 1").max(65535,"Port cannot exceed 65535"),ti=Q().int("Threshold must be a whole number").min(1,"Threshold must be at least 1").max(2147483647,"Threshold is too large"),ri=Q().int("Noise level must be a whole number").min(0,"Noise level must be at least 0").max(255,"Noise level cannot exceed 255"),ni=Q().int("Log level must be a whole number").min(1,"Log level must be at least 1").max(9,"Log level cannot exceed 9"),si=Q().int("Device ID must be a whole number").min(1,"Device ID must be at least 1").max(999,"Device ID cannot exceed 999"),oi=Q().int("Must be a whole number").min(0,"Must be 0 or greater");be().transform(e=>{const t=e.toLowerCase();return t==="on"||t==="true"||t==="1"});function ai(e,t){const n={device_name:$t,camera_name:$t,device_id:si,target_dir:zt,snapshot_filename:De,picture_filename:De,movie_filename:De,log_file:zt,framerate:Ot,stream_maxrate:Ot,width:Xa,height:ei,stream_quality:Mt,picture_quality:Mt,stream_port:It,webcontrol_port:It,threshold:ti,noise_level:ri,minimum_motion_frames:oi,log_level:ni}[e];if(!n)return typeof t=="string"&&rt(t)?{success:!1,error:"Value cannot contain directory traversal sequences (.. or ~/)"}:{success:!0};const o=n.safeParse(t);return o.success?{success:!0}:{success:!1,error:o.error.issues[0]?.message??"Invalid value"}}async function ii(){return await Te("/0/api/system/reboot",{})}async function ci(){return await Te("/0/api/system/shutdown",{})}async function li(){return await Te("/0/api/system/service-restart",{})}function ui({config:e,onChange:t,getError:r,originalConfig:n,systemStatus:o}){const{addToast:a}=Ye(),i=o?.actions?.service??!1,c=o?.actions?.power??!1,l=(g,S="")=>e[g]?.value??S,u=(g,S="")=>n?.[g]?.value??S,d=g=>e[g]?.password_set===!0,h=g=>n?.[g]?.password_set===!0,f=async()=>{if(window.confirm("Are you sure you want to reboot the Pi? The system will restart and be unavailable for about a minute."))try{await ii(),a("Rebooting... The system will be back online shortly.","info")}catch(g){a(g.message||"Failed to reboot. Power control may be disabled in config.","error")}},p=async()=>{if(window.confirm("Are you sure you want to shutdown the Pi? You will need to physically power it back on."))try{await ci(),a("Shutting down... The system will power off.","warning")}catch(g){a(g.message||"Failed to shutdown. Power control may be disabled in config.","error")}},v=async()=>{if(window.confirm("Are you sure you want to restart the Motion service? Active streams will be interrupted briefly."))try{await li(),a("Restarting Motion... Streams will resume shortly.","info")}catch(g){a(g.message||"Failed to restart service. Service control may be disabled in config.","error")}};return s.jsxs(s.Fragment,{children:[s.jsx(O,{title:"Device Controls",description:"Service and system power management",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"flex flex-col gap-4",children:[s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"Service Control"}),s.jsx("button",{onClick:v,disabled:!i,className:`px-4 py-2 rounded-lg text-sm transition-colors ${i?"bg-blue-600/20 text-blue-300 hover:bg-blue-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:i?void 0:"Enable with webcontrol_actions service=on",children:"Restart Motion"}),!i&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions service=on"})," to enable"]})]}),s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"System Power"}),s.jsxs("div",{className:"flex gap-3",children:[s.jsx("button",{onClick:f,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-yellow-600/20 text-yellow-300 hover:bg-yellow-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Restart Pi"}),s.jsx("button",{onClick:p,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-red-600/20 text-red-300 hover:bg-red-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Shutdown Pi"})]}),!c&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions power=on"})," to enable"]})]})]})}),s.jsxs(O,{title:"Authentication",description:"Web interface and stream access credentials",collapsible:!0,defaultOpen:!0,children:[s.jsxs("div",{className:"mb-6",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Web Interface"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Credentials for logging into this web interface. Format: username:password"}),u("webcontrol_authentication","")===""&&u("webcontrol_user_authentication","")===""&&s.jsx("div",{className:"mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg",children:s.jsxs("div",{className:"flex items-start gap-2",children:[s.jsx("svg",{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})}),s.jsxs("div",{className:"flex-1",children:[s.jsx("p",{className:"text-sm font-medium text-blue-300 mb-1",children:"Initial Setup Available"}),s.jsxs("p",{className:"text-xs text-blue-300/80",children:["Configure authentication now to secure your Motion installation. During initial setup, you can set credentials without changing"," ",s.jsx("code",{className:"text-xs bg-surface px-1 rounded",children:"webcontrol_parms"})," in the config file. Once authentication is configured, it will require restart to apply."]})]})]})}),s.jsxs("div",{className:"mb-4",children:[s.jsx("label",{className:"block text-sm font-medium mb-1 text-gray-300",children:"Admin Username"}),s.jsx("input",{type:"text",value:"admin",disabled:!0,className:`w-full px-3 py-2 bg-surface-elevated border border-gray-700 rounded-lg + text-gray-500 cursor-not-allowed`}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Admin username is fixed for security"})]}),s.jsx(j,{label:"Admin Password",value:String(l("webcontrol_authentication","")).split(":")[1]||"",onChange:g=>{const S=String(l("webcontrol_authentication","")).split(":")[0]||"admin";t("webcontrol_authentication",`${S}:${g}`)},type:"password",placeholder:d("webcontrol_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_authentication")?"Password is configured. Enter a new password to change it.":"Administrator password (click eye icon to reveal)",originalValue:h("webcontrol_authentication")?"[set]":""}),s.jsx(j,{label:"Viewer Username",value:String(l("webcontrol_user_authentication","")).split(":")[0]||"",onChange:g=>{const S=String(l("webcontrol_user_authentication","")).split(":")[1]||"";t("webcontrol_user_authentication",g?`${g}:${S}`:"")},helpText:"View-only username (can view streams but not change settings)",error:r?.("webcontrol_user_authentication"),originalValue:String(u("webcontrol_user_authentication","")).split(":")[0]||"",showVisibilityToggle:!1}),s.jsx(j,{label:"Viewer Password",value:String(l("webcontrol_user_authentication","")).split(":")[1]||"",onChange:g=>{const S=String(l("webcontrol_user_authentication","")).split(":")[0]||"";t("webcontrol_user_authentication",S?`${S}:${g}`:"")},type:"password",placeholder:d("webcontrol_user_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_user_authentication")?"Password is configured. Enter a new password to change it.":"View-only password (click eye icon to reveal)",originalValue:h("webcontrol_user_authentication")?"[set]":""})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Direct Stream Access"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Authentication for direct stream URLs (embedded in websites, VLC, home automation)"}),s.jsx(D,{label:"Authentication Mode",value:String(l("webcontrol_auth_method","0")),onChange:g=>t("webcontrol_auth_method",g),options:[{value:"0",label:"None - No authentication required"},{value:"1",label:"Basic - Simple username/password (use with HTTPS)"},{value:"2",label:"Digest - Secure hash-based (recommended)"}],helpText:"Controls authentication for direct stream access and external API clients. The web UI uses session-based login instead.",error:r?.("webcontrol_auth_method")})]})]}),s.jsxs(O,{title:"Daemon",description:"Motion process settings",collapsible:!0,defaultOpen:!1,children:[s.jsx(V,{label:"Run as Daemon",value:l("daemon",!1),onChange:g=>t("daemon",g),helpText:"Run Motion in background mode"}),s.jsx(j,{label:"PID File",value:String(l("pid_file","")),onChange:g=>t("pid_file",g),helpText:"Path to process ID file. Leave empty to let systemd manage the PID.",error:r?.("pid_file"),originalValue:String(u("pid_file",""))}),s.jsx(j,{label:"Log File",value:String(l("log_file","")),onChange:g=>t("log_file",g),helpText:"Path to log file. Leave empty to use journald (view with: journalctl -u motion).",error:r?.("log_file"),originalValue:String(u("log_file",""))}),s.jsx(D,{label:"Log Level",value:String(l("log_level","6")),onChange:g=>t("log_level",g),options:[{value:"1",label:"Emergency"},{value:"2",label:"Alert"},{value:"3",label:"Critical"},{value:"4",label:"Error"},{value:"5",label:"Warning"},{value:"6",label:"Notice"},{value:"7",label:"Info"},{value:"8",label:"Debug"},{value:"9",label:"All"}],helpText:"Verbosity level for logging",error:r?.("log_level")})]}),s.jsxs(O,{title:"Web Server",description:"API server configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(j,{label:"Port",value:String(l("webcontrol_port","8080")),onChange:g=>t("webcontrol_port",g),type:"number",helpText:"Primary web server port",error:r?.("webcontrol_port"),originalValue:String(u("webcontrol_port","8080"))}),s.jsx(V,{label:"Localhost Only",value:l("webcontrol_localhost",!1),onChange:g=>t("webcontrol_localhost",g),helpText:"Restrict access to localhost only (127.0.0.1)"}),s.jsx(V,{label:"TLS/HTTPS",value:l("webcontrol_tls",!1),onChange:g=>t("webcontrol_tls",g),helpText:"Enable HTTPS encryption"}),l("webcontrol_tls",!1)&&s.jsxs(s.Fragment,{children:[s.jsx(j,{label:"TLS Certificate",value:String(l("webcontrol_cert","")),onChange:g=>t("webcontrol_cert",g),helpText:"Path to TLS certificate file (.crt or .pem)",error:r?.("webcontrol_cert")}),s.jsx(j,{label:"TLS Private Key",value:String(l("webcontrol_key","")),onChange:g=>t("webcontrol_key",g),helpText:"Path to TLS private key file (.key or .pem)",error:r?.("webcontrol_key")})]})]})]})}function di({config:e,onChange:t,getError:r}){const n=(h,f="")=>e[h]?.value??f,o=Number(n("width",640)),a=Number(n("height",480)),i=ot(o,a),c=st.some(h=>h.width===o&&h.height===a),l=h=>{if(h==="custom")return;const{width:f,height:p}=jr(h);t("width",f),t("height",p)},u=h=>{t("width",Number(h))},d=h=>{t("height",Number(h))};return s.jsxs(O,{title:"Device Settings",description:"Basic camera configuration and identification",collapsible:!0,defaultOpen:!1,children:[s.jsx(j,{label:"Camera Name",value:String(n("device_name","")),onChange:h=>t("device_name",h),placeholder:"My Camera",helpText:"Friendly name for this camera",error:r?.("device_name")}),s.jsx(D,{label:"Resolution",value:c?i:"custom",onChange:l,options:[...st.map(h=>({value:ot(h.width,h.height),label:h.label})),{value:"custom",label:"Custom"}],helpText:"Video resolution (width x height)"}),!c&&s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(j,{label:"Width",value:String(o),onChange:u,type:"number",helpText:"Custom width in pixels",error:r?.("width")}),s.jsx(j,{label:"Height",value:String(a),onChange:d,type:"number",helpText:"Custom height in pixels",error:r?.("height")})]}),s.jsx(C,{label:"Framerate",value:Number(n("framerate",15)),onChange:h=>t("framerate",h),min:2,max:30,unit:" fps",helpText:"Frames per second (higher uses more CPU)",error:r?.("framerate")}),s.jsx(D,{label:"Rotation",value:String(n("rotate",0)),onChange:h=>t("rotate",Number(h)),options:wr.map(h=>({value:String(h.value),label:h.label})),helpText:"Rotate camera image"})]})}function lr(e){const{data:t,isLoading:r}=Zt();return x.useMemo(()=>{if(r||!t?.status)return{isLoading:!0,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const n=`cam${e}`,o=t.status[n];if(!o)return{isLoading:!1,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const a=o.camera_type??"unknown";return{isLoading:!1,cameraType:a,cameraDevice:o.camera_device??"",isConnected:!o.lost_connection,features:{hasLibcamControls:a==="libcam",hasV4L2Controls:a==="v4l2",hasNetcamConfig:a==="netcam",hasDualStream:a==="netcam"&&o.has_high_stream===!0,supportsPassthrough:a==="netcam"},...a==="libcam"&&{libcamCapabilities:o.supportedControls},...a==="v4l2"&&{v4l2Controls:o.v4l2_controls},...a==="netcam"&&{netcamStatus:o.netcam_status}}},[t,e,r])}function mi({cameraId:e}){const{cameraType:t,cameraDevice:r,isConnected:n}=lr(e);return s.jsx(O,{title:"Camera Source",description:"Camera connection and type information",collapsible:!0,defaultOpen:!0,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Type:"}),s.jsx(hi,{type:t})]}),s.jsxs("div",{className:"flex items-start gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400 pt-0.5",children:"Device:"}),r?s.jsx("div",{className:"flex-1",children:s.jsx("code",{className:"text-sm text-gray-200 bg-gray-800 px-2 py-1 rounded",children:r})}):s.jsx("span",{className:"text-sm text-gray-500 italic",children:"Not configured"})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Status:"}),s.jsx(pi,{isConnected:n})]}),t==="unknown"&&s.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[s.jsx("p",{className:"text-sm text-gray-300 mb-2",children:s.jsx("strong",{children:"No camera configured"})}),s.jsx("p",{className:"text-sm text-gray-400 mb-3",children:"Configure a camera by setting one of the following:"}),s.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"libcam_device"})," - Raspberry Pi camera"]}),s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"v4l2_device"})," - USB webcam (e.g., /dev/video0)"]}),s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"netcam_url"})," - IP camera (RTSP/HTTP URL)"]})]})]})]})})}function hi({type:e}){const t={libcam:"bg-purple-500/20 text-purple-300 border-purple-500/30",v4l2:"bg-blue-500/20 text-blue-300 border-blue-500/30",netcam:"bg-green-500/20 text-green-300 border-green-500/30",unknown:"bg-gray-500/20 text-gray-400 border-gray-500/30"},r={libcam:"libcamera (Pi Camera)",v4l2:"V4L2 (USB Camera)",netcam:"Network Camera (IP)",unknown:"Not Configured"};return s.jsx("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${t[e]}`,children:r[e]})}function pi({isConnected:e}){return s.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${e?"bg-green-500/20 text-green-300 border-green-500/30":"bg-red-500/20 text-red-300 border-red-500/30"}`,children:[s.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e?"bg-green-400":"bg-red-400"}`}),e?"Connected":"Disconnected"]})}function fi({config:e,onChange:t,getError:r,capabilities:n,originalConfig:o}){const a=(l,u="")=>e[l]?.value??u,i=(l,u="")=>o?.[l]?.value??u,c=!!a("libcam_awb_enable",!1);return s.jsxs(O,{title:"libcamera Controls",description:"Raspberry Pi camera controls (libcamera only)",collapsible:!0,defaultOpen:!1,children:[s.jsx(C,{label:"Brightness",value:Number(a("libcam_brightness",0)),onChange:l=>t("libcam_brightness",l),min:-1,max:1,step:.1,helpText:"Brightness adjustment (-1.0 to 1.0)",error:r?.("libcam_brightness")}),s.jsx(C,{label:"Contrast",value:Number(a("libcam_contrast",1)),onChange:l=>t("libcam_contrast",l),min:0,max:32,step:.5,helpText:"Contrast adjustment (0.0 to 32.0)",error:r?.("libcam_contrast")}),s.jsx(C,{label:"Gain (ISO)",value:Number(a("libcam_gain",1)),onChange:l=>t("libcam_gain",l),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)",error:r?.("libcam_gain")}),s.jsx(V,{label:"Auto White Balance",value:c,onChange:l=>t("libcam_awb_enable",l),helpText:"Enable automatic white balance"}),c&&s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"AWB Mode",value:String(a("libcam_awb_mode",0)),onChange:l=>t("libcam_awb_mode",Number(l)),options:Sr.map(l=>({value:String(l.value),label:l.label})),helpText:"White balance mode"}),n?.AwbLocked!==!1&&s.jsx(V,{label:"Lock AWB",value:!!a("libcam_awb_locked",!1),onChange:l=>t("libcam_awb_locked",l),helpText:"Lock white balance settings"})]}),!c&&s.jsxs(s.Fragment,{children:[n?.ColourTemperature!==!1&&s.jsx(C,{label:"Color Temperature",value:Number(a("libcam_colour_temp",0)),onChange:l=>t("libcam_colour_temp",l),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)",error:r?.("libcam_colour_temp")}),s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(C,{label:"Red Gain",value:Number(a("libcam_colour_gain_r",1)),onChange:l=>t("libcam_colour_gain_r",l),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)",error:r?.("libcam_colour_gain_r")}),s.jsx(C,{label:"Blue Gain",value:Number(a("libcam_colour_gain_b",1)),onChange:l=>t("libcam_colour_gain_b",l),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)",error:r?.("libcam_colour_gain_b")})]}),n?.ColourTemperature===!1&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),n?.AfMode?s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"Autofocus Mode",value:String(a("libcam_af_mode",0)),onChange:l=>t("libcam_af_mode",Number(l)),options:Nr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus control mode"}),Number(a("libcam_af_mode",0))===0&&n?.LensPosition&&s.jsx(C,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:r?.("libcam_lens_position")}),Number(a("libcam_af_mode",0))>0&&s.jsxs(s.Fragment,{children:[n?.AfRange&&s.jsx(D,{label:"Autofocus Range",value:String(a("libcam_af_range",0)),onChange:l=>t("libcam_af_range",Number(l)),options:kr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus range preference"}),n?.AfSpeed&&s.jsx(D,{label:"Autofocus Speed",value:String(a("libcam_af_speed",0)),onChange:l=>t("libcam_af_speed",Number(l)),options:Cr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus adjustment speed"})]})]}):n!==void 0?s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Autofocus:"})," Not supported on this camera.",n?.LensPosition?s.jsx("span",{children:" Manual focus (lens position) is available."}):s.jsx("span",{children:" This camera has fixed focus."})]}):null,!n?.AfMode&&n?.LensPosition&&s.jsx(C,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:r?.("libcam_lens_position")}),s.jsx(j,{label:"Buffer Count",value:String(a("libcam_buffer_count",4)),onChange:l=>t("libcam_buffer_count",Number(l)),type:"number",helpText:"Frame buffers for capture (2-8). Higher values reduce frame drops under load but use more memory. Default 4 works for most setups; increase to 6-8 if seeing drops at high framerates.",error:r?.("libcam_buffer_count"),originalValue:String(i("libcam_buffer_count",4))})]})}function gi({config:e,onChange:t,controls:r,getError:n}){if(!r||r.length===0)return s.jsx(O,{title:"Camera Controls",description:"USB camera settings",collapsible:!0,defaultOpen:!1,children:s.jsx("p",{className:"text-gray-400 text-sm",children:"No controls available for this camera."})});const o=vi(r);return s.jsx(O,{title:"Camera Controls",description:"USB camera settings (V4L2)",collapsible:!0,defaultOpen:!1,children:Object.entries(o).map(([a,i])=>s.jsxs("div",{className:"space-y-4",children:[a!=="Other"&&s.jsx("h4",{className:"text-sm font-medium text-gray-300 mt-4 first:mt-0",children:a}),i.map(c=>s.jsx(xi,{control:c,value:bi(e,c),onChange:l=>t(`v4l2_${c.id}`,l),error:n?.(`v4l2_${c.id}`)},c.id))]},a))})}function xi({control:e,value:t,onChange:r,error:n}){switch(e.type){case"boolean":return s.jsx(V,{label:e.name,value:!!t,onChange:r,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`});case"menu":return s.jsx(D,{label:e.name,value:String(t),onChange:o=>r(Number(o)),options:e.menuItems?.map(o=>({value:String(o.value),label:o.label}))??[],helpText:`Default: ${e.default}`,error:n});default:return s.jsx(C,{label:e.name,value:Number(t),onChange:r,min:e.min,max:e.max,step:e.step??1,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`,error:n})}}function bi(e,t){const r=`v4l2_${t.id}`,n=e[r]?.value;return n!==void 0?t.type==="boolean"?!!n:Number(n):t.type==="boolean"?!!t.current:t.current}function vi(e){const t={"Image Quality":[],"Exposure & Gain":[],"White Balance":[],Focus:[],Other:[]},r={"Image Quality":["brightness","contrast","saturation","hue","sharpness","gamma"],"Exposure & Gain":["exposure","gain","iso","backlight"],"White Balance":["white","balance","color","colour","temperature"],Focus:["focus","zoom","pan","tilt","lens"]};for(const n of e){const o=n.name.toLowerCase();let a=!1;for(const[i,c]of Object.entries(r))if(c.some(l=>o.includes(l))){t[i].push(n),a=!0;break}a||t.Other.push(n)}return Object.fromEntries(Object.entries(t).filter(([n,o])=>o.length>0))}function _i({config:e,onChange:t,connectionStatus:r,hasDualStream:n,getError:o}){const a=(i,c="")=>e[i]?.value??c;return s.jsx(O,{title:"Network Camera",description:"IP camera connection settings (RTSP/HTTP)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[r&&s.jsxs("div",{className:"flex items-center gap-3 pb-2",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Connection:"}),s.jsx(yi,{status:r})]}),s.jsx(j,{label:"Stream URL",value:String(a("netcam_url","")),onChange:i=>t("netcam_url",i),placeholder:"rtsp://192.168.1.100:554/stream",helpText:"RTSP, HTTP, HTTPS, or file:// URL for the camera stream",error:o?.("netcam_url")}),s.jsx(j,{label:"Credentials",value:String(a("netcam_userpass","")),onChange:i=>t("netcam_userpass",i),type:"password",placeholder:"username:password",helpText:"Leave empty if camera doesn't require authentication",error:o?.("netcam_userpass")}),s.jsx(j,{label:"FFmpeg Parameters",value:String(a("netcam_params","")),onChange:i=>t("netcam_params",i),placeholder:"-rtsp_transport tcp",helpText:"Advanced: Custom FFmpeg input options (e.g., -rtsp_transport tcp)",error:o?.("netcam_params")}),n&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"border-t border-gray-700 my-4 pt-4",children:[s.jsx("h4",{className:"text-sm font-medium text-gray-300 mb-3",children:"High Resolution Stream"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Optional secondary stream for higher quality recordings while using lower resolution for motion detection."})]}),s.jsx(j,{label:"High-Res URL",value:String(a("netcam_high_url","")),onChange:i=>t("netcam_high_url",i),placeholder:"rtsp://192.168.1.100:554/stream1",helpText:"Optional: Higher resolution stream for recordings",error:o?.("netcam_high_url")}),s.jsx(j,{label:"High-Res FFmpeg Parameters",value:String(a("netcam_high_params","")),onChange:i=>t("netcam_high_params",i),placeholder:"-rtsp_transport tcp",helpText:"FFmpeg parameters for high-resolution stream",error:o?.("netcam_high_params")})]}),s.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[s.jsx("p",{className:"text-sm text-gray-300 mb-2",children:s.jsx("strong",{children:"Supported Protocols"})}),s.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"RTSP:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"rtsp://"})," - Most IP cameras"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"HTTP:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"http://"})," - MJPEG streams"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"HTTPS:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"https://"})," - Secure streams"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"File:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"file://"})," - Local video files"]})]})]})]})})}function yi({status:e}){const t={connected:{color:"bg-green-500/20 text-green-300 border-green-500/30",text:"Connected"},reading:{color:"bg-blue-500/20 text-blue-300 border-blue-500/30",text:"Reading"},not_connected:{color:"bg-red-500/20 text-red-300 border-red-500/30",text:"Not Connected"},reconnecting:{color:"bg-yellow-500/20 text-yellow-300 border-yellow-500/30",text:"Reconnecting"},unknown:{color:"bg-gray-500/20 text-gray-400 border-gray-500/30",text:"Unknown"}},{color:r,text:n}=t[e]||t.unknown;return s.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${r}`,children:[s.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e==="connected"?"bg-green-400":e==="reading"?"bg-blue-400 animate-pulse":e==="reconnecting"?"bg-yellow-400 animate-pulse":"bg-red-400"}`}),n]})}function wi({config:e,onChange:t,getError:r}){const n=(_,b="")=>e[_]?.value??b,o=String(n("text_left","")),a=String(n("text_right","")),i=at(o),c=at(a),[l,u]=x.useState(i==="custom"?o:""),[d,h]=x.useState(c==="custom"?a:""),f=_=>{_==="custom"?t("text_left",l):t("text_left",it(_))},p=_=>{_==="custom"?t("text_right",d):t("text_right",it(_))},v=_=>{u(_),t("text_left",_)},g=_=>{h(_),t("text_right",_)},S=[{value:"disabled",label:"Disabled"},{value:"camera-name",label:"Camera Name"},{value:"timestamp",label:"Timestamp"},{value:"custom",label:"Custom Text"}];return s.jsxs(O,{title:"Text Overlay",description:"Add text overlays to video frames",collapsible:!0,defaultOpen:!1,children:[s.jsx(D,{label:"Left Text",value:i,onChange:f,options:S,helpText:"Text displayed in top-left corner"}),i==="custom"&&s.jsx(j,{label:"Custom Left Text",value:l,onChange:v,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:r?.("text_left")}),s.jsx(D,{label:"Right Text",value:c,onChange:p,options:S,helpText:"Text displayed in top-right corner"}),c==="custom"&&s.jsx(j,{label:"Custom Right Text",value:d,onChange:g,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:r?.("text_right")}),s.jsx(C,{label:"Text Scale",value:Number(n("text_scale",1)),onChange:_=>t("text_scale",_),min:1,max:10,unit:"x",helpText:"Text size multiplier (1-10)",error:r?.("text_scale")})]})}const ji=[{value:"100",label:"Full (100%)"},{value:"75",label:"High (75%)"},{value:"50",label:"Medium (50%)"},{value:"25",label:"Low (25%)"},{value:"10",label:"Minimal (10%)"}];function Si({config:e,onChange:t,getError:r}){const n=(a,i="")=>e[a]?.value??i,o=!n("stream_localhost",!1);return s.jsxs(O,{title:"Video Streaming",description:"Live MJPEG stream configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(V,{label:"Enable Video Streaming",value:o,onChange:a=>t("stream_localhost",!a),helpText:"Enable/disable live MJPEG streaming. When disabled, stream is only accessible from localhost."}),o&&s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"Streaming Resolution",value:String(n("stream_preview_scale",100)),onChange:a=>t("stream_preview_scale",Number(a)),options:ji,helpText:"Scale stream as percentage of source resolution. Lower = less bandwidth and CPU."}),s.jsx(C,{label:"Stream Quality",value:Number(n("stream_quality",50)),onChange:a=>t("stream_quality",a),min:1,max:100,unit:"%",helpText:"JPEG compression quality (1-100). Higher = better quality, more bandwidth.",error:r?.("stream_quality")}),s.jsx(C,{label:"Stream Max Framerate",value:Number(n("stream_maxrate",15)),onChange:a=>t("stream_maxrate",a),min:1,max:30,unit:" fps",helpText:"Maximum frames per second (lower = less bandwidth and CPU)",error:r?.("stream_maxrate")}),s.jsx(V,{label:"Show Motion Boxes",value:!!n("stream_motion",!1),onChange:a=>t("stream_motion",a),helpText:"Display motion detection boxes in stream"}),s.jsx(D,{label:"Direct Stream Access Security",value:String(n("webcontrol_auth_method",0)),onChange:a=>t("webcontrol_auth_method",Number(a)),options:Tr.map(a=>({value:String(a.value),label:a.label})),helpText:"Authentication when streams are accessed directly (embedded in other websites, VLC, home automation). None = open access on trusted networks only. Basic = use with HTTPS. Digest = recommended."}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Stream URL:"})," ",s.jsx("code",{children:"http://[hostname]:[port]/[cam]/mjpg/stream"})]}),s.jsxs("p",{className:"mt-1",children:[s.jsx("strong",{children:"Note:"})," Streaming resolution scales the output to reduce bandwidth and CPU usage. Server-side resizing is always performed by Motion."]})]})]})]})}function Ni({config:e,onChange:t,getError:r}){const n=(d,h="")=>e[d]?.value??h,o=Number(n("width",640)),a=Number(n("height",480)),i=Number(n("threshold",1500)),c=Pr(i,o,a),l=d=>{const h=Number(d),f=zr(h,o,a);t("threshold",f)},u=[{value:"",label:"Off"},{value:"EedDl",label:"Light"},{value:"EedDl",label:"Medium (default)"},{value:"EedDl",label:"Heavy"}];return s.jsx(O,{title:"Motion Detection",description:"Configure motion detection sensitivity and behavior",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(C,{label:"Threshold",value:c,onChange:d=>l(String(d)),min:0,max:20,step:.1,unit:"%",helpText:`Percentage of frame that must change (${i} pixels at ${o}x${a}). Higher = less sensitive.`,error:r?.("threshold")}),s.jsx(j,{label:"Threshold Maximum",value:String(n("threshold_maximum",0)),onChange:d=>t("threshold_maximum",Number(d)),type:"number",helpText:"Maximum threshold for auto-tuning (0 = disabled)",error:r?.("threshold_maximum")}),s.jsx(V,{label:"Auto-tune Threshold",value:n("threshold_tune",!1),onChange:d=>t("threshold_tune",d),helpText:"Automatically adjust threshold based on noise levels"}),s.jsx(V,{label:"Auto-tune Noise Level",value:n("noise_tune",!1),onChange:d=>t("noise_tune",d),helpText:"Automatically determine optimal noise level"}),s.jsx(C,{label:"Noise Level",value:Number(n("noise_level",32)),onChange:d=>t("noise_level",d),min:1,max:255,helpText:"Noise tolerance (1-255). Lower values detect smaller motions.",error:r?.("noise_level")}),s.jsx(C,{label:"Light Switch Detection",value:Number(n("lightswitch_percent",0)),onChange:d=>t("lightswitch_percent",d),min:0,max:100,unit:"%",helpText:"Ignore sudden brightness changes (0 = disabled). Prevents false triggers from lights turning on/off.",error:r?.("lightswitch_percent")}),s.jsx(D,{label:"Despeckle Filter",value:String(n("despeckle_filter","")),onChange:d=>t("despeckle_filter",d),options:u,helpText:"Remove noise speckles from motion detection"}),s.jsx(C,{label:"Smart Mask Speed",value:Number(n("smart_mask_speed",0)),onChange:d=>t("smart_mask_speed",d),min:0,max:10,helpText:"Auto-mask static areas (0 = disabled, 1-10 = speed). Higher values adapt faster to static objects.",error:r?.("smart_mask_speed")}),s.jsx(D,{label:"Locate Motion Mode",value:String(n("locate_motion_mode","off")),onChange:d=>t("locate_motion_mode",d),options:$r,helpText:"Draw box around motion area. 'Preview' = stream only, 'On' = saved images, 'Both' = both."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Event Timing"}),s.jsx(j,{label:"Event Gap (seconds)",value:String(n("event_gap",60)),onChange:d=>t("event_gap",Number(d)),type:"number",min:"0",helpText:"Seconds of no motion before ending an event. Prevents splitting continuous motion into multiple events.",error:r?.("event_gap")}),s.jsx(j,{label:"Pre-Capture (frames)",value:String(n("pre_capture",0)),onChange:d=>t("pre_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture before motion detected. Uses CPU/memory to buffer frames.",error:r?.("pre_capture")}),s.jsx(j,{label:"Post-Capture (frames)",value:String(n("post_capture",0)),onChange:d=>t("post_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture after motion stops",error:r?.("post_capture")}),s.jsx(j,{label:"Minimum Motion Frames",value:String(n("minimum_motion_frames",1)),onChange:d=>t("minimum_motion_frames",Number(d)),type:"number",min:"1",helpText:"Consecutive frames with motion required to trigger event. Filters brief false positives.",error:r?.("minimum_motion_frames")})]})]})})}function ki({config:e,onChange:t,getError:r}){const n=(f,p="")=>e[f]?.value??p,o=String(n("picture_output","off")),a=Number(n("snapshot_interval",0)),i=Or(o,a),[c,l]=x.useState(i),u=f=>{l(f);const p=Mr(f);t("picture_output",p.picture_output),p.snapshot_interval!==void 0&&t("snapshot_interval",p.snapshot_interval)},d=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered (all frames)"},{value:"motion-triggered-one",label:"Motion Triggered (first frame only)"},{value:"best",label:"Best Quality Frame"},{value:"center",label:"Center Frame"},{value:"interval-snapshots",label:"Interval Snapshots"},{value:"manual",label:"Manual Only"}],h=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(O,{title:"Picture Settings",description:"Configure picture capture and snapshots",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(D,{label:"Capture Mode",value:c,onChange:u,options:d,helpText:"When to capture still images during motion events"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Current settings:"})," picture_output=",o,a>0&&`, snapshot_interval=${a}s`]}),c==="motion-triggered"&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"text-xs text-yellow-300 bg-yellow-900/30 border border-yellow-700 p-3 rounded",children:[s.jsx("strong",{children:"Warning:"})," This mode captures every frame during motion. At 15fps, continuous motion can generate 900+ pictures per minute. Configure limits below to prevent runaway capture."]}),s.jsx(j,{label:"Max Pictures Per Event",value:String(n("picture_max_per_event",0)),onChange:f=>t("picture_max_per_event",Number(f)),type:"number",min:"0",max:"100000",helpText:"Maximum pictures per motion event (0 = unlimited)",error:r?.("picture_max_per_event")}),s.jsx(j,{label:"Min Interval Between Pictures (ms)",value:String(n("picture_min_interval",0)),onChange:f=>t("picture_min_interval",Number(f)),type:"number",min:"0",max:"60000",helpText:"Minimum milliseconds between captures (0 = no limit). 1000ms = 1 picture/second.",error:r?.("picture_min_interval")})]}),c==="interval-snapshots"&&s.jsx(j,{label:"Snapshot Interval (seconds)",value:String(n("snapshot_interval",60)),onChange:f=>t("snapshot_interval",Number(f)),type:"number",min:"1",helpText:"Seconds between snapshots (independent of motion)",error:r?.("snapshot_interval")}),s.jsx(C,{label:"Picture Quality",value:Number(n("picture_quality",75)),onChange:f=>t("picture_quality",f),min:1,max:100,unit:"%",helpText:"JPEG quality (1-100). Higher = better quality, larger files.",error:r?.("picture_quality")}),s.jsx(j,{label:"Picture Filename Pattern",value:String(n("picture_filename","%Y%m%d%H%M%S-%q")),onChange:f=>t("picture_filename",f),helpText:`Format codes: ${h}`,error:r?.("picture_filename")}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S-%q"})," → ",s.jsx("code",{children:"2025-01-29/143022-05.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/143022.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/143022.jpg"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S-%q"})," → ",s.jsx("code",{children:"20250129143022-05.jpg"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",h]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]})]})})}function Ci(){return qe({queryKey:["deviceInfo"],queryFn:async()=>{const e=await fetch("/0/api/system/status");if(!e.ok)throw new Error("Failed to fetch device info");return e.json()},staleTime:6e4,retry:1})}function At(e){return e?.pi_generation===5}function Ti(e){return e?.pi_generation===4}function he(e){return e?.hardware_encoders?.h264_v4l2m2m===!0}function Pi(e,t=70){return(e?.temperature?.celsius??0)>t}function $i({config:e,onChange:t,getError:r,showPassthrough:n=!0}){const{data:o}=Ci(),a=(b,I="")=>e[b]?.value??I,i=a("movie_output",!1),c=a("movie_output_motion",!1),l=a("emulate_motion",!1),u=Ir(i,c,l),[d,h]=x.useState(u),f=b=>{h(b);const I=Er(b);t("movie_output",I.movie_output),I.movie_output_motion!==void 0&&t("movie_output_motion",I.movie_output_motion),I.emulate_motion!==void 0&&t("emulate_motion",I.emulate_motion)},p=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered"},{value:"continuous",label:"Continuous Recording"}],v=["%Y - Year","%m - Month","%d - Day","%H - Hour","%M - Minute","%S - Second","%v - Event number","%$ - Camera name"].join(", "),g=String(a("movie_container","mp4")),S=()=>!o||he(o)?ct:ct.filter(b=>!re(b.value)),_=()=>!(a("movie_passthrough",!1)||re(g)||g==="webm");return s.jsx(O,{title:"Movie Settings",description:"Configure video recording settings",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(D,{label:"Recording Mode",value:d,onChange:f,options:p,helpText:"When to record video. Motion Triggered = only during events, Continuous = always record."}),s.jsx(C,{label:"Movie Quality",value:Number(a("movie_quality",75)),onChange:b=>t("movie_quality",b),min:1,max:100,unit:"%",helpText:"Video encoding quality (1-100). Higher = better quality, larger files, more CPU.",error:r?.("movie_quality")}),s.jsx(j,{label:"Movie Filename Pattern",value:String(a("movie_filename","%Y%m%d%H%M%S")),onChange:b=>t("movie_filename",b),helpText:`Format codes: ${v}`,error:r?.("movie_filename")}),s.jsx(D,{label:"Container Format",value:String(a("movie_container","mp4")),onChange:b=>t("movie_container",b),options:S(),helpText:"Video container format. Hardware encoding requires v4l2m2m support."}),re(g)&&he(o)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Active:"})," Using h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%."]}),o&&!he(o)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Not Available:"})," This device does not have a hardware H.264 encoder.",At(o)&&" Pi 5 does not include a hardware encoder."," ","Hardware encoding options (h264_v4l2m2m) are hidden. Using software encoding (~40-70% CPU)."]}),re(g)&&!o&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding:"})," Uses h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%. Only available on devices with v4l2m2m support."]}),Ie(g)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"High CPU Warning:"})," H.265/HEVC software encoding uses 80-100% CPU on Raspberry Pi. Not recommended for continuous recording. Consider H.264 for better performance."]}),he(o)&&!re(g)&&!a("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Available:"}),' This device has a hardware H.264 encoder. Select "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" to reduce CPU from ~40-70% to ~10%.']}),!o&&!re(g)&&!a("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-2",children:[s.jsx("strong",{children:"Tip:"}),' If your device has hardware encoding support (e.g., Raspberry Pi 4), consider selecting "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" for ~10% CPU instead of ~40-70% with software encoding.']}),g==="webm"&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"WebM Format:"})," Uses VP8 codec, optimized for web streaming. Encoder preset setting does not apply to VP8."]}),_()&&s.jsx(D,{label:"Encoder Preset",value:String(a("movie_encoder_preset","medium")),onChange:b=>t("movie_encoder_preset",b),options:Ar.map(b=>({value:b.value,label:b.label})),helpText:"Tradeoff between CPU usage and video quality. Lower presets use less CPU but produce lower quality video. Requires restart to take effect."}),s.jsx(j,{label:"Max Duration (seconds)",value:String(a("movie_max_time",0)),onChange:b=>t("movie_max_time",Number(b)),type:"number",min:"0",helpText:"Maximum movie length (0 = unlimited). Splits long events into multiple files.",error:r?.("movie_max_time")}),n&&s.jsx(V,{label:"Passthrough Mode",value:a("movie_passthrough",!1),onChange:b=>t("movie_passthrough",b),helpText:"Copy codec without re-encoding (NETCAM only). Reduces CPU but may cause compatibility issues."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"2025-01-29/143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%v-%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/42-143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%v"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/42.mkv"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S"})," → ",s.jsx("code",{children:"20250129143022.mkv"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",v]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]}),d==="continuous"&&At(o)&&!a("movie_passthrough",!1)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Pi 5 CPU Warning:"})," Pi 5 does not have a hardware H.264 encoder. Continuous recording uses software encoding (~35-60% CPU constant).",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsx("li",{children:'Use encoder preset "Ultrafast" to reduce CPU by ~30%'}),s.jsx("li",{children:"Add active cooling (fan) to prevent thermal throttling"}),s.jsx("li",{children:"Enable passthrough if source is already H.264"})]})]}),d==="continuous"&&Ti(o)&&he(o)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording on Pi 4:"})," Camera will record 24/7.",re(g)?s.jsx("span",{children:" Using hardware encoder - expect ~10% CPU usage."}):a("movie_passthrough",!1)?s.jsx("span",{children:" Passthrough mode enabled - expect ~5-10% CPU usage."}):s.jsx("span",{children:" Consider using hardware encoder (MKV/MP4 H.264 Hardware) for ~10% CPU instead of ~40-70%."})]}),d==="continuous"&&!o&&s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording:"})," Camera will record 24/7 regardless of motion. Expected CPU usage on Raspberry Pi:",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 4 with hardware encoder:"})," ~10% CPU"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 5 or Pi 4 software encoding:"})," ~35-60% CPU depending on preset"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Passthrough mode:"})," ~5-10% CPU (if source is H.264)"]})]})]}),Pi(o)&&s.jsxs("div",{className:"text-xs text-red-400 bg-red-950/30 p-3 rounded",children:[s.jsx("strong",{children:"High Temperature Warning:"})," Device is running at ",o?.temperature?.celsius.toFixed(1),"°C. Consider reducing encoding quality or adding active cooling."]})]})})}function zi({config:e,onChange:t,getError:r,originalConfig:n}){const o=(c,l="")=>e[c]?.value??l,a=(c,l="")=>n?.[c]?.value??l,i=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(O,{title:"Storage",description:"Base directory and periodic snapshot settings for this camera",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(j,{label:"Base Storage Directory",value:String(o("target_dir","/var/lib/motion")),onChange:c=>t("target_dir",c),helpText:"Root directory for ALL camera files. Picture and movie filename patterns (configured in their sections) create paths relative to this directory.",error:r?.("target_dir"),originalValue:String(a("target_dir","/var/lib/motion"))}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Filename Patterns"}),s.jsx(j,{label:"Snapshot Filename",value:String(o("snapshot_filename","%Y%m%d%H%M%S-snapshot")),onChange:c=>t("snapshot_filename",c),helpText:"Format for periodic snapshot filenames (strftime syntax)",error:r?.("snapshot_filename")}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Example:"})," ",s.jsx("code",{children:"%Y%m%d%H%M%S-snapshot"})," → ",s.jsx("code",{children:"20250129143022-snapshot.jpg"})]}),s.jsxs("p",{children:[s.jsx("strong",{children:"With subdirs:"})," ",s.jsx("code",{children:"%$/%Y-%m-%d/snapshot-%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/snapshot-143022.jpg"})]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-1",children:[s.jsxs("p",{children:["Available codes: ",i]}),s.jsxs("p",{className:"mt-2 text-blue-200",children:[s.jsx("strong",{children:"How it works:"})," The Base Storage Directory above sets where files go. Picture and Movie sections set filename patterns (which can include subdirectories like ",s.jsx("code",{children:"%Y-%m-%d/"}),")."]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"File Cleanup (Future)"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("p",{children:"Automatic file retention and cleanup based on age/size will be available in a future update."}),s.jsxs("p",{className:"mt-2",children:["For now, use ",s.jsx("code",{children:"cleandir_params"})," in the Motion configuration file or manual cleanup scripts."]})]})]}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"💡 Network Storage:"})," For network shares (NFS, SMB), ensure target_dir points to a mounted directory. Test write permissions before starting recording."]})]})})}const ee=["sun","mon","tue","wed","thu","fri","sat"],Et={sun:{full:"Sunday",short:"Sun"},mon:{full:"Monday",short:"Mon"},tue:{full:"Tuesday",short:"Tue"},wed:{full:"Wednesday",short:"Wed"},thu:{full:"Thursday",short:"Thu"},fri:{full:"Friday",short:"Fri"},sat:{full:"Saturday",short:"Sat"}},ne=96,xe=15;function ur(){return{sun:new Set,mon:new Set,tue:new Set,wed:new Set,thu:new Set,fri:new Set,sat:new Set}}function Oi(e){const t=new Map,r=e.trim().split(/\s+/);for(const n of r){const o=n.indexOf("=");if(o===-1)continue;const a=n.slice(0,o).toLowerCase(),i=n.slice(o+1);t.has(a)||t.set(a,[]),t.get(a).push(i)}return t}function Mi(e){if(e.length!==9||e[4]!=="-")return[];const t=parseInt(e.slice(0,2),10),r=parseInt(e.slice(2,4),10),n=parseInt(e.slice(5,7),10),o=parseInt(e.slice(7,9),10);if(isNaN(t)||isNaN(r)||isNaN(n)||isNaN(o)||t<0||t>23||r<0||r>59||n<0||n>23||o<0||o>59)return[];const a=t*4+Math.floor(r/xe),i=n*4+Math.floor(o/xe),c=[];for(let l=a;l<=i&&lc-l),r=[];let n=t[0],o=t[0];for(let c=1;cDt(e[i],e.sun))&&e.sun.size>0){const i=je(Array.from(e.sun));for(const c of i)n.push(`sun-sat=${Se(c)}`);return n.join(" ")}if(["mon","tue","wed","thu","fri"].every(i=>Dt(e[i],e.mon))&&e.mon.size>0){const i=je(Array.from(e.mon));for(const c of i)n.push(`mon-fri=${Se(c)}`);for(const c of["sat","sun"])if(e[c].size>0){const l=je(Array.from(e[c]));for(const u of l)n.push(`${c}=${Se(u)}`)}return n.join(" ")}for(const i of ee){if(e[i].size===0)continue;const c=je(Array.from(e[i]));for(const l of c)n.push(`${i}=${Se(l)}`)}return n.join(" ")}function Dt(e,t){if(e.size!==t.size)return!1;for(const r of e)if(!t.has(r))return!1;return!0}function Ai(e,t){const r=Be(e),n=Ve(t),o=(a,i)=>{const c=a===0?12:a>12?a-12:a,l=a<12?"am":"pm",u=i===0?"":`:${String(i).padStart(2,"0")}`;return`${c}${u}${l}`};return`${o(r.hour,r.min)} - ${o(n.hour,n.min)}`}function Ei(e){return ee.every(t=>e[t].size===0)}function Di(e){const t=ee.filter(n=>e[n].size>0);return t.length===0?"No time ranges selected":t.length===7?"All days configured":t.map(n=>n.charAt(0).toUpperCase()+n.slice(1,3)).join(", ")}function Ri({value:e,onChange:t}){const{schedule:r,defaultOn:n,action:o}=x.useMemo(()=>Ii(e),[e]),a=x.useCallback(p=>{const v=Re(p,n,o);t(v)},[t,n,o]),i=x.useCallback(p=>{const v=Ze(r);v[p].size===ne?v[p]=new Set:v[p]=new Set(Array.from({length:ne},(g,S)=>S)),a(v)},[r,a]),c=x.useCallback((p,v,g,S)=>{const _=Ze(r),b=new Set(r[p]),[I,G]=v<=g?[v,g]:[g,v];for(let H=I;H<=G;H++)S?b.add(H):b.delete(H);_[p]=b,a(_)},[r,a]),l=x.useCallback(p=>{const v=Ze(r);v[p]=new Set,a(v)},[r,a]),u=x.useCallback(()=>{a(ur())},[a]),d=x.useCallback(p=>{const v=Re(r,p,o);t(v)},[r,o,t]),h=x.useCallback(p=>{const v=Re(r,n,p);t(v)},[r,n,t]),f=x.useCallback(p=>{t(p)},[t]);return{schedule:r,defaultOn:n,action:o,updateSchedule:a,toggleDay:i,setRange:c,clearDay:l,clearAll:u,setDefaultOn:d,setAction:h,applyPreset:f}}function Ze(e){return{sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)}}const Zi=x.memo(function({isSelected:t,isInDragRange:r,isDragSelect:n,isHourBoundary:o,onPointerDown:a,onPointerEnter:i}){let c;r?c=n?"bg-primary/70":"bg-surface-hover":t?c="bg-primary":c="bg-surface";const l=o?"border-t border-gray-700/50":"";return s.jsx("div",{className:`h-[6px] ${c} ${l} cursor-pointer transition-colors duration-75`,onPointerDown:a,onPointerEnter:i})}),Fi=x.memo(function({day:t,schedule:r,dragState:n,onPointerDown:o,onPointerMove:a}){const i=x.useMemo(()=>{if(!n.isDragging||n.startDay!==t)return null;const l=n.startIndex,u=n.currentIndex;return{from:Math.min(l,u),to:Math.max(l,u)}},[n.isDragging,n.startDay,n.startIndex,n.currentIndex,t]),c=x.useMemo(()=>Array.from({length:ne},(l,u)=>({isSelected:r.has(u),isInDragRange:i!==null&&u>=i.from&&u<=i.to,isHourBoundary:u%4===0})),[r,i]);return s.jsx("div",{className:"flex flex-col",children:c.map((l,u)=>s.jsx(Zi,{index:u,isSelected:l.isSelected,isInDragRange:l.isInDragRange,isDragSelect:n.selectMode,isHourBoundary:l.isHourBoundary,onPointerDown:()=>o(u),onPointerEnter:()=>a(u)},u))})}),Li=[0,2,4,6,8,10,12,14,16,18,20,22];function Ui(e){return e===0?"12a":e===12?"12p":e<12?`${e}a`:`${e-12}p`}const Hi=x.memo(function(){return s.jsx("div",{className:"flex flex-col pr-1 text-xs text-gray-500 select-none",children:Li.map(t=>s.jsx("div",{className:"flex items-start justify-end",style:{height:"48px"},children:s.jsx("span",{className:"-mt-1.5",children:Ui(t)})},t))})}),Ne={isDragging:!1,startDay:null,startIndex:null,currentIndex:null,selectMode:!0};function Vi({schedule:e,onScheduleChange:t,disabled:r=!1}){const[n,o]=x.useState(Ne),a=x.useRef(null),i=x.useCallback((f,p)=>{if(r)return;const v=e[f].has(p);o({isDragging:!0,startDay:f,startIndex:p,currentIndex:p,selectMode:!v})},[e,r]),c=x.useCallback((f,p)=>{!n.isDragging||f!==n.startDay||o(v=>({...v,currentIndex:p}))},[n.isDragging,n.startDay]),l=x.useCallback(()=>{if(!n.isDragging||n.startDay===null||n.startIndex===null||n.currentIndex===null){o(Ne);return}const f={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)},p=new Set(e[n.startDay]),[v,g]=n.startIndex<=n.currentIndex?[n.startIndex,n.currentIndex]:[n.currentIndex,n.startIndex];for(let S=v;S<=g;S++)n.selectMode?p.add(S):p.delete(S);f[n.startDay]=p,t(f),o(Ne)},[n,e,t]);x.useEffect(()=>{const f=p=>{p.key==="Escape"&&n.isDragging&&o(Ne)};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)},[n.isDragging]),x.useEffect(()=>(n.isDragging?(document.body.style.touchAction="none",document.body.style.userSelect="none"):(document.body.style.touchAction="",document.body.style.userSelect=""),()=>{document.body.style.touchAction="",document.body.style.userSelect=""}),[n.isDragging]);const u=x.useCallback(f=>{if(r)return;const p={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)};p[f].size===ne?p[f]=new Set:p[f]=new Set(Array.from({length:ne},(v,g)=>g)),t(p)},[e,t,r]),h=(()=>{if(!n.isDragging||n.startIndex===null||n.currentIndex===null)return null;const f=Math.min(n.startIndex,n.currentIndex),p=Math.max(n.startIndex,n.currentIndex);return Ai(f,p)})();return s.jsxs("div",{className:"select-none",children:[s.jsxs("div",{className:"flex mb-1",children:[s.jsx("div",{className:"w-8 shrink-0"}),s.jsx("div",{className:"grid grid-cols-7 gap-px flex-1",children:ee.map(f=>{const p=e[f].size===ne,v=e[f].size>0;return s.jsxs("button",{type:"button",onClick:()=>u(f),disabled:r,className:`text-xs font-medium py-1 rounded-t transition-colors ${p?"bg-primary text-white":v?"bg-primary/30 text-primary":"bg-surface-elevated text-gray-400 hover:bg-surface-hover"} ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,title:`Click to ${p?"clear":"select all"} ${Et[f].full}`,children:[s.jsx("span",{className:"hidden sm:inline",children:Et[f].short}),s.jsx("span",{className:"sm:hidden",children:f.charAt(0).toUpperCase()})]},f)})})]}),s.jsxs("div",{className:"flex",children:[s.jsx("div",{className:"w-8 shrink-0",children:s.jsx(Hi,{})}),s.jsx("div",{ref:a,className:`grid grid-cols-7 gap-px bg-surface-elevated flex-1 rounded ${r?"opacity-50":""}`,style:{touchAction:"none"},onPointerUp:l,onPointerLeave:l,onPointerCancel:l,children:ee.map(f=>s.jsx(Fi,{day:f,schedule:e[f],dragState:n,onPointerDown:p=>i(f,p),onPointerMove:p=>c(f,p)},f))})]}),h&&s.jsx("div",{className:"mt-2 text-center",children:s.jsxs("span",{className:"text-xs bg-surface-elevated px-2 py-1 rounded text-gray-300",children:[n.selectMode?"Selecting":"Deselecting",":"," ",s.jsx("span",{className:"text-white font-medium",children:h})]})}),s.jsx("div",{className:"mt-2 text-xs text-gray-500 text-center",children:"Click and drag to select time ranges. Click day header to toggle entire day."})]})}const Bi=[{label:"Business Hours",value:"default=true action=pause mon-fri=0900-1700",description:"Pause Mon-Fri 9am-5pm"},{label:"Night Watch",value:"default=false action=pause sun-sat=1800-2359 sun-sat=0000-0600",description:"Active 6pm-6am only"},{label:"Weekends Only",value:"default=false action=pause sat=0000-2359 sun=0000-2359",description:"Active Sat & Sun only"},{label:"Always On",value:"default=true action=pause",description:"No schedule restrictions"}],Wi=x.memo(function({defaultOn:t,action:r,onDefaultOnChange:n,onActionChange:o,onApplyPreset:a,onClearAll:i,disabled:c=!1}){return s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Detection Default"}),s.jsxs("select",{value:t?"on":"off",onChange:l=>n(l.target.value==="on"),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"on",children:"On by default"}),s.jsx("option",{value:"off",children:"Off by default"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:t?"Schedule defines when detection is paused":"Schedule defines when detection is active"})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Schedule Action"}),s.jsxs("select",{value:r,onChange:l=>o(l.target.value),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"pause",children:"Pause detection"}),s.jsx("option",{value:"stop",children:"Stop camera"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:r==="pause"?"Camera runs but ignores motion":"Camera completely stops during schedule"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-2",children:"Quick Presets"}),s.jsxs("div",{className:"flex flex-wrap gap-2",children:[Bi.map(l=>s.jsx("button",{type:"button",onClick:()=>a(l.value),disabled:c,className:"px-3 py-1.5 text-xs bg-surface-elevated hover:bg-surface-hover border border-gray-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:l.description,children:l.label},l.label)),s.jsx("button",{type:"button",onClick:i,disabled:c,className:"px-3 py-1.5 text-xs bg-danger/20 hover:bg-danger/30 text-danger border border-danger/30 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:"Clear all time ranges",children:"Clear All"})]})]})]})});function Rt({value:e,onChange:t,helpText:r,error:n,disabled:o=!1}){const{schedule:a,defaultOn:i,action:c,updateSchedule:l,setDefaultOn:u,setAction:d,applyPreset:h,clearAll:f}=Ri({value:e,onChange:t}),p=Ei(a),v=Di(a);return s.jsxs("div",{className:"space-y-4",children:[s.jsx(Wi,{defaultOn:i,action:c,onDefaultOnChange:u,onActionChange:d,onApplyPreset:h,onClearAll:f,disabled:o}),s.jsx("div",{className:"border border-gray-700 rounded-lg p-4 bg-surface",children:s.jsx(Vi,{schedule:a,onScheduleChange:l,disabled:o})}),s.jsxs("div",{className:"flex items-center justify-between text-sm",children:[s.jsx("span",{className:"text-gray-400",children:p?s.jsx("span",{className:"text-yellow-400",children:"No time ranges configured"}):s.jsxs(s.Fragment,{children:["Schedule: ",s.jsx("span",{className:"text-white",children:v})]})}),i?s.jsx("span",{className:"text-xs text-gray-500",children:"Detection paused during selected times"}):s.jsx("span",{className:"text-xs text-gray-500",children:"Detection active during selected times"})]}),r&&!n&&s.jsx("p",{className:"text-sm text-gray-400",children:r}),n&&s.jsx("p",{className:"text-sm text-red-400",role:"alert",children:n}),s.jsxs("details",{className:"text-xs",children:[s.jsx("summary",{className:"cursor-pointer text-gray-500 hover:text-gray-400",children:"Show raw schedule format"}),s.jsx("code",{className:"block mt-2 p-2 bg-surface-elevated rounded text-gray-400 break-all",children:e||"(empty)"})]})]})}function Yi({config:e,onChange:t,getError:r}){const n=(d,h="")=>e[d]?.value??h,o=String(n("schedule_params","")),a=o.trim()!=="",i=d=>{d?t("schedule_params","default=true action=pause mon-fri=0900-1700"):t("schedule_params","")},c=String(n("picture_schedule_params","")),l=c.trim()!=="",u=d=>{d?t("picture_schedule_params","default=false action=pause mon-fri=0900-1700"):t("picture_schedule_params","")};return s.jsx(O,{title:"Schedules",description:"Configure when motion detection and continuous recording are active",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-6",children:[s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Motion Detection Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when motion detection is active or paused"}),s.jsx(V,{label:"Enable Motion Detection Schedule",value:a,onChange:i,helpText:"When enabled, motion detection follows the schedule below"}),a&&s.jsx(Rt,{value:o,onChange:d=>t("schedule_params",d),error:r?.("schedule_params")})]}),s.jsx("div",{className:"border-t border-gray-700"}),s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Continuous Recording Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when continuous picture capture (timelapse) is active"}),s.jsx(V,{label:"Enable Continuous Recording Schedule",value:l,onChange:u,helpText:"When enabled, continuous recording follows the schedule below"}),l&&s.jsx(Rt,{value:c,onChange:d=>t("picture_schedule_params",d),error:r?.("picture_schedule_params")})]})]})})}const We={gridColumns:2,gridRows:2,fitFramesVertically:!1,playbackFramerateFactor:1,playbackResolutionFactor:1,theme:"dark"};function qi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{...We,...t}}catch(t){console.error("Failed to parse preferences:",t)}return We}function Ji(){const[e,t]=x.useState(qi),r=(n,o)=>{const a={...e,[n]:o};t(a),localStorage.setItem("motion-ui-preferences",JSON.stringify(a))};return s.jsx(O,{title:"UI Preferences",description:"User interface preferences (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Dashboard Layout"}),s.jsx(C,{label:"Grid Columns",value:e.gridColumns,onChange:n=>r("gridColumns",n),min:1,max:4,helpText:"Number of camera columns in dashboard grid (1-4)"}),s.jsx(C,{label:"Grid Rows",value:e.gridRows,onChange:n=>r("gridRows",n),min:1,max:4,helpText:"Number of camera rows in dashboard grid (1-4)"}),s.jsx(V,{label:"Fit Frames Vertically",value:e.fitFramesVertically,onChange:n=>r("fitFramesVertically",n),helpText:"Fit camera frames to viewport height instead of width"})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Playback Settings"}),s.jsx(C,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:n=>r("playbackFramerateFactor",n),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(C,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:n=>r("playbackResolutionFactor",n),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Appearance"}),s.jsx(D,{label:"Theme",value:e.theme,onChange:n=>r("theme",n),options:[{value:"dark",label:"Dark"},{value:"light",label:"Light (Coming Soon)"},{value:"auto",label:"Auto (System Preference)"}],helpText:"UI color theme",disabled:!0}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Light theme and auto theme switching will be available in a future update."]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("button",{onClick:()=>{localStorage.removeItem("motion-ui-preferences"),t(We)},className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors text-sm",children:"Reset to Defaults"}),s.jsx("p",{className:"text-xs text-gray-400 mt-2",children:"Clears all saved preferences and returns to default settings"})]})]})})}const Fe={playbackFramerateFactor:1,playbackResolutionFactor:1};function Gi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{playbackFramerateFactor:t.playbackFramerateFactor??Fe.playbackFramerateFactor,playbackResolutionFactor:t.playbackResolutionFactor??Fe.playbackResolutionFactor}}catch(t){console.error("Failed to parse preferences:",t)}return Fe}function Ki(){const[e,t]=x.useState(Gi),r=(n,o)=>{const a={...e,[n]:o};t(a);const i=localStorage.getItem("motion-ui-preferences");let c={};if(i)try{c=JSON.parse(i)}catch(l){console.error("Failed to parse existing preferences:",l)}localStorage.setItem("motion-ui-preferences",JSON.stringify({...c,...a}))};return s.jsx(O,{title:"Playback Settings",description:"Video playback preferences for this camera (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsx(C,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:n=>r("playbackFramerateFactor",n),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(C,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:n=>r("playbackResolutionFactor",n),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]})})}function Qi({cameraId:e}){const{addToast:t}=Ye(),r=Ft(),n=x.useRef(null),o=x.useRef(null),[a,i]=x.useState("motion"),[c,l]=x.useState("rectangle"),[u,d]=x.useState(!1),[h,f]=x.useState(null),[p,v]=x.useState(null),[g,S]=x.useState([]),[_,b]=x.useState([]),[I,G]=x.useState(!1),[H,R]=x.useState(!1),{data:te,isLoading:Oe}=qe({queryKey:["mask",e,a],queryFn:()=>Lt(`/${e}/api/mask/${a}`)}),[z,N]=x.useState({width:640,height:480}),A=nt({mutationFn:k=>Te(`/${e}/api/mask/${a}`,k),onSuccess:()=>{t(`${a==="motion"?"Motion":"Privacy"} mask saved`,"success"),r.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to save mask","error")}}),E=nt({mutationFn:()=>xr(`/${e}/api/mask/${a}`),onSuccess:()=>{t("Mask deleted","success"),S([]),r.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to delete mask","error")}}),W=x.useCallback(k=>{const w=n.current;if(!w)return{x:0,y:0};const T=w.getBoundingClientRect(),K=z.width/T.width,ae=z.height/T.height;return{x:Math.round((k.clientX-T.left)*K),y:Math.round((k.clientY-T.top)*ae)}},[z]),X=x.useCallback(()=>{const k=n.current;if(!k)return;const w=k.getContext("2d");if(w&&(w.clearRect(0,0,k.width,k.height),w.fillStyle="rgba(255, 0, 0, 0.4)",w.strokeStyle="rgba(255, 0, 0, 0.8)",w.lineWidth=2,g.forEach(T=>{T.length<3||(w.beginPath(),w.moveTo(T[0].x,T[0].y),T.slice(1).forEach(K=>w.lineTo(K.x,K.y)),w.closePath(),w.fill(),w.stroke())}),_.length>0&&(w.beginPath(),w.moveTo(_[0].x,_[0].y),_.slice(1).forEach(T=>w.lineTo(T.x,T.y)),p&&w.lineTo(p.x,p.y),w.stroke(),w.fillStyle="rgba(255, 255, 0, 0.8)",_.forEach(T=>{w.beginPath(),w.arc(T.x,T.y,4,0,Math.PI*2),w.fill()})),c==="rectangle"&&u&&h&&p)){w.fillStyle="rgba(255, 0, 0, 0.4)",w.strokeStyle="rgba(255, 0, 0, 0.8)";const T=Math.min(h.x,p.x),K=Math.min(h.y,p.y),ae=Math.abs(p.x-h.x),ve=Math.abs(p.y-h.y);w.fillRect(T,K,ae,ve),w.strokeRect(T,K,ae,ve)}},[g,_,p,h,u,c]);x.useEffect(()=>{X()},[X]);const Y=x.useCallback(k=>{const w=W(k);c==="rectangle"?(d(!0),f(w),v(w)):b(T=>[...T,w])},[c,W]),Me=x.useCallback(k=>{const w=W(k);v(w)},[W]),de=x.useCallback(()=>{if(c==="rectangle"&&u&&h&&p){const k=Math.min(h.x,p.x),w=Math.min(h.y,p.y),T=Math.max(h.x,p.x),K=Math.max(h.y,p.y);if(T-k>5&&K-w>5){const ae=[{x:k,y:w},{x:T,y:w},{x:T,y:K},{x:k,y:K}];S(ve=>[...ve,ae])}}d(!1),f(null)},[c,u,h,p]),q=x.useCallback(()=>{c==="polygon"&&_.length>=3&&(S(k=>[...k,_]),b([]))},[c,_]),me=x.useCallback(()=>{S([]),b([]),f(null),v(null),d(!1)},[]),dr=x.useCallback(()=>{_.length>0?b(k=>k.slice(0,-1)):g.length>0&&S(k=>k.slice(0,-1))},[_.length,g.length]),mr=x.useCallback(()=>{if(g.length===0){t("Draw at least one mask area first","warning");return}A.mutate({polygons:g,width:z.width,height:z.height,invert:I})},[g,z,I,A,t]),hr=x.useCallback(()=>{window.confirm(`Delete the ${a} mask? This cannot be undone.`)&&E.mutate()},[a,E]),pr=x.useCallback(k=>{const w=k.currentTarget;N({width:w.naturalWidth,height:w.naturalHeight}),R(!1)},[]),fr=x.useMemo(()=>{const k=gr(),w=`/${e}/mjpg/stream`;return k?`${w}?token=${encodeURIComponent(k)}`:w},[e]);return s.jsxs(O,{title:"Mask Editor",description:"Draw mask areas on the camera feed to define motion detection or privacy zones",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"flex gap-4 mb-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Mask Type"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>{i("motion"),me()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="motion"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Motion"}),s.jsx("button",{onClick:()=>{i("privacy"),me()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="privacy"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Privacy"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Draw Mode"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>l("rectangle"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="rectangle"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Rectangle"}),s.jsx("button",{onClick:()=>l("polygon"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="polygon"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Polygon"})]})]}),s.jsx("div",{className:"flex items-end",children:s.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[s.jsx("input",{type:"checkbox",checked:I,onChange:k=>G(k.target.checked),className:"rounded"}),"Invert (detect outside areas)"]})})]}),Oe?s.jsx("div",{className:"text-sm text-gray-400 mb-4",children:"Loading mask info..."}):te?.exists?s.jsxs("div",{className:"text-sm text-green-500 mb-4",children:["Current mask: ",te.path," (",te.width,"x",te.height,")"]}):s.jsxs("div",{className:"text-sm text-gray-400 mb-4",children:["No ",a," mask configured"]}),s.jsxs("div",{ref:o,className:"relative bg-black rounded-lg overflow-hidden mb-4",children:[H?s.jsx("div",{className:"aspect-video bg-surface flex items-center justify-center text-gray-400",children:s.jsxs("div",{className:"text-center",children:[s.jsx("p",{children:"Camera stream unavailable"}),s.jsx("p",{className:"text-xs mt-1",children:"Draw on blank canvas (640x480)"})]})}):s.jsx("img",{src:fr,alt:"Camera stream",onLoad:pr,onError:()=>R(!0),className:"w-full h-auto",style:{display:"block"}}),s.jsx("canvas",{ref:n,width:z.width,height:z.height,onMouseDown:Y,onMouseMove:Me,onMouseUp:de,onMouseLeave:de,onDoubleClick:q,className:"absolute inset-0 w-full h-full cursor-crosshair",style:{touchAction:"none"}})]}),s.jsx("div",{className:"text-xs text-gray-400 mb-4",children:c==="rectangle"?s.jsxs("p",{children:["Click and drag to draw rectangles. Red areas will be ",a==="motion"?"ignored for motion detection":"blacked out for privacy","."]}):s.jsx("p",{children:"Click to add polygon points. Double-click to complete the polygon."})}),s.jsxs("div",{className:"flex gap-3 flex-wrap",children:[s.jsx("button",{onClick:dr,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Undo"}),s.jsx("button",{onClick:me,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Clear All"}),s.jsx("button",{onClick:mr,disabled:A.isPending||g.length===0,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:A.isPending?"Saving...":"Save Mask"}),te?.exists&&s.jsx("button",{onClick:hr,disabled:E.isPending,className:"px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 rounded transition-colors disabled:opacity-50",children:E.isPending?"Deleting...":"Delete Mask"})]}),g.length>0&&s.jsxs("div",{className:"text-xs text-gray-400 mt-3",children:[g.length," mask area",g.length!==1?"s":""," drawn"]})]})}const pe={custom:{label:"Custom Command",description:"Enter your own shell command",command:""},webhook:{label:"Webhook (HTTP POST)",description:"Send HTTP POST to a URL",command:`curl -s -X POST -H "Content-Type: application/json" -d '{"camera":"%t","event":"%v","time":"%Y-%m-%d %T"}' "YOUR_WEBHOOK_URL"`},telegram:{label:"Telegram Bot",description:"Send message via Telegram Bot API",command:'curl -s -X POST "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage" -d "chat_id=YOUR_CHAT_ID&text=Motion detected on %t at %Y-%m-%d %T"'},email:{label:"Email (msmtp)",description:"Send email using msmtp",command:'echo -e "Subject: Motion Alert\\n\\nMotion detected on camera %t at %Y-%m-%d %T" | msmtp recipient@example.com'},pushover:{label:"Pushover",description:"Send push notification via Pushover",command:'curl -s -F "token=YOUR_APP_TOKEN" -F "user=YOUR_USER_KEY" -F "message=Motion on %t at %T" https://api.pushover.net/1/messages.json'}};function Xi({config:e,onChange:t,getError:r}){const[n,o]=x.useState("custom"),a=(c,l="")=>e[c]?.value??l,i=c=>{const l=pe[n];l.command&&t(c,l.command)};return s.jsxs(O,{title:"Notifications & Scripts",description:"Configure commands that run on motion events. Use script hooks to send notifications via webhooks, Telegram, email, or custom scripts.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsx("h4",{className:"font-medium mb-2",children:"Available Variables"}),s.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-gray-400",children:[s.jsx("code",{children:"%f"})," ",s.jsx("span",{children:"Filename"}),s.jsx("code",{children:"%t"})," ",s.jsx("span",{children:"Camera name"}),s.jsx("code",{children:"%v"})," ",s.jsx("span",{children:"Event number"}),s.jsx("code",{children:"%Y"})," ",s.jsx("span",{children:"Year (4 digit)"}),s.jsx("code",{children:"%m"})," ",s.jsx("span",{children:"Month (01-12)"}),s.jsx("code",{children:"%d"})," ",s.jsx("span",{children:"Day (01-31)"}),s.jsx("code",{children:"%H"})," ",s.jsx("span",{children:"Hour (00-23)"}),s.jsx("code",{children:"%M"})," ",s.jsx("span",{children:"Minute (00-59)"}),s.jsx("code",{children:"%S"})," ",s.jsx("span",{children:"Second (00-59)"}),s.jsx("code",{children:"%T"})," ",s.jsx("span",{children:"Time HH:MM:SS"})]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-3",children:Object.keys(pe).map(c=>s.jsx("button",{onClick:()=>o(c),className:`px-3 py-1.5 text-sm rounded transition-colors ${n===c?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:pe[c].label},c))}),n!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-3",children:[s.jsx("p",{children:pe[n].description}),s.jsx("pre",{className:"mt-2 p-2 bg-surface rounded text-xs overflow-x-auto whitespace-pre-wrap break-all",children:pe[n].command})]}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>i("on_event_start"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Event Start"}),s.jsx("button",{onClick:()=>i("on_picture_save"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Picture Save"}),s.jsx("button",{onClick:()=>i("on_movie_end"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Movie End"})]})]}),s.jsx(j,{label:"On Event Start",value:String(a("on_event_start","")),onChange:c=>t("on_event_start",c),placeholder:"Command to run when motion event starts",helpText:"Executed when a new motion event begins",error:r?.("on_event_start")}),s.jsx(j,{label:"On Event End",value:String(a("on_event_end","")),onChange:c=>t("on_event_end",c),placeholder:"Command to run when motion event ends",helpText:"Executed when motion event ends (after gap timeout)",error:r?.("on_event_end")}),s.jsx(j,{label:"On Motion Detected",value:String(a("on_motion_detected","")),onChange:c=>t("on_motion_detected",c),placeholder:"Command to run on each motion frame",helpText:"Executed on every frame with motion (can be frequent!)",error:r?.("on_motion_detected")}),s.jsx(j,{label:"On Picture Save",value:String(a("on_picture_save","")),onChange:c=>t("on_picture_save",c),placeholder:"Command to run when picture is saved",helpText:"Executed after a snapshot is saved (%f = filename)",error:r?.("on_picture_save")}),s.jsx(j,{label:"On Movie Start",value:String(a("on_movie_start","")),onChange:c=>t("on_movie_start",c),placeholder:"Command to run when recording starts",helpText:"Executed when video recording begins",error:r?.("on_movie_start")}),s.jsx(j,{label:"On Movie End",value:String(a("on_movie_end","")),onChange:c=>t("on_movie_end",c),placeholder:"Command to run when recording ends",helpText:"Executed when video recording is complete (%f = filename)",error:r?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Example: Send Picture via Telegram"}),s.jsx("pre",{className:"text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap",children:`# On Picture Save: +curl -F chat_id="YOUR_CHAT_ID" \\ + -F photo=@"%f" \\ + -F caption="Motion on %t at %T" \\ + "https://api.telegram.org/botYOUR_TOKEN/sendPhoto"`})]})]})}const ie={rclone:{label:"Rclone",description:"Universal cloud storage sync (supports 40+ providers)",pictureCmd:'rclone copy "%f" remote:motion/pictures/%Y%m%d/',movieCmd:'rclone copy "%f" remote:motion/movies/%Y%m%d/'},s3:{label:"AWS S3",description:"Amazon S3 or compatible storage (MinIO, DigitalOcean Spaces)",pictureCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/pictures/%Y%m%d/',movieCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/movies/%Y%m%d/'},gdrive:{label:"Google Drive",description:"Upload via gdrive CLI",pictureCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"',movieCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"'},dropbox:{label:"Dropbox",description:"Upload via Dropbox-Uploader script",pictureCmd:'dropbox_uploader.sh upload "%f" /motion/pictures/',movieCmd:'dropbox_uploader.sh upload "%f" /motion/movies/'},sftp:{label:"SFTP/SCP",description:"Upload to remote server via SSH",pictureCmd:'scp "%f" user@server:/backup/motion/pictures/',movieCmd:'scp "%f" user@server:/backup/motion/movies/'},custom:{label:"Custom",description:"Enter your own upload command",pictureCmd:"",movieCmd:""}};function ec({config:e,onChange:t,getError:r}){const[n,o]=x.useState("rclone"),a=(u,d="")=>e[u]?.value??d,i=String(a("on_picture_save","")),c=String(a("on_movie_end","")),l=()=>{const u=ie[n];u.pictureCmd&&t("on_picture_save",u.pictureCmd),u.movieCmd&&t("on_movie_end",u.movieCmd)};return s.jsxs(O,{title:"Cloud Upload",description:"Configure automatic upload of pictures and videos to cloud storage. Uses the same event hooks as notifications.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsxs("p",{className:"text-gray-400 mb-2",children:["Cloud uploads are triggered by the ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_picture_save"})," and ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_movie_end"})," event hooks. Select a provider template below or enter a custom command."]}),s.jsxs("p",{className:"text-xs text-gray-500",children:[s.jsx("strong",{children:"Note:"})," You must install the required CLI tools (rclone, aws-cli, etc.) on your Pi before uploads will work."]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Cloud Provider Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:Object.keys(ie).map(u=>s.jsx("button",{onClick:()=>o(u),className:`px-3 py-1.5 text-sm rounded transition-colors ${n===u?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:ie[u].label},u))}),n!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-4",children:[s.jsx("p",{className:"mb-2",children:ie[n].description}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Picture:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[n].pictureCmd})]}),s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Movie:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[n].movieCmd})]})]})]}),s.jsx("button",{onClick:l,disabled:n==="custom",className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:"Apply Template"})]}),s.jsx(j,{label:"Picture Upload Command",value:i,onChange:u=>t("on_picture_save",u),placeholder:"Command to upload pictures",helpText:"Runs after each picture is saved. %f = filename, %Y%m%d = date",error:r?.("on_picture_save")}),s.jsx(j,{label:"Movie Upload Command",value:c,onChange:u=>t("on_movie_end",u),placeholder:"Command to upload videos",helpText:"Runs after each video recording completes. %f = filename",error:r?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Guides"}),s.jsxs("div",{className:"space-y-4 text-xs text-gray-400",children:[s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"Rclone Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install rclone +sudo apt install rclone + +# Configure a remote (interactive) +rclone config + +# Test upload +rclone copy /path/to/file remote:folder/`})]}),s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"AWS S3 Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install AWS CLI +sudo apt install awscli + +# Configure credentials +aws configure + +# Test upload +aws s3 cp /path/to/file s3://bucket/`})]})]})]})]})}function nc(){const{role:e}=br(),{addToast:t}=Ye(),[r,n]=x.useState("0"),[o,a]=x.useState({}),[i,c]=x.useState({}),[l,u]=x.useState(!1),d=Ft(),{data:h,isLoading:f,error:p}=qe({queryKey:["config"],queryFn:async()=>{const N=await Lt("/0/api/config");return N.csrf_token&&yr(N.csrf_token),N}}),v=vr(),{data:g}=Zt(),{data:S}=Dr(Number(r)),_=lr(Number(r));x.useEffect(()=>{a({}),c({})},[r]);const b=x.useCallback((N,A)=>{a(W=>({...W,[N]:A}));const E=ai(N,String(A));c(W=>{if(E.success){const{[N]:X,...Y}=W;return Y}else return{...W,[N]:E.error??"Invalid value"}})},[]),I=Object.keys(o).length>0,G=Object.keys(i).length>0,H=x.useMemo(()=>{if(!h)return{};const N=h.configuration.default||{};if(r==="0")return N;{const A=h.configuration[`cam${r}`]||{};return{...N,...A}}},[h,r]),R=x.useMemo(()=>{if(!h)return{};const N={...H};for(const[A,E]of Object.entries(o))N[A]?N[A]={...N[A],value:E}:N[A]={value:E,enabled:!0,category:0,type:"string"};return N},[h,H,o]),te=async()=>{if(!I){t("No changes to save","info");return}if(G){t("Please fix validation errors before saving","error");return}u(!0);const N=parseInt(r,10);try{const A=await v.mutateAsync({camId:N,changes:o});await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]});const E=A?.summary,W=A?.applied||[];if(!E)t(`Saved ${Object.keys(o).length} setting(s)`,"success"),a({});else{const X=W.filter(Y=>!Y.error&&Y.hot_reload===!1).map(Y=>Y.param);if(X.length>0){t(`Restarting camera to apply ${X.length} setting(s): ${X.join(", ")}...`,"info"),a({});const Y=await _r(N);Zr(N),window.dispatchEvent(new CustomEvent(Fr,{detail:{cameraId:N}})),await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]}),Y?t(`Applied ${X.length} setting(s). Camera restarted successfully.`,"success"):t("Settings saved. Camera is restarting - refresh page if stream doesn't recover.","warning")}else if(E.errors>0){const Y=W.filter(q=>q.error).map(q=>q.param),Me=W.filter(q=>!q.error).map(q=>q.param),de={};for(const[q,me]of Object.entries(o))Me.includes(q)||(de[q]=me);a(de),E.success>0?t(`Saved ${E.success} setting(s). ${E.errors} failed: ${Y.join(", ")}`,"warning"):t(`Failed to save settings: ${Y.join(", ")}`,"error")}else t(`Successfully saved ${E.success} setting(s)`,"success"),a({})}}catch(A){console.error("Failed to save settings:",A),t("Failed to save settings. Check browser console for details.","error")}finally{u(!1)}},Oe=()=>{a({}),c({}),t("Changes discarded","info")},z=N=>i[N];return e!=="admin"?s.jsx("div",{className:"p-4 sm:p-6",children:s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[s.jsx("svg",{className:"w-16 h-16 mx-auto text-yellow-500 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),s.jsx("h1",{className:"text-2xl font-bold mb-2",children:"Admin Access Required"}),s.jsx("p",{className:"text-gray-400",children:"You must be logged in as an administrator to access settings."})]})}):f?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsxs("div",{className:"animate-pulse",children:[s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"}),s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"})]})]}):p?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsx("div",{className:"bg-danger/10 border border-danger rounded-lg p-4",children:s.jsx("p",{className:"text-danger",children:"Failed to load configuration"})})]}):h?s.jsxs("div",{className:"p-6",children:[s.jsx("div",{className:"sticky top-[73px] z-40 -mx-6 px-6 py-3 bg-surface/95 backdrop-blur border-b border-gray-800 mb-6",children:s.jsxs("div",{className:"flex items-center justify-between",children:[s.jsxs("div",{className:"flex items-center gap-4",children:[s.jsx("h2",{className:"text-2xl font-bold",children:"Settings"}),s.jsx("div",{children:s.jsxs("select",{value:r,onChange:N=>n(N.target.value),className:"px-3 py-1.5 bg-surface-elevated border border-gray-700 rounded-lg text-sm",children:[s.jsx("option",{value:"0",children:"Global Settings"}),h.cameras&&Object.entries(h.cameras).map(([N,A])=>{if(N==="count"||typeof A=="number")return null;const E=A;return s.jsx("option",{value:String(E.id),children:E.name||`Camera ${E.id}`},E.id)})]})})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[I&&!G&&s.jsx("span",{className:"text-yellow-200 text-sm",children:"Unsaved changes"}),G&&s.jsx("span",{className:"text-red-200 text-sm",children:"Fix errors below"}),I&&s.jsx("button",{onClick:Oe,disabled:l,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded-lg transition-colors disabled:opacity-50",children:"Discard"}),s.jsx("button",{onClick:te,disabled:!I||l||G,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:l?"Saving...":I?"Save Changes":"Saved"})]})]})}),r==="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-6",children:s.jsx("p",{className:"text-sm text-blue-200",children:"Global settings apply to the Motion daemon and web server. To configure camera-specific settings, select a camera from the dropdown above."})}),s.jsx(ui,{config:R,onChange:b,getError:z,originalConfig:H,systemStatus:g}),s.jsx(Ji,{}),s.jsx(O,{title:"About",description:"Motion version information",collapsible:!0,defaultOpen:!1,children:s.jsxs("p",{className:"text-sm text-gray-400",children:["Motion Version: ",h.version]})})]}),r!=="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-surface-elevated rounded-lg p-4 mb-6",children:s.jsx(Rr,{cameraId:Number(r),readOnly:!1})}),s.jsx(mi,{cameraId:Number(r)}),_.features.hasLibcamControls&&s.jsx(fi,{config:R,onChange:b,getError:z,capabilities:S,originalConfig:H}),_.features.hasV4L2Controls&&s.jsx(gi,{config:R,onChange:b,controls:_.v4l2Controls,getError:z}),_.features.hasNetcamConfig&&s.jsx(_i,{config:R,onChange:b,connectionStatus:_.netcamStatus,hasDualStream:_.features.hasDualStream,getError:z}),s.jsx(di,{config:R,onChange:b,getError:z}),s.jsx($i,{config:R,onChange:b,getError:z,showPassthrough:_.features.supportsPassthrough}),s.jsx(Si,{config:R,onChange:b,getError:z}),s.jsx(ki,{config:R,onChange:b,getError:z}),s.jsx(Ni,{config:R,onChange:b,getError:z}),s.jsx(Qi,{cameraId:parseInt(r,10)}),s.jsx(Yi,{config:R,onChange:b,getError:z}),s.jsx(zi,{config:R,onChange:b,getError:z,originalConfig:H}),s.jsx(wi,{config:R,onChange:b,getError:z}),s.jsx(Xi,{config:R,onChange:b,getError:z}),s.jsx(ec,{config:R,onChange:b,getError:z}),s.jsx(Ki,{})]})]}):null}export{nc as Settings}; diff --git a/data/webui/assets/index-BK6lPNRb.css b/data/webui/assets/index-BK6lPNRb.css deleted file mode 100644 index 0eed9af4..00000000 --- a/data/webui/assets/index-BK6lPNRb.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}body{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-1{left:.25rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-4{top:1rem}.top-\[73px\]{top:73px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[101\]{z-index:101}.z-\[150\]{z-index:150}.z-\[60\]{z-index:60}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1\.5{margin-top:-.375rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-\[6px\]{height:6px}.h-auto{height:auto}.h-full{height:100%}.max-h-\[70vh\]{max-height:70vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[250px\]{min-width:250px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-grab{cursor:grab}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-px{gap:1px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-blue-500\/20{border-color:#3b82f633}.border-blue-500\/30{border-color:#3b82f64d}.border-danger{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-danger\/30{border-color:#ef44444d}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-700\/50{border-color:#37415180}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-600\/30{border-color:#dc26264d}.border-surface{--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.border-surface-elevated{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-yellow-500\/50{border-color:#eab30880}.border-yellow-600\/30{border-color:#ca8a044d}.border-yellow-700{--tw-border-opacity: 1;border-color:rgb(161 98 7 / var(--tw-border-opacity, 1))}.bg-amber-950\/30{background-color:#451a034d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600\/20{background-color:#2563eb33}.bg-blue-950\/30{background-color:#1725544d}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-danger\/10{background-color:#ef44441a}.bg-danger\/20{background-color:#ef444433}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600\/20{background-color:#4b556333}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-950\/30{background-color:#052e164d}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-primary\/20{background-color:#3b82f633}.bg-primary\/30{background-color:#3b82f64d}.bg-primary\/70{background-color:#3b82f6b3}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/10{background-color:#dc26261a}.bg-red-600\/20{background-color:#dc262633}.bg-red-600\/80{background-color:#dc2626cc}.bg-red-950\/30{background-color:#450a0a4d}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.bg-surface-elevated{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-surface-hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.bg-surface\/95{background-color:#1a1a1af2}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.bg-yellow-600\/10{background-color:#ca8a041a}.bg-yellow-600\/20{background-color:#ca8a0433}.bg-yellow-900\/30{background-color:#713f124d}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-4{padding-left:1rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-300\/80{color:#93c5fdcc}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in{animation:slide-in .3s ease-out}.hover\:border-danger:hover{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.hover\:bg-blue-600\/30:hover{background-color:#2563eb4d}.hover\:bg-danger\/20:hover{background-color:#ef444433}.hover\:bg-danger\/30:hover{background-color:#ef44444d}.hover\:bg-danger\/80:hover{background-color:#ef4444cc}.hover\:bg-primary-hover:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-red-600\/40:hover{background-color:#dc262666}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-elevated:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-hover:hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-600\/30:hover{background-color:#ca8a044d}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-primary:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-2:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-primary:hover{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-yellow-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(234 179 8 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width: 0px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-primary\/30:disabled{background-color:#3b82f64d}.disabled\:bg-surface\/30:disabled{background-color:#1a1a1a4d}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group:focus-within .group-focus-within\:opacity-100{opacity:1}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:mb-6{margin-bottom:1.5rem}.sm\:inline{display:inline}.sm\:hidden{display:none}.sm\:gap-6{gap:1.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:py-8{padding-top:2rem;padding-bottom:2rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/data/webui/assets/index-CVdKRgH6.css b/data/webui/assets/index-CVdKRgH6.css new file mode 100644 index 00000000..6249b3c9 --- /dev/null +++ b/data/webui/assets/index-CVdKRgH6.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}body{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-1{left:.25rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-4{top:1rem}.top-\[73px\]{top:73px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[101\]{z-index:101}.z-\[150\]{z-index:150}.z-\[60\]{z-index:60}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.-mt-1\.5{margin-top:-.375rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-\[6px\]{height:6px}.h-auto{height:auto}.h-full{height:100%}.max-h-\[70vh\]{max-height:70vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[250px\]{min-width:250px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-grab{cursor:grab}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-px{gap:1px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-blue-500\/20{border-color:#3b82f633}.border-blue-500\/30{border-color:#3b82f64d}.border-danger{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-danger\/30{border-color:#ef44444d}.border-gray-500\/30{border-color:#6b72804d}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-700\/50{border-color:#37415180}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-green-500\/30{border-color:#22c55e4d}.border-purple-500\/30{border-color:#a855f74d}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-500\/30{border-color:#ef44444d}.border-red-600\/30{border-color:#dc26264d}.border-surface{--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.border-surface-elevated{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-yellow-500\/30{border-color:#eab3084d}.border-yellow-500\/50{border-color:#eab30880}.border-yellow-600\/30{border-color:#ca8a044d}.border-yellow-700{--tw-border-opacity: 1;border-color:rgb(161 98 7 / var(--tw-border-opacity, 1))}.bg-amber-950\/30{background-color:#451a034d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-500\/20{background-color:#3b82f633}.bg-blue-600\/20{background-color:#2563eb33}.bg-blue-950\/30{background-color:#1725544d}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-danger\/10{background-color:#ef44441a}.bg-danger\/20{background-color:#ef444433}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-500\/20{background-color:#6b728033}.bg-gray-600\/20{background-color:#4b556333}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-950\/30{background-color:#052e164d}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-primary\/20{background-color:#3b82f633}.bg-primary\/30{background-color:#3b82f64d}.bg-primary\/70{background-color:#3b82f6b3}.bg-purple-500\/20{background-color:#a855f733}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/20{background-color:#ef444433}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/10{background-color:#dc26261a}.bg-red-600\/20{background-color:#dc262633}.bg-red-600\/80{background-color:#dc2626cc}.bg-red-950\/30{background-color:#450a0a4d}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.bg-surface-elevated{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-surface-hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.bg-surface\/95{background-color:#1a1a1af2}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-500\/20{background-color:#eab30833}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.bg-yellow-600\/10{background-color:#ca8a041a}.bg-yellow-600\/20{background-color:#ca8a0433}.bg-yellow-900\/30{background-color:#713f124d}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-4{padding-left:1rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-300\/80{color:#93c5fdcc}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in{animation:slide-in .3s ease-out}.first\:mt-0:first-child{margin-top:0}.hover\:border-danger:hover{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.hover\:bg-blue-600\/30:hover{background-color:#2563eb4d}.hover\:bg-danger\/20:hover{background-color:#ef444433}.hover\:bg-danger\/30:hover{background-color:#ef44444d}.hover\:bg-danger\/80:hover{background-color:#ef4444cc}.hover\:bg-primary-hover:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-red-600\/40:hover{background-color:#dc262666}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-elevated:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-hover:hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-600\/30:hover{background-color:#ca8a044d}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-primary:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-2:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-primary:hover{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-yellow-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(234 179 8 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width: 0px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-primary\/30:disabled{background-color:#3b82f64d}.disabled\:bg-surface\/30:disabled{background-color:#1a1a1a4d}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group:focus-within .group-focus-within\:opacity-100{opacity:1}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:mb-6{margin-bottom:1.5rem}.sm\:inline{display:inline}.sm\:hidden{display:none}.sm\:gap-6{gap:1.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:py-8{padding-top:2rem;padding-bottom:2rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/data/webui/assets/index-DCzq8GhF.js b/data/webui/assets/index-tiawrtsp.js similarity index 99% rename from data/webui/assets/index-DCzq8GhF.js rename to data/webui/assets/index-tiawrtsp.js index d743da22..32e85df7 100644 --- a/data/webui/assets/index-DCzq8GhF.js +++ b/data/webui/assets/index-tiawrtsp.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-D701LIV_.js","assets/parameterMappings-CUuSfEkB.js","assets/Settings-Ca5UNPDy.js"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-D7oUR9hz.js","assets/parameterMappings-BmLxmuw_.js","assets/Settings-JwhcLbnw.js"])))=>i.map(i=>d[i]); (function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))r(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const m of h.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function c(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function r(d){if(d.ep)return;d.ep=!0;const h=c(d);fetch(d.href,h)}})();function om(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var tr={exports:{}},kn={};var Hd;function E0(){if(Hd)return kn;Hd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function c(r,d,h){var m=null;if(h!==void 0&&(m=""+h),d.key!==void 0&&(m=""+d.key),"key"in d){h={};for(var v in d)v!=="key"&&(h[v]=d[v])}else h=d;return d=h.ref,{$$typeof:n,type:r,key:m,ref:d!==void 0?d:null,props:h}}return kn.Fragment=s,kn.jsx=c,kn.jsxs=c,kn}var Bd;function T0(){return Bd||(Bd=1,tr.exports=E0()),tr.exports}var _=T0(),er={exports:{}},P={};var Qd;function x0(){if(Qd)return P;Qd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),m=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),D=Symbol.iterator;function q(b){return b===null||typeof b!="object"?null:(b=D&&b[D]||b["@@iterator"],typeof b=="function"?b:null)}var H={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,Y={};function Q(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}Q.prototype.isReactComponent={},Q.prototype.setState=function(b,w){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,w,"setState")},Q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function X(){}X.prototype=Q.prototype;function J(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}var ht=J.prototype=new X;ht.constructor=J,B(ht,Q.prototype),ht.isPureReactComponent=!0;var ct=Array.isArray;function Rt(){}var F={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(b,w,G){var K=G.ref;return{$$typeof:n,type:b,key:w,ref:K!==void 0?K:null,props:G}}function Qt(b,w){return zt(b.type,w,b.props)}function $t(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function te(b){var w={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(G){return w[G]})}var Yl=/\/+/g;function Ze(b,w){return typeof b=="object"&&b!==null&&b.key!=null?te(""+b.key):w.toString(36)}function Ne(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Rt,Rt):(b.status="pending",b.then(function(w){b.status==="pending"&&(b.status="fulfilled",b.value=w)},function(w){b.status==="pending"&&(b.status="rejected",b.reason=w)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function U(b,w,G,K,I){var lt=typeof b;(lt==="undefined"||lt==="boolean")&&(b=null);var yt=!1;if(b===null)yt=!0;else switch(lt){case"bigint":case"string":case"number":yt=!0;break;case"object":switch(b.$$typeof){case n:case s:yt=!0;break;case T:return yt=b._init,U(yt(b._payload),w,G,K,I)}}if(yt)return I=I(b),yt=K===""?"."+Ze(b,0):K,ct(I)?(G="",yt!=null&&(G=yt.replace(Yl,"$&/")+"/"),U(I,w,G,"",function(tn){return tn})):I!=null&&($t(I)&&(I=Qt(I,G+(I.key==null||b&&b.key===I.key?"":(""+I.key).replace(Yl,"$&/")+"/")+yt)),w.push(I)),1;yt=0;var Wt=K===""?".":K+":";if(ct(b))for(var Ut=0;Ut>>1,Tt=U[gt];if(0>>1;gtd(G,W))Kd(I,G)?(U[gt]=I,U[K]=W,gt=K):(U[gt]=G,U[w]=W,gt=w);else if(Kd(I,W))U[gt]=I,U[K]=W,gt=K;else break t}}return L}function d(U,L){var W=U.sortIndex-L.sortIndex;return W!==0?W:U.id-L.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;n.unstable_now=function(){return h.now()}}else{var m=Date,v=m.now();n.unstable_now=function(){return m.now()-v}}var p=[],y=[],T=1,x=null,D=3,q=!1,H=!1,B=!1,Y=!1,Q=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,J=typeof setImmediate<"u"?setImmediate:null;function ht(U){for(var L=c(y);L!==null;){if(L.callback===null)r(y);else if(L.startTime<=U)r(y),L.sortIndex=L.expirationTime,s(p,L);else break;L=c(y)}}function ct(U){if(B=!1,ht(U),!H)if(c(p)!==null)H=!0,Rt||(Rt=!0,te());else{var L=c(y);L!==null&&Ne(ct,L.startTime-U)}}var Rt=!1,F=-1,rt=5,zt=-1;function Qt(){return Y?!0:!(n.unstable_now()-ztU&&Qt());){var gt=x.callback;if(typeof gt=="function"){x.callback=null,D=x.priorityLevel;var Tt=gt(x.expirationTime<=U);if(U=n.unstable_now(),typeof Tt=="function"){x.callback=Tt,ht(U),L=!0;break e}x===c(p)&&r(p),ht(U)}else r(p);x=c(p)}if(x!==null)L=!0;else{var b=c(y);b!==null&&Ne(ct,b.startTime-U),L=!1}}break t}finally{x=null,D=W,q=!1}L=void 0}}finally{L?te():Rt=!1}}}var te;if(typeof J=="function")te=function(){J($t)};else if(typeof MessageChannel<"u"){var Yl=new MessageChannel,Ze=Yl.port2;Yl.port1.onmessage=$t,te=function(){Ze.postMessage(null)}}else te=function(){Q($t,0)};function Ne(U,L){F=Q(function(){U(n.unstable_now())},L)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(U){U.callback=null},n.unstable_forceFrameRate=function(U){0>U||125gt?(U.sortIndex=W,s(y,U),c(p)===null&&U===c(y)&&(B?(X(F),F=-1):B=!0,Ne(ct,W-gt))):(U.sortIndex=Tt,s(p,U),H||q||(H=!0,Rt||(Rt=!0,te()))),U},n.unstable_shouldYield=Qt,n.unstable_wrapCallback=function(U){var L=D;return function(){var W=D;D=L;try{return U.apply(this,arguments)}finally{D=W}}}})(nr)),nr}var Gd;function A0(){return Gd||(Gd=1,ar.exports=O0()),ar.exports}var ur={exports:{}},Ft={};var Xd;function C0(){if(Xd)return Ft;Xd=1;var n=gr();function s(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),ur.exports=C0(),ur.exports}var Kd;function z0(){if(Kd)return Fn;Kd=1;var n=A0(),s=gr(),c=M0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=gt[Tt],gt[Tt]=null,Tt--)}function G(t,e){Tt++,gt[Tt]=t.current,t.current=e}var K=b(null),I=b(null),lt=b(null),yt=b(null);function Wt(t,e){switch(G(lt,e),G(I,t),G(K,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?id(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=id(e),t=sd(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}w(K),G(K,t)}function Ut(){w(K),w(I),w(lt)}function tn(t){t.memoizedState!==null&&G(yt,t);var e=K.current,l=sd(e,t.type);e!==l&&(G(I,t),G(K,l))}function uu(t){I.current===t&&(w(K),w(I)),yt.current===t&&(w(yt),Zn._currentValue=W)}var qi,jr;function Gl(t){if(qi===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);qi=e&&e[1]||"",jr=-1)":-1 to m(q.target.checked),className:`rounded border-gray-700 bg-surface text-primary focus:ring-primary focus:ring-offset-0`,disabled:y}),_.jsx("span",{className:"text-sm text-gray-400",children:"Remember me (until browser closes)"})]})}),v&&_.jsx("div",{className:`mb-4 p-3 bg-red-600/10 border border-red-600/30 rounded-lg text-red-200 text-sm`,children:v}),_.jsx("button",{type:"submit",className:`w-full py-2 bg-primary hover:bg-primary-hover rounded-lg - transition-colors disabled:opacity-50`,disabled:y,children:y?"Signing in...":"Sign In"})]})]}),_.jsx("p",{className:"text-xs text-gray-500 text-center mt-4",children:"Use HTTPS in production for secure authentication"})]})})}Vm();function a1({children:n}){const s=Ql(),c=Om(),{data:r,isLoading:d,error:h}=Ll({queryKey:["auth","status"],queryFn:Jm,retry:1,staleTime:3e4}),m=()=>{s.invalidateQueries({queryKey:["auth"]}),c("/")};return d?_.jsx("div",{className:"min-h-screen bg-surface flex items-center justify-center",children:_.jsx("div",{className:"animate-pulse text-text-secondary",children:"Loading..."})}):h?_.jsx("div",{className:"min-h-screen bg-surface flex items-center justify-center",children:_.jsx("div",{className:"text-red-500",children:"Unable to connect to Motion. Please check the server."})}):r?.auth_required?r?.authenticated?_.jsx(_.Fragment,{children:n}):_.jsx(l1,{onSuccess:m}):_.jsx(_.Fragment,{children:n})}const n1="modulepreload",u1=function(n){return"/"+n},fm={},zr=function(s,c,r){let d=Promise.resolve();if(c&&c.length>0){let p=function(y){return Promise.all(y.map(T=>Promise.resolve(T).then(x=>({status:"fulfilled",value:x}),x=>({status:"rejected",reason:x}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),v=m?.nonce||m?.getAttribute("nonce");d=p(c.map(y=>{if(y=u1(y),y in fm)return;fm[y]=!0;const T=y.endsWith(".css"),x=T?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${y}"]${x}`))return;const D=document.createElement("link");if(D.rel=T?"stylesheet":n1,T||(D.as="script"),D.crossOrigin="",D.href=y,v&&D.setAttribute("nonce",v),document.head.appendChild(D),T)return new Promise((q,H)=>{D.addEventListener("load",q),D.addEventListener("error",()=>H(new Error(`Unable to preload CSS for ${y}`)))})}))}function h(m){const v=new Event("vite:preloadError",{cancelable:!0});if(v.payload=m,window.dispatchEvent(v),!v.defaultPrevented)throw m}return d.then(m=>{for(const v of m||[])v.status==="rejected"&&h(v.reason);return s().catch(h)})},ji=parseInt("10000",10),_r=1,i1={400:"Invalid request. Please check your input.",401:"Authentication required. Please sign in.",403:"Access denied. You do not have permission for this action.",404:"Resource not found.",408:"Request timed out. Please try again.",500:"Server error. Please try again later.",502:"Server unavailable. Motion may be restarting.",503:"Service unavailable. Motion may be restarting.",504:"Gateway timeout. Please try again."};let Ge=null;class mt extends Error{status;userMessage;constructor(s,c){super(s),this.name="ApiClientError",this.status=c,this.userMessage=c?i1[c]??s:s}}async function wi(n){try{return await n.json()}catch{throw new mt("Invalid JSON response from server",n.status)}}function $a(n,s){if(!n)return{};try{return JSON.parse(n)}catch{throw new mt("Invalid JSON response from server",s)}}function Dr(n){return n===502||n===503||n===504}function Ur(n){return new Promise(s=>setTimeout(s,n))}async function oa(n,s=0){const c=new AbortController,r=setTimeout(()=>c.abort(),ji),d=ol();try{const h=await fetch(n,{method:"GET",headers:{Accept:"application/json",...d&&{"X-Session-Token":d}},credentials:"same-origin",signal:c.signal});if(clearTimeout(r),!h.ok){if(h.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(Dr(h.status)&&s<_r)return await Ur(1e3*(s+1)),oa(n,s+1);throw new mt(`HTTP ${h.status}: ${h.statusText}`,h.status)}return wi(h)}catch(h){throw clearTimeout(r),h instanceof mt?h:h instanceof Error&&h.name==="AbortError"?new mt("Request timeout",408):new mt(h instanceof Error?h.message:"Unknown error")}}async function Fm(n,s,c=0,r=ji){const d=new AbortController,h=setTimeout(()=>d.abort(),r),m=ol(),v=nu();try{const p=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...m&&{"X-Session-Token":m},...v&&{"X-CSRF-Token":v}},credentials:"same-origin",body:JSON.stringify(s),signal:d.signal});if(clearTimeout(h),p.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(p.status===403){try{const T=await fetch("/0/api/config",{headers:{Accept:"application/json",...m&&{"X-Session-Token":m}},signal:d.signal});if(T.ok){const x=await wi(T);if(x.csrf_token){Mr(x.csrf_token);const D=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...m&&{"X-Session-Token":m},"X-CSRF-Token":x.csrf_token},credentials:"same-origin",body:JSON.stringify(s),signal:d.signal});if(D.ok){const q=await D.text();return $a(q,D.status)}throw(D.status===401||D.status===403)&&(be(),Ge?.(D.status)),new mt("Request failed after CSRF refresh",D.status)}}}catch(T){if(T instanceof mt)throw T}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(Dr(p.status)&&c<_r)return await Ur(1e3*(c+1)),Fm(n,s,c+1,r);if(!p.ok)throw new mt(`HTTP ${p.status}: ${p.statusText}`,p.status);const y=await p.text();return $a(y,p.status)}catch(p){throw clearTimeout(h),p instanceof mt?p:p instanceof Error&&p.name==="AbortError"?new mt("Request timeout",408):new mt(p instanceof Error?p.message:"Unknown error")}}async function $m(n,s,c=0){const r=new AbortController,d=setTimeout(()=>r.abort(),ji),h=ol(),m=nu();try{const v=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...h&&{"X-Session-Token":h},...m&&{"X-CSRF-Token":m}},credentials:"same-origin",body:JSON.stringify(s),signal:r.signal});if(clearTimeout(d),v.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(v.status===403){try{const y=await fetch("/0/api/config",{headers:{Accept:"application/json",...h&&{"X-Session-Token":h}},signal:r.signal});if(y.ok){const T=await wi(y);if(T.csrf_token){Mr(T.csrf_token);const x=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...h&&{"X-Session-Token":h},"X-CSRF-Token":T.csrf_token},credentials:"same-origin",body:JSON.stringify(s),signal:r.signal});if(x.ok){const D=await x.text();return $a(D,x.status)}throw(x.status===401||x.status===403)&&(be(),Ge?.(x.status)),new mt("Request failed after CSRF refresh",x.status)}}}catch(y){if(y instanceof mt)throw y}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(Dr(v.status)&&c<_r)return await Ur(1e3*(c+1)),$m(n,s,c+1);if(!v.ok)throw new mt(`HTTP ${v.status}: ${v.statusText}`,v.status);const p=await v.text();return $a(p,v.status)}catch(v){throw clearTimeout(d),v instanceof mt?v:v instanceof Error&&v.name==="AbortError"?new mt("Request timeout",408):new mt(v instanceof Error?v.message:"Unknown error")}}async function Nr(n){const s=new AbortController,c=setTimeout(()=>s.abort(),ji),r=ol(),d=nu();try{const h=await fetch(n,{method:"DELETE",headers:{Accept:"application/json",...r&&{"X-Session-Token":r},...d&&{"X-CSRF-Token":d}},credentials:"same-origin",signal:s.signal});if(clearTimeout(c),h.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(h.status===403){try{const v=await fetch("/0/api/config",{headers:{Accept:"application/json",...r&&{"X-Session-Token":r}},signal:s.signal});if(v.ok){const p=await wi(v);if(p.csrf_token){Mr(p.csrf_token);const y=await fetch(n,{method:"DELETE",headers:{Accept:"application/json",...r&&{"X-Session-Token":r},"X-CSRF-Token":p.csrf_token},credentials:"same-origin",signal:s.signal});if(y.ok){const T=await y.text();return $a(T,y.status)}throw(y.status===401||y.status===403)&&(be(),Ge?.(y.status)),new mt("Request failed after CSRF refresh",y.status)}}}catch(v){if(v instanceof mt)throw v}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(!h.ok)throw new mt(`HTTP ${h.status}: ${h.statusText}`,h.status);const m=await h.text();return $a(m,h.status)}catch(h){throw clearTimeout(c),h instanceof mt?h:h instanceof Error&&h.name==="AbortError"?new mt("Request timeout",408):new mt(h instanceof Error?h.message:"Unknown error")}}function s1(){const n=ol(),s=nu(),c=new AbortController;setTimeout(()=>c.abort(),5e3),fetch("/0/api/config/write",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...n&&{"X-Session-Token":n},...s&&{"X-CSRF-Token":s}},credentials:"same-origin",body:JSON.stringify({}),signal:c.signal}).catch(()=>{})}function c1(n=0){const s=ol(),c=nu(),r=new AbortController;setTimeout(()=>r.abort(),2e3),fetch(`/${n}/api/camera/restart`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...s&&{"X-Session-Token":s},...c&&{"X-CSRF-Token":c}},credentials:"same-origin",body:JSON.stringify({}),signal:r.signal}).catch(()=>{})}async function b1(n){await Fm(`/${n}/api/camera/snapshot`,{})}async function r1(n,s=30,c=1e3){const r=ol();for(let d=0;dh.abort(),3e3),v=await fetch("/0/api/system/status",{method:"GET",headers:{Accept:"application/json",...r&&{"X-Session-Token":r}},credentials:"same-origin",signal:h.signal});if(clearTimeout(m),v.ok)return!0}catch{}await new Promise(h=>setTimeout(h,c))}return!1}async function E1(n){return s1(),await new Promise(s=>setTimeout(s,500)),c1(n),await new Promise(s=>setTimeout(s,1e3)),r1(n,30,1e3)}const ce={config:n=>["config",n],cameras:["cameras"],pictures:n=>["pictures",n],movies:n=>["movies",n],mediaDates:(n,s)=>["media-dates",n,s],mediaFolders:(n,s)=>["media-folders",n,s],temperature:["temperature"],systemStatus:["systemStatus"],cameraStatus:["cameraStatus"]};function T1(){return Ll({queryKey:ce.cameras,queryFn:async()=>(await oa("/0/api/cameras")).cameras,staleTime:6e4})}function x1(n,s=0,c=100,r=null,d){return Ll({queryKey:[...ce.pictures(n),s,c,r],queryFn:()=>{let h=`/${n}/api/media/pictures?offset=${s}&limit=${c}`;return r&&(h+=`&date=${r}`),oa(h)},staleTime:3e4,...d})}function R1(n,s=0,c=100,r=null,d){return Ll({queryKey:[...ce.movies(n),s,c,r],queryFn:()=>{let h=`/${n}/api/media/movies?offset=${s}&limit=${c}`;return r&&(h+=`&date=${r}`),oa(h)},staleTime:3e4,...d})}function O1(n,s="",c=0,r=100,d){return Ll({queryKey:[...ce.mediaFolders(n,s),c,r],queryFn:()=>{let h=`/${n}/api/media/folders?offset=${c}&limit=${r}`;return s&&(h+=`&path=${encodeURIComponent(s)}`),oa(h)},staleTime:3e4,...d})}function A1(){const n=Ql();return Ui({mutationFn:async({camId:s,path:c})=>Nr(`/${s}/api/media/folders/files?path=${encodeURIComponent(c)}`),onSuccess:(s,{camId:c,path:r})=>{n.invalidateQueries({queryKey:ce.mediaFolders(c,r)});const d=r.includes("/")?r.substring(0,r.lastIndexOf("/")):"";n.invalidateQueries({queryKey:ce.mediaFolders(c,d)}),n.invalidateQueries({queryKey:ce.pictures(c)}),n.invalidateQueries({queryKey:ce.movies(c)})}})}function Wm(){return Ll({queryKey:ce.systemStatus,queryFn:()=>oa("/0/api/system/status"),refetchInterval:1e4,refetchIntervalInBackground:!1,staleTime:5e3})}function C1(n){return Ll({queryKey:ce.cameraStatus,queryFn:()=>oa("/0/api/system/status"),refetchInterval:2e3,refetchIntervalInBackground:!1,staleTime:1e3,enabled:n?.enabled??!0,retry:!1,select:s=>{const c=[];if(s.status)for(const r in s.status)r.startsWith("cam")&&c.push(s.status[r]);return c}})}function M1(){const n=Ql();return Ui({mutationFn:async({camId:s,changes:c})=>$m(`/${s}/api/config`,c),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.config(c)}),n.invalidateQueries({queryKey:ce.cameras})}})}function z1(){const n=Ql();return Ui({mutationFn:async({camId:s,pictureId:c})=>Nr(`/${s}/api/media/picture/${c}`),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.pictures(c)})}})}function _1(){const n=Ql();return Ui({mutationFn:async({camId:s,movieId:c})=>Nr(`/${s}/api/media/movie/${c}`),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.movies(c)})}})}const rr=R.memo(function({variant:s="desktop"}){const{data:c}=Wm(),r=h=>h<1024?`${h} B`:h<1024*1024?`${(h/1024).toFixed(0)} KB`:h<1024*1024*1024?`${(h/(1024*1024)).toFixed(0)} MB`:`${(h/(1024*1024*1024)).toFixed(1)} GB`,d=h=>h>=80?"text-red-500":h>=70?"text-yellow-500":"text-green-500";return c?s==="desktop"?_.jsxs("div",{className:"flex items-center gap-3 text-xs border-l border-gray-700 pl-4",children:[c.temperature&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"})}),_.jsxs("span",{className:d(c.temperature.celsius),children:[c.temperature.celsius.toFixed(1),"°C"]})]}),c.memory&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("span",{className:"text-gray-400",children:"RAM:"}),_.jsxs("span",{children:[c.memory.percent.toFixed(0),"%"]})]}),c.disk&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("span",{className:"text-gray-400",children:"Disk:"}),_.jsxs("span",{children:[r(c.disk.used)," / ",r(c.disk.total)]})]})]}):s==="mobile"?c.temperature?_.jsxs("span",{className:`text-xs ${d(c.temperature.celsius)}`,children:[c.temperature.celsius.toFixed(0),"°C"]}):null:s==="mobile-menu"?_.jsxs("div",{className:"flex flex-wrap gap-3 px-3 py-2 text-xs text-gray-400 border-t border-gray-800 mt-2 pt-3",children:[c.temperature&&_.jsxs("span",{className:d(c.temperature.celsius),children:["Temp: ",c.temperature.celsius.toFixed(1),"°C"]}),c.memory&&_.jsxs("span",{children:["RAM: ",c.memory.percent.toFixed(0),"%"]}),c.disk&&_.jsxs("span",{children:["Disk: ",r(c.disk.used)," / ",r(c.disk.total)]})]}):null:null}),f1=R.memo(function(){const{data:s}=Wm();return s?.version?_.jsxs("span",{className:"text-xs text-gray-500 hidden sm:inline",children:["v",s.version]}):null}),o1=R.memo(function(){const s=Ql(),{isAuthenticated:c,role:r,authRequired:d}=e1(),[h,m]=R.useState(!1),v=async()=>{await Ig(),s.invalidateQueries({queryKey:["auth"]})};return _.jsxs("div",{className:"min-h-screen bg-surface",children:[_.jsx("header",{className:"bg-surface-elevated border-b border-gray-800 sticky top-0 z-[150]",children:_.jsxs("div",{className:"container mx-auto px-4 py-3 md:py-4",children:[_.jsxs("nav",{className:"flex items-center justify-between",children:[_.jsxs("div",{className:"flex items-center gap-2 md:gap-4",children:[_.jsx("h1",{className:"text-xl md:text-2xl font-bold",children:"Motion"}),_.jsx(f1,{})]}),_.jsxs("div",{className:"hidden md:flex items-center gap-6",children:[_.jsxs("div",{className:"flex items-center gap-4",children:[_.jsx(Hl,{to:"/",className:"hover:text-primary",children:"Dashboard"}),r==="admin"&&_.jsx(Hl,{to:"/settings",className:"hover:text-primary",children:"Settings"}),_.jsx(Hl,{to:"/media",className:"hover:text-primary",children:"Media"}),d&&c&&_.jsxs("button",{onClick:v,className:"flex items-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-surface-elevated transition-colors",title:"Logout",children:[_.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"})}),_.jsx("span",{className:"text-xs text-green-500",children:r==="admin"?"Admin":"User"})]}),!d&&_.jsx("div",{className:"px-3 py-1.5 rounded-lg bg-yellow-500/10",children:_.jsx("span",{className:"text-xs text-yellow-500",children:"No Authentication"})})]}),_.jsx(rr,{variant:"desktop"})]}),_.jsxs("div",{className:"flex items-center gap-2 md:hidden",children:[_.jsx(rr,{variant:"mobile"}),_.jsx("button",{onClick:()=>m(!h),className:"p-2 rounded-lg hover:bg-surface transition-colors","aria-label":"Toggle menu",children:h?_.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})}):_.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"})})})]})]}),h&&_.jsx("div",{className:"md:hidden mt-3 pt-3 border-t border-gray-800",children:_.jsxs("div",{className:"flex flex-col gap-2",children:[_.jsx(Hl,{to:"/",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Dashboard"}),r==="admin"&&_.jsx(Hl,{to:"/settings",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Settings"}),_.jsx(Hl,{to:"/media",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Media"}),d&&c&&_.jsxs("button",{onClick:()=>{v(),m(!1)},className:"flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-surface transition-colors text-left",children:[_.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"})}),_.jsxs("span",{className:"text-green-500",children:["Logout (",r==="admin"?"Admin":"User",")"]})]}),!d&&_.jsx("div",{className:"px-3 py-2 rounded-lg bg-yellow-500/10",children:_.jsx("span",{className:"text-xs text-yellow-500",children:"No Authentication"})}),_.jsx(rr,{variant:"mobile-menu"})]})})]})}),_.jsx("main",{className:"container mx-auto px-4 py-4 md:py-8",children:_.jsx(_p,{})})]})}),h1=R.lazy(()=>zr(()=>import("./Dashboard-D701LIV_.js"),__vite__mapDeps([0,1])).then(n=>({default:n.Dashboard}))),d1=R.lazy(()=>zr(()=>import("./Settings-Ca5UNPDy.js"),__vite__mapDeps([2,1])).then(n=>({default:n.Settings}))),m1=R.lazy(()=>zr(()=>import("./Media-6m4Qsu8Y.js"),[]).then(n=>({default:n.Media})));function y1(){return _.jsx("div",{className:"flex items-center justify-center h-64",children:_.jsx("div",{className:"animate-pulse text-text-secondary",children:"Loading..."})})}function v1(){return _.jsx(R.Suspense,{fallback:_.jsx(y1,{}),children:_.jsx(Up,{children:_.jsxs(Wn,{path:"/",element:_.jsx(o1,{}),children:[_.jsx(Wn,{index:!0,element:_.jsx(h1,{})}),_.jsx(Wn,{path:"settings",element:_.jsx(d1,{})}),_.jsx(Wn,{path:"media",element:_.jsx(m1,{})})]})})})}const p1=new jg({defaultOptions:{queries:{staleTime:3e4,retry:2,refetchOnWindowFocus:!1}}});U0.createRoot(document.getElementById("root")).render(_.jsx(R0.StrictMode,{children:_.jsx(Jg,{children:_.jsx(wg,{client:p1,children:_.jsx(kg,{children:_.jsx(lg,{children:_.jsx(t1,{children:_.jsx(a1,{children:_.jsx(v1,{})})})})})})})}));export{S1 as a,T1 as b,e1 as c,C1 as d,Ll as e,oa as f,Mr as g,Fm as h,Ql as i,_ as j,Ui as k,ol as l,Nr as m,Wm as n,E1 as o,$m as p,ce as q,R as r,x1 as s,b1 as t,M1 as u,R1 as v,O1 as w,z1 as x,_1 as y,A1 as z}; + transition-colors disabled:opacity-50`,disabled:y,children:y?"Signing in...":"Sign In"})]})]}),_.jsx("p",{className:"text-xs text-gray-500 text-center mt-4",children:"Use HTTPS in production for secure authentication"})]})})}Vm();function a1({children:n}){const s=Ql(),c=Om(),{data:r,isLoading:d,error:h}=Ll({queryKey:["auth","status"],queryFn:Jm,retry:1,staleTime:3e4}),m=()=>{s.invalidateQueries({queryKey:["auth"]}),c("/")};return d?_.jsx("div",{className:"min-h-screen bg-surface flex items-center justify-center",children:_.jsx("div",{className:"animate-pulse text-text-secondary",children:"Loading..."})}):h?_.jsx("div",{className:"min-h-screen bg-surface flex items-center justify-center",children:_.jsx("div",{className:"text-red-500",children:"Unable to connect to Motion. Please check the server."})}):r?.auth_required?r?.authenticated?_.jsx(_.Fragment,{children:n}):_.jsx(l1,{onSuccess:m}):_.jsx(_.Fragment,{children:n})}const n1="modulepreload",u1=function(n){return"/"+n},fm={},zr=function(s,c,r){let d=Promise.resolve();if(c&&c.length>0){let p=function(y){return Promise.all(y.map(T=>Promise.resolve(T).then(x=>({status:"fulfilled",value:x}),x=>({status:"rejected",reason:x}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),v=m?.nonce||m?.getAttribute("nonce");d=p(c.map(y=>{if(y=u1(y),y in fm)return;fm[y]=!0;const T=y.endsWith(".css"),x=T?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${y}"]${x}`))return;const D=document.createElement("link");if(D.rel=T?"stylesheet":n1,T||(D.as="script"),D.crossOrigin="",D.href=y,v&&D.setAttribute("nonce",v),document.head.appendChild(D),T)return new Promise((q,H)=>{D.addEventListener("load",q),D.addEventListener("error",()=>H(new Error(`Unable to preload CSS for ${y}`)))})}))}function h(m){const v=new Event("vite:preloadError",{cancelable:!0});if(v.payload=m,window.dispatchEvent(v),!v.defaultPrevented)throw m}return d.then(m=>{for(const v of m||[])v.status==="rejected"&&h(v.reason);return s().catch(h)})},ji=parseInt("10000",10),_r=1,i1={400:"Invalid request. Please check your input.",401:"Authentication required. Please sign in.",403:"Access denied. You do not have permission for this action.",404:"Resource not found.",408:"Request timed out. Please try again.",500:"Server error. Please try again later.",502:"Server unavailable. Motion may be restarting.",503:"Service unavailable. Motion may be restarting.",504:"Gateway timeout. Please try again."};let Ge=null;class mt extends Error{status;userMessage;constructor(s,c){super(s),this.name="ApiClientError",this.status=c,this.userMessage=c?i1[c]??s:s}}async function wi(n){try{return await n.json()}catch{throw new mt("Invalid JSON response from server",n.status)}}function $a(n,s){if(!n)return{};try{return JSON.parse(n)}catch{throw new mt("Invalid JSON response from server",s)}}function Dr(n){return n===502||n===503||n===504}function Ur(n){return new Promise(s=>setTimeout(s,n))}async function oa(n,s=0){const c=new AbortController,r=setTimeout(()=>c.abort(),ji),d=ol();try{const h=await fetch(n,{method:"GET",headers:{Accept:"application/json",...d&&{"X-Session-Token":d}},credentials:"same-origin",signal:c.signal});if(clearTimeout(r),!h.ok){if(h.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(Dr(h.status)&&s<_r)return await Ur(1e3*(s+1)),oa(n,s+1);throw new mt(`HTTP ${h.status}: ${h.statusText}`,h.status)}return wi(h)}catch(h){throw clearTimeout(r),h instanceof mt?h:h instanceof Error&&h.name==="AbortError"?new mt("Request timeout",408):new mt(h instanceof Error?h.message:"Unknown error")}}async function Fm(n,s,c=0,r=ji){const d=new AbortController,h=setTimeout(()=>d.abort(),r),m=ol(),v=nu();try{const p=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...m&&{"X-Session-Token":m},...v&&{"X-CSRF-Token":v}},credentials:"same-origin",body:JSON.stringify(s),signal:d.signal});if(clearTimeout(h),p.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(p.status===403){try{const T=await fetch("/0/api/config",{headers:{Accept:"application/json",...m&&{"X-Session-Token":m}},signal:d.signal});if(T.ok){const x=await wi(T);if(x.csrf_token){Mr(x.csrf_token);const D=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...m&&{"X-Session-Token":m},"X-CSRF-Token":x.csrf_token},credentials:"same-origin",body:JSON.stringify(s),signal:d.signal});if(D.ok){const q=await D.text();return $a(q,D.status)}throw(D.status===401||D.status===403)&&(be(),Ge?.(D.status)),new mt("Request failed after CSRF refresh",D.status)}}}catch(T){if(T instanceof mt)throw T}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(Dr(p.status)&&c<_r)return await Ur(1e3*(c+1)),Fm(n,s,c+1,r);if(!p.ok)throw new mt(`HTTP ${p.status}: ${p.statusText}`,p.status);const y=await p.text();return $a(y,p.status)}catch(p){throw clearTimeout(h),p instanceof mt?p:p instanceof Error&&p.name==="AbortError"?new mt("Request timeout",408):new mt(p instanceof Error?p.message:"Unknown error")}}async function $m(n,s,c=0){const r=new AbortController,d=setTimeout(()=>r.abort(),ji),h=ol(),m=nu();try{const v=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...h&&{"X-Session-Token":h},...m&&{"X-CSRF-Token":m}},credentials:"same-origin",body:JSON.stringify(s),signal:r.signal});if(clearTimeout(d),v.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(v.status===403){try{const y=await fetch("/0/api/config",{headers:{Accept:"application/json",...h&&{"X-Session-Token":h}},signal:r.signal});if(y.ok){const T=await wi(y);if(T.csrf_token){Mr(T.csrf_token);const x=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...h&&{"X-Session-Token":h},"X-CSRF-Token":T.csrf_token},credentials:"same-origin",body:JSON.stringify(s),signal:r.signal});if(x.ok){const D=await x.text();return $a(D,x.status)}throw(x.status===401||x.status===403)&&(be(),Ge?.(x.status)),new mt("Request failed after CSRF refresh",x.status)}}}catch(y){if(y instanceof mt)throw y}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(Dr(v.status)&&c<_r)return await Ur(1e3*(c+1)),$m(n,s,c+1);if(!v.ok)throw new mt(`HTTP ${v.status}: ${v.statusText}`,v.status);const p=await v.text();return $a(p,v.status)}catch(v){throw clearTimeout(d),v instanceof mt?v:v instanceof Error&&v.name==="AbortError"?new mt("Request timeout",408):new mt(v instanceof Error?v.message:"Unknown error")}}async function Nr(n){const s=new AbortController,c=setTimeout(()=>s.abort(),ji),r=ol(),d=nu();try{const h=await fetch(n,{method:"DELETE",headers:{Accept:"application/json",...r&&{"X-Session-Token":r},...d&&{"X-CSRF-Token":d}},credentials:"same-origin",signal:s.signal});if(clearTimeout(c),h.status===401)throw be(),Ge?.(401),new mt("Session expired",401);if(h.status===403){try{const v=await fetch("/0/api/config",{headers:{Accept:"application/json",...r&&{"X-Session-Token":r}},signal:s.signal});if(v.ok){const p=await wi(v);if(p.csrf_token){Mr(p.csrf_token);const y=await fetch(n,{method:"DELETE",headers:{Accept:"application/json",...r&&{"X-Session-Token":r},"X-CSRF-Token":p.csrf_token},credentials:"same-origin",signal:s.signal});if(y.ok){const T=await y.text();return $a(T,y.status)}throw(y.status===401||y.status===403)&&(be(),Ge?.(y.status)),new mt("Request failed after CSRF refresh",y.status)}}}catch(v){if(v instanceof mt)throw v}throw be(),Ge?.(403),new mt("CSRF validation failed",403)}if(!h.ok)throw new mt(`HTTP ${h.status}: ${h.statusText}`,h.status);const m=await h.text();return $a(m,h.status)}catch(h){throw clearTimeout(c),h instanceof mt?h:h instanceof Error&&h.name==="AbortError"?new mt("Request timeout",408):new mt(h instanceof Error?h.message:"Unknown error")}}function s1(){const n=ol(),s=nu(),c=new AbortController;setTimeout(()=>c.abort(),5e3),fetch("/0/api/config/write",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...n&&{"X-Session-Token":n},...s&&{"X-CSRF-Token":s}},credentials:"same-origin",body:JSON.stringify({}),signal:c.signal}).catch(()=>{})}function c1(n=0){const s=ol(),c=nu(),r=new AbortController;setTimeout(()=>r.abort(),2e3),fetch(`/${n}/api/camera/restart`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...s&&{"X-Session-Token":s},...c&&{"X-CSRF-Token":c}},credentials:"same-origin",body:JSON.stringify({}),signal:r.signal}).catch(()=>{})}async function b1(n){await Fm(`/${n}/api/camera/snapshot`,{})}async function r1(n,s=30,c=1e3){const r=ol();for(let d=0;dh.abort(),3e3),v=await fetch("/0/api/system/status",{method:"GET",headers:{Accept:"application/json",...r&&{"X-Session-Token":r}},credentials:"same-origin",signal:h.signal});if(clearTimeout(m),v.ok)return!0}catch{}await new Promise(h=>setTimeout(h,c))}return!1}async function E1(n){return s1(),await new Promise(s=>setTimeout(s,500)),c1(n),await new Promise(s=>setTimeout(s,1e3)),r1(n,30,1e3)}const ce={config:n=>["config",n],cameras:["cameras"],pictures:n=>["pictures",n],movies:n=>["movies",n],mediaDates:(n,s)=>["media-dates",n,s],mediaFolders:(n,s)=>["media-folders",n,s],temperature:["temperature"],systemStatus:["systemStatus"],cameraStatus:["cameraStatus"]};function T1(){return Ll({queryKey:ce.cameras,queryFn:async()=>(await oa("/0/api/cameras")).cameras,staleTime:6e4})}function x1(n,s=0,c=100,r=null,d){return Ll({queryKey:[...ce.pictures(n),s,c,r],queryFn:()=>{let h=`/${n}/api/media/pictures?offset=${s}&limit=${c}`;return r&&(h+=`&date=${r}`),oa(h)},staleTime:3e4,...d})}function R1(n,s=0,c=100,r=null,d){return Ll({queryKey:[...ce.movies(n),s,c,r],queryFn:()=>{let h=`/${n}/api/media/movies?offset=${s}&limit=${c}`;return r&&(h+=`&date=${r}`),oa(h)},staleTime:3e4,...d})}function O1(n,s="",c=0,r=100,d){return Ll({queryKey:[...ce.mediaFolders(n,s),c,r],queryFn:()=>{let h=`/${n}/api/media/folders?offset=${c}&limit=${r}`;return s&&(h+=`&path=${encodeURIComponent(s)}`),oa(h)},staleTime:3e4,...d})}function A1(){const n=Ql();return Ui({mutationFn:async({camId:s,path:c})=>Nr(`/${s}/api/media/folders/files?path=${encodeURIComponent(c)}`),onSuccess:(s,{camId:c,path:r})=>{n.invalidateQueries({queryKey:ce.mediaFolders(c,r)});const d=r.includes("/")?r.substring(0,r.lastIndexOf("/")):"";n.invalidateQueries({queryKey:ce.mediaFolders(c,d)}),n.invalidateQueries({queryKey:ce.pictures(c)}),n.invalidateQueries({queryKey:ce.movies(c)})}})}function Wm(){return Ll({queryKey:ce.systemStatus,queryFn:()=>oa("/0/api/system/status"),refetchInterval:1e4,refetchIntervalInBackground:!1,staleTime:5e3})}function C1(n){return Ll({queryKey:ce.cameraStatus,queryFn:()=>oa("/0/api/system/status"),refetchInterval:2e3,refetchIntervalInBackground:!1,staleTime:1e3,enabled:n?.enabled??!0,retry:!1,select:s=>{const c=[];if(s.status)for(const r in s.status)r.startsWith("cam")&&c.push(s.status[r]);return c}})}function M1(){const n=Ql();return Ui({mutationFn:async({camId:s,changes:c})=>$m(`/${s}/api/config`,c),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.config(c)}),n.invalidateQueries({queryKey:ce.cameras})}})}function z1(){const n=Ql();return Ui({mutationFn:async({camId:s,pictureId:c})=>Nr(`/${s}/api/media/picture/${c}`),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.pictures(c)})}})}function _1(){const n=Ql();return Ui({mutationFn:async({camId:s,movieId:c})=>Nr(`/${s}/api/media/movie/${c}`),onSuccess:(s,{camId:c})=>{n.invalidateQueries({queryKey:ce.movies(c)})}})}const rr=R.memo(function({variant:s="desktop"}){const{data:c}=Wm(),r=h=>h<1024?`${h} B`:h<1024*1024?`${(h/1024).toFixed(0)} KB`:h<1024*1024*1024?`${(h/(1024*1024)).toFixed(0)} MB`:`${(h/(1024*1024*1024)).toFixed(1)} GB`,d=h=>h>=80?"text-red-500":h>=70?"text-yellow-500":"text-green-500";return c?s==="desktop"?_.jsxs("div",{className:"flex items-center gap-3 text-xs border-l border-gray-700 pl-4",children:[c.temperature&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"})}),_.jsxs("span",{className:d(c.temperature.celsius),children:[c.temperature.celsius.toFixed(1),"°C"]})]}),c.memory&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("span",{className:"text-gray-400",children:"RAM:"}),_.jsxs("span",{children:[c.memory.percent.toFixed(0),"%"]})]}),c.disk&&_.jsxs("div",{className:"flex items-center gap-1",children:[_.jsx("span",{className:"text-gray-400",children:"Disk:"}),_.jsxs("span",{children:[r(c.disk.used)," / ",r(c.disk.total)]})]})]}):s==="mobile"?c.temperature?_.jsxs("span",{className:`text-xs ${d(c.temperature.celsius)}`,children:[c.temperature.celsius.toFixed(0),"°C"]}):null:s==="mobile-menu"?_.jsxs("div",{className:"flex flex-wrap gap-3 px-3 py-2 text-xs text-gray-400 border-t border-gray-800 mt-2 pt-3",children:[c.temperature&&_.jsxs("span",{className:d(c.temperature.celsius),children:["Temp: ",c.temperature.celsius.toFixed(1),"°C"]}),c.memory&&_.jsxs("span",{children:["RAM: ",c.memory.percent.toFixed(0),"%"]}),c.disk&&_.jsxs("span",{children:["Disk: ",r(c.disk.used)," / ",r(c.disk.total)]})]}):null:null}),f1=R.memo(function(){const{data:s}=Wm();return s?.version?_.jsxs("span",{className:"text-xs text-gray-500 hidden sm:inline",children:["v",s.version]}):null}),o1=R.memo(function(){const s=Ql(),{isAuthenticated:c,role:r,authRequired:d}=e1(),[h,m]=R.useState(!1),v=async()=>{await Ig(),s.invalidateQueries({queryKey:["auth"]})};return _.jsxs("div",{className:"min-h-screen bg-surface",children:[_.jsx("header",{className:"bg-surface-elevated border-b border-gray-800 sticky top-0 z-[150]",children:_.jsxs("div",{className:"container mx-auto px-4 py-3 md:py-4",children:[_.jsxs("nav",{className:"flex items-center justify-between",children:[_.jsxs("div",{className:"flex items-center gap-2 md:gap-4",children:[_.jsx("h1",{className:"text-xl md:text-2xl font-bold",children:"Motion"}),_.jsx(f1,{})]}),_.jsxs("div",{className:"hidden md:flex items-center gap-6",children:[_.jsxs("div",{className:"flex items-center gap-4",children:[_.jsx(Hl,{to:"/",className:"hover:text-primary",children:"Dashboard"}),r==="admin"&&_.jsx(Hl,{to:"/settings",className:"hover:text-primary",children:"Settings"}),_.jsx(Hl,{to:"/media",className:"hover:text-primary",children:"Media"}),d&&c&&_.jsxs("button",{onClick:v,className:"flex items-center gap-1.5 px-3 py-1.5 rounded-lg hover:bg-surface-elevated transition-colors",title:"Logout",children:[_.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"})}),_.jsx("span",{className:"text-xs text-green-500",children:r==="admin"?"Admin":"User"})]}),!d&&_.jsx("div",{className:"px-3 py-1.5 rounded-lg bg-yellow-500/10",children:_.jsx("span",{className:"text-xs text-yellow-500",children:"No Authentication"})})]}),_.jsx(rr,{variant:"desktop"})]}),_.jsxs("div",{className:"flex items-center gap-2 md:hidden",children:[_.jsx(rr,{variant:"mobile"}),_.jsx("button",{onClick:()=>m(!h),className:"p-2 rounded-lg hover:bg-surface transition-colors","aria-label":"Toggle menu",children:h?_.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})}):_.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"})})})]})]}),h&&_.jsx("div",{className:"md:hidden mt-3 pt-3 border-t border-gray-800",children:_.jsxs("div",{className:"flex flex-col gap-2",children:[_.jsx(Hl,{to:"/",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Dashboard"}),r==="admin"&&_.jsx(Hl,{to:"/settings",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Settings"}),_.jsx(Hl,{to:"/media",onClick:()=>m(!1),className:"px-3 py-2 rounded-lg hover:bg-surface transition-colors",children:"Media"}),d&&c&&_.jsxs("button",{onClick:()=>{v(),m(!1)},className:"flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-surface transition-colors text-left",children:[_.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:_.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"})}),_.jsxs("span",{className:"text-green-500",children:["Logout (",r==="admin"?"Admin":"User",")"]})]}),!d&&_.jsx("div",{className:"px-3 py-2 rounded-lg bg-yellow-500/10",children:_.jsx("span",{className:"text-xs text-yellow-500",children:"No Authentication"})}),_.jsx(rr,{variant:"mobile-menu"})]})})]})}),_.jsx("main",{className:"container mx-auto px-4 py-4 md:py-8",children:_.jsx(_p,{})})]})}),h1=R.lazy(()=>zr(()=>import("./Dashboard-D7oUR9hz.js"),__vite__mapDeps([0,1])).then(n=>({default:n.Dashboard}))),d1=R.lazy(()=>zr(()=>import("./Settings-JwhcLbnw.js"),__vite__mapDeps([2,1])).then(n=>({default:n.Settings}))),m1=R.lazy(()=>zr(()=>import("./Media-4gl6Y8Bi.js"),[]).then(n=>({default:n.Media})));function y1(){return _.jsx("div",{className:"flex items-center justify-center h-64",children:_.jsx("div",{className:"animate-pulse text-text-secondary",children:"Loading..."})})}function v1(){return _.jsx(R.Suspense,{fallback:_.jsx(y1,{}),children:_.jsx(Up,{children:_.jsxs(Wn,{path:"/",element:_.jsx(o1,{}),children:[_.jsx(Wn,{index:!0,element:_.jsx(h1,{})}),_.jsx(Wn,{path:"settings",element:_.jsx(d1,{})}),_.jsx(Wn,{path:"media",element:_.jsx(m1,{})})]})})})}const p1=new jg({defaultOptions:{queries:{staleTime:3e4,retry:2,refetchOnWindowFocus:!1}}});U0.createRoot(document.getElementById("root")).render(_.jsx(R0.StrictMode,{children:_.jsx(Jg,{children:_.jsx(wg,{client:p1,children:_.jsx(kg,{children:_.jsx(lg,{children:_.jsx(t1,{children:_.jsx(a1,{children:_.jsx(v1,{})})})})})})})}));export{S1 as a,T1 as b,e1 as c,C1 as d,Ll as e,oa as f,Mr as g,Fm as h,Wm as i,_ as j,Ql as k,Ui as l,ol as m,Nr as n,E1 as o,$m as p,ce as q,R as r,x1 as s,b1 as t,M1 as u,R1 as v,O1 as w,z1 as x,_1 as y,A1 as z}; diff --git a/data/webui/assets/parameterMappings-CUuSfEkB.js b/data/webui/assets/parameterMappings-BmLxmuw_.js similarity index 99% rename from data/webui/assets/parameterMappings-CUuSfEkB.js rename to data/webui/assets/parameterMappings-BmLxmuw_.js index 1666669b..cbc57686 100644 --- a/data/webui/assets/parameterMappings-CUuSfEkB.js +++ b/data/webui/assets/parameterMappings-BmLxmuw_.js @@ -1,4 +1,4 @@ -import{r as h,l as V,j as e,h as P,m as H,p as K,f as T,e as $,i as A,k as F,a as q,o as I}from"./index-DCzq8GhF.js";function B(t,r){const[l,a]=h.useState({imageUrl:null,streamFps:0,isConnected:!1,error:null}),o=h.useRef([]),d=h.useRef(null),i=h.useRef(null),p=h.useCallback(()=>{const c=Date.now()-1e3;return o.current=o.current.filter(s=>s>c),o.current.length},[]);return h.useEffect(()=>{const m=setInterval(()=>{const c=p();a(s=>({...s,streamFps:c}))},1e3);return()=>clearInterval(m)},[p]),h.useEffect(()=>{d.current&&d.current.abort(),i.current&&(URL.revokeObjectURL(i.current),i.current=null),o.current=[],a({imageUrl:null,streamFps:0,isConnected:!1,error:null});const m=new AbortController;return d.current=m,(async()=>{try{const s=V();let w=`/${t}/mjpg/stream`;const b=new URLSearchParams;s&&b.set("token",s),b.set("_k",String(r)),w+="?"+b.toString();const v=await fetch(w,{signal:m.signal,credentials:"same-origin"});if(!v.ok)throw new Error(`HTTP ${v.status}: ${v.statusText}`);if(!v.body)throw new Error("No response body");a(g=>({...g,isConnected:!0,error:null}));const f=v.body.getReader();let n=new Uint8Array(0);for(;;){const{done:g,value:j}=await f.read();if(g)break;const k=new Uint8Array(n.length+j.length);k.set(n),k.set(j,n.length),n=k;let C=0;for(;C({...x,imageUrl:_})),C=u+2,n=n.slice(C),C=0}}}catch(s){if(s instanceof Error&&s.name==="AbortError")return;console.error("MJPEG stream error:",s),a(w=>({...w,isConnected:!1,error:s instanceof Error?s.message:"Stream error"}))}})(),()=>{m.abort(),i.current&&(URL.revokeObjectURL(i.current),i.current=null)}},[t,r]),l}const R="motion-camera-restart-timestamps";function E(t){try{const r=localStorage.getItem(R);if(r)return JSON.parse(r)[String(t)]||0}catch{}return 0}function Z(t){try{const r=localStorage.getItem(R),l=r?JSON.parse(r):{};l[String(t)]=Date.now(),t!==0&&(l[0]=Date.now()),localStorage.setItem(R,JSON.stringify(l))}catch{}}const O="camera-restarted";function ee({cameraId:t,className:r="",onStreamFpsChange:l}){const a=h.useRef(E(t)),[o,d]=h.useState(()=>E(t)),{imageUrl:i,streamFps:p,isConnected:m,error:c}=B(t,o),s=h.useRef(l);h.useEffect(()=>{s.current=l}),h.useEffect(()=>{s.current&&s.current(p)},[p]);const w=h.useCallback(()=>{setTimeout(()=>{d(b=>b+1)},2e3)},[]);return h.useEffect(()=>{const b=v=>{const f=v.detail?.cameraId;if(!f||f===0||f===t){const n=Date.now();a.current=n,d(n)}};return window.addEventListener(O,b),()=>{window.removeEventListener(O,b)}},[t]),h.useEffect(()=>{const b=()=>{const f=E(t),n=t!==0?E(0):0,g=Math.max(f,n);g>a.current&&(a.current=g,d(g))};b();const v=setInterval(b,5e3);return()=>clearInterval(v)},[t]),h.useEffect(()=>{c&&!m&&w()},[c,m,w]),c&&!i?e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"aspect-video flex items-center justify-center bg-gray-900 rounded-lg",children:e.jsxs("div",{className:"text-center p-4",children:[e.jsx("svg",{className:"w-12 h-12 mx-auto text-red-500 mb-2",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),e.jsx("p",{className:"text-red-500 text-sm",children:c})]})})}):i?e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"relative aspect-video bg-black rounded-lg overflow-hidden",children:e.jsx("img",{src:i,alt:`Camera ${t} stream`,className:"absolute inset-0 w-full h-full object-contain"})})}):e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"relative aspect-video bg-gray-900 animate-pulse rounded-lg",children:e.jsx("div",{className:"absolute top-4 right-4",children:e.jsxs("svg",{className:"w-8 h-8 text-gray-600 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]})})})})}function te({label:t,value:r,onChange:l,options:a,disabled:o=!1,required:d=!1,helpText:i,error:p}){const m=s=>{l(s.target.value)},c=!!p;return e.jsxs("div",{className:"mb-4",children:[e.jsxs("label",{className:"block text-sm font-medium mb-1",children:[t,d&&e.jsx("span",{className:"text-red-500 ml-1",children:"*"})]}),e.jsx("select",{value:r,onChange:m,disabled:o,required:d,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${c?"border-red-500 focus:ring-red-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":c,"aria-describedby":c?`${t}-error`:void 0,children:a.map(s=>e.jsx("option",{value:s.value,children:s.label},s.value))}),c&&e.jsx("p",{id:`${t}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:p}),i&&!c&&e.jsx("p",{className:"mt-1 text-sm text-gray-400",children:i})]})}function re({label:t,value:r,onChange:l,disabled:a=!1,helpText:o,error:d}){const i=()=>{a||l(!r)},p=!!d;return e.jsx("div",{className:"mb-4",children:e.jsxs("label",{className:"flex items-center cursor-pointer",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{type:"checkbox",checked:r,onChange:i,disabled:a,className:"sr-only","aria-invalid":p}),e.jsx("div",{className:`block w-14 h-8 rounded-full transition-colors ${r?"bg-primary":"bg-surface-elevated"} ${a?"opacity-50 cursor-not-allowed":""} ${p?"ring-2 ring-red-500":""}`}),e.jsx("div",{className:`absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition-transform ${r?"transform translate-x-6":""}`})]}),e.jsxs("div",{className:"ml-3",children:[e.jsx("span",{className:"text-sm font-medium",children:t}),p&&e.jsx("p",{className:"text-sm text-red-400",role:"alert",children:d}),o&&!p&&e.jsx("p",{className:"text-sm text-gray-400",children:o})]})]})})}function ae({label:t,value:r,onChange:l,min:a,max:o,step:d=1,unit:i="",disabled:p=!1,required:m=!1,helpText:c,error:s,showValue:w=!0,scale:b="linear"}){const v=u=>{if(b==="linear")return u;if(u===0)return a;const y=a===0?1:a,S=Math.log(y),_=Math.log(o),x=Math.log(u);return y+(x-S)/(_-S)*(o-y)},f=u=>{if(b==="linear")return u;if(u===a)return 0;const y=a===0?1:a,S=Math.log(y),_=Math.log(o),x=(u-y)/(o-y),L=S+x*(_-S);return Math.round(Math.exp(L))},n=u=>{const y=Number(u.target.value),S=f(y);l(S)},g=!!s,j=v(r),k=(j-a)/(o-a)*100,C=b==="logarithmic"?f(a+(o-a)/2):(a+o)/2,N=u=>u===0&&a===0?"0":Number.isInteger(u)?u.toString():u.toFixed(d>=1?0:d.toString().split(".")[1]?.length||1);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("div",{className:"flex justify-between items-center mb-1",children:[e.jsxs("label",{className:"block text-sm font-medium",children:[t,m&&e.jsx("span",{className:"text-red-500 ml-1",children:"*"})]}),w&&e.jsxs("span",{className:"text-sm font-mono text-gray-400",children:[r,i]})]}),e.jsxs("div",{className:"relative",children:[e.jsx("input",{type:"range",value:j,onChange:n,min:a,max:o,step:b==="logarithmic"?1:d,disabled:p,className:`w-full h-2 rounded-lg appearance-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed +import{r as h,m as V,j as e,h as P,n as H,p as K,f as T,e as $,k as A,l as F,a as q,o as I}from"./index-tiawrtsp.js";function B(t,r){const[l,a]=h.useState({imageUrl:null,streamFps:0,isConnected:!1,error:null}),o=h.useRef([]),d=h.useRef(null),i=h.useRef(null),p=h.useCallback(()=>{const c=Date.now()-1e3;return o.current=o.current.filter(s=>s>c),o.current.length},[]);return h.useEffect(()=>{const m=setInterval(()=>{const c=p();a(s=>({...s,streamFps:c}))},1e3);return()=>clearInterval(m)},[p]),h.useEffect(()=>{d.current&&d.current.abort(),i.current&&(URL.revokeObjectURL(i.current),i.current=null),o.current=[],a({imageUrl:null,streamFps:0,isConnected:!1,error:null});const m=new AbortController;return d.current=m,(async()=>{try{const s=V();let w=`/${t}/mjpg/stream`;const b=new URLSearchParams;s&&b.set("token",s),b.set("_k",String(r)),w+="?"+b.toString();const v=await fetch(w,{signal:m.signal,credentials:"same-origin"});if(!v.ok)throw new Error(`HTTP ${v.status}: ${v.statusText}`);if(!v.body)throw new Error("No response body");a(g=>({...g,isConnected:!0,error:null}));const f=v.body.getReader();let n=new Uint8Array(0);for(;;){const{done:g,value:j}=await f.read();if(g)break;const k=new Uint8Array(n.length+j.length);k.set(n),k.set(j,n.length),n=k;let C=0;for(;C({...x,imageUrl:_})),C=u+2,n=n.slice(C),C=0}}}catch(s){if(s instanceof Error&&s.name==="AbortError")return;console.error("MJPEG stream error:",s),a(w=>({...w,isConnected:!1,error:s instanceof Error?s.message:"Stream error"}))}})(),()=>{m.abort(),i.current&&(URL.revokeObjectURL(i.current),i.current=null)}},[t,r]),l}const R="motion-camera-restart-timestamps";function E(t){try{const r=localStorage.getItem(R);if(r)return JSON.parse(r)[String(t)]||0}catch{}return 0}function Z(t){try{const r=localStorage.getItem(R),l=r?JSON.parse(r):{};l[String(t)]=Date.now(),t!==0&&(l[0]=Date.now()),localStorage.setItem(R,JSON.stringify(l))}catch{}}const O="camera-restarted";function ee({cameraId:t,className:r="",onStreamFpsChange:l}){const a=h.useRef(E(t)),[o,d]=h.useState(()=>E(t)),{imageUrl:i,streamFps:p,isConnected:m,error:c}=B(t,o),s=h.useRef(l);h.useEffect(()=>{s.current=l}),h.useEffect(()=>{s.current&&s.current(p)},[p]);const w=h.useCallback(()=>{setTimeout(()=>{d(b=>b+1)},2e3)},[]);return h.useEffect(()=>{const b=v=>{const f=v.detail?.cameraId;if(!f||f===0||f===t){const n=Date.now();a.current=n,d(n)}};return window.addEventListener(O,b),()=>{window.removeEventListener(O,b)}},[t]),h.useEffect(()=>{const b=()=>{const f=E(t),n=t!==0?E(0):0,g=Math.max(f,n);g>a.current&&(a.current=g,d(g))};b();const v=setInterval(b,5e3);return()=>clearInterval(v)},[t]),h.useEffect(()=>{c&&!m&&w()},[c,m,w]),c&&!i?e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"aspect-video flex items-center justify-center bg-gray-900 rounded-lg",children:e.jsxs("div",{className:"text-center p-4",children:[e.jsx("svg",{className:"w-12 h-12 mx-auto text-red-500 mb-2",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),e.jsx("p",{className:"text-red-500 text-sm",children:c})]})})}):i?e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"relative aspect-video bg-black rounded-lg overflow-hidden",children:e.jsx("img",{src:i,alt:`Camera ${t} stream`,className:"absolute inset-0 w-full h-full object-contain"})})}):e.jsx("div",{className:`w-full ${r}`,children:e.jsx("div",{className:"relative aspect-video bg-gray-900 animate-pulse rounded-lg",children:e.jsx("div",{className:"absolute top-4 right-4",children:e.jsxs("svg",{className:"w-8 h-8 text-gray-600 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]})})})})}function te({label:t,value:r,onChange:l,options:a,disabled:o=!1,required:d=!1,helpText:i,error:p}){const m=s=>{l(s.target.value)},c=!!p;return e.jsxs("div",{className:"mb-4",children:[e.jsxs("label",{className:"block text-sm font-medium mb-1",children:[t,d&&e.jsx("span",{className:"text-red-500 ml-1",children:"*"})]}),e.jsx("select",{value:r,onChange:m,disabled:o,required:d,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${c?"border-red-500 focus:ring-red-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":c,"aria-describedby":c?`${t}-error`:void 0,children:a.map(s=>e.jsx("option",{value:s.value,children:s.label},s.value))}),c&&e.jsx("p",{id:`${t}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:p}),i&&!c&&e.jsx("p",{className:"mt-1 text-sm text-gray-400",children:i})]})}function re({label:t,value:r,onChange:l,disabled:a=!1,helpText:o,error:d}){const i=()=>{a||l(!r)},p=!!d;return e.jsx("div",{className:"mb-4",children:e.jsxs("label",{className:"flex items-center cursor-pointer",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{type:"checkbox",checked:r,onChange:i,disabled:a,className:"sr-only","aria-invalid":p}),e.jsx("div",{className:`block w-14 h-8 rounded-full transition-colors ${r?"bg-primary":"bg-surface-elevated"} ${a?"opacity-50 cursor-not-allowed":""} ${p?"ring-2 ring-red-500":""}`}),e.jsx("div",{className:`absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition-transform ${r?"transform translate-x-6":""}`})]}),e.jsxs("div",{className:"ml-3",children:[e.jsx("span",{className:"text-sm font-medium",children:t}),p&&e.jsx("p",{className:"text-sm text-red-400",role:"alert",children:d}),o&&!p&&e.jsx("p",{className:"text-sm text-gray-400",children:o})]})]})})}function ae({label:t,value:r,onChange:l,min:a,max:o,step:d=1,unit:i="",disabled:p=!1,required:m=!1,helpText:c,error:s,showValue:w=!0,scale:b="linear"}){const v=u=>{if(b==="linear")return u;if(u===0)return a;const y=a===0?1:a,S=Math.log(y),_=Math.log(o),x=Math.log(u);return y+(x-S)/(_-S)*(o-y)},f=u=>{if(b==="linear")return u;if(u===a)return 0;const y=a===0?1:a,S=Math.log(y),_=Math.log(o),x=(u-y)/(o-y),L=S+x*(_-S);return Math.round(Math.exp(L))},n=u=>{const y=Number(u.target.value),S=f(y);l(S)},g=!!s,j=v(r),k=(j-a)/(o-a)*100,C=b==="logarithmic"?f(a+(o-a)/2):(a+o)/2,N=u=>u===0&&a===0?"0":Number.isInteger(u)?u.toString():u.toFixed(d>=1?0:d.toString().split(".")[1]?.length||1);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("div",{className:"flex justify-between items-center mb-1",children:[e.jsxs("label",{className:"block text-sm font-medium",children:[t,m&&e.jsx("span",{className:"text-red-500 ml-1",children:"*"})]}),w&&e.jsxs("span",{className:"text-sm font-mono text-gray-400",children:[r,i]})]}),e.jsxs("div",{className:"relative",children:[e.jsx("input",{type:"range",value:j,onChange:n,min:a,max:o,step:b==="logarithmic"?1:d,disabled:p,className:`w-full h-2 rounded-lg appearance-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed ${g?"slider-error":"slider-default"}`,style:{background:`linear-gradient(to right, rgb(59, 130, 246) 0%, rgb(59, 130, 246) ${k}%, diff --git a/data/webui/index.html b/data/webui/index.html index cb46ca0d..c6b638cf 100644 --- a/data/webui/index.html +++ b/data/webui/index.html @@ -5,8 +5,8 @@ frontend - - + +
diff --git a/frontend/src/components/settings/CameraSourceSettings.tsx b/frontend/src/components/settings/CameraSourceSettings.tsx index 79907b13..1e552da6 100644 --- a/frontend/src/components/settings/CameraSourceSettings.tsx +++ b/frontend/src/components/settings/CameraSourceSettings.tsx @@ -4,8 +4,6 @@ import type { CameraType } from '@/api/types'; export interface CameraSourceSettingsProps { cameraId: number; - config: Record; - onChange: (param: string, value: string | number | boolean) => void; } /** @@ -15,10 +13,8 @@ export interface CameraSourceSettingsProps { * Shows a badge indicating the camera backend (libcam/v4l2/netcam/unknown). * * @param cameraId - Camera ID - * @param config - Camera configuration - * @param onChange - Configuration change handler */ -export function CameraSourceSettings({ cameraId, config, onChange }: CameraSourceSettingsProps) { +export function CameraSourceSettings({ cameraId }: CameraSourceSettingsProps) { const { cameraType, cameraDevice, isConnected } = useCameraInfo(cameraId); return ( diff --git a/frontend/src/hooks/useCameraInfo.ts b/frontend/src/hooks/useCameraInfo.ts index dc928644..11d4ae95 100644 --- a/frontend/src/hooks/useCameraInfo.ts +++ b/frontend/src/hooks/useCameraInfo.ts @@ -69,7 +69,7 @@ export function useCameraInfo(cameraId: number): CameraInfo { } // Get camera status from response - const camKey = `cam${cameraId}`; + const camKey = `cam${cameraId}` as `cam${number}`; const cam = status.status[camKey]; // If camera not found, return unknown state diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index b5ebb26c..3395018c 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -428,8 +428,6 @@ export function Settings() { {/* 2. Camera Source */} {/* 3. Type-Specific Camera Controls */} diff --git a/src/camera.cpp b/src/camera.cpp index f1fd2359..85dee42c 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -2410,3 +2410,30 @@ void cls_camera::clear_libcam_ignored_controls() libcam->clear_ignored_controls(); } } + +/* V4L2 accessors for web API */ +bool cls_camera::has_v4l2() const +{ + return v4l2cam != nullptr; +} + +vec_v4l2ctrl cls_camera::get_v4l2_controls() +{ + #ifdef HAVE_V4L2 + if (v4l2cam != nullptr) { + return v4l2cam->get_device_ctrls(); + } + #endif + return vec_v4l2ctrl(); +} + +/* NETCAM accessors for web API */ +bool cls_camera::has_netcam() const +{ + return netcam != nullptr; +} + +bool cls_camera::has_netcam_high() const +{ + return netcam_high != nullptr; +} diff --git a/src/camera.hpp b/src/camera.hpp index f05dfd97..9a52de4c 100644 --- a/src/camera.hpp +++ b/src/camera.hpp @@ -32,6 +32,12 @@ #include #include +/* Forward declarations for camera type implementations */ +#ifdef HAVE_V4L2 + struct ctx_v4l2ctrl_item; + typedef std::vector vec_v4l2ctrl; +#endif + enum CAMERA_TYPE { CAMERA_TYPE_UNKNOWN, CAMERA_TYPE_V4L2, @@ -242,6 +248,14 @@ class cls_camera { std::vector get_libcam_ignored_controls(); void clear_libcam_ignored_controls(); + /* V4L2 accessors for web API */ + bool has_v4l2() const; + vec_v4l2ctrl get_v4l2_controls(); + + /* NETCAM accessors for web API */ + bool has_netcam() const; + bool has_netcam_high() const; + private: cls_movie *movie_norm; cls_movie *movie_motion; diff --git a/src/webu_json.cpp b/src/webu_json.cpp index 5bed19d2..649d8b61 100644 --- a/src/webu_json.cpp +++ b/src/webu_json.cpp @@ -702,8 +702,8 @@ void cls_webu_json::status_vars(int indx_cam) /* Add V4L2 controls array (if V4L2 camera) */ #ifdef HAVE_V4L2 - if (cam->camera_type == CAMERA_TYPE_V4L2 && cam->v4l2cam != nullptr) { - vec_v4l2ctrl controls = cam->v4l2cam->get_device_ctrls(); + if (cam->camera_type == CAMERA_TYPE_V4L2 && cam->has_v4l2()) { + vec_v4l2ctrl controls = cam->get_v4l2_controls(); webua->resp_page += ",\"v4l2_controls\":["; bool first_ctrl = true; for (const auto& ctrl : controls) { @@ -734,7 +734,7 @@ void cls_webu_json::status_vars(int indx_cam) #endif /* Add NETCAM status and high stream indicator (if NETCAM) */ - if (cam->camera_type == CAMERA_TYPE_NETCAM && cam->netcam != nullptr) { + if (cam->camera_type == CAMERA_TYPE_NETCAM && cam->has_netcam()) { std::string netcam_status_str; switch (cam->netcam->status) { case NETCAM_CONNECTED: netcam_status_str = "connected"; break; @@ -746,7 +746,7 @@ void cls_webu_json::status_vars(int indx_cam) webua->resp_page += ",\"netcam_status\":\"" + netcam_status_str + "\""; // Check if high resolution stream is configured - if (cam->netcam_high != nullptr) { + if (cam->has_netcam_high()) { webua->resp_page += ",\"has_high_stream\":true"; } else { webua->resp_page += ",\"has_high_stream\":false"; From d3f86603ff5e9208f127829f88de8deed95f4251 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sun, 25 Jan 2026 21:27:36 -0600 Subject: [PATCH 07/10] feat: Add camera auto-detection and management system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements intelligent camera detection with user-confirmed addition workflow. Enables first-run camera setup and runtime camera management via Settings UI. Backend Changes: - New cam_detect module for Pi/USB/Network camera detection - Platform identification (Pi model, libcamera/V4L2 capabilities) - Sensor-aware defaults (imx708/219/477/296) - V4L2 blacklist filtering (ISP devices) - Persistent device ID resolution (/dev/v4l/by-id/) - 5 new API endpoints: platform, detected, add, delete, test API Endpoints: - GET /0/api/cameras/platform - Platform info - GET /0/api/cameras/detected - List unconfigured cameras - POST /0/api/cameras - Add camera from detection - DELETE /{camId}/api/cameras - Remove camera - POST /0/api/cameras/test - Test netcam connection Frontend Changes: - Camera Management section in Settings (Global Settings) - Multi-step Add Camera Wizard (Select → Configure → Test → Complete) - Detected camera cards with specs and resolutions - Configured camera cards with view/remove actions - First-run welcome experience for zero-camera setups - React Query hooks for detection and management Key Features: - Auto-detects Pi cameras (CSI) and USB cameras (V4L2) - Manual network camera configuration with connection test - CPU-efficient on-demand detection (no background scanning) - Sensor-specific optimal defaults for Pi camera modules - Frontend caching (30s detected, ∞ platform) Files Added: - src/cam_detect.{cpp,hpp} - frontend/src/components/settings/CameraManagement.tsx - frontend/src/components/settings/AddCameraWizard.tsx - frontend/src/components/settings/DetectedCameraCard.tsx - frontend/src/components/settings/ConfiguredCameraCard.tsx Co-Authored-By: Claude Sonnet 4.5 --- frontend/src/api/queries.ts | 70 ++- frontend/src/api/types.ts | 42 ++ .../components/settings/AddCameraWizard.tsx | 377 +++++++++++++ .../components/settings/CameraManagement.tsx | 110 ++++ .../settings/ConfiguredCameraCard.tsx | 95 ++++ .../settings/DetectedCameraCard.tsx | 82 +++ frontend/src/pages/Settings.tsx | 11 + src/Makefile.am | 1 + src/cam_detect.cpp | 495 ++++++++++++++++++ src/cam_detect.hpp | 119 +++++ src/conf.cpp | 45 ++ src/conf.hpp | 1 + src/motion.cpp | 3 + src/motion.hpp | 2 + src/webu_ans.cpp | 44 +- src/webu_json.cpp | 215 ++++++++ src/webu_json.hpp | 7 + 17 files changed, 1717 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/settings/AddCameraWizard.tsx create mode 100644 frontend/src/components/settings/CameraManagement.tsx create mode 100644 frontend/src/components/settings/ConfiguredCameraCard.tsx create mode 100644 frontend/src/components/settings/DetectedCameraCard.tsx create mode 100644 src/cam_detect.cpp create mode 100644 src/cam_detect.hpp diff --git a/frontend/src/api/queries.ts b/frontend/src/api/queries.ts index 01d88f53..3d6afd8c 100644 --- a/frontend/src/api/queries.ts +++ b/frontend/src/api/queries.ts @@ -1,5 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { apiGet, apiDelete, apiPatch } from './client'; +import { apiGet, apiDelete, apiPatch, apiPost } from './client'; import { updateSessionCsrf } from './session'; import type { MotionConfig, @@ -12,6 +12,10 @@ import type { DeleteFolderFilesResponse, TemperatureResponse, SystemStatus, + PlatformInfo, + DetectedCamerasResponse, + AddCameraRequest, + TestNetcamRequest, } from './types'; // Query keys for cache management @@ -25,6 +29,8 @@ export const queryKeys = { temperature: ['temperature'] as const, systemStatus: ['systemStatus'] as const, cameraStatus: ['cameraStatus'] as const, + platformInfo: ['platformInfo'] as const, + detectedCameras: ['detectedCameras'] as const, }; // Fetch full Motion config (includes cameras list) @@ -255,3 +261,65 @@ export function useDeleteMovie() { }, }); } + + +// Camera Detection - Get platform information +export function usePlatformInfo() { + return useQuery({ + queryKey: queryKeys.platformInfo, + queryFn: () => apiGet("/0/api/cameras/platform"), + staleTime: Infinity, // Platform info doesn't change during runtime + }); +} + +// Camera Detection - Get detected cameras +export function useDetectedCameras() { + return useQuery({ + queryKey: queryKeys.detectedCameras, + queryFn: () => apiGet("/0/api/cameras/detected"), + staleTime: 30000, // Cache for 30 seconds + }); +} + +// Camera Detection - Add detected camera +export function useAddCamera() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (camera: AddCameraRequest) => { + return apiPost<{ status: string; message: string }>("/0/api/cameras", camera); + }, + onSuccess: () => { + // Invalidate config and cameras cache to refetch fresh data + queryClient.invalidateQueries({ queryKey: queryKeys.config(0) }); + queryClient.invalidateQueries({ queryKey: queryKeys.cameras }); + queryClient.invalidateQueries({ queryKey: queryKeys.detectedCameras }); + }, + }); +} + +// Camera Detection - Delete camera +export function useDeleteCamera() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ camId }: { camId: number }) => { + return apiDelete<{ status: string; message: string }>(`/${camId}/api/cameras`); + }, + onSuccess: () => { + // Invalidate config and cameras cache to refetch fresh data + queryClient.invalidateQueries({ queryKey: queryKeys.config(0) }); + queryClient.invalidateQueries({ queryKey: queryKeys.cameras }); + queryClient.invalidateQueries({ queryKey: queryKeys.detectedCameras }); + }, + }); +} + +// Camera Detection - Test network camera connection +export function useTestNetcam() { + return useMutation({ + mutationFn: async (request: TestNetcamRequest) => { + return apiPost<{ status: string; message: string }>("/0/api/cameras/test", request); + }, + }); +} diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index efdb1a75..66b702c4 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -11,6 +11,48 @@ export interface Camera { all_ypct_en?: number; } +// Camera detection types +export interface DetectedCamera { + type: CameraType; + device_id: string; + device_path: string; + device_name: string; + sensor_model?: string; + default_width: number; + default_height: number; + default_fps: number; + resolutions: [number, number][]; +} + +export interface PlatformInfo { + is_raspberry_pi: boolean; + pi_model: string; + has_libcamera: boolean; + has_v4l2: boolean; +} + +export interface DetectedCamerasResponse { + cameras: DetectedCamera[]; +} + +export interface AddCameraRequest { + type: CameraType; + device_id: string; + device_path: string; + device_name: string; + sensor_model?: string; + width: number; + height: number; + fps: number; +} + +export interface TestNetcamRequest { + url: string; + user?: string; + pass?: string; + timeout?: number; +} + // Cameras list response from /0/config export interface CamerasResponse { count: number; diff --git a/frontend/src/components/settings/AddCameraWizard.tsx b/frontend/src/components/settings/AddCameraWizard.tsx new file mode 100644 index 00000000..578c81f8 --- /dev/null +++ b/frontend/src/components/settings/AddCameraWizard.tsx @@ -0,0 +1,377 @@ +import { useState } from 'react'; +import type { DetectedCamera, PlatformInfo, AddCameraRequest } from '../../api/types'; +import { useAddCamera, useTestNetcam } from '../../api/queries'; + +interface AddCameraWizardProps { + onClose: () => void; + detectedCameras: DetectedCamera[]; + platformInfo?: PlatformInfo; +} + +type WizardStep = 'select' | 'configure' | 'test' | 'complete'; + +export default function AddCameraWizard({ onClose, detectedCameras, platformInfo }: AddCameraWizardProps) { + const [step, setStep] = useState('select'); + const [selectedCamera, setSelectedCamera] = useState(null); + const [isManualNetcam, setIsManualNetcam] = useState(false); + + // Configuration state + const [deviceName, setDeviceName] = useState(''); + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + const [fps, setFps] = useState(0); + const [netcamUrl, setNetcamUrl] = useState(''); + const [netcamUser, setNetcamUser] = useState(''); + const [netcamPass, setNetcamPass] = useState(''); + + const addCamera = useAddCamera(); + const testNetcam = useTestNetcam(); + + const handleSelectCamera = (camera: DetectedCamera) => { + setSelectedCamera(camera); + setDeviceName(camera.device_name); + setWidth(camera.default_width); + setHeight(camera.default_height); + setFps(camera.default_fps); + setStep('configure'); + }; + + const handleManualNetcam = () => { + setIsManualNetcam(true); + setDeviceName('Network Camera'); + setWidth(1920); + setHeight(1080); + setFps(15); + setStep('configure'); + }; + + const handleTestNetcam = async () => { + if (!netcamUrl) return; + + testNetcam.mutate( + { + url: netcamUrl, + user: netcamUser || undefined, + pass: netcamPass || undefined, + timeout: 10, + }, + { + onSuccess: (data) => { + if (data.status === 'ok') { + setStep('complete'); + } + }, + } + ); + }; + + const handleAddCamera = () => { + const request: AddCameraRequest = { + type: isManualNetcam ? 'netcam' : selectedCamera!.type, + device_id: isManualNetcam ? netcamUrl : selectedCamera!.device_id, + device_path: isManualNetcam ? netcamUrl : selectedCamera!.device_path, + device_name: deviceName, + sensor_model: selectedCamera?.sensor_model, + width, + height, + fps, + }; + + addCamera.mutate(request, { + onSuccess: () => { + setStep('complete'); + setTimeout(() => { + onClose(); + }, 2000); + }, + }); + }; + + return ( +
+
+ {/* Header */} +
+
+

Add Camera

+ +
+ {/* Progress steps */} +
+ {(['select', 'configure', isManualNetcam ? 'test' : null, 'complete'].filter(Boolean) as WizardStep[]).map( + (s, idx, arr) => ( +
+
= idx ? 'bg-accent' : 'bg-secondary/20' + }`} + /> + {idx < arr.length - 1 &&
} +
+ ) + )} +
+
+ + {/* Content */} +
+ {/* Step 1: Select camera */} + {step === 'select' && ( +
+
+

Select a Camera

+

+ Choose from detected cameras or add a network camera manually +

+
+ + {/* Detected cameras */} + {detectedCameras.length > 0 && ( +
+ {detectedCameras.map((camera, idx) => ( + + ))} +
+ )} + + {/* Manual network camera */} + + + {detectedCameras.length === 0 && ( +

+ No cameras detected. Add a network camera manually or connect a camera. +

+ )} +
+ )} + + {/* Step 2: Configure */} + {step === 'configure' && ( +
+
+

Configure Camera

+

Set camera name and capture settings

+
+ +
+
+ + setDeviceName(e.target.value)} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + placeholder="e.g., Front Door" + /> +
+ + {isManualNetcam && ( + <> +
+ + setNetcamUrl(e.target.value)} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent font-mono text-sm" + placeholder="rtsp://192.168.1.100:554/stream" + /> +
+ +
+
+ + setNetcamUser(e.target.value)} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + placeholder="admin" + /> +
+
+ + setNetcamPass(e.target.value)} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + placeholder="••••••" + /> +
+
+ + )} + +
+
+ + setWidth(parseInt(e.target.value))} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + /> +
+
+ + setHeight(parseInt(e.target.value))} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + /> +
+
+ + setFps(parseInt(e.target.value))} + className="w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent" + /> +
+
+
+
+ )} + + {/* Step 3: Test (netcam only) */} + {step === 'test' && isManualNetcam && ( +
+
+

Test Connection

+

+ Verify that the camera URL is accessible +

+
+ +
+
{netcamUrl}
+
+ + {testNetcam.isError && ( +
+ Connection test failed. Check the URL and credentials. +
+ )} + + {testNetcam.isSuccess && testNetcam.data.status === 'ok' && ( +
+ ✓ Connection successful! +
+ )} +
+ )} + + {/* Step 4: Complete */} + {step === 'complete' && ( +
+
+

Camera Added!

+

+ {deviceName} has been added to your configuration. +

+
+ )} +
+ + {/* Footer */} + {step !== 'complete' && ( +
+ + + +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/settings/CameraManagement.tsx b/frontend/src/components/settings/CameraManagement.tsx new file mode 100644 index 00000000..205dcec5 --- /dev/null +++ b/frontend/src/components/settings/CameraManagement.tsx @@ -0,0 +1,110 @@ +import { useState } from 'react'; +import { useCameras, usePlatformInfo, useDetectedCameras } from '../../api/queries'; +import AddCameraWizard from './AddCameraWizard'; +import DetectedCameraCard from './DetectedCameraCard'; +import ConfiguredCameraCard from './ConfiguredCameraCard'; + +export default function CameraManagement() { + const [showWizard, setShowWizard] = useState(false); + const { data: cameras = [], isLoading: camerasLoading } = useCameras(); + const { data: platformInfo, isLoading: platformLoading } = usePlatformInfo(); + const { data: detectedData, isLoading: detectedLoading, refetch: refetchDetected } = useDetectedCameras(); + + const detectedCameras = detectedData?.cameras || []; + const hasDetectedCameras = detectedCameras.length > 0; + + if (camerasLoading || platformLoading) { + return ( +
+

Loading camera information...

+
+ ); + } + + return ( +
+ {/* Header */} +
+
+

Camera Management

+

+ Add, remove, and configure cameras + {platformInfo?.is_raspberry_pi && ` • ${platformInfo.pi_model}`} +

+
+ +
+ + {/* First-run experience */} + {cameras.length === 0 && ( +
+
📷
+

Welcome to Motion

+

+ Get started by adding your first camera. Motion will automatically detect connected cameras + or you can manually configure a network camera. +

+ +
+ )} + + {/* Configured cameras */} + {cameras.length > 0 && ( +
+

Configured Cameras

+
+ {cameras.map((camera) => ( + + ))} +
+
+ )} + + {/* Detected cameras */} + {!detectedLoading && hasDetectedCameras && ( +
+

+ Available Cameras ({detectedCameras.length}) +

+

+ These cameras were detected but not yet configured +

+
+ {detectedCameras.map((camera, index) => ( + setShowWizard(true)} + /> + ))} +
+
+ )} + + {/* Add camera wizard */} + {showWizard && ( + setShowWizard(false)} + detectedCameras={detectedCameras} + platformInfo={platformInfo} + /> + )} +
+ ); +} diff --git a/frontend/src/components/settings/ConfiguredCameraCard.tsx b/frontend/src/components/settings/ConfiguredCameraCard.tsx new file mode 100644 index 00000000..c6affbea --- /dev/null +++ b/frontend/src/components/settings/ConfiguredCameraCard.tsx @@ -0,0 +1,95 @@ +import { useState } from 'react'; +import type { Camera } from '../../api/types'; +import { useDeleteCamera } from '../../api/queries'; + +interface ConfiguredCameraCardProps { + camera: Camera; +} + +export default function ConfiguredCameraCard({ camera }: ConfiguredCameraCardProps) { + const [showConfirm, setShowConfirm] = useState(false); + const deleteCamera = useDeleteCamera(); + + const handleDelete = () => { + deleteCamera.mutate({ camId: camera.id }, { + onSuccess: () => { + setShowConfirm(false); + }, + }); + }; + + return ( +
+
+
+
+
+
+
{camera.name}
+ + Active + +
+ +
+
Camera ID:
+
{camera.id}
+ {camera.width && camera.height && ( + <> +
Resolution:
+
+ {camera.width}x{camera.height} +
+ + )} +
Stream URL:
+
+ + {camera.url} + +
+
+
+
+ +
+ + View + + {!showConfirm ? ( + + ) : ( +
+ + +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/components/settings/DetectedCameraCard.tsx b/frontend/src/components/settings/DetectedCameraCard.tsx new file mode 100644 index 00000000..420504d5 --- /dev/null +++ b/frontend/src/components/settings/DetectedCameraCard.tsx @@ -0,0 +1,82 @@ +import type { DetectedCamera } from '../../api/types'; + +interface DetectedCameraCardProps { + camera: DetectedCamera; + onAdd: () => void; +} + +export default function DetectedCameraCard({ camera, onAdd }: DetectedCameraCardProps) { + const typeLabel = { + libcam: 'Pi Camera (libcamera)', + v4l2: 'USB Camera (V4L2)', + netcam: 'Network Camera', + }[camera.type]; + + const typeIcon = { + libcam: '🎥', + v4l2: '📹', + netcam: '🌐', + }[camera.type]; + + return ( +
+
+
+
{typeIcon}
+
+
+
{camera.device_name}
+ + {typeLabel} + +
+ +
+ {camera.sensor_model && ( + <> +
Sensor:
+
{camera.sensor_model}
+ + )} +
Device:
+
+ {camera.device_path} +
+
Default:
+
+ {camera.default_width}x{camera.default_height} @ {camera.default_fps}fps +
+
+ + {camera.resolutions.length > 0 && ( +
+ + Available resolutions ({camera.resolutions.length}) + +
+ {camera.resolutions.slice(0, 10).map(([w, h], idx) => ( + + {w}x{h} + + ))} + {camera.resolutions.length > 10 && ( + + +{camera.resolutions.length - 10} more + + )} +
+
+ )} +
+
+ + +
+
+ ); +} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 3395018c..edce4c7b 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -10,6 +10,7 @@ import { useBatchUpdateConfig, useSystemStatus } from '@/api/queries' import { validateConfigParam } from '@/lib/validation' import { SystemSettings } from '@/components/settings/SystemSettings' import { DeviceSettings } from '@/components/settings/DeviceSettings' +import { CameraManagement } from '@/components/settings/CameraManagement' import { CameraSourceSettings } from '@/components/settings/CameraSourceSettings' import { LibcameraSettings } from '@/components/settings/LibcameraSettings' import { V4L2Settings } from '@/components/settings/V4L2Settings' @@ -400,6 +401,16 @@ export function Settings() {
+ {/* Camera Management - Global only */} + + + + {/* UI Preferences - Global only */} diff --git a/src/Makefile.am b/src/Makefile.am index 2c070013..12556c27 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,6 +29,7 @@ bin_PROGRAMS = motion motion-setup motion_SOURCES = \ alg.hpp alg.cpp \ alg_sec.hpp alg_sec.cpp \ + cam_detect.hpp cam_detect.cpp \ conf.hpp conf.cpp \ conf_file.hpp conf_file.cpp \ conf_profile.hpp conf_profile.cpp \ diff --git a/src/cam_detect.cpp b/src/cam_detect.cpp new file mode 100644 index 00000000..2e83360e --- /dev/null +++ b/src/cam_detect.cpp @@ -0,0 +1,495 @@ +/* + * This file is part of Motion. + * + * Motion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Motion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Motion. If not, see . + * + */ + +/* + * cam_detect.cpp - Camera Auto-Detection Implementation + * + * Implements intelligent camera detection for Pi cameras (CSI/libcamera), + * USB/V4L2 cameras with device info extraction, blacklist filtering, + * and sensor-aware default configuration. + * + */ + +#include "motion.hpp" +#include "cam_detect.hpp" +#include "logger.hpp" +#include "conf.hpp" +#include +#include +#include +#include + +#ifdef HAVE_V4L2 + #include + #include + #include + #include +#endif + +#ifdef HAVE_LIBCAM + #include + using namespace libcamera; +#endif + +cls_cam_detect::cls_cam_detect(cls_motapp *p_app) +{ + app = p_app; +} + +cls_cam_detect::~cls_cam_detect() +{ +} + +/* Read platform model from device tree */ +std::string cls_cam_detect::get_pi_model() +{ + std::ifstream file("/proc/device-tree/model"); + std::string model; + + if (file.is_open()) { + std::getline(file, model); + file.close(); + + /* Parse out Pi version: "Raspberry Pi 5 Model B Rev 1.0" -> "Pi 5" */ + if (model.find("Raspberry Pi") != std::string::npos) { + size_t pos = model.find("Raspberry Pi"); + std::string version = model.substr(pos + 13); + + /* Extract just the number/version */ + size_t space = version.find(' '); + if (space != std::string::npos) { + version = version.substr(0, space); + return "Pi " + version; + } + } + } + + return ""; +} + +/* Check if running on Raspberry Pi */ +bool cls_cam_detect::is_raspberry_pi() +{ + struct stat buffer; + return (stat("/proc/device-tree/model", &buffer) == 0); +} + +/* Check if libcamera is available */ +bool cls_cam_detect::has_libcamera_support() +{ +#ifdef HAVE_LIBCAM + return true; +#else + return false; +#endif +} + +/* Check if V4L2 is available */ +bool cls_cam_detect::has_v4l2_support() +{ +#ifdef HAVE_V4L2 + return true; +#else + return false; +#endif +} + +/* Get platform information */ +ctx_platform_info cls_cam_detect::get_platform_info() +{ + ctx_platform_info info; + + info.is_raspberry_pi = is_raspberry_pi(); + info.pi_model = info.is_raspberry_pi ? get_pi_model() : ""; + info.has_libcamera = has_libcamera_support(); + info.has_v4l2 = has_v4l2_support(); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , "Platform: %s%s, libcamera: %s, V4L2: %s" + , info.is_raspberry_pi ? info.pi_model.c_str() : "Generic" + , info.is_raspberry_pi ? "" : " Linux" + , info.has_libcamera ? "yes" : "no" + , info.has_v4l2 ? "yes" : "no"); + + return info; +} + +/* Apply sensor-specific defaults */ +void cls_cam_detect::apply_sensor_defaults(ctx_detected_cam &cam) +{ + /* Sensor-aware defaults based on known Pi camera modules */ + if (cam.sensor_model == "imx708") { + /* Pi Camera v3 (12MP) - optimized defaults */ + cam.default_width = 1920; + cam.default_height = 1080; + cam.default_fps = 30; + } else if (cam.sensor_model == "imx219") { + /* Pi Camera v2 (8MP) - optimized defaults */ + cam.default_width = 1640; + cam.default_height = 1232; + cam.default_fps = 30; + } else if (cam.sensor_model == "imx477") { + /* Pi HQ Camera (12.3MP) - optimized defaults */ + cam.default_width = 1920; + cam.default_height = 1080; + cam.default_fps = 30; + } else if (cam.sensor_model == "imx296") { + /* Pi GS Camera (1.6MP) */ + cam.default_width = 1456; + cam.default_height = 1088; + cam.default_fps = 60; + } else { + /* Generic defaults */ + cam.default_width = 1280; + cam.default_height = 720; + cam.default_fps = 15; + } +} + +/* Check if device is already configured */ +bool cls_cam_detect::is_device_configured(const std::string &device_id, + const std::string &device_path) +{ + for (int i = 0; i < app->cam_cnt; i++) { + cls_camera *cam = app->cam_list[i]; + + /* Check libcam_device */ + std::string cfg_libcam = cam->cfg->libcam_device; + if (!cfg_libcam.empty() && cfg_libcam == device_path) { + return true; + } + + /* Check v4l2_device (can be /dev/video0 or /dev/v4l/by-id/...) */ + std::string cfg_v4l2 = cam->cfg->v4l2_device; + if (!cfg_v4l2.empty()) { + if (cfg_v4l2 == device_path || cfg_v4l2 == device_id) { + return true; + } + } + } + + return false; +} + +#ifdef HAVE_LIBCAM +/* Detect Pi cameras using libcamera */ +std::vector cls_cam_detect::detect_libcam() +{ + std::vector cameras; + + try { + /* Create camera manager directly */ + std::unique_ptr cam_mgr = std::make_unique(); + + int retcd = cam_mgr->start(); + if (retcd < 0) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO + , "Camera manager not available, skipping libcamera detection"); + return cameras; + } + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , "cam_mgr started. Total cameras available: %d" + , (int)cam_mgr->cameras().size()); + + /* Filter for Pi cameras (exclude USB/UVC) */ + for (const auto& cam_item : cam_mgr->cameras()) { + std::string id = cam_item->id(); + + /* Filter out USB cameras (UVC devices) + * Pi cameras have IDs like: /base/axi/pcie@120000/rp1/i2c@88000/imx708@1a + * USB cameras have IDs containing "usb" or are UVC devices + */ + std::string id_lower = id; + std::transform(id_lower.begin(), id_lower.end(), id_lower.begin(), ::tolower); + + if (id_lower.find("usb") != std::string::npos || + id_lower.find("uvc") != std::string::npos) { + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO + , "Skipping USB camera: %s", id.c_str()); + continue; + } + + ctx_detected_cam detected; + detected.type = CAM_DETECT_LIBCAM; + detected.device_path = id; + detected.device_id = detected.device_path; /* For libcam, ID == path */ + + /* Extract sensor model from camera ID */ + /* Format: /base/axi/pcie@120000/rp1/i2c@88000/imx708@1a */ + size_t sensor_pos = detected.device_path.rfind('/'); + if (sensor_pos != std::string::npos) { + std::string sensor_part = detected.device_path.substr(sensor_pos + 1); + size_t at_pos = sensor_part.find('@'); + if (at_pos != std::string::npos) { + detected.sensor_model = sensor_part.substr(0, at_pos); + } + } + + /* Generate friendly name */ + if (!detected.sensor_model.empty()) { + detected.device_name = "Pi Camera (" + detected.sensor_model + ")"; + } else { + detected.device_name = "Pi Camera"; + } + + /* Apply sensor-specific defaults */ + apply_sensor_defaults(detected); + + /* Check if already configured */ + detected.already_configured = is_device_configured( + detected.device_id, detected.device_path); + + cameras.push_back(detected); + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO + , "Detected libcamera: %s [%s] %dx%d@%dfps%s" + , detected.device_name.c_str() + , detected.sensor_model.c_str() + , detected.default_width + , detected.default_height + , detected.default_fps + , detected.already_configured ? " (configured)" : ""); + } + + cam_mgr->stop(); + + } catch (const std::exception &e) { + MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO + , "Error detecting libcamera devices: %s", e.what()); + } + + return cameras; +} +#else +std::vector cls_cam_detect::detect_libcam() +{ + return std::vector(); +} +#endif + +#ifdef HAVE_V4L2 +/* Check if device name matches blacklist */ +bool cls_cam_detect::is_v4l2_blacklisted(const std::string &device_name) +{ + for (const auto &blacklisted : v4l2_blacklist) { + if (device_name.find(blacklisted) != std::string::npos) { + return true; + } + } + return false; +} + +/* Get V4L2 device friendly name */ +std::string cls_cam_detect::get_v4l2_device_name(const std::string &device_path) +{ + int fd = open(device_path.c_str(), O_RDONLY); + if (fd < 0) { + return ""; + } + + struct v4l2_capability cap; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { + close(fd); + return std::string(reinterpret_cast(cap.card)); + } + + close(fd); + return ""; +} + +/* Get persistent device ID from /dev/v4l/by-id/ */ +std::string cls_cam_detect::get_v4l2_persistent_id(const std::string &device_path) +{ + /* Look for symlink in /dev/v4l/by-id/ pointing to this device */ + DIR *dir = opendir("/dev/v4l/by-id"); + if (!dir) { + return device_path; /* Fallback to device_path */ + } + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + std::string link_path = std::string("/dev/v4l/by-id/") + entry->d_name; + char target[PATH_MAX]; + ssize_t len = readlink(link_path.c_str(), target, sizeof(target) - 1); + + if (len > 0) { + target[len] = '\0'; + + /* Resolve relative path */ + char resolved_target[PATH_MAX]; + if (realpath(link_path.c_str(), resolved_target) != NULL) { + if (device_path == resolved_target) { + closedir(dir); + return link_path; + } + } + } + } + + closedir(dir); + return device_path; /* No persistent ID found, use device path */ +} + +/* Get available resolutions for device */ +std::vector> cls_cam_detect::get_device_resolutions(const std::string &device_path) +{ + std::vector> resolutions; + + int fd = open(device_path.c_str(), O_RDONLY); + if (fd < 0) { + return resolutions; + } + + /* Enumerate frame sizes */ + struct v4l2_frmsizeenum frmsize; + memset(&frmsize, 0, sizeof(frmsize)); + frmsize.pixel_format = V4L2_PIX_FMT_YUYV; /* Common format */ + + int index = 0; + while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { + if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + resolutions.push_back(std::make_pair( + frmsize.discrete.width, frmsize.discrete.height)); + } + frmsize.index = ++index; + } + + close(fd); + return resolutions; +} + +/* Detect V4L2/USB cameras */ +std::vector cls_cam_detect::detect_v4l2() +{ + std::vector cameras; + + /* Enumerate /dev/video* devices */ + for (int i = 0; i < 10; i++) { + std::string device_path = "/dev/video" + std::to_string(i); + + int fd = open(device_path.c_str(), O_RDONLY); + if (fd < 0) { + continue; /* Device doesn't exist */ + } + + struct v4l2_capability cap; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) != 0) { + close(fd); + continue; + } + + std::string device_name = std::string(reinterpret_cast(cap.card)); + close(fd); + + /* Skip blacklisted devices (ISP devices, not cameras) */ + if (is_v4l2_blacklisted(device_name)) { + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO + , "Skipping blacklisted device: %s (%s)" + , device_path.c_str(), device_name.c_str()); + continue; + } + + /* Skip non-capture devices */ + if (!(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) { + continue; + } + + ctx_detected_cam detected; + detected.type = CAM_DETECT_V4L2; + detected.device_path = device_path; + detected.device_id = get_v4l2_persistent_id(device_path); + detected.device_name = device_name.empty() ? "USB Camera" : device_name; + detected.sensor_model = ""; /* V4L2 doesn't expose sensor model */ + + /* Get available resolutions */ + detected.resolutions = get_device_resolutions(device_path); + + /* Apply generic defaults */ + apply_sensor_defaults(detected); + + /* Check if already configured */ + detected.already_configured = is_device_configured( + detected.device_id, detected.device_path); + + cameras.push_back(detected); + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO + , "Detected V4L2: %s [%s] %dx%d@%dfps%s" + , detected.device_name.c_str() + , detected.device_path.c_str() + , detected.default_width + , detected.default_height + , detected.default_fps + , detected.already_configured ? " (configured)" : ""); + } + + return cameras; +} +#else +std::vector cls_cam_detect::detect_v4l2() +{ + return std::vector(); +} +#endif + +/* Detect all cameras (libcam + V4L2) */ +std::vector cls_cam_detect::detect_cameras() +{ + std::vector all_cameras; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Starting camera detection..."); + + /* Detect Pi cameras via libcamera */ + auto libcam_cameras = detect_libcam(); + all_cameras.insert(all_cameras.end(), libcam_cameras.begin(), libcam_cameras.end()); + + /* Detect USB/V4L2 cameras */ + auto v4l2_cameras = detect_v4l2(); + all_cameras.insert(all_cameras.end(), v4l2_cameras.begin(), v4l2_cameras.end()); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , "Camera detection complete: %d total, %d unconfigured" + , (int)all_cameras.size() + , (int)std::count_if(all_cameras.begin(), all_cameras.end(), + [](const ctx_detected_cam &c) { return !c.already_configured; })); + + return all_cameras; +} + +/* Test network camera connection */ +bool cls_cam_detect::test_netcam(const std::string &url, const std::string &user, + const std::string &pass, int timeout_sec) +{ + /* TODO: Implement netcam connection testing + * This will require making a test HTTP/RTSP request to the URL + * and verifying we can get a response within the timeout. + * For now, return true as a placeholder. + */ + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO + , "Testing netcam connection: %s (timeout: %ds)" + , url.c_str(), timeout_sec); + + /* Placeholder - actual implementation would test the connection */ + return true; +} diff --git a/src/cam_detect.hpp b/src/cam_detect.hpp new file mode 100644 index 00000000..6edb4702 --- /dev/null +++ b/src/cam_detect.hpp @@ -0,0 +1,119 @@ +/* + * This file is part of Motion. + * + * Motion is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Motion is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Motion. If not, see . + * + */ + +/* + * cam_detect.hpp - Camera Auto-Detection System + * + * Provides intelligent camera detection for Pi cameras (CSI/libcamera), + * USB/V4L2 cameras, and network cameras with device info extraction, + * blacklist filtering, and sensor-aware default configuration. + * + */ + +#ifndef _INCLUDE_CAM_DETECT_HPP_ +#define _INCLUDE_CAM_DETECT_HPP_ + +#include +#include +#include + +/* Camera type detected */ +enum CAM_DETECT_TYPE { + CAM_DETECT_LIBCAM, /* Pi camera via libcamera (CSI) */ + CAM_DETECT_V4L2, /* USB/V4L2 camera */ + CAM_DETECT_NETCAM /* Network camera (manually configured) */ +}; + +/* Detected camera information */ +struct ctx_detected_cam { + CAM_DETECT_TYPE type; + std::string device_id; /* Persistent identifier (e.g., /dev/v4l/by-id/...) */ + std::string device_path; /* /dev/video0 or libcam id */ + std::string device_name; /* Human-readable name */ + std::string sensor_model; /* imx708, imx219, etc. */ + int default_width; + int default_height; + int default_fps; + bool already_configured; /* True if this device is already in config */ + std::vector> resolutions; +}; + +/* Platform information */ +struct ctx_platform_info { + bool is_raspberry_pi; + std::string pi_model; /* "Pi 4", "Pi 5", etc. */ + bool has_libcamera; + bool has_v4l2; +}; + +class cls_motapp; + +/* Camera detection and platform identification */ +class cls_cam_detect { + public: + cls_cam_detect(cls_motapp *p_app); + ~cls_cam_detect(); + + /* Get platform information (CPU, capabilities) */ + ctx_platform_info get_platform_info(); + + /* Detect all cameras (libcam + V4L2) */ + std::vector detect_cameras(); + + /* Test network camera connection */ + bool test_netcam(const std::string &url, const std::string &user, + const std::string &pass, int timeout_sec); + + private: + cls_motapp *app; + + /* Platform detection */ + bool is_raspberry_pi(); + std::string get_pi_model(); + bool has_libcamera_support(); + bool has_v4l2_support(); + + /* Pi camera detection (CSI via libcamera) */ + std::vector detect_libcam(); + + /* USB/V4L2 camera detection */ + std::vector detect_v4l2(); + + /* Check if device is already configured */ + bool is_device_configured(const std::string &device_id, + const std::string &device_path); + + /* Apply sensor-aware defaults */ + void apply_sensor_defaults(ctx_detected_cam &cam); + + /* Get available resolutions for device */ + std::vector> get_device_resolutions(const std::string &device_path); + + /* V4L2 helpers */ + std::string get_v4l2_device_name(const std::string &device_path); + std::string get_v4l2_persistent_id(const std::string &device_path); + bool is_v4l2_blacklisted(const std::string &device_name); + + /* V4L2 blacklist (ISP devices, not actual cameras) */ + const std::vector v4l2_blacklist = { + "bcm2835-codec", "pispbe", "bcm2835-isp", + "rpivid", "unicam", "rp1-cfe" + }; +}; + +#endif /* _INCLUDE_CAM_DETECT_HPP_ */ diff --git a/src/conf.cpp b/src/conf.cpp index fe5caf2a..9ee1c896 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -39,6 +39,7 @@ #include "camera.hpp" #include "sound.hpp" #include "conf.hpp" +#include "cam_detect.hpp" #include "parm_registry.hpp" #include "conf_file.hpp" @@ -1324,6 +1325,50 @@ void cls_config::camera_add(std::string fname, bool srcdir) app->cam_cnt = (int)app->cam_list.size(); } +/* + * Add camera from detection system + * Creates a new camera configuration with detected device parameters + */ +void cls_config::camera_add_from_detection(const ctx_detected_cam &detected) +{ + /* Create camera with empty filename (will be generated) */ + camera_add("", false); + + /* Get the newly created camera */ + cls_camera *cam = app->cam_list[app->cam_cnt - 1]; + + /* Configure device-specific parameters */ + if (detected.type == CAM_DETECT_LIBCAM) { + cam->conf_src->edit_set("libcam_device", detected.device_path); + } else if (detected.type == CAM_DETECT_V4L2) { + /* Use persistent device ID if available, otherwise use device path */ + cam->conf_src->edit_set("v4l2_device", detected.device_id); + } else if (detected.type == CAM_DETECT_NETCAM) { + cam->conf_src->edit_set("netcam_url", detected.device_path); + } + + /* Set common parameters */ + cam->conf_src->edit_set("device_name", detected.device_name); + cam->conf_src->edit_set("width", std::to_string(detected.default_width)); + cam->conf_src->edit_set("height", std::to_string(detected.default_height)); + cam->conf_src->edit_set("framerate", std::to_string(detected.default_fps)); + + /* Copy updated configuration to runtime config */ + cam->cfg->parms_copy(cam->conf_src); + + /* Write configuration file */ + cam->conf_src->parms_write(); + + /* Request camera start */ + pthread_mutex_lock(&app->mutex_camlst); + cam->handler_stop = false; + pthread_mutex_unlock(&app->mutex_camlst); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Camera added from detection: %s [%s]", + detected.device_name.c_str(), detected.device_path.c_str()); +} + /* Create default configuration file name*/ void cls_config::sound_filenm() { diff --git a/src/conf.hpp b/src/conf.hpp index 46e4bc34..39466c38 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -331,6 +331,7 @@ bool& snd_show = parm_snd.snd_show; void camera_add(std::string fname, bool srcdir); + void camera_add_from_detection(const ctx_detected_cam &detected); void sound_add(std::string fname, bool srcdir); void camera_filenm(); void sound_filenm(); diff --git a/src/motion.cpp b/src/motion.cpp index e415d0d0..6782d322 100644 --- a/src/motion.cpp +++ b/src/motion.cpp @@ -33,6 +33,7 @@ #include "allcam.hpp" #include "schedule.hpp" #include "camera.hpp" +#include "cam_detect.hpp" #include "sound.hpp" #include "dbse.hpp" #include "webu.hpp" @@ -557,6 +558,7 @@ void cls_motapp::init(int p_argc, char *p_argv[]) allcam = new cls_allcam(this); schedule = new cls_schedule(this); thumbnail = new cls_thumbnail(this); + cam_detect = new cls_cam_detect(this); if ((cam_cnt > 0) || (snd_cnt > 0)) { for (indx=0; indxapi_profiles_delete(); mhd_send(); + } else if (uri_cmd1 == "api" && uri_cmd2 == "cameras" && uri_cmd3.empty()) { + /* DELETE /{camId}/api/cameras - remove camera */ + if (webu_json == nullptr) { + webu_json = new cls_webu_json(this); + } + webu_json->api_cameras_delete(); + mhd_send(); } else { /* DELETE not allowed for other endpoints */ resp_type = WEBUI_RESP_TEXT; @@ -1196,7 +1203,16 @@ void cls_webu_ans::answer_get() webu_json->api_system_status(); mhd_send(); } else if (uri_cmd2 == "cameras") { - webu_json->api_cameras(); + if (uri_cmd3 == "platform") { + /* GET /0/api/cameras/platform */ + webu_json->api_cameras_platform(); + } else if (uri_cmd3 == "detected") { + /* GET /0/api/cameras/detected */ + webu_json->api_cameras_detected(); + } else { + /* GET /0/api/cameras */ + webu_json->api_cameras(); + } mhd_send(); } else if (uri_cmd2 == "config") { webu_json->api_config(); @@ -1509,6 +1525,32 @@ mhdrslt cls_webu_ans::answer_main(struct MHD_Connection *p_connection } mhd_send(); retcd = MHD_YES; + } else if (uri_cmd1 == "api" && uri_cmd2 == "cameras" && uri_cmd3 == "test") { + /* POST /0/api/cameras/test - test netcam connection */ + if (*upload_data_size > 0) { + raw_body.append(upload_data, *upload_data_size); + *upload_data_size = 0; + return MHD_YES; + } + if (webu_json == nullptr) { + webu_json = new cls_webu_json(this); + } + webu_json->api_cameras_test_netcam(); + mhd_send(); + retcd = MHD_YES; + } else if (uri_cmd1 == "api" && uri_cmd2 == "cameras" && uri_cmd3.empty()) { + /* POST /0/api/cameras - add detected camera */ + if (*upload_data_size > 0) { + raw_body.append(upload_data, *upload_data_size); + *upload_data_size = 0; + return MHD_YES; + } + if (webu_json == nullptr) { + webu_json = new cls_webu_json(this); + } + webu_json->api_cameras_add(); + mhd_send(); + retcd = MHD_YES; } else { /* Unknown POST endpoint - reject */ bad_request(); diff --git a/src/webu_json.cpp b/src/webu_json.cpp index 649d8b61..1a71837f 100644 --- a/src/webu_json.cpp +++ b/src/webu_json.cpp @@ -30,6 +30,7 @@ #include "camera.hpp" #include "conf.hpp" #include "conf_profile.hpp" +#include "cam_detect.hpp" #include "logger.hpp" #include "webu.hpp" #include "webu_ans.hpp" @@ -3760,6 +3761,220 @@ void cls_webu_json::api_camera_ptz() webua->resp_page = "{\"status\":\"ok\",\"action\":\"" + action + "\"}"; } +/* + * Camera detection API: Get platform information + * GET /0/api/cameras/platform + * Returns platform details (Pi model, capabilities) + */ +void cls_webu_json::api_cameras_platform() +{ + webua->resp_type = WEBUI_RESP_JSON; + + auto platform_info = app->cam_detect->get_platform_info(); + + webua->resp_page = "{"; + webua->resp_page += "\"is_raspberry_pi\":"; + webua->resp_page += platform_info.is_raspberry_pi ? "true" : "false"; + webua->resp_page += ",\"pi_model\":\"" + escstr(platform_info.pi_model) + "\""; + webua->resp_page += ",\"has_libcamera\":"; + webua->resp_page += platform_info.has_libcamera ? "true" : "false"; + webua->resp_page += ",\"has_v4l2\":"; + webua->resp_page += platform_info.has_v4l2 ? "true" : "false"; + webua->resp_page += "}"; +} + +/* + * Camera detection API: Get detected cameras + * GET /0/api/cameras/detected + * Returns list of unconfigured cameras + */ +void cls_webu_json::api_cameras_detected() +{ + webua->resp_type = WEBUI_RESP_JSON; + + auto cameras = app->cam_detect->detect_cameras(); + + webua->resp_page = "{\"cameras\":["; + + bool first = true; + for (const auto &cam : cameras) { + /* Only return unconfigured cameras */ + if (cam.already_configured) { + continue; + } + + if (!first) { + webua->resp_page += ","; + } + first = false; + + webua->resp_page += "{"; + webua->resp_page += "\"type\":\""; + if (cam.type == CAM_DETECT_LIBCAM) { + webua->resp_page += "libcam"; + } else if (cam.type == CAM_DETECT_V4L2) { + webua->resp_page += "v4l2"; + } else { + webua->resp_page += "netcam"; + } + webua->resp_page += "\""; + webua->resp_page += ",\"device_id\":\"" + escstr(cam.device_id) + "\""; + webua->resp_page += ",\"device_path\":\"" + escstr(cam.device_path) + "\""; + webua->resp_page += ",\"device_name\":\"" + escstr(cam.device_name) + "\""; + webua->resp_page += ",\"sensor_model\":\"" + escstr(cam.sensor_model) + "\""; + webua->resp_page += ",\"default_width\":" + std::to_string(cam.default_width); + webua->resp_page += ",\"default_height\":" + std::to_string(cam.default_height); + webua->resp_page += ",\"default_fps\":" + std::to_string(cam.default_fps); + + /* Add available resolutions */ + webua->resp_page += ",\"resolutions\":["; + for (size_t i = 0; i < cam.resolutions.size(); i++) { + if (i > 0) { + webua->resp_page += ","; + } + webua->resp_page += "[" + std::to_string(cam.resolutions[i].first); + webua->resp_page += "," + std::to_string(cam.resolutions[i].second) + "]"; + } + webua->resp_page += "]"; + + webua->resp_page += "}"; + } + + webua->resp_page += "]}"; +} + +/* + * Camera detection API: Add camera from detection + * POST /0/api/cameras + * Adds a detected camera to the configuration + */ +void cls_webu_json::api_cameras_add() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + /* Parse JSON body */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, + _("JSON parse error: %s"), parser.getError().c_str()); + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid JSON\"}"; + return; + } + + /* Get camera details from JSON */ + std::string type = parser.getString("type", ""); + std::string device_id = parser.getString("device_id", ""); + std::string device_path = parser.getString("device_path", ""); + std::string device_name = parser.getString("device_name", ""); + std::string sensor_model = parser.getString("sensor_model", ""); + int width = parser.getInt("width", 0); + int height = parser.getInt("height", 0); + int fps = parser.getInt("fps", 0); + + if (type.empty() || device_path.empty()) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Missing required fields\"}"; + return; + } + + /* Create detected camera struct */ + ctx_detected_cam detected; + if (type == "libcam") { + detected.type = CAM_DETECT_LIBCAM; + } else if (type == "v4l2") { + detected.type = CAM_DETECT_V4L2; + } else if (type == "netcam") { + detected.type = CAM_DETECT_NETCAM; + } else { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid camera type\"}"; + return; + } + + detected.device_id = device_id; + detected.device_path = device_path; + detected.device_name = device_name; + detected.sensor_model = sensor_model; + detected.default_width = width; + detected.default_height = height; + detected.default_fps = fps; + + /* Add camera to configuration */ + pthread_mutex_lock(&app->mutex_post); + app->conf_src->camera_add_from_detection(detected); + pthread_mutex_unlock(&app->mutex_post); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Camera added via API: %s [%s]", device_name.c_str(), device_path.c_str()); + + webua->resp_page = "{\"status\":\"ok\",\"message\":\"Camera added successfully\"}"; +} + +/* + * Camera detection API: Delete camera + * DELETE /{camId}/api/cameras + * Removes a camera from the configuration + */ +void cls_webu_json::api_cameras_delete() +{ + webua->resp_type = WEBUI_RESP_JSON; + + if (!validate_csrf()) { + return; + } + + if (webua->camindx < 0 || webua->camindx >= app->cam_cnt) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid camera ID\"}"; + return; + } + + pthread_mutex_lock(&app->mutex_post); + app->cam_delete = webua->device_id; + pthread_mutex_unlock(&app->mutex_post); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, + "Camera delete requested via API: camera %d", webua->device_id); + + webua->resp_page = "{\"status\":\"ok\",\"message\":\"Camera will be removed\"}"; +} + +/* + * Camera detection API: Test network camera connection + * POST /0/api/cameras/test + * Tests if a network camera URL is accessible + */ +void cls_webu_json::api_cameras_test_netcam() +{ + webua->resp_type = WEBUI_RESP_JSON; + + /* Parse JSON body */ + JsonParser parser; + if (!parser.parse(webua->raw_body)) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Invalid JSON\"}"; + return; + } + + std::string url = parser.getString("url", ""); + std::string user = parser.getString("user", ""); + std::string pass = parser.getString("pass", ""); + int timeout = parser.getInt("timeout", 5); + + if (url.empty()) { + webua->resp_page = "{\"status\":\"error\",\"message\":\"URL is required\"}"; + return; + } + + bool success = app->cam_detect->test_netcam(url, user, pass, timeout); + + if (success) { + webua->resp_page = "{\"status\":\"ok\",\"message\":\"Connection successful\"}"; + } else { + webua->resp_page = "{\"status\":\"error\",\"message\":\"Connection failed\"}"; + } +} + void cls_webu_json::main() { pthread_mutex_lock(&app->mutex_post); diff --git a/src/webu_json.hpp b/src/webu_json.hpp index 28ae6cf4..9a6773b7 100644 --- a/src/webu_json.hpp +++ b/src/webu_json.hpp @@ -66,6 +66,13 @@ void api_profiles_apply(); /* POST /0/api/profiles/{id}/apply */ void api_profiles_set_default(); /* POST /0/api/profiles/{id}/default */ + /* Camera detection API endpoints */ + void api_cameras_platform(); /* GET /0/api/cameras/platform */ + void api_cameras_detected(); /* GET /0/api/cameras/detected */ + void api_cameras_add(); /* POST /0/api/cameras */ + void api_cameras_delete(); /* DELETE /{camId}/api/cameras */ + void api_cameras_test_netcam(); /* POST /0/api/cameras/test */ + /* Camera action API endpoints (JSON replacements for legacy POST) */ void api_config_write(); /* POST /0/api/config/write */ void api_camera_restart(); /* POST /{camId}/api/camera/restart */ From dbf20f9c716c995f42456ba313a477077c5e98d0 Mon Sep 17 00:00:00 2001 From: Trent Shuey Date: Sun, 25 Jan 2026 21:30:24 -0600 Subject: [PATCH 08/10] feat: Add camera switcher to Quick Settings bottom sheet Enable seamless camera navigation within the Quick Settings panel via dropdown selector, allowing users to switch between cameras without closing and reopening the sheet. The switcher automatically hides for single-camera setups to reduce UI clutter. Changes: - Add CameraSwitcher component with dropdown for multi-camera selection - Integrate switcher into Dashboard bottom sheet header - Simplify sheet title (camera name now shown in dropdown) Co-Authored-By: Claude Sonnet 4.5 --- frontend/src/components/CameraSwitcher.tsx | 29 ++++++++++++++++++++++ frontend/src/pages/Dashboard.tsx | 20 +++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/CameraSwitcher.tsx diff --git a/frontend/src/components/CameraSwitcher.tsx b/frontend/src/components/CameraSwitcher.tsx new file mode 100644 index 00000000..d4f8e010 --- /dev/null +++ b/frontend/src/components/CameraSwitcher.tsx @@ -0,0 +1,29 @@ +import type { Camera } from '@/api/types' + +interface CameraSwitcherProps { + cameras: Camera[] + selectedId: number | null + onSelect: (id: number) => void +} + +export function CameraSwitcher({ cameras, selectedId, onSelect }: CameraSwitcherProps) { + // No switcher needed for single camera + if (cameras.length <= 1) return null + + return ( + + ) +} diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 91a52316..d96b13f8 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -6,6 +6,7 @@ import { QuickSettings } from '@/components/QuickSettings' import { FullscreenButton } from '@/components/FullscreenButton' import { SettingsButton } from '@/components/SettingsButton' import { SnapshotButton } from '@/components/SnapshotButton' +import { CameraSwitcher } from '@/components/CameraSwitcher' import { useCameras, useCameraStatus } from '@/api/queries' import { apiGet } from '@/api/client' import { updateSessionCsrf } from '@/api/session' @@ -77,11 +78,14 @@ export function Dashboard() { setSheetOpen(false) } - // Get camera name for sheet title - const selectedCamera = cameras?.find((c) => c.id === selectedCameraId) - const sheetTitle = selectedCamera - ? `Quick Settings - ${selectedCamera.name}` - : 'Quick Settings' + // Camera switcher for bottom sheet header (only shown with multiple cameras) + const cameraSwitcher = cameras && cameras.length > 1 ? ( + + ) : null // Build config for selected camera (merge camera-specific with defaults) const configForCamera = useMemo(() => { @@ -196,7 +200,8 @@ export function Dashboard() { {selectedCameraId && ( {selectedCameraId && ( Date: Sun, 25 Jan 2026 21:52:48 -0600 Subject: [PATCH 09/10] fix: TypeScript type errors and enhance CI pipeline - Add type casting for API POST calls to resolve TypeScript errors - Split CI workflow into separate frontend and backend jobs - Add frontend linting and build verification to CI - Remove unused platformInfo prop from AddCameraWizard - Add support for unknown camera type in DetectedCameraCard - Change CameraManagement to default export - Regenerate build artifacts with fixes Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/ci.yml | 36 ++++++++++++++++++- data/webui/assets/Dashboard-C1886l1P.js | 3 ++ data/webui/assets/Dashboard-D7oUR9hz.js | 1 - .../{Media-4gl6Y8Bi.js => Media-Cj2hjH__.js} | 2 +- data/webui/assets/Settings-CrTNsWoa.js | 22 ++++++++++++ data/webui/assets/Settings-JwhcLbnw.js | 22 ------------ data/webui/assets/index-CVdKRgH6.css | 1 - .../{index-tiawrtsp.js => index-DEo73YRp.js} | 16 ++++----- data/webui/assets/index-DUblyUBi.css | 1 + ...xmuw_.js => parameterMappings-Bp9bMVsO.js} | 8 ++--- data/webui/index.html | 4 +-- frontend/src/api/queries.ts | 4 +-- .../components/settings/AddCameraWizard.tsx | 2 +- .../settings/DetectedCameraCard.tsx | 2 ++ frontend/src/pages/Settings.tsx | 2 +- 15 files changed, 82 insertions(+), 44 deletions(-) create mode 100644 data/webui/assets/Dashboard-C1886l1P.js delete mode 100644 data/webui/assets/Dashboard-D7oUR9hz.js rename data/webui/assets/{Media-4gl6Y8Bi.js => Media-Cj2hjH__.js} (91%) create mode 100644 data/webui/assets/Settings-CrTNsWoa.js delete mode 100644 data/webui/assets/Settings-JwhcLbnw.js delete mode 100644 data/webui/assets/index-CVdKRgH6.css rename data/webui/assets/{index-tiawrtsp.js => index-DEo73YRp.js} (53%) create mode 100644 data/webui/assets/index-DUblyUBi.css rename data/webui/assets/{parameterMappings-BmLxmuw_.js => parameterMappings-Bp9bMVsO.js} (95%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df79a4aa..07cf4169 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,41 @@ concurrency: cancel-in-progress: true jobs: - build: + frontend: + name: Frontend Build & Lint + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout source + uses: actions/checkout@main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Build frontend + run: npm run build + + - name: Verify build artifacts + run: | + test -f ../data/webui/index.html || { echo "index.html not found"; exit 1; } + test -d ../data/webui/assets || { echo "assets directory not found"; exit 1; } + echo "✅ Frontend build artifacts verified" + + backend: + name: Backend Build (${{ matrix.cxx }}, ${{ matrix.libc }}) runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/data/webui/assets/Dashboard-C1886l1P.js b/data/webui/assets/Dashboard-C1886l1P.js new file mode 100644 index 00000000..01db6c14 --- /dev/null +++ b/data/webui/assets/Dashboard-C1886l1P.js @@ -0,0 +1,3 @@ +import{r as a,j as e,u as W,a as O,t as Y,b as q,c as Q,d as G,e as H,f as z,g as V}from"./index-DEo73YRp.js";import{u as U,p as K,a as $,C as J,F as y,b as M,c as E,A as X,d as Z,e as F}from"./parameterMappings-Bp9bMVsO.js";function ee({isOpen:n,onClose:o,sheetRef:l,closeThreshold:c=.3}){const[d,m]=a.useState(!1),[g,b]=a.useState(0),v=a.useRef(0),h=a.useRef(0),r=a.useRef(0),i=a.useRef(0),j=a.useRef(0),x=a.useCallback(u=>{m(!0),v.current=u,h.current=u,j.current=u,i.current=Date.now(),r.current=0},[]),p=a.useCallback(u=>{if(!d)return;const _=Date.now(),s=_-i.current,f=u-j.current;s>0&&(r.current=f/s),i.current=_,j.current=u,h.current=u;const k=Math.max(0,u-v.current);b(k)},[d]),N=a.useCallback(()=>{if(!d)return;m(!1);const u=l.current?.offsetHeight||400;(g>u*c||r.current>.5)&&o(),b(0),r.current=0},[d,g,o,c,l]),w=a.useCallback(u=>{const _=u.touches[0],s=l.current?.getBoundingClientRect().top||0;_.clientY-s<=60&&x(_.clientY)},[x,l]),T=a.useCallback(u=>{d&&(u.preventDefault(),p(u.touches[0].clientY))},[d,p]),t=a.useCallback(()=>{N()},[N]),C=a.useCallback(u=>{const _=l.current?.getBoundingClientRect().top||0;if(u.clientY-_<=60){x(u.clientY);const f=P=>{p(P.clientY)},k=()=>{N(),document.removeEventListener("mousemove",f),document.removeEventListener("mouseup",k)};document.addEventListener("mousemove",f),document.addEventListener("mouseup",k)}},[x,p,N,l]),S=n?d?`translateY(${g}px)`:"translateY(0)":"translateY(100%)";return{handlers:{onTouchStart:w,onTouchMove:T,onTouchEnd:t,onMouseDown:C},style:{transform:S},state:{isDragging:d,dragOffset:g}}}function B({isOpen:n,onClose:o,title:l,children:c,headerRight:d}){const m=a.useRef(null),g=a.useRef(null),b=a.useRef(null),{handlers:v,style:h}=ee({isOpen:n,onClose:o,sheetRef:m});a.useEffect(()=>{if(!n)return;const i=x=>{x.key==="Escape"&&o()},j=x=>{if(x.key!=="Tab"||!m.current)return;const p=m.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),N=p[0],w=p[p.length-1];N&&(x.shiftKey?document.activeElement===N&&(x.preventDefault(),w?.focus()):document.activeElement===w&&(x.preventDefault(),N?.focus()))};return document.addEventListener("keydown",i),document.addEventListener("keydown",j),()=>{document.removeEventListener("keydown",i),document.removeEventListener("keydown",j)}},[n,o]),a.useEffect(()=>(n?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[n]),a.useEffect(()=>{n?(b.current=document.activeElement,setTimeout(()=>{if(m.current){const i=m.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');i.length>0&&i[0].focus()}},100)):b.current&&b.current.focus()},[n]);const r=a.useCallback(i=>{i.target===i.currentTarget&&o()},[o]);return n?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"fixed inset-0 z-[100]",onClick:r,"aria-hidden":"true"}),e.jsxs("div",{ref:m,className:"fixed bottom-0 left-0 right-0 z-[101] bg-surface/95 backdrop-blur-sm rounded-t-2xl shadow-2xl transition-transform duration-300 ease-out border-t border-surface-elevated",style:{maxHeight:"45vh",transform:h.transform},role:"dialog","aria-modal":"true","aria-label":l,...v,children:[e.jsx("div",{className:"flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none",children:e.jsx("div",{className:"w-12 h-1.5 bg-gray-500 rounded-full"})}),e.jsxs("div",{className:"flex items-center justify-between px-4 pb-3 border-b border-surface-elevated",children:[e.jsx("h2",{className:"text-lg font-semibold",children:l}),e.jsxs("div",{className:"flex items-center gap-3",children:[d,e.jsx("button",{type:"button",onClick:o,className:"p-2 hover:bg-surface-elevated rounded-full transition-colors","aria-label":"Close",children:e.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})]}),e.jsx("div",{ref:g,className:"overflow-y-auto overscroll-contain px-4 py-4",style:{maxHeight:"calc(45vh - 80px)"},children:c})]})]}):null}function L({title:n,defaultOpen:o=!0,children:l}){const[c,d]=a.useState(o);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full py-2 text-left",onClick:()=>d(!c),children:[e.jsx("span",{className:"font-medium text-sm",children:n}),e.jsx("svg",{className:`w-4 h-4 transition-transform ${c?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),c&&e.jsx("div",{className:"pt-2",children:l})]})}function A({cameraId:n,config:o}){const{mutate:l,isPending:c}=W(),[d,m]=a.useState(null),[g,b]=a.useState({}),v=a.useRef({}),{data:h}=U(n);a.useEffect(()=>()=>{Object.values(v.current).forEach(clearTimeout)},[]),a.useEffect(()=>{b({})},[o]);const r=a.useCallback((t,C="")=>t in g?g[t]:o[t]?.value??C,[o,g]);a.useEffect(()=>{Object.values(v.current).forEach(clearTimeout),v.current={}},[n]);const i=a.useCallback((t,C)=>{b(u=>({...u,[t]:C})),v.current[t]&&clearTimeout(v.current[t]);const S=n;v.current[t]=setTimeout(()=>{l({camId:S,changes:{[t]:C}},{onSuccess:()=>{m(t),setTimeout(()=>m(null),1e3)}})},300)},[n,l]),j=a.useCallback((t,C)=>{b(S=>({...S,[t]:C})),l({camId:n,changes:{[t]:C}},{onSuccess:()=>{m(t),setTimeout(()=>m(null),1e3)}})},[n,l]),x=Number(r("width",640)),p=Number(r("height",480)),N=Number(r("threshold",1500)),w=K(N,x,p),T=a.useCallback(t=>{const C=$(t,x,p);i("threshold",C)},[x,p,i]);return e.jsxs("div",{className:"space-y-2",children:[e.jsx(J,{cameraId:n,readOnly:!0}),e.jsxs(L,{title:"Stream",defaultOpen:!1,children:[e.jsx(y,{label:"Quality",value:Number(r("stream_quality",50)),onChange:t=>i("stream_quality",t),min:1,max:100,unit:"%",helpText:"JPEG compression quality"}),e.jsx(y,{label:"Max Framerate",value:Number(r("stream_maxrate",15)),onChange:t=>i("stream_maxrate",t),min:1,max:30,unit:" fps",helpText:"Maximum stream framerate"})]}),e.jsxs(L,{title:"Image",defaultOpen:!1,children:[e.jsx(y,{label:"Brightness",value:Number(r("libcam_brightness",0)),onChange:t=>i("libcam_brightness",t),min:-1,max:1,step:.1,helpText:"Brightness adjustment"}),e.jsx(y,{label:"Contrast",value:Number(r("libcam_contrast",1)),onChange:t=>i("libcam_contrast",t),min:0,max:32,step:.5,helpText:"Contrast adjustment"}),e.jsx(y,{label:"Gain (ISO)",value:Number(r("libcam_gain",1)),onChange:t=>i("libcam_gain",t),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)"}),e.jsx(M,{label:"Auto White Balance",value:!!r("libcam_awb_enable",!0),onChange:t=>j("libcam_awb_enable",t),helpText:"Enable automatic white balance"}),!!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[e.jsx(E,{label:"AWB Mode",value:String(r("libcam_awb_mode",0)),onChange:t=>j("libcam_awb_mode",Number(t)),options:X.map(t=>({value:String(t.value),label:t.label})),helpText:"White balance mode"}),h?.AwbLocked!==!1&&e.jsx(M,{label:"Lock AWB",value:!!r("libcam_awb_locked",!1),onChange:t=>j("libcam_awb_locked",t),helpText:"Lock white balance settings"})]}),!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[h?.ColourTemperature!==!1&&e.jsx(y,{label:"Color Temperature",value:Number(r("libcam_colour_temp",0)),onChange:t=>i("libcam_colour_temp",t),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)"}),e.jsx(y,{label:"Red Gain",value:Number(r("libcam_colour_gain_r",1)),onChange:t=>i("libcam_colour_gain_r",t),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)"}),e.jsx(y,{label:"Blue Gain",value:Number(r("libcam_colour_gain_b",1)),onChange:t=>i("libcam_colour_gain_b",t),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)"}),h?.ColourTemperature===!1&&e.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[e.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),h?.AfMode&&e.jsxs(e.Fragment,{children:[e.jsx(E,{label:"Autofocus Mode",value:String(r("libcam_af_mode",0)),onChange:t=>j("libcam_af_mode",Number(t)),options:Z.map(t=>({value:String(t.value),label:t.label})),helpText:"Focus control mode"}),Number(r("libcam_af_mode",0))===0&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>i("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),!h?.AfMode&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>i("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),e.jsxs(L,{title:"Detection",defaultOpen:!1,children:[e.jsx(y,{label:"Threshold",value:w,onChange:T,min:0,max:20,step:.1,unit:"%",helpText:"Motion sensitivity (higher = less sensitive)"}),e.jsx(y,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:t=>i("noise_level",t),min:1,max:255,helpText:"Noise tolerance"}),e.jsx(M,{label:"Auto-tune Noise",value:!!r("noise_tune",!1),onChange:t=>j("noise_tune",t),helpText:"Automatically adjust noise level"})]}),c&&e.jsx("div",{className:"text-center text-sm text-gray-400 py-2",children:"Applying..."}),d&&!c&&e.jsx("div",{className:"text-center text-sm text-green-400 py-2",children:"Applied!"})]})}function R({cameraId:n}){const[o,l]=a.useState(!1),c=d=>{d.stopPropagation();const m=document.querySelector(`[data-camera-id="${n}"]`);m&&(o?document.exitFullscreen&&document.exitFullscreen():m.requestFullscreen&&m.requestFullscreen())};return a.useEffect(()=>{const d=()=>{l(!!document.fullscreenElement)};return document.addEventListener("fullscreenchange",d),()=>{document.removeEventListener("fullscreenchange",d)}},[]),e.jsx("button",{type:"button",onClick:c,className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Toggle fullscreen",title:"Toggle fullscreen",children:e.jsx("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:o?e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"}):e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"})})})}function D({cameraId:n,onClick:o}){return e.jsx("button",{type:"button",onClick:l=>{l.stopPropagation(),o(n)},className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Quick settings",title:"Quick settings",children:e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function I({cameraId:n}){const[o,l]=a.useState(!1),{addToast:c}=O(),d=async m=>{if(m.stopPropagation(),!o){l(!0);try{await Y(n),c("Snapshot captured","success")}catch(g){c(g instanceof Error?g.message:"Failed to capture snapshot","error")}finally{l(!1)}}};return e.jsx("button",{type:"button",onClick:d,disabled:o,className:"p-1.5 hover:bg-surface rounded-full transition-colors disabled:opacity-50","aria-label":"Take snapshot",title:"Take snapshot",children:o?e.jsxs("svg",{className:"w-5 h-5 text-gray-400 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}):e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 13a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function te({cameras:n,selectedId:o,onSelect:l}){return n.length<=1?null:e.jsx("select",{value:o??"",onChange:c=>l(Number(c.target.value)),className:`px-3 py-1.5 bg-surface-elevated border border-gray-700 + rounded-lg text-sm focus:border-primary focus:ring-1 + focus:ring-primary cursor-pointer`,"aria-label":"Select camera",children:n.map(c=>e.jsx("option",{value:c.id,children:c.name},c.id))})}function ne(){const{data:n,isLoading:o,error:l}=q(),{role:c,isAuthenticated:d,authRequired:m}=Q(),{data:g}=G({enabled:!m||d}),[b,v]=a.useState(!1),[h,r]=a.useState(null),[i,j]=a.useState({}),x=s=>g?.find(f=>f.id===s)?.fps??0,p=s=>i[s]??0,N=s=>f=>{j(k=>({...k,[s]:f}))},{data:w}=H({queryKey:["config"],queryFn:async()=>{const s=await z("/0/api/config");return s.csrf_token&&V(s.csrf_token),s},enabled:b,staleTime:3e4}),T=s=>{r(s),v(!0)},t=()=>{v(!1)},C=n&&n.length>1?e.jsx(te,{cameras:n,selectedId:h,onSelect:r}):null,S=a.useMemo(()=>{if(!w||!h)return{};const s=w.configuration?.default||{},f=w.configuration?.[`cam${h}`]||{};return{...s,...f}},[w,h]);if(o)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsx("div",{className:"flex flex-col items-center gap-6",children:[1].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 animate-pulse w-full max-w-4xl",children:[e.jsx("div",{className:"h-6 bg-surface rounded w-1/3 mb-4"}),e.jsx("div",{className:"aspect-video bg-surface rounded"})]},s))})]});if(l)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-danger/10 border border-danger rounded-lg p-4 max-w-2xl mx-auto",children:[e.jsxs("p",{className:"text-danger",children:["Failed to load cameras: ",l instanceof Error?l.message:"Unknown error"]}),e.jsx("button",{className:"mt-2 text-sm text-primary hover:underline",onClick:()=>window.location.reload(),children:"Retry"})]})]});if(!n||n.length===0)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[e.jsx("svg",{className:"w-16 h-16 mx-auto text-gray-600 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"})}),e.jsx("p",{className:"text-gray-400 text-lg",children:"No cameras configured"}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:"Add cameras in Motion's configuration file"})]})]});const u=n.length;if(u===1){const s=n[0],f=x(s.id),k=p(s.id);return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("div",{className:"max-w-5xl mx-auto",children:e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(k>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[k," / ",f," fps"]}),c==="admin"&&e.jsx(I,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),c==="admin"&&e.jsx(D,{cameraId:s.id,onClick:T})]})]}),e.jsx(F,{cameraId:s.id,onStreamFpsChange:N(s.id)})]})}),e.jsx(B,{isOpen:b,onClose:t,title:"Quick Settings",headerRight:C,children:h&&e.jsx(A,{cameraId:h,config:S})})]})}const _=()=>u===2?"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6":u<=4?"grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6":"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6";return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsxs("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:["Cameras (",u,")"]}),e.jsx("div",{className:_(),children:n.map(s=>{const f=x(s.id),k=p(s.id);return e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium text-sm sm:text-base",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(k>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[k," / ",f," fps"]}),c==="admin"&&e.jsx(I,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),c==="admin"&&e.jsx(D,{cameraId:s.id,onClick:T})]})]}),e.jsx(F,{cameraId:s.id,onStreamFpsChange:N(s.id)})]},s.id)})}),e.jsx(B,{isOpen:b,onClose:t,title:"Quick Settings",headerRight:C,children:h&&e.jsx(A,{cameraId:h,config:S})})]})}export{ne as Dashboard}; diff --git a/data/webui/assets/Dashboard-D7oUR9hz.js b/data/webui/assets/Dashboard-D7oUR9hz.js deleted file mode 100644 index 1c47508c..00000000 --- a/data/webui/assets/Dashboard-D7oUR9hz.js +++ /dev/null @@ -1 +0,0 @@ -import{r as a,j as e,u as W,a as O,t as Y,b as q,c as Q,d as G,e as H,f as z,g as V}from"./index-tiawrtsp.js";import{u as U,p as $,a as K,C as J,F as y,b as L,c as F,A as X,d as Z,e as B}from"./parameterMappings-BmLxmuw_.js";function ee({isOpen:n,onClose:i,sheetRef:o,closeThreshold:m=.3}){const[c,d]=a.useState(!1),[g,b]=a.useState(0),v=a.useRef(0),h=a.useRef(0),r=a.useRef(0),l=a.useRef(0),j=a.useRef(0),x=a.useCallback(u=>{d(!0),v.current=u,h.current=u,j.current=u,l.current=Date.now(),r.current=0},[]),p=a.useCallback(u=>{if(!c)return;const k=Date.now(),T=k-l.current,s=u-j.current;T>0&&(r.current=s/T),l.current=k,j.current=u,h.current=u;const f=Math.max(0,u-v.current);b(f)},[c]),N=a.useCallback(()=>{if(!c)return;d(!1);const u=o.current?.offsetHeight||400;(g>u*m||r.current>.5)&&i(),b(0),r.current=0},[c,g,i,m,o]),w=a.useCallback(u=>{const k=u.touches[0],T=o.current?.getBoundingClientRect().top||0;k.clientY-T<=60&&x(k.clientY)},[x,o]),M=a.useCallback(u=>{c&&(u.preventDefault(),p(u.touches[0].clientY))},[c,p]),t=a.useCallback(()=>{N()},[N]),C=a.useCallback(u=>{const k=o.current?.getBoundingClientRect().top||0;if(u.clientY-k<=60){x(u.clientY);const s=_=>{p(_.clientY)},f=()=>{N(),document.removeEventListener("mousemove",s),document.removeEventListener("mouseup",f)};document.addEventListener("mousemove",s),document.addEventListener("mouseup",f)}},[x,p,N,o]),S=n?c?`translateY(${g}px)`:"translateY(0)":"translateY(100%)";return{handlers:{onTouchStart:w,onTouchMove:M,onTouchEnd:t,onMouseDown:C},style:{transform:S},state:{isDragging:c,dragOffset:g}}}function A({isOpen:n,onClose:i,title:o,children:m,headerRight:c}){const d=a.useRef(null),g=a.useRef(null),b=a.useRef(null),{handlers:v,style:h}=ee({isOpen:n,onClose:i,sheetRef:d});a.useEffect(()=>{if(!n)return;const l=x=>{x.key==="Escape"&&i()},j=x=>{if(x.key!=="Tab"||!d.current)return;const p=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),N=p[0],w=p[p.length-1];N&&(x.shiftKey?document.activeElement===N&&(x.preventDefault(),w?.focus()):document.activeElement===w&&(x.preventDefault(),N?.focus()))};return document.addEventListener("keydown",l),document.addEventListener("keydown",j),()=>{document.removeEventListener("keydown",l),document.removeEventListener("keydown",j)}},[n,i]),a.useEffect(()=>(n?document.body.style.overflow="hidden":document.body.style.overflow="",()=>{document.body.style.overflow=""}),[n]),a.useEffect(()=>{n?(b.current=document.activeElement,setTimeout(()=>{if(d.current){const l=d.current.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');l.length>0&&l[0].focus()}},100)):b.current&&b.current.focus()},[n]);const r=a.useCallback(l=>{l.target===l.currentTarget&&i()},[i]);return n?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"fixed inset-0 z-[100]",onClick:r,"aria-hidden":"true"}),e.jsxs("div",{ref:d,className:"fixed bottom-0 left-0 right-0 z-[101] bg-surface/95 backdrop-blur-sm rounded-t-2xl shadow-2xl transition-transform duration-300 ease-out border-t border-surface-elevated",style:{maxHeight:"45vh",transform:h.transform},role:"dialog","aria-modal":"true","aria-label":o,...v,children:[e.jsx("div",{className:"flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none",children:e.jsx("div",{className:"w-12 h-1.5 bg-gray-500 rounded-full"})}),e.jsxs("div",{className:"flex items-center justify-between px-4 pb-3 border-b border-surface-elevated",children:[e.jsx("h2",{className:"text-lg font-semibold",children:o}),e.jsxs("div",{className:"flex items-center gap-3",children:[c,e.jsx("button",{type:"button",onClick:i,className:"p-2 hover:bg-surface-elevated rounded-full transition-colors","aria-label":"Close",children:e.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})]}),e.jsx("div",{ref:g,className:"overflow-y-auto overscroll-contain px-4 py-4",style:{maxHeight:"calc(45vh - 80px)"},children:m})]})]}):null}function E({title:n,defaultOpen:i=!0,children:o}){const[m,c]=a.useState(i);return e.jsxs("div",{className:"mb-4",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full py-2 text-left",onClick:()=>c(!m),children:[e.jsx("span",{className:"font-medium text-sm",children:n}),e.jsx("svg",{className:`w-4 h-4 transition-transform ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&e.jsx("div",{className:"pt-2",children:o})]})}function D({cameraId:n,config:i}){const{mutate:o,isPending:m}=W(),[c,d]=a.useState(null),[g,b]=a.useState({}),v=a.useRef({}),{data:h}=U(n);a.useEffect(()=>()=>{Object.values(v.current).forEach(clearTimeout)},[]),a.useEffect(()=>{b({})},[i]);const r=a.useCallback((t,C="")=>t in g?g[t]:i[t]?.value??C,[i,g]);a.useEffect(()=>{Object.values(v.current).forEach(clearTimeout),v.current={}},[n]);const l=a.useCallback((t,C)=>{b(u=>({...u,[t]:C})),v.current[t]&&clearTimeout(v.current[t]);const S=n;v.current[t]=setTimeout(()=>{o({camId:S,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},300)},[n,o]),j=a.useCallback((t,C)=>{b(S=>({...S,[t]:C})),o({camId:n,changes:{[t]:C}},{onSuccess:()=>{d(t),setTimeout(()=>d(null),1e3)}})},[n,o]),x=Number(r("width",640)),p=Number(r("height",480)),N=Number(r("threshold",1500)),w=$(N,x,p),M=a.useCallback(t=>{const C=K(t,x,p);l("threshold",C)},[x,p,l]);return e.jsxs("div",{className:"space-y-2",children:[e.jsx(J,{cameraId:n,readOnly:!0}),e.jsxs(E,{title:"Stream",defaultOpen:!1,children:[e.jsx(y,{label:"Quality",value:Number(r("stream_quality",50)),onChange:t=>l("stream_quality",t),min:1,max:100,unit:"%",helpText:"JPEG compression quality"}),e.jsx(y,{label:"Max Framerate",value:Number(r("stream_maxrate",15)),onChange:t=>l("stream_maxrate",t),min:1,max:30,unit:" fps",helpText:"Maximum stream framerate"})]}),e.jsxs(E,{title:"Image",defaultOpen:!1,children:[e.jsx(y,{label:"Brightness",value:Number(r("libcam_brightness",0)),onChange:t=>l("libcam_brightness",t),min:-1,max:1,step:.1,helpText:"Brightness adjustment"}),e.jsx(y,{label:"Contrast",value:Number(r("libcam_contrast",1)),onChange:t=>l("libcam_contrast",t),min:0,max:32,step:.5,helpText:"Contrast adjustment"}),e.jsx(y,{label:"Gain (ISO)",value:Number(r("libcam_gain",1)),onChange:t=>l("libcam_gain",t),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)"}),e.jsx(L,{label:"Auto White Balance",value:!!r("libcam_awb_enable",!0),onChange:t=>j("libcam_awb_enable",t),helpText:"Enable automatic white balance"}),!!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"AWB Mode",value:String(r("libcam_awb_mode",0)),onChange:t=>j("libcam_awb_mode",Number(t)),options:X.map(t=>({value:String(t.value),label:t.label})),helpText:"White balance mode"}),h?.AwbLocked!==!1&&e.jsx(L,{label:"Lock AWB",value:!!r("libcam_awb_locked",!1),onChange:t=>j("libcam_awb_locked",t),helpText:"Lock white balance settings"})]}),!r("libcam_awb_enable",!0)&&e.jsxs(e.Fragment,{children:[h?.ColourTemperature!==!1&&e.jsx(y,{label:"Color Temperature",value:Number(r("libcam_colour_temp",0)),onChange:t=>l("libcam_colour_temp",t),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)"}),e.jsx(y,{label:"Red Gain",value:Number(r("libcam_colour_gain_r",1)),onChange:t=>l("libcam_colour_gain_r",t),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)"}),e.jsx(y,{label:"Blue Gain",value:Number(r("libcam_colour_gain_b",1)),onChange:t=>l("libcam_colour_gain_b",t),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)"}),h?.ColourTemperature===!1&&e.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[e.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),h?.AfMode&&e.jsxs(e.Fragment,{children:[e.jsx(F,{label:"Autofocus Mode",value:String(r("libcam_af_mode",0)),onChange:t=>j("libcam_af_mode",Number(t)),options:Z.map(t=>({value:String(t.value),label:t.label})),helpText:"Focus control mode"}),Number(r("libcam_af_mode",0))===0&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),!h?.AfMode&&h?.LensPosition&&e.jsx(y,{label:"Lens Position",value:Number(r("libcam_lens_position",0)),onChange:t=>l("libcam_lens_position",t),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)"})]}),e.jsxs(E,{title:"Detection",defaultOpen:!1,children:[e.jsx(y,{label:"Threshold",value:w,onChange:M,min:0,max:20,step:.1,unit:"%",helpText:"Motion sensitivity (higher = less sensitive)"}),e.jsx(y,{label:"Noise Level",value:Number(r("noise_level",32)),onChange:t=>l("noise_level",t),min:1,max:255,helpText:"Noise tolerance"}),e.jsx(L,{label:"Auto-tune Noise",value:!!r("noise_tune",!1),onChange:t=>j("noise_tune",t),helpText:"Automatically adjust noise level"})]}),m&&e.jsx("div",{className:"text-center text-sm text-gray-400 py-2",children:"Applying..."}),c&&!m&&e.jsx("div",{className:"text-center text-sm text-green-400 py-2",children:"Applied!"})]})}function R({cameraId:n}){const[i,o]=a.useState(!1),m=c=>{c.stopPropagation();const d=document.querySelector(`[data-camera-id="${n}"]`);d&&(i?document.exitFullscreen&&document.exitFullscreen():d.requestFullscreen&&d.requestFullscreen())};return a.useEffect(()=>{const c=()=>{o(!!document.fullscreenElement)};return document.addEventListener("fullscreenchange",c),()=>{document.removeEventListener("fullscreenchange",c)}},[]),e.jsx("button",{type:"button",onClick:m,className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Toggle fullscreen",title:"Toggle fullscreen",children:e.jsx("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:i?e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"}):e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"})})})}function I({cameraId:n,onClick:i}){return e.jsx("button",{type:"button",onClick:o=>{o.stopPropagation(),i(n)},className:"p-1.5 hover:bg-surface rounded-full transition-colors","aria-label":"Quick settings",title:"Quick settings",children:e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function P({cameraId:n}){const[i,o]=a.useState(!1),{addToast:m}=O(),c=async d=>{if(d.stopPropagation(),!i){o(!0);try{await Y(n),m("Snapshot captured","success")}catch(g){m(g instanceof Error?g.message:"Failed to capture snapshot","error")}finally{o(!1)}}};return e.jsx("button",{type:"button",onClick:c,disabled:i,className:"p-1.5 hover:bg-surface rounded-full transition-colors disabled:opacity-50","aria-label":"Take snapshot",title:"Take snapshot",children:i?e.jsxs("svg",{className:"w-5 h-5 text-gray-400 animate-spin",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}):e.jsxs("svg",{className:"w-5 h-5 text-gray-400 hover:text-gray-200",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 13a3 3 0 11-6 0 3 3 0 016 0z"})]})})}function ae(){const{data:n,isLoading:i,error:o}=q(),{role:m,isAuthenticated:c,authRequired:d}=Q(),{data:g}=G({enabled:!d||c}),[b,v]=a.useState(!1),[h,r]=a.useState(null),[l,j]=a.useState({}),x=s=>g?.find(f=>f.id===s)?.fps??0,p=s=>l[s]??0,N=s=>f=>{j(_=>({..._,[s]:f}))},{data:w}=H({queryKey:["config"],queryFn:async()=>{const s=await z("/0/api/config");return s.csrf_token&&V(s.csrf_token),s},enabled:b,staleTime:3e4}),M=s=>{r(s),v(!0)},t=()=>{v(!1)},C=n?.find(s=>s.id===h),S=C?`Quick Settings - ${C.name}`:"Quick Settings",u=a.useMemo(()=>{if(!w||!h)return{};const s=w.configuration?.default||{},f=w.configuration?.[`cam${h}`]||{};return{...s,...f}},[w,h]);if(i)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsx("div",{className:"flex flex-col items-center gap-6",children:[1].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 animate-pulse w-full max-w-4xl",children:[e.jsx("div",{className:"h-6 bg-surface rounded w-1/3 mb-4"}),e.jsx("div",{className:"aspect-video bg-surface rounded"})]},s))})]});if(o)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-danger/10 border border-danger rounded-lg p-4 max-w-2xl mx-auto",children:[e.jsxs("p",{className:"text-danger",children:["Failed to load cameras: ",o instanceof Error?o.message:"Unknown error"]}),e.jsx("button",{className:"mt-2 text-sm text-primary hover:underline",onClick:()=>window.location.reload(),children:"Retry"})]})]});if(!n||n.length===0)return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:"Camera Dashboard"}),e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[e.jsx("svg",{className:"w-16 h-16 mx-auto text-gray-600 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"})}),e.jsx("p",{className:"text-gray-400 text-lg",children:"No cameras configured"}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:"Add cameras in Motion's configuration file"})]})]});const k=n.length;if(k===1){const s=n[0],f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsx("div",{className:"max-w-5xl mx-auto",children:e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}const T=()=>k===2?"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6":k<=4?"grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6":"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6";return e.jsxs("div",{className:"p-4 sm:p-6",children:[e.jsxs("h2",{className:"text-2xl sm:text-3xl font-bold mb-4 sm:mb-6",children:["Cameras (",k,")"]}),e.jsx("div",{className:T(),children:n.map(s=>{const f=x(s.id),_=p(s.id);return e.jsxs("div",{className:"bg-surface-elevated rounded-lg overflow-hidden shadow-lg","data-camera-id":s.id,children:[e.jsxs("div",{className:"px-4 py-3 border-b border-surface flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2 h-2 bg-green-500 rounded-full animate-pulse"}),e.jsx("h3",{className:"font-medium text-sm sm:text-base",children:s.name})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[s.width&&s.height&&e.jsxs("span",{className:"text-xs text-gray-500",children:[s.width,"x",s.height]}),(_>0||f>0)&&e.jsxs("span",{className:"text-xs font-mono text-gray-400 cursor-help",title:"streaming / capture frame rate",children:[_," / ",f," fps"]}),m==="admin"&&e.jsx(P,{cameraId:s.id}),e.jsx(R,{cameraId:s.id}),m==="admin"&&e.jsx(I,{cameraId:s.id,onClick:M})]})]}),e.jsx(B,{cameraId:s.id,onStreamFpsChange:N(s.id)})]},s.id)})}),e.jsx(A,{isOpen:b,onClose:t,title:S,children:h&&e.jsx(D,{cameraId:h,config:u})})]})}export{ae as Dashboard}; diff --git a/data/webui/assets/Media-4gl6Y8Bi.js b/data/webui/assets/Media-Cj2hjH__.js similarity index 91% rename from data/webui/assets/Media-4gl6Y8Bi.js rename to data/webui/assets/Media-Cj2hjH__.js index b7990dce..aaf18935 100644 --- a/data/webui/assets/Media-4gl6Y8Bi.js +++ b/data/webui/assets/Media-Cj2hjH__.js @@ -1 +1 @@ -import{j as e,a as ie,c as ne,k as re,r as n,q as R,b as oe,s as ce,v as de,w as ue,x as me,y as xe,z as pe,m as he}from"./index-tiawrtsp.js";function O({offset:a,limit:c,total:r,onPageChange:f,context:l}){const v=Math.floor(a/c)+1,i=Math.ceil(r/c),b=r===0?0:a+1,t=Math.min(a+c,r),x=a>0,d=a+c1&&e.jsxs("div",{className:"flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>f(Math.max(0,a-c)),disabled:!x,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Previous page",children:"◀ Previous"}),e.jsxs("span",{className:"px-3 py-1 text-sm text-gray-400",children:["Page ",v," of ",i]}),e.jsx("button",{onClick:()=>f(a+c),disabled:!d,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Next page",children:"Next ▶"})]})]})}function k(a){const c=he();if(!c)return a;const r=a.includes("?")?"&":"?";return`${a}${r}token=${encodeURIComponent(c)}`}const g=100;function fe(a,c){if(!a||a.length!==8)return null;const r=parseInt(a.substring(0,4),10),f=parseInt(a.substring(4,6),10)-1,l=parseInt(a.substring(6,8),10);if(isNaN(r)||isNaN(f)||isNaN(l))return null;let v=0,i=0,b=0;if(c){const x=c.split(":");x.length>=2&&(v=parseInt(x[0],10)||0,i=parseInt(x[1],10)||0,b=parseInt(x[2],10)||0)}const t=new Date(r,f,l,v,i,b);return isNaN(t.getTime())?null:t}function be(){const{addToast:a}=ie(),{role:c}=ne(),r=re(),f=c==="admin",[l,v]=n.useState(1),[i,b]=n.useState("pictures"),[t,x]=n.useState("all"),[d,P]=n.useState(""),[Y,y]=n.useState(0),[o,w]=n.useState(null),[p,C]=n.useState(null),[j,A]=n.useState(null),N=Y*g;n.useEffect(()=>{y(0)},[l,i,d]),n.useEffect(()=>{t==="all"&&P("")},[t]),n.useEffect(()=>{t==="all"&&(r.invalidateQueries({queryKey:R.movies(l)}),r.invalidateQueries({queryKey:R.pictures(l)}))},[t,l,r]),n.useEffect(()=>{t==="folders"&&r.invalidateQueries({predicate:s=>Array.isArray(s.queryKey)&&s.queryKey[0]==="media-folders"&&s.queryKey[1]===l})},[t,d,l,r]);const{data:Z}=oe(),{data:B,isLoading:J}=ce(l,N,g,null,{enabled:t==="all"&&i==="pictures"}),{data:E,isLoading:X}=de(l,N,g,null,{enabled:t==="all"&&i==="movies"}),{data:u,isLoading:ee}=ue(l,d,N,g,{enabled:t==="folders"}),$=me(),L=xe(),F=pe(),T=t==="folders"?ee:i==="pictures"?J:X,q=t==="folders"?u?.files??[]:i==="pictures"?B?.pictures??[]:E?.movies??[],D=t==="folders"?u?.total_files??0:i==="pictures"?B?.total_count??0:E?.total_count??0,I=s=>s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:s<1024*1024*1024?`${(s/(1024*1024)).toFixed(1)} MB`:`${(s/(1024*1024*1024)).toFixed(1)} GB`,K=(s,m)=>{const h=fe(s,m);return h?h.toLocaleDateString()+" "+h.toLocaleTimeString():"Unknown date"},se=n.useCallback((s,m)=>{m.stopPropagation(),C(s)},[]),te=n.useCallback(async()=>{if(p)try{const s="type"in p?p.type:i;s==="picture"||s==="pictures"?await $.mutateAsync({camId:l,pictureId:p.id}):await L.mutateAsync({camId:l,movieId:p.id}),a(`${s==="picture"||s==="pictures"?"Picture":"Movie"} deleted`,"success"),C(null),o?.id===p.id&&w(null)}catch{a("Failed to delete file","error")}},[p,i,l,$,L,a,o]),V=n.useCallback(()=>{C(null)},[]),W=n.useCallback((s,m)=>{A({path:s,fileCount:m})},[]),ae=n.useCallback(async()=>{if(j)try{const s=await F.mutateAsync({camId:l,path:j.path});a(`Deleted ${s.deleted.movies} movies, ${s.deleted.pictures} pictures`,"success"),A(null)}catch{a("Failed to delete folder contents","error")}},[j,l,F,a]),U=n.useCallback(()=>{A(null)},[]),z=n.useCallback(s=>{P(s),y(0)},[]),le=n.useCallback(()=>{u?.parent!==null&&(P(u?.parent??""),y(0))},[u]),S=$.isPending||L.isPending,_=F.isPending,G=d?d.split("/"):[];return e.jsxs("div",{className:"p-6",children:[e.jsx("div",{className:"flex items-center justify-between mb-6",children:e.jsx("h2",{className:"text-3xl font-bold",children:"Media"})}),e.jsxs("div",{className:"flex flex-wrap gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"camera-select",className:"block text-sm font-medium mb-2",children:"Camera"}),e.jsx("select",{id:"camera-select",value:l,onChange:s=>v(parseInt(s.target.value)),className:"px-3 py-2 bg-surface border border-surface-elevated rounded-lg",children:Z?.map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"Type"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>b("pictures"),className:`px-4 py-2 rounded-lg transition-colors ${i==="pictures"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Pictures"}),e.jsx("button",{onClick:()=>b("movies"),className:`px-4 py-2 rounded-lg transition-colors ${i==="movies"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Movies"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"View"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>x("all"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="all"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"All"}),e.jsx("button",{onClick:()=>x("folders"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="folders"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Folders"})]})]})]}),t==="folders"&&e.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3",children:[e.jsx("svg",{className:"w-5 h-5 text-gray-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"})}),e.jsx("span",{className:"text-sm font-medium text-gray-300",children:"Browse Folders"})]}),e.jsxs("div",{className:"flex items-center gap-1 text-sm flex-wrap",children:[e.jsx("button",{onClick:()=>z(""),className:"hover:text-primary px-1",children:"Root"}),G.map((s,m)=>{const h=G.slice(0,m+1).join("/");return e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("button",{onClick:()=>z(h),className:"hover:text-primary px-1",children:s})]},m)})]}),u?.parent!==null&&e.jsxs("button",{onClick:le,className:"mt-2 flex items-center gap-2 text-sm text-gray-400 hover:text-white",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 19l-7-7 7-7"})}),"Up to parent folder"]})]}),t==="folders"&&u&&u.folders.length>0&&e.jsx("div",{className:"mb-6 grid gap-2 md:grid-cols-2 lg:grid-cols-4",children:u.folders.map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 hover:ring-2 hover:ring-primary cursor-pointer transition-all group",children:[e.jsx("button",{onClick:()=>z(s.path),className:"w-full text-left",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("svg",{className:"w-8 h-8 text-yellow-500 flex-shrink-0",fill:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{d:"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"font-medium truncate",children:s.name}),e.jsxs("p",{className:"text-xs text-gray-400",children:[s.file_count," files - ",I(s.total_size)]})]})]})}),f&&s.file_count>0&&e.jsx("button",{onClick:m=>{m.stopPropagation(),W(s.path,s.file_count)},className:"mt-2 w-full px-2 py-1 text-xs bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded opacity-0 group-hover:opacity-100 transition-opacity",title:`Delete all ${s.file_count} media files in this folder`,children:"Delete All Media"})]},s.path))}),t==="folders"&&f&&d&&u&&u.total_files>0&&e.jsx("div",{className:"mb-4 flex justify-end",children:e.jsxs("button",{onClick:()=>W(d,u.total_files),className:"px-3 py-1.5 text-sm bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded-lg flex items-center gap-2",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})}),"Delete All Media in This Folder (",u.total_files,")"]})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),T?e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:[1,2,3,4,5,6,7,8].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg animate-pulse",children:[e.jsx("div",{className:"aspect-video bg-surface rounded-t-lg"}),e.jsxs("div",{className:"p-3",children:[e.jsx("div",{className:"h-4 bg-surface rounded w-3/4 mb-2"}),e.jsx("div",{className:"h-3 bg-surface rounded w-1/2"})]})]},s))}):q.length===0?e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center",children:[e.jsx("p",{className:"text-gray-400",children:t==="folders"?"No media files in this folder":`No ${i} found`}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:t==="folders"?"Navigate to a folder with media files":i==="pictures"?"Motion detection snapshots will appear here":"Recorded videos will appear here"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:q.map(s=>{const m="type"in s?s.type:i==="pictures"?"picture":"movie",h=s.thumbnail||void 0;return e.jsxs("button",{className:"bg-surface-elevated rounded-lg overflow-hidden cursor-pointer hover:ring-2 hover:ring-primary focus:ring-2 focus:ring-primary focus:outline-none transition-all group relative text-left w-full",onClick:()=>w(s),"aria-label":`View ${s.filename}`,children:[e.jsx("button",{onClick:M=>se(s,M),className:"absolute top-2 right-2 z-10 p-1.5 bg-red-600/80 hover:bg-red-600 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity",title:"Delete","aria-label":`Delete ${s.filename}`,children:e.jsx("svg",{className:"w-4 h-4 text-white",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})})}),e.jsxs("div",{className:"aspect-video bg-surface flex items-center justify-center relative overflow-hidden",children:[m==="picture"?e.jsx("img",{src:k(s.path),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy"}):h?e.jsx("img",{src:k(h),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy",onError:M=>{M.currentTarget.style.display="none";const H=M.currentTarget.parentElement;if(H){const Q=H.querySelector(".fallback-icon");Q&&(Q.style.display="flex")}}}):null,e.jsx("div",{className:`fallback-icon text-gray-400 absolute inset-0 flex items-center justify-center ${h?"hidden":""}`,children:e.jsxs("svg",{className:"w-16 h-16",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]})})]}),e.jsxs("div",{className:"p-3",children:[e.jsx("p",{className:"text-sm font-medium truncate",children:s.filename}),e.jsxs("div",{className:"flex justify-between text-xs text-gray-400 mt-1",children:[e.jsx("span",{children:I(s.size)}),e.jsx("span",{children:s.date?K(s.date,s.time):""})]})]})]},`${t}-${d}-${s.id}`)})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4",onClick:()=>w(null),children:e.jsxs("div",{className:"max-w-6xl w-full bg-surface-elevated rounded-lg overflow-hidden",onClick:s=>s.stopPropagation(),children:[e.jsxs("div",{className:"p-4 border-b border-surface flex justify-between items-center",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:o.filename}),e.jsxs("p",{className:"text-sm text-gray-400",children:[I(o.size)," ",o.date&&`- ${K(o.date,o.time)}`]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("a",{href:k(o.path),download:o.filename,className:"px-3 py-1 bg-primary hover:bg-primary-hover rounded text-sm",onClick:s=>s.stopPropagation(),children:"Download"}),e.jsx("button",{onClick:s=>{s.stopPropagation(),C(o)},className:"px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm",children:"Delete"}),e.jsx("button",{onClick:()=>w(null),className:"px-3 py-1 bg-surface hover:bg-surface-elevated rounded text-sm",children:"Close"})]})]}),e.jsx("div",{className:"p-4",children:("type"in o?o.type:i)==="picture"?e.jsx("img",{src:k(o.path),alt:o.filename,className:"w-full h-auto max-h-[70vh] object-contain"}):e.jsx("video",{src:k(o.path),controls:!0,className:"w-full h-auto max-h-[70vh]",autoPlay:!0,children:"Your browser does not support video playback."})})]})}),p&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:V,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2",children:"Delete File?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsx("span",{className:"font-medium text-white",children:p.filename}),"? This action cannot be undone."]}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:V,disabled:S,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:te,disabled:S,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:S?"Deleting...":"Delete"})]})]})})}),j&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:U,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2 text-red-400",children:"Delete All Media Files?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsxs("span",{className:"font-medium text-white",children:["all ",j.fileCount," media files"]})," in folder ",e.jsx("span",{className:"font-mono text-primary",children:j.path||"root"}),"?"]}),e.jsx("p",{className:"text-sm text-yellow-500 mb-4",children:"This will delete movies, pictures, and their thumbnails. Subfolders will NOT be deleted. This action cannot be undone."}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:U,disabled:_,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:ae,disabled:_,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:_?"Deleting...":"Delete All"})]})]})})})]})}export{be as Media}; +import{j as e,a as ie,c as ne,p as re,r as n,y as R,b as oe,z as ce,A as de,B as ue,C as me,D as xe,E as pe,s as he}from"./index-DEo73YRp.js";function O({offset:a,limit:c,total:r,onPageChange:f,context:l}){const v=Math.floor(a/c)+1,i=Math.ceil(r/c),b=r===0?0:a+1,t=Math.min(a+c,r),x=a>0,d=a+c1&&e.jsxs("div",{className:"flex gap-2 items-center",children:[e.jsx("button",{onClick:()=>f(Math.max(0,a-c)),disabled:!x,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Previous page",children:"◀ Previous"}),e.jsxs("span",{className:"px-3 py-1 text-sm text-gray-400",children:["Page ",v," of ",i]}),e.jsx("button",{onClick:()=>f(a+c),disabled:!d,className:"px-3 py-1 bg-surface-elevated hover:bg-surface rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm transition-colors","aria-label":"Next page",children:"Next ▶"})]})]})}function k(a){const c=he();if(!c)return a;const r=a.includes("?")?"&":"?";return`${a}${r}token=${encodeURIComponent(c)}`}const g=100;function fe(a,c){if(!a||a.length!==8)return null;const r=parseInt(a.substring(0,4),10),f=parseInt(a.substring(4,6),10)-1,l=parseInt(a.substring(6,8),10);if(isNaN(r)||isNaN(f)||isNaN(l))return null;let v=0,i=0,b=0;if(c){const x=c.split(":");x.length>=2&&(v=parseInt(x[0],10)||0,i=parseInt(x[1],10)||0,b=parseInt(x[2],10)||0)}const t=new Date(r,f,l,v,i,b);return isNaN(t.getTime())?null:t}function be(){const{addToast:a}=ie(),{role:c}=ne(),r=re(),f=c==="admin",[l,v]=n.useState(1),[i,b]=n.useState("pictures"),[t,x]=n.useState("all"),[d,P]=n.useState(""),[Y,y]=n.useState(0),[o,w]=n.useState(null),[p,C]=n.useState(null),[j,A]=n.useState(null),N=Y*g;n.useEffect(()=>{y(0)},[l,i,d]),n.useEffect(()=>{t==="all"&&P("")},[t]),n.useEffect(()=>{t==="all"&&(r.invalidateQueries({queryKey:R.movies(l)}),r.invalidateQueries({queryKey:R.pictures(l)}))},[t,l,r]),n.useEffect(()=>{t==="folders"&&r.invalidateQueries({predicate:s=>Array.isArray(s.queryKey)&&s.queryKey[0]==="media-folders"&&s.queryKey[1]===l})},[t,d,l,r]);const{data:Z}=oe(),{data:E,isLoading:J}=ce(l,N,g,null,{enabled:t==="all"&&i==="pictures"}),{data:_,isLoading:X}=de(l,N,g,null,{enabled:t==="all"&&i==="movies"}),{data:u,isLoading:ee}=ue(l,d,N,g,{enabled:t==="folders"}),$=me(),L=xe(),F=pe(),T=t==="folders"?ee:i==="pictures"?J:X,K=t==="folders"?u?.files??[]:i==="pictures"?E?.pictures??[]:_?.movies??[],D=t==="folders"?u?.total_files??0:i==="pictures"?E?.total_count??0:_?.total_count??0,I=s=>s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:s<1024*1024*1024?`${(s/(1024*1024)).toFixed(1)} MB`:`${(s/(1024*1024*1024)).toFixed(1)} GB`,V=(s,m)=>{const h=fe(s,m);return h?h.toLocaleDateString()+" "+h.toLocaleTimeString():"Unknown date"},se=n.useCallback((s,m)=>{m.stopPropagation(),C(s)},[]),te=n.useCallback(async()=>{if(p)try{const s="type"in p?p.type:i;s==="picture"||s==="pictures"?await $.mutateAsync({camId:l,pictureId:p.id}):await L.mutateAsync({camId:l,movieId:p.id}),a(`${s==="picture"||s==="pictures"?"Picture":"Movie"} deleted`,"success"),C(null),o?.id===p.id&&w(null)}catch{a("Failed to delete file","error")}},[p,i,l,$,L,a,o]),q=n.useCallback(()=>{C(null)},[]),W=n.useCallback((s,m)=>{A({path:s,fileCount:m})},[]),ae=n.useCallback(async()=>{if(j)try{const s=await F.mutateAsync({camId:l,path:j.path});a(`Deleted ${s.deleted.movies} movies, ${s.deleted.pictures} pictures`,"success"),A(null)}catch{a("Failed to delete folder contents","error")}},[j,l,F,a]),U=n.useCallback(()=>{A(null)},[]),z=n.useCallback(s=>{P(s),y(0)},[]),le=n.useCallback(()=>{u?.parent!==null&&(P(u?.parent??""),y(0))},[u]),S=$.isPending||L.isPending,B=F.isPending,G=d?d.split("/"):[];return e.jsxs("div",{className:"p-6",children:[e.jsx("div",{className:"flex items-center justify-between mb-6",children:e.jsx("h2",{className:"text-3xl font-bold",children:"Media"})}),e.jsxs("div",{className:"flex flex-wrap gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"camera-select",className:"block text-sm font-medium mb-2",children:"Camera"}),e.jsx("select",{id:"camera-select",value:l,onChange:s=>v(parseInt(s.target.value)),className:"px-3 py-2 bg-surface border border-surface-elevated rounded-lg",children:Z?.map(s=>e.jsx("option",{value:s.id,children:s.name},s.id))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"Type"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>b("pictures"),className:`px-4 py-2 rounded-lg transition-colors ${i==="pictures"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Pictures"}),e.jsx("button",{onClick:()=>b("movies"),className:`px-4 py-2 rounded-lg transition-colors ${i==="movies"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,disabled:t==="folders",children:"Movies"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-2",children:"View"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>x("all"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="all"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"All"}),e.jsx("button",{onClick:()=>x("folders"),className:`px-3 py-2 rounded-lg transition-colors text-sm ${t==="folders"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Folders"})]})]})]}),t==="folders"&&e.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3",children:[e.jsx("svg",{className:"w-5 h-5 text-gray-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"})}),e.jsx("span",{className:"text-sm font-medium text-gray-300",children:"Browse Folders"})]}),e.jsxs("div",{className:"flex items-center gap-1 text-sm flex-wrap",children:[e.jsx("button",{onClick:()=>z(""),className:"hover:text-primary px-1",children:"Root"}),G.map((s,m)=>{const h=G.slice(0,m+1).join("/");return e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("button",{onClick:()=>z(h),className:"hover:text-primary px-1",children:s})]},m)})]}),u?.parent!==null&&e.jsxs("button",{onClick:le,className:"mt-2 flex items-center gap-2 text-sm text-gray-400 hover:text-white",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 19l-7-7 7-7"})}),"Up to parent folder"]})]}),t==="folders"&&u&&u.folders.length>0&&e.jsx("div",{className:"mb-6 grid gap-2 md:grid-cols-2 lg:grid-cols-4",children:u.folders.map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-4 hover:ring-2 hover:ring-primary cursor-pointer transition-all group",children:[e.jsx("button",{onClick:()=>z(s.path),className:"w-full text-left",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("svg",{className:"w-8 h-8 text-yellow-500 flex-shrink-0",fill:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{d:"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"font-medium truncate",children:s.name}),e.jsxs("p",{className:"text-xs text-gray-400",children:[s.file_count," files - ",I(s.total_size)]})]})]})}),f&&s.file_count>0&&e.jsx("button",{onClick:m=>{m.stopPropagation(),W(s.path,s.file_count)},className:"mt-2 w-full px-2 py-1 text-xs bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded opacity-0 group-hover:opacity-100 transition-opacity",title:`Delete all ${s.file_count} media files in this folder`,children:"Delete All Media"})]},s.path))}),t==="folders"&&f&&d&&u&&u.total_files>0&&e.jsx("div",{className:"mb-4 flex justify-end",children:e.jsxs("button",{onClick:()=>W(d,u.total_files),className:"px-3 py-1.5 text-sm bg-red-600/20 text-red-400 hover:bg-red-600/40 rounded-lg flex items-center gap-2",children:[e.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})}),"Delete All Media in This Folder (",u.total_files,")"]})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),T?e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:[1,2,3,4,5,6,7,8].map(s=>e.jsxs("div",{className:"bg-surface-elevated rounded-lg animate-pulse",children:[e.jsx("div",{className:"aspect-video bg-surface rounded-t-lg"}),e.jsxs("div",{className:"p-3",children:[e.jsx("div",{className:"h-4 bg-surface rounded w-3/4 mb-2"}),e.jsx("div",{className:"h-3 bg-surface rounded w-1/2"})]})]},s))}):K.length===0?e.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center",children:[e.jsx("p",{className:"text-gray-400",children:t==="folders"?"No media files in this folder":`No ${i} found`}),e.jsx("p",{className:"text-sm text-gray-500 mt-2",children:t==="folders"?"Navigate to a folder with media files":i==="pictures"?"Motion detection snapshots will appear here":"Recorded videos will appear here"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-3 lg:grid-cols-4",children:K.map(s=>{const m="type"in s?s.type:i==="pictures"?"picture":"movie",h=s.thumbnail||void 0;return e.jsxs("button",{className:"bg-surface-elevated rounded-lg overflow-hidden cursor-pointer hover:ring-2 hover:ring-primary focus:ring-2 focus:ring-primary focus:outline-none transition-all group relative text-left w-full",onClick:()=>w(s),"aria-label":`View ${s.filename}`,children:[e.jsx("button",{onClick:M=>se(s,M),className:"absolute top-2 right-2 z-10 p-1.5 bg-red-600/80 hover:bg-red-600 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity",title:"Delete","aria-label":`Delete ${s.filename}`,children:e.jsx("svg",{className:"w-4 h-4 text-white",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})})}),e.jsxs("div",{className:"aspect-video bg-surface flex items-center justify-center relative overflow-hidden",children:[m==="picture"?e.jsx("img",{src:k(s.path),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy"}):h?e.jsx("img",{src:k(h),alt:s.filename,className:"w-full h-full object-cover",loading:"lazy",onError:M=>{M.currentTarget.style.display="none";const H=M.currentTarget.parentElement;if(H){const Q=H.querySelector(".fallback-icon");Q&&(Q.style.display="flex")}}}):null,e.jsx("div",{className:`fallback-icon text-gray-400 absolute inset-0 flex items-center justify-center ${h?"hidden":""}`,children:e.jsxs("svg",{className:"w-16 h-16",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]})})]}),e.jsxs("div",{className:"p-3",children:[e.jsx("p",{className:"text-sm font-medium truncate",children:s.filename}),e.jsxs("div",{className:"flex justify-between text-xs text-gray-400 mt-1",children:[e.jsx("span",{children:I(s.size)}),e.jsx("span",{children:s.date?V(s.date,s.time):""})]})]})]},`${t}-${d}-${s.id}`)})}),!T&&D>0&&e.jsx(O,{offset:N,limit:g,total:D,onPageChange:s=>y(s/g),context:t==="folders"&&d?`in ${d}`:void 0}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4",onClick:()=>w(null),children:e.jsxs("div",{className:"max-w-6xl w-full bg-surface-elevated rounded-lg overflow-hidden",onClick:s=>s.stopPropagation(),children:[e.jsxs("div",{className:"p-4 border-b border-surface flex justify-between items-center",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:o.filename}),e.jsxs("p",{className:"text-sm text-gray-400",children:[I(o.size)," ",o.date&&`- ${V(o.date,o.time)}`]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("a",{href:k(o.path),download:o.filename,className:"px-3 py-1 bg-primary hover:bg-primary-hover rounded text-sm",onClick:s=>s.stopPropagation(),children:"Download"}),e.jsx("button",{onClick:s=>{s.stopPropagation(),C(o)},className:"px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm",children:"Delete"}),e.jsx("button",{onClick:()=>w(null),className:"px-3 py-1 bg-surface hover:bg-surface-elevated rounded text-sm",children:"Close"})]})]}),e.jsx("div",{className:"p-4",children:("type"in o?o.type:i)==="picture"?e.jsx("img",{src:k(o.path),alt:o.filename,className:"w-full h-auto max-h-[70vh] object-contain"}):e.jsx("video",{src:k(o.path),controls:!0,className:"w-full h-auto max-h-[70vh]",autoPlay:!0,children:"Your browser does not support video playback."})})]})}),p&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:q,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2",children:"Delete File?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsx("span",{className:"font-medium text-white",children:p.filename}),"? This action cannot be undone."]}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:q,disabled:S,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:te,disabled:S,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:S?"Deleting...":"Delete"})]})]})})}),j&&e.jsx("div",{className:"fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4",onClick:U,children:e.jsx("div",{className:"w-full max-w-md bg-surface-elevated rounded-lg",onClick:s=>s.stopPropagation(),children:e.jsxs("div",{className:"p-6",children:[e.jsx("h3",{className:"text-xl font-bold mb-2 text-red-400",children:"Delete All Media Files?"}),e.jsxs("p",{className:"text-gray-400 mb-4",children:["Are you sure you want to delete ",e.jsxs("span",{className:"font-medium text-white",children:["all ",j.fileCount," media files"]})," in folder ",e.jsx("span",{className:"font-mono text-primary",children:j.path||"root"}),"?"]}),e.jsx("p",{className:"text-sm text-yellow-500 mb-4",children:"This will delete movies, pictures, and their thumbnails. Subfolders will NOT be deleted. This action cannot be undone."}),e.jsxs("div",{className:"flex gap-3 justify-end",children:[e.jsx("button",{onClick:U,disabled:B,className:"px-4 py-2 bg-surface hover:bg-surface-elevated rounded-lg disabled:opacity-50",children:"Cancel"}),e.jsx("button",{onClick:ae,disabled:B,className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg disabled:opacity-50",children:B?"Deleting...":"Delete All"})]})]})})})]})}export{be as Media}; diff --git a/data/webui/assets/Settings-CrTNsWoa.js b/data/webui/assets/Settings-CrTNsWoa.js new file mode 100644 index 00000000..3d6f1cb8 --- /dev/null +++ b/data/webui/assets/Settings-CrTNsWoa.js @@ -0,0 +1,22 @@ +import{r as x,j as r,h as Pe,a as Ye,i as gr,k as xr,l as br,b as vr,m as yr,n as _r,o as Ft,e as qe,p as Zt,q as nt,s as jr,v as wr,f as Lt,c as Nr,u as Sr,w as Cr,g as kr}from"./index-DEo73YRp.js";import{c as R,b as Y,R as st,f as at,F as P,g as Tr,h as Pr,A as $r,d as zr,i as Mr,j as Or,m as ot,k as it,l as Ir,p as Ar,L as Dr,a as Er,n as Rr,o as Fr,q as Zr,r as re,s as Ie,E as Lr,t as Ur,M as ct,u as Hr,C as Vr,v as Br,w as Wr}from"./parameterMappings-Bp9bMVsO.js";function S({label:e,value:t,onChange:n,type:s="text",placeholder:a,disabled:o=!1,required:i=!1,helpText:c,error:l,min:u,max:d,step:m,originalValue:p,showVisibilityToggle:f}){const[y,g]=x.useState(!1),w=D=>{n(D.target.value)},_=!!l,v=p!==void 0&&String(t)!==String(p),$=s==="password",B=f??$,Z=$&&y?"text":s;return r.jsxs("div",{className:"mb-4",children:[r.jsxs("label",{className:"block text-sm font-medium mb-1",children:[e,i&&r.jsx("span",{className:"text-red-500 ml-1",children:"*"}),v&&r.jsx("span",{className:"ml-2 text-xs text-yellow-400",children:"(modified)"})]}),r.jsxs("div",{className:"relative",children:[r.jsx("input",{type:Z,value:t,onChange:w,placeholder:a,disabled:o,required:i,min:u,max:d,step:m,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${B?"pr-10":""} ${_?"border-red-500 focus:ring-red-500":v?"border-yellow-500/50 focus:ring-yellow-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":_,"aria-describedby":_?`${e}-error`:void 0}),B&&r.jsx("button",{type:"button",onClick:()=>g(!y),className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-200 transition-colors","aria-label":y?"Hide password":"Show password",children:y?r.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"})}):r.jsxs("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})]})})]}),_&&r.jsx("p",{id:`${e}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:l}),c&&!_&&r.jsx("p",{className:"mt-1 text-sm text-gray-400",children:c})]})}function O({title:e,description:t,children:n,collapsible:s=!1,defaultOpen:a=!0}){const[o,i]=x.useState(a),c=()=>{s&&i(!o)};return r.jsxs("div",{className:"bg-surface-elevated rounded-lg p-6 mb-6",children:[r.jsxs("div",{className:`flex items-center justify-between ${s?"cursor-pointer":""}`,onClick:c,children:[r.jsxs("div",{children:[r.jsx("h3",{className:"text-lg font-semibold",children:e}),t&&r.jsx("p",{className:"text-sm text-gray-400 mt-1",children:t})]}),s&&r.jsx("svg",{className:`w-5 h-5 transition-transform ${o?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),o&&r.jsx("div",{className:"mt-4",children:n})]})}function h(e,t,n){function s(c,l){if(c._zod||Object.defineProperty(c,"_zod",{value:{def:l,constr:i,traits:new Set},enumerable:!1}),c._zod.traits.has(e))return;c._zod.traits.add(e),t(c,l);const u=i.prototype,d=Object.keys(u);for(let m=0;mn?.Parent&&c instanceof n.Parent?!0:c?._zod?.traits?.has(e)}),Object.defineProperty(i,"name",{value:e}),i}class le extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class Ut extends Error{constructor(t){super(`Encountered unidirectional transform during encode: ${t}`),this.name="ZodEncodeError"}}const Yr={};function se(e){return Yr}function Le(e,t){return typeof t=="bigint"?t.toString():t}function Je(e){return e==null}function Ge(e){const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length;return e.slice(t,n)}function qr(e,t){const n=(e.toString().split(".")[1]||"").length,s=t.toString();let a=(s.split(".")[1]||"").length;if(a===0&&/\d?e-\d?/.test(s)){const l=s.match(/\d?e-(\d?)/);l?.[1]&&(a=Number.parseInt(l[1]))}const o=n>a?n:a,i=Number.parseInt(e.toFixed(o).replace(".","")),c=Number.parseInt(t.toFixed(o).replace(".",""));return i%c/10**o}const lt=Symbol("evaluating");function I(e,t,n){let s;Object.defineProperty(e,t,{get(){if(s!==lt)return s===void 0&&(s=lt,s=n()),s},set(a){Object.defineProperty(e,t,{value:a})},configurable:!0})}function Jr(...e){const t={};for(const n of e){const s=Object.getOwnPropertyDescriptors(n);Object.assign(t,s)}return Object.defineProperties({},t)}function Gr(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const Ht="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function ut(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ue(e){if(ut(e)===!1)return!1;const t=e.constructor;if(t===void 0||typeof t!="function")return!0;const n=t.prototype;return!(ut(n)===!1||Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")===!1)}function Vt(e){return Ue(e)?{...e}:Array.isArray(e)?[...e]:e}function Ke(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Kr(e,t,n){const s=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(s._zod.parent=e),s}function j(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}const Qr={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ce(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var s;return(s=n).path??(s.path=[]),n.path.unshift(e),n})}function _e(e){return typeof e=="string"?e:e?.message}function ae(e,t,n){const s={...e,path:e.path??[]};if(!e.message){const a=_e(e.inst?._zod.def?.error?.(e))??_e(t?.error?.(e))??_e(n.customError?.(e))??_e(n.localeError?.(e))??"Invalid input";s.message=a}return delete s.inst,delete s.continue,t?.reportInput||delete s.input,s}function Qe(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function xe(...e){const[t,n,s]=e;return typeof t=="string"?{message:t,code:"custom",input:n,inst:s}:{...t}}const Bt=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),e.message=JSON.stringify(t,Le,2),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},Wt=h("$ZodError",Bt),Yt=h("$ZodError",Bt,{Parent:Error});function en(e,t=n=>n.message){const n={},s=[];for(const a of e.issues)a.path.length>0?(n[a.path[0]]=n[a.path[0]]||[],n[a.path[0]].push(t(a))):s.push(t(a));return{formErrors:s,fieldErrors:n}}function tn(e,t=n=>n.message){const n={_errors:[]},s=a=>{for(const o of a.issues)if(o.code==="invalid_union"&&o.errors.length)o.errors.map(i=>s({issues:i}));else if(o.code==="invalid_key")s({issues:o.issues});else if(o.code==="invalid_element")s({issues:o.issues});else if(o.path.length===0)n._errors.push(t(o));else{let i=n,c=0;for(;c(t,n,s,a)=>{const o=s?Object.assign(s,{async:!1}):{async:!1},i=t._zod.run({value:n,issues:[]},o);if(i instanceof Promise)throw new le;if(i.issues.length){const c=new(a?.Err??e)(i.issues.map(l=>ae(l,o,se())));throw Ht(c,a?.callee),c}return i.value},et=e=>async(t,n,s,a)=>{const o=s?Object.assign(s,{async:!0}):{async:!0};let i=t._zod.run({value:n,issues:[]},o);if(i instanceof Promise&&(i=await i),i.issues.length){const c=new(a?.Err??e)(i.issues.map(l=>ae(l,o,se())));throw Ht(c,a?.callee),c}return i.value},$e=e=>(t,n,s)=>{const a=s?{...s,async:!1}:{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new le;return o.issues.length?{success:!1,error:new(e??Wt)(o.issues.map(i=>ae(i,a,se())))}:{success:!0,data:o.value}},rn=$e(Yt),ze=e=>async(t,n,s)=>{const a=s?Object.assign(s,{async:!0}):{async:!0};let o=t._zod.run({value:n,issues:[]},a);return o instanceof Promise&&(o=await o),o.issues.length?{success:!1,error:new e(o.issues.map(i=>ae(i,a,se())))}:{success:!0,data:o.value}},nn=ze(Yt),sn=e=>(t,n,s)=>{const a=s?Object.assign(s,{direction:"backward"}):{direction:"backward"};return Xe(e)(t,n,a)},an=e=>(t,n,s)=>Xe(e)(t,n,s),on=e=>async(t,n,s)=>{const a=s?Object.assign(s,{direction:"backward"}):{direction:"backward"};return et(e)(t,n,a)},cn=e=>async(t,n,s)=>et(e)(t,n,s),ln=e=>(t,n,s)=>{const a=s?Object.assign(s,{direction:"backward"}):{direction:"backward"};return $e(e)(t,n,a)},un=e=>(t,n,s)=>$e(e)(t,n,s),dn=e=>async(t,n,s)=>{const a=s?Object.assign(s,{direction:"backward"}):{direction:"backward"};return ze(e)(t,n,a)},mn=e=>async(t,n,s)=>ze(e)(t,n,s),hn=/^[cC][^\s-]{8,}$/,pn=/^[0-9a-z]+$/,fn=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,gn=/^[0-9a-vA-V]{20}$/,xn=/^[A-Za-z0-9]{27}$/,bn=/^[a-zA-Z0-9_-]{21}$/,vn=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,yn=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,dt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,_n=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,jn="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function wn(){return new RegExp(jn,"u")}const Nn=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,Sn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,Cn=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,kn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,Tn=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,qt=/^[A-Za-z0-9_-]*$/,Pn=/^\+(?:[0-9]){6,14}[0-9]$/,Jt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",$n=new RegExp(`^${Jt}$`);function Gt(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function zn(e){return new RegExp(`^${Gt(e)}$`)}function Mn(e){const t=Gt({precision:e.precision}),n=["Z"];e.local&&n.push(""),e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const s=`${t}(?:${n.join("|")})`;return new RegExp(`^${Jt}T(?:${s})$`)}const On=e=>{const t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},In=/^-?\d+$/,An=/^-?\d+(?:\.\d+)?/,Dn=/^[^A-Z]*$/,En=/^[^a-z]*$/,q=h("$ZodCheck",(e,t)=>{var n;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),Kt={number:"number",bigint:"bigint",object:"date"},Qt=h("$ZodCheckLessThan",(e,t)=>{q.init(e,t);const n=Kt[typeof t.value];e._zod.onattach.push(s=>{const a=s._zod.bag,o=(t.inclusive?a.maximum:a.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?s.value<=t.value:s.value{q.init(e,t);const n=Kt[typeof t.value];e._zod.onattach.push(s=>{const a=s._zod.bag,o=(t.inclusive?a.minimum:a.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>o&&(t.inclusive?a.minimum=t.value:a.exclusiveMinimum=t.value)}),e._zod.check=s=>{(t.inclusive?s.value>=t.value:s.value>t.value)||s.issues.push({origin:n,code:"too_small",minimum:t.value,input:s.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),Rn=h("$ZodCheckMultipleOf",(e,t)=>{q.init(e,t),e._zod.onattach.push(n=>{var s;(s=n._zod.bag).multipleOf??(s.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof n.value=="bigint"?n.value%t.value===BigInt(0):qr(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),Fn=h("$ZodCheckNumberFormat",(e,t)=>{q.init(e,t),t.format=t.format||"float64";const n=t.format?.includes("int"),s=n?"int":"number",[a,o]=Qr[t.format];e._zod.onattach.push(i=>{const c=i._zod.bag;c.format=t.format,c.minimum=a,c.maximum=o,n&&(c.pattern=In)}),e._zod.check=i=>{const c=i.value;if(n){if(!Number.isInteger(c)){i.issues.push({expected:s,format:t.format,code:"invalid_type",continue:!1,input:c,inst:e});return}if(!Number.isSafeInteger(c)){c>0?i.issues.push({input:c,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:s,continue:!t.abort}):i.issues.push({input:c,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:s,continue:!t.abort});return}}co&&i.issues.push({origin:"number",input:c,code:"too_big",maximum:o,inst:e})}}),Zn=h("$ZodCheckMaxLength",(e,t)=>{var n;q.init(e,t),(n=e._zod.def).when??(n.when=s=>{const a=s.value;return!Je(a)&&a.length!==void 0}),e._zod.onattach.push(s=>{const a=s._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const a=s.value;if(a.length<=t.maximum)return;const i=Qe(a);s.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:a,inst:e,continue:!t.abort})}}),Ln=h("$ZodCheckMinLength",(e,t)=>{var n;q.init(e,t),(n=e._zod.def).when??(n.when=s=>{const a=s.value;return!Je(a)&&a.length!==void 0}),e._zod.onattach.push(s=>{const a=s._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>a&&(s._zod.bag.minimum=t.minimum)}),e._zod.check=s=>{const a=s.value;if(a.length>=t.minimum)return;const i=Qe(a);s.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:a,inst:e,continue:!t.abort})}}),Un=h("$ZodCheckLengthEquals",(e,t)=>{var n;q.init(e,t),(n=e._zod.def).when??(n.when=s=>{const a=s.value;return!Je(a)&&a.length!==void 0}),e._zod.onattach.push(s=>{const a=s._zod.bag;a.minimum=t.length,a.maximum=t.length,a.length=t.length}),e._zod.check=s=>{const a=s.value,o=a.length;if(o===t.length)return;const i=Qe(a),c=o>t.length;s.issues.push({origin:i,...c?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:s.value,inst:e,continue:!t.abort})}}),Me=h("$ZodCheckStringFormat",(e,t)=>{var n,s;q.init(e,t),e._zod.onattach.push(a=>{const o=a._zod.bag;o.format=t.format,t.pattern&&(o.patterns??(o.patterns=new Set),o.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=a=>{t.pattern.lastIndex=0,!t.pattern.test(a.value)&&a.issues.push({origin:"string",code:"invalid_format",format:t.format,input:a.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(s=e._zod).check??(s.check=()=>{})}),Hn=h("$ZodCheckRegex",(e,t)=>{Me.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:"string",code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),Vn=h("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=Dn),Me.init(e,t)}),Bn=h("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=En),Me.init(e,t)}),Wn=h("$ZodCheckIncludes",(e,t)=>{q.init(e,t);const n=Ke(t.includes),s=new RegExp(typeof t.position=="number"?`^.{${t.position}}${n}`:n);t.pattern=s,e._zod.onattach.push(a=>{const o=a._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(s)}),e._zod.check=a=>{a.value.includes(t.includes,t.position)||a.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:a.value,inst:e,continue:!t.abort})}}),Yn=h("$ZodCheckStartsWith",(e,t)=>{q.init(e,t);const n=new RegExp(`^${Ke(t.prefix)}.*`);t.pattern??(t.pattern=n),e._zod.onattach.push(s=>{const a=s._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(n)}),e._zod.check=s=>{s.value.startsWith(t.prefix)||s.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:s.value,inst:e,continue:!t.abort})}}),qn=h("$ZodCheckEndsWith",(e,t)=>{q.init(e,t);const n=new RegExp(`.*${Ke(t.suffix)}$`);t.pattern??(t.pattern=n),e._zod.onattach.push(s=>{const a=s._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(n)}),e._zod.check=s=>{s.value.endsWith(t.suffix)||s.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:s.value,inst:e,continue:!t.abort})}}),Jn=h("$ZodCheckOverwrite",(e,t)=>{q.init(e,t),e._zod.check=n=>{n.value=t.tx(n.value)}}),Gn={major:4,minor:2,patch:1},L=h("$ZodType",(e,t)=>{var n;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=Gn;const s=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&s.unshift(e);for(const a of s)for(const o of a._zod.onattach)o(e);if(s.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{const a=(i,c,l)=>{let u=ce(i),d;for(const m of c){if(m._zod.def.when){if(!m._zod.def.when(i))continue}else if(u)continue;const p=i.issues.length,f=m._zod.check(i);if(f instanceof Promise&&l?.async===!1)throw new le;if(d||f instanceof Promise)d=(d??Promise.resolve()).then(async()=>{await f,i.issues.length!==p&&(u||(u=ce(i,p)))});else{if(i.issues.length===p)continue;u||(u=ce(i,p))}}return d?d.then(()=>i):i},o=(i,c,l)=>{if(ce(i))return i.aborted=!0,i;const u=a(c,s,l);if(u instanceof Promise){if(l.async===!1)throw new le;return u.then(d=>e._zod.parse(d,l))}return e._zod.parse(u,l)};e._zod.run=(i,c)=>{if(c.skipChecks)return e._zod.parse(i,c);if(c.direction==="backward"){const u=e._zod.parse({value:i.value,issues:[]},{...c,skipChecks:!0});return u instanceof Promise?u.then(d=>o(d,i,c)):o(u,i,c)}const l=e._zod.parse(i,c);if(l instanceof Promise){if(c.async===!1)throw new le;return l.then(u=>a(u,s,c))}return a(l,s,c)}}e["~standard"]={validate:a=>{try{const o=rn(e,a);return o.success?{value:o.data}:{issues:o.error?.issues}}catch{return nn(e,a).then(i=>i.success?{value:i.data}:{issues:i.error?.issues})}},vendor:"zod",version:1}}),tt=h("$ZodString",(e,t)=>{L.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??On(e._zod.bag),e._zod.parse=(n,s)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value=="string"||n.issues.push({expected:"string",code:"invalid_type",input:n.value,inst:e}),n}}),A=h("$ZodStringFormat",(e,t)=>{Me.init(e,t),tt.init(e,t)}),Kn=h("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=yn),A.init(e,t)}),Qn=h("$ZodUUID",(e,t)=>{if(t.version){const s={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(s===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=dt(s))}else t.pattern??(t.pattern=dt());A.init(e,t)}),Xn=h("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=_n),A.init(e,t)}),es=h("$ZodURL",(e,t)=>{A.init(e,t),e._zod.check=n=>{try{const s=n.value.trim(),a=new URL(s);t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(a.hostname)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:t.hostname.source,input:n.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(a.protocol.endsWith(":")?a.protocol.slice(0,-1):a.protocol)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:n.value,inst:e,continue:!t.abort})),t.normalize?n.value=a.href:n.value=s;return}catch{n.issues.push({code:"invalid_format",format:"url",input:n.value,inst:e,continue:!t.abort})}}}),ts=h("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=wn()),A.init(e,t)}),rs=h("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=bn),A.init(e,t)}),ns=h("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=hn),A.init(e,t)}),ss=h("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=pn),A.init(e,t)}),as=h("$ZodULID",(e,t)=>{t.pattern??(t.pattern=fn),A.init(e,t)}),os=h("$ZodXID",(e,t)=>{t.pattern??(t.pattern=gn),A.init(e,t)}),is=h("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=xn),A.init(e,t)}),cs=h("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=Mn(t)),A.init(e,t)}),ls=h("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=$n),A.init(e,t)}),us=h("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=zn(t)),A.init(e,t)}),ds=h("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=vn),A.init(e,t)}),ms=h("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=Nn),A.init(e,t),e._zod.bag.format="ipv4"}),hs=h("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=Sn),A.init(e,t),e._zod.bag.format="ipv6",e._zod.check=n=>{try{new URL(`http://[${n.value}]`)}catch{n.issues.push({code:"invalid_format",format:"ipv6",input:n.value,inst:e,continue:!t.abort})}}}),ps=h("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=Cn),A.init(e,t)}),fs=h("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=kn),A.init(e,t),e._zod.check=n=>{const s=n.value.split("/");try{if(s.length!==2)throw new Error;const[a,o]=s;if(!o)throw new Error;const i=Number(o);if(`${i}`!==o)throw new Error;if(i<0||i>128)throw new Error;new URL(`http://[${a}]`)}catch{n.issues.push({code:"invalid_format",format:"cidrv6",input:n.value,inst:e,continue:!t.abort})}}});function er(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const gs=h("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=Tn),A.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=n=>{er(n.value)||n.issues.push({code:"invalid_format",format:"base64",input:n.value,inst:e,continue:!t.abort})}});function xs(e){if(!qt.test(e))return!1;const t=e.replace(/[-_]/g,s=>s==="-"?"+":"/"),n=t.padEnd(Math.ceil(t.length/4)*4,"=");return er(n)}const bs=h("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=qt),A.init(e,t),e._zod.bag.contentEncoding="base64url",e._zod.check=n=>{xs(n.value)||n.issues.push({code:"invalid_format",format:"base64url",input:n.value,inst:e,continue:!t.abort})}}),vs=h("$ZodE164",(e,t)=>{t.pattern??(t.pattern=Pn),A.init(e,t)});function ys(e,t=null){try{const n=e.split(".");if(n.length!==3)return!1;const[s]=n;if(!s)return!1;const a=JSON.parse(atob(s));return!("typ"in a&&a?.typ!=="JWT"||!a.alg||t&&(!("alg"in a)||a.alg!==t))}catch{return!1}}const _s=h("$ZodJWT",(e,t)=>{A.init(e,t),e._zod.check=n=>{ys(n.value,t.alg)||n.issues.push({code:"invalid_format",format:"jwt",input:n.value,inst:e,continue:!t.abort})}}),tr=h("$ZodNumber",(e,t)=>{L.init(e,t),e._zod.pattern=e._zod.bag.pattern??An,e._zod.parse=(n,s)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}const a=n.value;if(typeof a=="number"&&!Number.isNaN(a)&&Number.isFinite(a))return n;const o=typeof a=="number"?Number.isNaN(a)?"NaN":Number.isFinite(a)?void 0:"Infinity":void 0;return n.issues.push({expected:"number",code:"invalid_type",input:a,inst:e,...o?{received:o}:{}}),n}}),js=h("$ZodNumberFormat",(e,t)=>{Fn.init(e,t),tr.init(e,t)});function mt(e,t,n){e.issues.length&&t.issues.push(...Xr(n,e.issues)),t.value[n]=e.value}const ws=h("$ZodArray",(e,t)=>{L.init(e,t),e._zod.parse=(n,s)=>{const a=n.value;if(!Array.isArray(a))return n.issues.push({expected:"array",code:"invalid_type",input:a,inst:e}),n;n.value=Array(a.length);const o=[];for(let i=0;imt(u,n,i))):mt(l,n,i)}return o.length?Promise.all(o).then(()=>n):n}});function ht(e,t,n,s){for(const o of e)if(o.issues.length===0)return t.value=o.value,t;const a=e.filter(o=>!ce(o));return a.length===1?(t.value=a[0].value,a[0]):(t.issues.push({code:"invalid_union",input:t.value,inst:n,errors:e.map(o=>o.issues.map(i=>ae(i,s,se())))}),t)}const Ns=h("$ZodUnion",(e,t)=>{L.init(e,t),I(e._zod,"optin",()=>t.options.some(a=>a._zod.optin==="optional")?"optional":void 0),I(e._zod,"optout",()=>t.options.some(a=>a._zod.optout==="optional")?"optional":void 0),I(e._zod,"values",()=>{if(t.options.every(a=>a._zod.values))return new Set(t.options.flatMap(a=>Array.from(a._zod.values)))}),I(e._zod,"pattern",()=>{if(t.options.every(a=>a._zod.pattern)){const a=t.options.map(o=>o._zod.pattern);return new RegExp(`^(${a.map(o=>Ge(o.source)).join("|")})$`)}});const n=t.options.length===1,s=t.options[0]._zod.run;e._zod.parse=(a,o)=>{if(n)return s(a,o);let i=!1;const c=[];for(const l of t.options){const u=l._zod.run({value:a.value,issues:[]},o);if(u instanceof Promise)c.push(u),i=!0;else{if(u.issues.length===0)return u;c.push(u)}}return i?Promise.all(c).then(l=>ht(l,a,e,o)):ht(c,a,e,o)}}),Ss=h("$ZodIntersection",(e,t)=>{L.init(e,t),e._zod.parse=(n,s)=>{const a=n.value,o=t.left._zod.run({value:a,issues:[]},s),i=t.right._zod.run({value:a,issues:[]},s);return o instanceof Promise||i instanceof Promise?Promise.all([o,i]).then(([l,u])=>pt(n,l,u)):pt(n,o,i)}});function He(e,t){if(e===t)return{valid:!0,data:e};if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Ue(e)&&Ue(t)){const n=Object.keys(t),s=Object.keys(e).filter(o=>n.indexOf(o)!==-1),a={...e,...t};for(const o of s){const i=He(e[o],t[o]);if(!i.valid)return{valid:!1,mergeErrorPath:[o,...i.mergeErrorPath]};a[o]=i.data}return{valid:!0,data:a}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};const n=[];for(let s=0;s{L.init(e,t),e._zod.parse=(n,s)=>{if(s.direction==="backward")throw new Ut(e.constructor.name);const a=t.transform(n.value,n);if(s.async)return(a instanceof Promise?a:Promise.resolve(a)).then(i=>(n.value=i,n));if(a instanceof Promise)throw new le;return n.value=a,n}});function ft(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const ks=h("$ZodOptional",(e,t)=>{L.init(e,t),e._zod.optin="optional",e._zod.optout="optional",I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),I(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)})?$`):void 0}),e._zod.parse=(n,s)=>{if(t.innerType._zod.optin==="optional"){const a=t.innerType._zod.run(n,s);return a instanceof Promise?a.then(o=>ft(o,n.value)):ft(a,n.value)}return n.value===void 0?n:t.innerType._zod.run(n,s)}}),Ts=h("$ZodNullable",(e,t)=>{L.init(e,t),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"pattern",()=>{const n=t.innerType._zod.pattern;return n?new RegExp(`^(${Ge(n.source)}|null)$`):void 0}),I(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(n,s)=>n.value===null?n:t.innerType._zod.run(n,s)}),Ps=h("$ZodDefault",(e,t)=>{L.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,s)=>{if(s.direction==="backward")return t.innerType._zod.run(n,s);if(n.value===void 0)return n.value=t.defaultValue,n;const a=t.innerType._zod.run(n,s);return a instanceof Promise?a.then(o=>gt(o,t)):gt(a,t)}});function gt(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}const $s=h("$ZodPrefault",(e,t)=>{L.init(e,t),e._zod.optin="optional",I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,s)=>(s.direction==="backward"||n.value===void 0&&(n.value=t.defaultValue),t.innerType._zod.run(n,s))}),zs=h("$ZodNonOptional",(e,t)=>{L.init(e,t),I(e._zod,"values",()=>{const n=t.innerType._zod.values;return n?new Set([...n].filter(s=>s!==void 0)):void 0}),e._zod.parse=(n,s)=>{const a=t.innerType._zod.run(n,s);return a instanceof Promise?a.then(o=>xt(o,e)):xt(a,e)}});function xt(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}const Ms=h("$ZodCatch",(e,t)=>{L.init(e,t),I(e._zod,"optin",()=>t.innerType._zod.optin),I(e._zod,"optout",()=>t.innerType._zod.optout),I(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(n,s)=>{if(s.direction==="backward")return t.innerType._zod.run(n,s);const a=t.innerType._zod.run(n,s);return a instanceof Promise?a.then(o=>(n.value=o.value,o.issues.length&&(n.value=t.catchValue({...n,error:{issues:o.issues.map(i=>ae(i,s,se()))},input:n.value}),n.issues=[]),n)):(n.value=a.value,a.issues.length&&(n.value=t.catchValue({...n,error:{issues:a.issues.map(o=>ae(o,s,se()))},input:n.value}),n.issues=[]),n)}}),Os=h("$ZodPipe",(e,t)=>{L.init(e,t),I(e._zod,"values",()=>t.in._zod.values),I(e._zod,"optin",()=>t.in._zod.optin),I(e._zod,"optout",()=>t.out._zod.optout),I(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(n,s)=>{if(s.direction==="backward"){const o=t.out._zod.run(n,s);return o instanceof Promise?o.then(i=>je(i,t.in,s)):je(o,t.in,s)}const a=t.in._zod.run(n,s);return a instanceof Promise?a.then(o=>je(o,t.out,s)):je(a,t.out,s)}});function je(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}const Is=h("$ZodReadonly",(e,t)=>{L.init(e,t),I(e._zod,"propValues",()=>t.innerType._zod.propValues),I(e._zod,"values",()=>t.innerType._zod.values),I(e._zod,"optin",()=>t.innerType?._zod?.optin),I(e._zod,"optout",()=>t.innerType?._zod?.optout),e._zod.parse=(n,s)=>{if(s.direction==="backward")return t.innerType._zod.run(n,s);const a=t.innerType._zod.run(n,s);return a instanceof Promise?a.then(bt):bt(a)}});function bt(e){return e.value=Object.freeze(e.value),e}const As=h("$ZodCustom",(e,t)=>{q.init(e,t),L.init(e,t),e._zod.parse=(n,s)=>n,e._zod.check=n=>{const s=n.value,a=t.fn(s);if(a instanceof Promise)return a.then(o=>vt(o,n,s,e));vt(a,n,s,e)}});function vt(e,t,n,s){if(!e){const a={code:"custom",input:n,inst:s,path:[...s._zod.def.path??[]],continue:!s._zod.def.abort};s._zod.def.params&&(a.params=s._zod.def.params),t.issues.push(xe(a))}}var yt;class Ds{constructor(){this._map=new WeakMap,this._idmap=new Map}add(t,...n){const s=n[0];if(this._map.set(t,s),s&&typeof s=="object"&&"id"in s){if(this._idmap.has(s.id))throw new Error(`ID ${s.id} already exists in the registry`);this._idmap.set(s.id,t)}return this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(t){const n=this._map.get(t);return n&&typeof n=="object"&&"id"in n&&this._idmap.delete(n.id),this._map.delete(t),this}get(t){const n=t._zod.parent;if(n){const s={...this.get(n)??{}};delete s.id;const a={...s,...this._map.get(t)};return Object.keys(a).length?a:void 0}return this._map.get(t)}has(t){return this._map.has(t)}}function Es(){return new Ds}(yt=globalThis).__zod_globalRegistry??(yt.__zod_globalRegistry=Es());const ge=globalThis.__zod_globalRegistry;function Rs(e,t){return new e({type:"string",...j(t)})}function Fs(e,t){return new e({type:"string",format:"email",check:"string_format",abort:!1,...j(t)})}function _t(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...j(t)})}function Zs(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...j(t)})}function Ls(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...j(t)})}function Us(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...j(t)})}function Hs(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...j(t)})}function Vs(e,t){return new e({type:"string",format:"url",check:"string_format",abort:!1,...j(t)})}function Bs(e,t){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...j(t)})}function Ws(e,t){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...j(t)})}function Ys(e,t){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...j(t)})}function qs(e,t){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...j(t)})}function Js(e,t){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...j(t)})}function Gs(e,t){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...j(t)})}function Ks(e,t){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...j(t)})}function Qs(e,t){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...j(t)})}function Xs(e,t){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...j(t)})}function ea(e,t){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...j(t)})}function ta(e,t){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...j(t)})}function ra(e,t){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...j(t)})}function na(e,t){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...j(t)})}function sa(e,t){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...j(t)})}function aa(e,t){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...j(t)})}function oa(e,t){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...j(t)})}function ia(e,t){return new e({type:"string",format:"date",check:"string_format",...j(t)})}function ca(e,t){return new e({type:"string",format:"time",check:"string_format",precision:null,...j(t)})}function la(e,t){return new e({type:"string",format:"duration",check:"string_format",...j(t)})}function ua(e,t){return new e({type:"number",coerce:!0,checks:[],...j(t)})}function da(e,t){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...j(t)})}function jt(e,t){return new Qt({check:"less_than",...j(t),value:e,inclusive:!1})}function Ae(e,t){return new Qt({check:"less_than",...j(t),value:e,inclusive:!0})}function wt(e,t){return new Xt({check:"greater_than",...j(t),value:e,inclusive:!1})}function De(e,t){return new Xt({check:"greater_than",...j(t),value:e,inclusive:!0})}function Nt(e,t){return new Rn({check:"multiple_of",...j(t),value:e})}function rr(e,t){return new Zn({check:"max_length",...j(t),maximum:e})}function ke(e,t){return new Ln({check:"min_length",...j(t),minimum:e})}function nr(e,t){return new Un({check:"length_equals",...j(t),length:e})}function ma(e,t){return new Hn({check:"string_format",format:"regex",...j(t),pattern:e})}function ha(e){return new Vn({check:"string_format",format:"lowercase",...j(e)})}function pa(e){return new Bn({check:"string_format",format:"uppercase",...j(e)})}function fa(e,t){return new Wn({check:"string_format",format:"includes",...j(t),includes:e})}function ga(e,t){return new Yn({check:"string_format",format:"starts_with",...j(t),prefix:e})}function xa(e,t){return new qn({check:"string_format",format:"ends_with",...j(t),suffix:e})}function ue(e){return new Jn({check:"overwrite",tx:e})}function ba(e){return ue(t=>t.normalize(e))}function va(){return ue(e=>e.trim())}function ya(){return ue(e=>e.toLowerCase())}function _a(){return ue(e=>e.toUpperCase())}function ja(){return ue(e=>Gr(e))}function wa(e,t,n){return new e({type:"array",element:t,...j(n)})}function Na(e,t,n){return new e({type:"custom",check:"custom",fn:t,...j(n)})}function Sa(e){const t=Ca(n=>(n.addIssue=s=>{if(typeof s=="string")n.issues.push(xe(s,n.value,t._zod.def));else{const a=s;a.fatal&&(a.continue=!1),a.code??(a.code="custom"),a.input??(a.input=n.value),a.inst??(a.inst=t),a.continue??(a.continue=!t._zod.def.abort),n.issues.push(xe(a))}},e(n.value,n)));return t}function Ca(e,t){const n=new q({check:"custom",...j(t)});return n._zod.check=e,n}function sr(e){let t=e?.target??"draft-2020-12";return t==="draft-4"&&(t="draft-04"),t==="draft-7"&&(t="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??ge,target:t,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref",reused:e?.reused??"inline",external:e?.external??void 0}}function H(e,t,n={path:[],schemaPath:[]}){var s;const a=e._zod.def,o=t.seen.get(e);if(o)return o.count++,n.schemaPath.includes(e)&&(o.cycle=n.path),o.schema;const i={schema:{},count:1,cycle:void 0,path:n.path};t.seen.set(e,i);const c=e._zod.toJSONSchema?.();if(c)i.schema=c;else{const d={...n,schemaPath:[...n.schemaPath,e],path:n.path},m=e._zod.parent;if(m)i.ref=m,H(m,t,d),t.seen.get(m).isParent=!0;else if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,d);else{const p=i.schema,f=t.processors[a.type];if(!f)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${a.type}`);f(e,t,p,d)}}const l=t.metadataRegistry.get(e);return l&&Object.assign(i.schema,l),t.io==="input"&&U(e)&&(delete i.schema.examples,delete i.schema.default),t.io==="input"&&i.schema._prefault&&((s=i.schema).default??(s.default=i.schema._prefault)),delete i.schema._prefault,t.seen.get(e).schema}function ar(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const s=o=>{const i=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const d=e.external.registry.get(o[0])?.id,m=e.external.uri??(f=>f);if(d)return{ref:m(d)};const p=o[1].defId??o[1].schema.id??`schema${e.counter++}`;return o[1].defId=p,{defId:p,ref:`${m("__shared")}#/${i}/${p}`}}if(o[1]===n)return{ref:"#"};const l=`#/${i}/`,u=o[1].schema.id??`__schema${e.counter++}`;return{defId:u,ref:l+u}},a=o=>{if(o[1].schema.$ref)return;const i=o[1],{ref:c,defId:l}=s(o);i.def={...i.schema},l&&(i.defId=l);const u=i.schema;for(const d in u)delete u[d];u.$ref=c};if(e.cycles==="throw")for(const o of e.seen.entries()){const i=o[1];if(i.cycle)throw new Error(`Cycle detected: #/${i.cycle?.join("/")}/ + +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const o of e.seen.entries()){const i=o[1];if(t===o[0]){a(o);continue}if(e.external){const l=e.external.registry.get(o[0])?.id;if(t!==o[0]&&l){a(o);continue}}if(e.metadataRegistry.get(o[0])?.id){a(o);continue}if(i.cycle){a(o);continue}if(i.count>1&&e.reused==="ref"){a(o);continue}}}function or(e,t){const n=e.seen.get(t);if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const s=i=>{const c=e.seen.get(i),l=c.def??c.schema,u={...l};if(c.ref===null)return;const d=c.ref;if(c.ref=null,d){s(d);const m=e.seen.get(d).schema;m.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(l.allOf=l.allOf??[],l.allOf.push(m)):(Object.assign(l,m),Object.assign(l,u))}c.isParent||e.override({zodSchema:i,jsonSchema:l,path:c.path??[]})};for(const i of[...e.seen.entries()].reverse())s(i[0]);const a={};if(e.target==="draft-2020-12"?a.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?a.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?a.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){const i=e.external.registry.get(t)?.id;if(!i)throw new Error("Schema is missing an `id` property");a.$id=e.external.uri(i)}Object.assign(a,n.def??n.schema);const o=e.external?.defs??{};for(const i of e.seen.entries()){const c=i[1];c.def&&c.defId&&(o[c.defId]=c.def)}e.external||Object.keys(o).length>0&&(e.target==="draft-2020-12"?a.$defs=o:a.definitions=o);try{const i=JSON.parse(JSON.stringify(a));return Object.defineProperty(i,"~standard",{value:{...t["~standard"],jsonSchema:{input:Te(t,"input"),output:Te(t,"output")}},enumerable:!1,writable:!1}),i}catch{throw new Error("Error converting schema to JSON.")}}function U(e,t){const n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);const s=e._zod.def;if(s.type==="transform")return!0;if(s.type==="array")return U(s.element,n);if(s.type==="set")return U(s.valueType,n);if(s.type==="lazy")return U(s.getter(),n);if(s.type==="promise"||s.type==="optional"||s.type==="nonoptional"||s.type==="nullable"||s.type==="readonly"||s.type==="default"||s.type==="prefault")return U(s.innerType,n);if(s.type==="intersection")return U(s.left,n)||U(s.right,n);if(s.type==="record"||s.type==="map")return U(s.keyType,n)||U(s.valueType,n);if(s.type==="pipe")return U(s.in,n)||U(s.out,n);if(s.type==="object"){for(const a in s.shape)if(U(s.shape[a],n))return!0;return!1}if(s.type==="union"){for(const a of s.options)if(U(a,n))return!0;return!1}if(s.type==="tuple"){for(const a of s.items)if(U(a,n))return!0;return!!(s.rest&&U(s.rest,n))}return!1}const ka=(e,t={})=>n=>{const s=sr({...n,processors:t});return H(e,s),ar(s,e),or(s,e)},Te=(e,t)=>n=>{const{libraryOptions:s,target:a}=n??{},o=sr({...s??{},target:a,io:t,processors:{}});return H(e,o),ar(o,e),or(o,e)},Ta={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},Pa=(e,t,n,s)=>{const a=n;a.type="string";const{minimum:o,maximum:i,format:c,patterns:l,contentEncoding:u}=e._zod.bag;if(typeof o=="number"&&(a.minLength=o),typeof i=="number"&&(a.maxLength=i),c&&(a.format=Ta[c]??c,a.format===""&&delete a.format),u&&(a.contentEncoding=u),l&&l.size>0){const d=[...l];d.length===1?a.pattern=d[0].source:d.length>1&&(a.allOf=[...d.map(m=>({...t.target==="draft-07"||t.target==="draft-04"||t.target==="openapi-3.0"?{type:"string"}:{},pattern:m.source}))])}},$a=(e,t,n,s)=>{const a=n,{minimum:o,maximum:i,format:c,multipleOf:l,exclusiveMaximum:u,exclusiveMinimum:d}=e._zod.bag;typeof c=="string"&&c.includes("int")?a.type="integer":a.type="number",typeof d=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(a.minimum=d,a.exclusiveMinimum=!0):a.exclusiveMinimum=d),typeof o=="number"&&(a.minimum=o,typeof d=="number"&&t.target!=="draft-04"&&(d>=o?delete a.minimum:delete a.exclusiveMinimum)),typeof u=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(a.maximum=u,a.exclusiveMaximum=!0):a.exclusiveMaximum=u),typeof i=="number"&&(a.maximum=i,typeof u=="number"&&t.target!=="draft-04"&&(u<=i?delete a.maximum:delete a.exclusiveMaximum)),typeof l=="number"&&(a.multipleOf=l)},za=(e,t,n,s)=>{if(t.unrepresentable==="throw")throw new Error("Custom types cannot be represented in JSON Schema")},Ma=(e,t,n,s)=>{if(t.unrepresentable==="throw")throw new Error("Transforms cannot be represented in JSON Schema")},Oa=(e,t,n,s)=>{const a=n,o=e._zod.def,{minimum:i,maximum:c}=e._zod.bag;typeof i=="number"&&(a.minItems=i),typeof c=="number"&&(a.maxItems=c),a.type="array",a.items=H(o.element,t,{...s,path:[...s.path,"items"]})},Ia=(e,t,n,s)=>{const a=e._zod.def,o=a.inclusive===!1,i=a.options.map((c,l)=>H(c,t,{...s,path:[...s.path,o?"oneOf":"anyOf",l]}));o?n.oneOf=i:n.anyOf=i},Aa=(e,t,n,s)=>{const a=e._zod.def,o=H(a.left,t,{...s,path:[...s.path,"allOf",0]}),i=H(a.right,t,{...s,path:[...s.path,"allOf",1]}),c=u=>"allOf"in u&&Object.keys(u).length===1,l=[...c(o)?o.allOf:[o],...c(i)?i.allOf:[i]];n.allOf=l},Da=(e,t,n,s)=>{const a=e._zod.def,o=H(a.innerType,t,s),i=t.seen.get(e);t.target==="openapi-3.0"?(i.ref=a.innerType,n.nullable=!0):n.anyOf=[o,{type:"null"}]},Ea=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType},Ra=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType,n.default=JSON.parse(JSON.stringify(a.defaultValue))},Fa=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType,t.io==="input"&&(n._prefault=JSON.parse(JSON.stringify(a.defaultValue)))},Za=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType;let i;try{i=a.catchValue(void 0)}catch{throw new Error("Dynamic catch values are not supported in JSON Schema")}n.default=i},La=(e,t,n,s)=>{const a=e._zod.def,o=t.io==="input"?a.in._zod.def.type==="transform"?a.out:a.in:a.out;H(o,t,s);const i=t.seen.get(e);i.ref=o},Ua=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType,n.readOnly=!0},Ha=(e,t,n,s)=>{const a=e._zod.def;H(a.innerType,t,s);const o=t.seen.get(e);o.ref=a.innerType},Va=h("ZodISODateTime",(e,t)=>{cs.init(e,t),E.init(e,t)});function Ba(e){return oa(Va,e)}const Wa=h("ZodISODate",(e,t)=>{ls.init(e,t),E.init(e,t)});function Ya(e){return ia(Wa,e)}const qa=h("ZodISOTime",(e,t)=>{us.init(e,t),E.init(e,t)});function Ja(e){return ca(qa,e)}const Ga=h("ZodISODuration",(e,t)=>{ds.init(e,t),E.init(e,t)});function Ka(e){return la(Ga,e)}const Qa=(e,t)=>{Wt.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:n=>tn(e,n)},flatten:{value:n=>en(e,n)},addIssue:{value:n=>{e.issues.push(n),e.message=JSON.stringify(e.issues,Le,2)}},addIssues:{value:n=>{e.issues.push(...n),e.message=JSON.stringify(e.issues,Le,2)}},isEmpty:{get(){return e.issues.length===0}}})},K=h("ZodError",Qa,{Parent:Error}),Xa=Xe(K),eo=et(K),to=$e(K),ro=ze(K),no=sn(K),so=an(K),ao=on(K),oo=cn(K),io=ln(K),co=un(K),lo=dn(K),uo=mn(K),V=h("ZodType",(e,t)=>(L.init(e,t),Object.assign(e["~standard"],{jsonSchema:{input:Te(e,"input"),output:Te(e,"output")}}),e.toJSONSchema=ka(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{value:t}),e.check=(...n)=>e.clone(Jr(t,{checks:[...t.checks??[],...n.map(s=>typeof s=="function"?{_zod:{check:s,def:{check:"custom"},onattach:[]}}:s)]})),e.clone=(n,s)=>Kr(e,n,s),e.brand=()=>e,e.register=((n,s)=>(n.add(e,s),e)),e.parse=(n,s)=>Xa(e,n,s,{callee:e.parse}),e.safeParse=(n,s)=>to(e,n,s),e.parseAsync=async(n,s)=>eo(e,n,s,{callee:e.parseAsync}),e.safeParseAsync=async(n,s)=>ro(e,n,s),e.spa=e.safeParseAsync,e.encode=(n,s)=>no(e,n,s),e.decode=(n,s)=>so(e,n,s),e.encodeAsync=async(n,s)=>ao(e,n,s),e.decodeAsync=async(n,s)=>oo(e,n,s),e.safeEncode=(n,s)=>io(e,n,s),e.safeDecode=(n,s)=>co(e,n,s),e.safeEncodeAsync=async(n,s)=>lo(e,n,s),e.safeDecodeAsync=async(n,s)=>uo(e,n,s),e.refine=(n,s)=>e.check(Xo(n,s)),e.superRefine=n=>e.check(ei(n)),e.overwrite=n=>e.check(ue(n)),e.optional=()=>kt(e),e.nullable=()=>Tt(e),e.nullish=()=>kt(Tt(e)),e.nonoptional=n=>Wo(e,n),e.array=()=>Mo(e),e.or=n=>Io([e,n]),e.and=n=>Do(e,n),e.transform=n=>Pt(e,Ro(n)),e.default=n=>Uo(e,n),e.prefault=n=>Vo(e,n),e.catch=n=>qo(e,n),e.pipe=n=>Pt(e,n),e.readonly=()=>Ko(e),e.describe=n=>{const s=e.clone();return ge.add(s,{description:n}),s},Object.defineProperty(e,"description",{get(){return ge.get(e)?.description},configurable:!0}),e.meta=(...n)=>{if(n.length===0)return ge.get(e);const s=e.clone();return ge.add(s,n[0]),s},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),ir=h("_ZodString",(e,t)=>{tt.init(e,t),V.init(e,t),e._zod.processJSONSchema=(s,a,o)=>Pa(e,s,a);const n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...s)=>e.check(ma(...s)),e.includes=(...s)=>e.check(fa(...s)),e.startsWith=(...s)=>e.check(ga(...s)),e.endsWith=(...s)=>e.check(xa(...s)),e.min=(...s)=>e.check(ke(...s)),e.max=(...s)=>e.check(rr(...s)),e.length=(...s)=>e.check(nr(...s)),e.nonempty=(...s)=>e.check(ke(1,...s)),e.lowercase=s=>e.check(ha(s)),e.uppercase=s=>e.check(pa(s)),e.trim=()=>e.check(va()),e.normalize=(...s)=>e.check(ba(...s)),e.toLowerCase=()=>e.check(ya()),e.toUpperCase=()=>e.check(_a()),e.slugify=()=>e.check(ja())}),mo=h("ZodString",(e,t)=>{tt.init(e,t),ir.init(e,t),e.email=n=>e.check(Fs(ho,n)),e.url=n=>e.check(Vs(po,n)),e.jwt=n=>e.check(aa(Po,n)),e.emoji=n=>e.check(Bs(fo,n)),e.guid=n=>e.check(_t(St,n)),e.uuid=n=>e.check(Zs(we,n)),e.uuidv4=n=>e.check(Ls(we,n)),e.uuidv6=n=>e.check(Us(we,n)),e.uuidv7=n=>e.check(Hs(we,n)),e.nanoid=n=>e.check(Ws(go,n)),e.guid=n=>e.check(_t(St,n)),e.cuid=n=>e.check(Ys(xo,n)),e.cuid2=n=>e.check(qs(bo,n)),e.ulid=n=>e.check(Js(vo,n)),e.base64=n=>e.check(ra(Co,n)),e.base64url=n=>e.check(na(ko,n)),e.xid=n=>e.check(Gs(yo,n)),e.ksuid=n=>e.check(Ks(_o,n)),e.ipv4=n=>e.check(Qs(jo,n)),e.ipv6=n=>e.check(Xs(wo,n)),e.cidrv4=n=>e.check(ea(No,n)),e.cidrv6=n=>e.check(ta(So,n)),e.e164=n=>e.check(sa(To,n)),e.datetime=n=>e.check(Ba(n)),e.date=n=>e.check(Ya(n)),e.time=n=>e.check(Ja(n)),e.duration=n=>e.check(Ka(n))});function ve(e){return Rs(mo,e)}const E=h("ZodStringFormat",(e,t)=>{A.init(e,t),ir.init(e,t)}),ho=h("ZodEmail",(e,t)=>{Xn.init(e,t),E.init(e,t)}),St=h("ZodGUID",(e,t)=>{Kn.init(e,t),E.init(e,t)}),we=h("ZodUUID",(e,t)=>{Qn.init(e,t),E.init(e,t)}),po=h("ZodURL",(e,t)=>{es.init(e,t),E.init(e,t)}),fo=h("ZodEmoji",(e,t)=>{ts.init(e,t),E.init(e,t)}),go=h("ZodNanoID",(e,t)=>{rs.init(e,t),E.init(e,t)}),xo=h("ZodCUID",(e,t)=>{ns.init(e,t),E.init(e,t)}),bo=h("ZodCUID2",(e,t)=>{ss.init(e,t),E.init(e,t)}),vo=h("ZodULID",(e,t)=>{as.init(e,t),E.init(e,t)}),yo=h("ZodXID",(e,t)=>{os.init(e,t),E.init(e,t)}),_o=h("ZodKSUID",(e,t)=>{is.init(e,t),E.init(e,t)}),jo=h("ZodIPv4",(e,t)=>{ms.init(e,t),E.init(e,t)}),wo=h("ZodIPv6",(e,t)=>{hs.init(e,t),E.init(e,t)}),No=h("ZodCIDRv4",(e,t)=>{ps.init(e,t),E.init(e,t)}),So=h("ZodCIDRv6",(e,t)=>{fs.init(e,t),E.init(e,t)}),Co=h("ZodBase64",(e,t)=>{gs.init(e,t),E.init(e,t)}),ko=h("ZodBase64URL",(e,t)=>{bs.init(e,t),E.init(e,t)}),To=h("ZodE164",(e,t)=>{vs.init(e,t),E.init(e,t)}),Po=h("ZodJWT",(e,t)=>{_s.init(e,t),E.init(e,t)}),cr=h("ZodNumber",(e,t)=>{tr.init(e,t),V.init(e,t),e._zod.processJSONSchema=(s,a,o)=>$a(e,s,a),e.gt=(s,a)=>e.check(wt(s,a)),e.gte=(s,a)=>e.check(De(s,a)),e.min=(s,a)=>e.check(De(s,a)),e.lt=(s,a)=>e.check(jt(s,a)),e.lte=(s,a)=>e.check(Ae(s,a)),e.max=(s,a)=>e.check(Ae(s,a)),e.int=s=>e.check(Ct(s)),e.safe=s=>e.check(Ct(s)),e.positive=s=>e.check(wt(0,s)),e.nonnegative=s=>e.check(De(0,s)),e.negative=s=>e.check(jt(0,s)),e.nonpositive=s=>e.check(Ae(0,s)),e.multipleOf=(s,a)=>e.check(Nt(s,a)),e.step=(s,a)=>e.check(Nt(s,a)),e.finite=()=>e;const n=e._zod.bag;e.minValue=Math.max(n.minimum??Number.NEGATIVE_INFINITY,n.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null,e.maxValue=Math.min(n.maximum??Number.POSITIVE_INFINITY,n.exclusiveMaximum??Number.POSITIVE_INFINITY)??null,e.isInt=(n.format??"").includes("int")||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null}),$o=h("ZodNumberFormat",(e,t)=>{js.init(e,t),cr.init(e,t)});function Ct(e){return da($o,e)}const zo=h("ZodArray",(e,t)=>{ws.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Oa(e,n,s,a),e.element=t.element,e.min=(n,s)=>e.check(ke(n,s)),e.nonempty=n=>e.check(ke(1,n)),e.max=(n,s)=>e.check(rr(n,s)),e.length=(n,s)=>e.check(nr(n,s)),e.unwrap=()=>e.element});function Mo(e,t){return wa(zo,e,t)}const Oo=h("ZodUnion",(e,t)=>{Ns.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ia(e,n,s,a),e.options=t.options});function Io(e,t){return new Oo({type:"union",options:e,...j(t)})}const Ao=h("ZodIntersection",(e,t)=>{Ss.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Aa(e,n,s,a)});function Do(e,t){return new Ao({type:"intersection",left:e,right:t})}const Eo=h("ZodTransform",(e,t)=>{Cs.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ma(e,n),e._zod.parse=(n,s)=>{if(s.direction==="backward")throw new Ut(e.constructor.name);n.addIssue=o=>{if(typeof o=="string")n.issues.push(xe(o,n.value,t));else{const i=o;i.fatal&&(i.continue=!1),i.code??(i.code="custom"),i.input??(i.input=n.value),i.inst??(i.inst=e),n.issues.push(xe(i))}};const a=t.transform(n.value,n);return a instanceof Promise?a.then(o=>(n.value=o,n)):(n.value=a,n)}});function Ro(e){return new Eo({type:"transform",transform:e})}const Fo=h("ZodOptional",(e,t)=>{ks.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ha(e,n,s,a),e.unwrap=()=>e._zod.def.innerType});function kt(e){return new Fo({type:"optional",innerType:e})}const Zo=h("ZodNullable",(e,t)=>{Ts.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Da(e,n,s,a),e.unwrap=()=>e._zod.def.innerType});function Tt(e){return new Zo({type:"nullable",innerType:e})}const Lo=h("ZodDefault",(e,t)=>{Ps.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ra(e,n,s,a),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Uo(e,t){return new Lo({type:"default",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Ho=h("ZodPrefault",(e,t)=>{$s.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Fa(e,n,s,a),e.unwrap=()=>e._zod.def.innerType});function Vo(e,t){return new Ho({type:"prefault",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Bo=h("ZodNonOptional",(e,t)=>{zs.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ea(e,n,s,a),e.unwrap=()=>e._zod.def.innerType});function Wo(e,t){return new Bo({type:"nonoptional",innerType:e,...j(t)})}const Yo=h("ZodCatch",(e,t)=>{Ms.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Za(e,n,s,a),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function qo(e,t){return new Yo({type:"catch",innerType:e,catchValue:typeof t=="function"?t:()=>t})}const Jo=h("ZodPipe",(e,t)=>{Os.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>La(e,n,s,a),e.in=t.in,e.out=t.out});function Pt(e,t){return new Jo({type:"pipe",in:e,out:t})}const Go=h("ZodReadonly",(e,t)=>{Is.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>Ua(e,n,s,a),e.unwrap=()=>e._zod.def.innerType});function Ko(e){return new Go({type:"readonly",innerType:e})}const Qo=h("ZodCustom",(e,t)=>{As.init(e,t),V.init(e,t),e._zod.processJSONSchema=(n,s,a)=>za(e,n)});function Xo(e,t={}){return Na(Qo,e,t)}function ei(e){return Sa(e)}function ee(e){return ua(cr,e)}const ti=/^[A-Za-z0-9\-_+ ]*$/,ri=/^([A-Za-z0-9 ()/._-]|%\d*[CYmdHMSqvQtwhf$]|%\d*\{[a-z_]+\})*$/,ni=/^[A-Za-z0-9 ()/._-]*$/,si=/^[A-Za-z0-9 _+.@^~<>,-]*$/;function rt(e){return e.includes("..")||e.includes("~/")}const $t=ve().max(64,"Name must be 64 characters or less").regex(ti,"Name can only contain letters, numbers, hyphens, underscores, plus signs, and spaces"),Ee=ve().max(255,"Filename must be 255 characters or less").regex(ri,"Filename contains invalid characters. Use letters, numbers, underscores, hyphens, strftime codes (%Y, %m, %d), and Motion tokens (%{movienbr}, %v, etc.)").refine(e=>!rt(e),{message:"Filename cannot contain directory traversal sequences (.. or ~/)"}),zt=ve().max(4096,"Path must be 4096 characters or less").regex(ni,"Path contains invalid characters").refine(e=>!rt(e),{message:"Path cannot contain directory traversal sequences (.. or ~/)"});ve().max(255,"Email must be 255 characters or less").regex(si,"Email contains invalid characters");const Mt=ee().int("Framerate must be a whole number").min(1,"Framerate must be at least 1").max(100,"Framerate cannot exceed 100"),Ot=ee().int("Quality must be a whole number").min(1,"Quality must be at least 1%").max(100,"Quality cannot exceed 100%"),ai=ee().int("Width must be a whole number").min(160,"Width must be at least 160 pixels").max(4096,"Width cannot exceed 4096 pixels"),oi=ee().int("Height must be a whole number").min(120,"Height must be at least 120 pixels").max(2160,"Height cannot exceed 2160 pixels"),It=ee().int("Port must be a whole number").min(1,"Port must be at least 1").max(65535,"Port cannot exceed 65535"),ii=ee().int("Threshold must be a whole number").min(1,"Threshold must be at least 1").max(2147483647,"Threshold is too large"),ci=ee().int("Noise level must be a whole number").min(0,"Noise level must be at least 0").max(255,"Noise level cannot exceed 255"),li=ee().int("Log level must be a whole number").min(1,"Log level must be at least 1").max(9,"Log level cannot exceed 9"),ui=ee().int("Device ID must be a whole number").min(1,"Device ID must be at least 1").max(999,"Device ID cannot exceed 999"),di=ee().int("Must be a whole number").min(0,"Must be 0 or greater");ve().transform(e=>{const t=e.toLowerCase();return t==="on"||t==="true"||t==="1"});function mi(e,t){const s={device_name:$t,camera_name:$t,device_id:ui,target_dir:zt,snapshot_filename:Ee,picture_filename:Ee,movie_filename:Ee,log_file:zt,framerate:Mt,stream_maxrate:Mt,width:ai,height:oi,stream_quality:Ot,picture_quality:Ot,stream_port:It,webcontrol_port:It,threshold:ii,noise_level:ci,minimum_motion_frames:di,log_level:li}[e];if(!s)return typeof t=="string"&&rt(t)?{success:!1,error:"Value cannot contain directory traversal sequences (.. or ~/)"}:{success:!0};const a=s.safeParse(t);return a.success?{success:!0}:{success:!1,error:a.error.issues[0]?.message??"Invalid value"}}async function hi(){return await Pe("/0/api/system/reboot",{})}async function pi(){return await Pe("/0/api/system/shutdown",{})}async function fi(){return await Pe("/0/api/system/service-restart",{})}function gi({config:e,onChange:t,getError:n,originalConfig:s,systemStatus:a}){const{addToast:o}=Ye(),i=a?.actions?.service??!1,c=a?.actions?.power??!1,l=(g,w="")=>e[g]?.value??w,u=(g,w="")=>s?.[g]?.value??w,d=g=>e[g]?.password_set===!0,m=g=>s?.[g]?.password_set===!0,p=async()=>{if(window.confirm("Are you sure you want to reboot the Pi? The system will restart and be unavailable for about a minute."))try{await hi(),o("Rebooting... The system will be back online shortly.","info")}catch(g){o(g.message||"Failed to reboot. Power control may be disabled in config.","error")}},f=async()=>{if(window.confirm("Are you sure you want to shutdown the Pi? You will need to physically power it back on."))try{await pi(),o("Shutting down... The system will power off.","warning")}catch(g){o(g.message||"Failed to shutdown. Power control may be disabled in config.","error")}},y=async()=>{if(window.confirm("Are you sure you want to restart the Motion service? Active streams will be interrupted briefly."))try{await fi(),o("Restarting Motion... Streams will resume shortly.","info")}catch(g){o(g.message||"Failed to restart service. Service control may be disabled in config.","error")}};return r.jsxs(r.Fragment,{children:[r.jsx(O,{title:"Device Controls",description:"Service and system power management",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"flex flex-col gap-4",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"Service Control"}),r.jsx("button",{onClick:y,disabled:!i,className:`px-4 py-2 rounded-lg text-sm transition-colors ${i?"bg-blue-600/20 text-blue-300 hover:bg-blue-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:i?void 0:"Enable with webcontrol_actions service=on",children:"Restart Motion"}),!i&&r.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",r.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions service=on"})," to enable"]})]}),r.jsxs("div",{children:[r.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"System Power"}),r.jsxs("div",{className:"flex gap-3",children:[r.jsx("button",{onClick:p,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-yellow-600/20 text-yellow-300 hover:bg-yellow-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Restart Pi"}),r.jsx("button",{onClick:f,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-red-600/20 text-red-300 hover:bg-red-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Shutdown Pi"})]}),!c&&r.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",r.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions power=on"})," to enable"]})]})]})}),r.jsxs(O,{title:"Authentication",description:"Web interface and stream access credentials",collapsible:!0,defaultOpen:!0,children:[r.jsxs("div",{className:"mb-6",children:[r.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Web Interface"}),r.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Credentials for logging into this web interface. Format: username:password"}),u("webcontrol_authentication","")===""&&u("webcontrol_user_authentication","")===""&&r.jsx("div",{className:"mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg",children:r.jsxs("div",{className:"flex items-start gap-2",children:[r.jsx("svg",{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})}),r.jsxs("div",{className:"flex-1",children:[r.jsx("p",{className:"text-sm font-medium text-blue-300 mb-1",children:"Initial Setup Available"}),r.jsxs("p",{className:"text-xs text-blue-300/80",children:["Configure authentication now to secure your Motion installation. During initial setup, you can set credentials without changing"," ",r.jsx("code",{className:"text-xs bg-surface px-1 rounded",children:"webcontrol_parms"})," in the config file. Once authentication is configured, it will require restart to apply."]})]})]})}),r.jsxs("div",{className:"mb-4",children:[r.jsx("label",{className:"block text-sm font-medium mb-1 text-gray-300",children:"Admin Username"}),r.jsx("input",{type:"text",value:"admin",disabled:!0,className:`w-full px-3 py-2 bg-surface-elevated border border-gray-700 rounded-lg + text-gray-500 cursor-not-allowed`}),r.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Admin username is fixed for security"})]}),r.jsx(S,{label:"Admin Password",value:String(l("webcontrol_authentication","")).split(":")[1]||"",onChange:g=>{const w=String(l("webcontrol_authentication","")).split(":")[0]||"admin";t("webcontrol_authentication",`${w}:${g}`)},type:"password",placeholder:d("webcontrol_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_authentication")?"Password is configured. Enter a new password to change it.":"Administrator password (click eye icon to reveal)",originalValue:m("webcontrol_authentication")?"[set]":""}),r.jsx(S,{label:"Viewer Username",value:String(l("webcontrol_user_authentication","")).split(":")[0]||"",onChange:g=>{const w=String(l("webcontrol_user_authentication","")).split(":")[1]||"";t("webcontrol_user_authentication",g?`${g}:${w}`:"")},helpText:"View-only username (can view streams but not change settings)",error:n?.("webcontrol_user_authentication"),originalValue:String(u("webcontrol_user_authentication","")).split(":")[0]||"",showVisibilityToggle:!1}),r.jsx(S,{label:"Viewer Password",value:String(l("webcontrol_user_authentication","")).split(":")[1]||"",onChange:g=>{const w=String(l("webcontrol_user_authentication","")).split(":")[0]||"";t("webcontrol_user_authentication",w?`${w}:${g}`:"")},type:"password",placeholder:d("webcontrol_user_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_user_authentication")?"Password is configured. Enter a new password to change it.":"View-only password (click eye icon to reveal)",originalValue:m("webcontrol_user_authentication")?"[set]":""})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Direct Stream Access"}),r.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Authentication for direct stream URLs (embedded in websites, VLC, home automation)"}),r.jsx(R,{label:"Authentication Mode",value:String(l("webcontrol_auth_method","0")),onChange:g=>t("webcontrol_auth_method",g),options:[{value:"0",label:"None - No authentication required"},{value:"1",label:"Basic - Simple username/password (use with HTTPS)"},{value:"2",label:"Digest - Secure hash-based (recommended)"}],helpText:"Controls authentication for direct stream access and external API clients. The web UI uses session-based login instead.",error:n?.("webcontrol_auth_method")})]})]}),r.jsxs(O,{title:"Daemon",description:"Motion process settings",collapsible:!0,defaultOpen:!1,children:[r.jsx(Y,{label:"Run as Daemon",value:l("daemon",!1),onChange:g=>t("daemon",g),helpText:"Run Motion in background mode"}),r.jsx(S,{label:"PID File",value:String(l("pid_file","")),onChange:g=>t("pid_file",g),helpText:"Path to process ID file. Leave empty to let systemd manage the PID.",error:n?.("pid_file"),originalValue:String(u("pid_file",""))}),r.jsx(S,{label:"Log File",value:String(l("log_file","")),onChange:g=>t("log_file",g),helpText:"Path to log file. Leave empty to use journald (view with: journalctl -u motion).",error:n?.("log_file"),originalValue:String(u("log_file",""))}),r.jsx(R,{label:"Log Level",value:String(l("log_level","6")),onChange:g=>t("log_level",g),options:[{value:"1",label:"Emergency"},{value:"2",label:"Alert"},{value:"3",label:"Critical"},{value:"4",label:"Error"},{value:"5",label:"Warning"},{value:"6",label:"Notice"},{value:"7",label:"Info"},{value:"8",label:"Debug"},{value:"9",label:"All"}],helpText:"Verbosity level for logging",error:n?.("log_level")})]}),r.jsxs(O,{title:"Web Server",description:"API server configuration",collapsible:!0,defaultOpen:!1,children:[r.jsx(S,{label:"Port",value:String(l("webcontrol_port","8080")),onChange:g=>t("webcontrol_port",g),type:"number",helpText:"Primary web server port",error:n?.("webcontrol_port"),originalValue:String(u("webcontrol_port","8080"))}),r.jsx(Y,{label:"Localhost Only",value:l("webcontrol_localhost",!1),onChange:g=>t("webcontrol_localhost",g),helpText:"Restrict access to localhost only (127.0.0.1)"}),r.jsx(Y,{label:"TLS/HTTPS",value:l("webcontrol_tls",!1),onChange:g=>t("webcontrol_tls",g),helpText:"Enable HTTPS encryption"}),l("webcontrol_tls",!1)&&r.jsxs(r.Fragment,{children:[r.jsx(S,{label:"TLS Certificate",value:String(l("webcontrol_cert","")),onChange:g=>t("webcontrol_cert",g),helpText:"Path to TLS certificate file (.crt or .pem)",error:n?.("webcontrol_cert")}),r.jsx(S,{label:"TLS Private Key",value:String(l("webcontrol_key","")),onChange:g=>t("webcontrol_key",g),helpText:"Path to TLS private key file (.key or .pem)",error:n?.("webcontrol_key")})]})]})]})}function xi({config:e,onChange:t,getError:n}){const s=(m,p="")=>e[m]?.value??p,a=Number(s("width",640)),o=Number(s("height",480)),i=at(a,o),c=st.some(m=>m.width===a&&m.height===o),l=m=>{if(m==="custom")return;const{width:p,height:f}=Pr(m);t("width",p),t("height",f)},u=m=>{t("width",Number(m))},d=m=>{t("height",Number(m))};return r.jsxs(O,{title:"Device Settings",description:"Basic camera configuration and identification",collapsible:!0,defaultOpen:!1,children:[r.jsx(S,{label:"Camera Name",value:String(s("device_name","")),onChange:m=>t("device_name",m),placeholder:"My Camera",helpText:"Friendly name for this camera",error:n?.("device_name")}),r.jsx(R,{label:"Resolution",value:c?i:"custom",onChange:l,options:[...st.map(m=>({value:at(m.width,m.height),label:m.label})),{value:"custom",label:"Custom"}],helpText:"Video resolution (width x height)"}),!c&&r.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[r.jsx(S,{label:"Width",value:String(a),onChange:u,type:"number",helpText:"Custom width in pixels",error:n?.("width")}),r.jsx(S,{label:"Height",value:String(o),onChange:d,type:"number",helpText:"Custom height in pixels",error:n?.("height")})]}),r.jsx(P,{label:"Framerate",value:Number(s("framerate",15)),onChange:m=>t("framerate",m),min:2,max:30,unit:" fps",helpText:"Frames per second (higher uses more CPU)",error:n?.("framerate")}),r.jsx(R,{label:"Rotation",value:String(s("rotate",0)),onChange:m=>t("rotate",Number(m)),options:Tr.map(m=>({value:String(m.value),label:m.label})),helpText:"Rotate camera image"})]})}function bi({onClose:e,detectedCameras:t}){const[n,s]=x.useState("select"),[a,o]=x.useState(null),[i,c]=x.useState(!1),[l,u]=x.useState(""),[d,m]=x.useState(0),[p,f]=x.useState(0),[y,g]=x.useState(0),[w,_]=x.useState(""),[v,$]=x.useState(""),[B,Z]=x.useState(""),D=gr(),W=xr(),de=b=>{o(b),u(b.device_name),m(b.default_width),f(b.default_height),g(b.default_fps),s("configure")},T=()=>{c(!0),u("Network Camera"),m(1920),f(1080),g(15),s("configure")},C=async()=>{w&&W.mutate({url:w,user:v||void 0,pass:B||void 0,timeout:10},{onSuccess:b=>{b.status==="ok"&&s("complete")}})},z=()=>{const b={type:i?"netcam":a.type,device_id:i?w:a.device_id,device_path:i?w:a.device_path,device_name:l,sensor_model:a?.sensor_model,width:d,height:p,fps:y};D.mutate(b,{onSuccess:()=>{s("complete"),setTimeout(()=>{e()},2e3)}})};return r.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4",children:r.jsxs("div",{className:"bg-background rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col",children:[r.jsxs("div",{className:"p-6 border-b border-secondary/20",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("h2",{className:"text-xl font-semibold text-primary",children:"Add Camera"}),r.jsx("button",{onClick:e,className:"text-secondary hover:text-primary transition-colors",children:"✕"})]}),r.jsx("div",{className:"mt-4 flex items-center gap-2",children:["select","configure",i?"test":null,"complete"].filter(Boolean).map((b,F,Q)=>r.jsxs("div",{className:"flex items-center flex-1",children:[r.jsx("div",{className:`flex-1 h-1 rounded ${Q.indexOf(n)>=F?"bg-accent":"bg-secondary/20"}`}),F0&&r.jsx("div",{className:"space-y-2",children:t.map((b,F)=>r.jsx("button",{onClick:()=>de(b),className:"w-full p-4 text-left bg-secondary/10 rounded-lg border border-secondary/20 hover:border-accent transition-colors",children:r.jsxs("div",{className:"flex items-center gap-3",children:[r.jsx("div",{className:"text-2xl",children:b.type==="libcam"?"🎥":"📹"}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:"font-medium text-primary",children:b.device_name}),r.jsxs("div",{className:"text-xs text-secondary",children:[b.sensor_model&&`${b.sensor_model} • `,b.default_width,"x",b.default_height," @ ",b.default_fps,"fps"]})]})]})},`${b.device_id}-${F}`))}),r.jsx("button",{onClick:T,className:"w-full p-4 text-left bg-secondary/10 rounded-lg border border-secondary/20 hover:border-accent transition-colors",children:r.jsxs("div",{className:"flex items-center gap-3",children:[r.jsx("div",{className:"text-2xl",children:"🌐"}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:"font-medium text-primary",children:"Add Network Camera"}),r.jsx("div",{className:"text-xs text-secondary",children:"RTSP, HTTP, or other network camera URL"})]})]})}),t.length===0&&r.jsx("p",{className:"text-sm text-secondary text-center py-4",children:"No cameras detected. Add a network camera manually or connect a camera."})]}),n==="configure"&&r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{children:[r.jsx("h3",{className:"text-lg font-medium text-primary mb-2",children:"Configure Camera"}),r.jsx("p",{className:"text-sm text-secondary",children:"Set camera name and capture settings"})]}),r.jsxs("div",{className:"space-y-3",children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Camera Name"}),r.jsx("input",{type:"text",value:l,onChange:b=>u(b.target.value),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent",placeholder:"e.g., Front Door"})]}),i&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Camera URL *"}),r.jsx("input",{type:"text",value:w,onChange:b=>_(b.target.value),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent font-mono text-sm",placeholder:"rtsp://192.168.1.100:554/stream"})]}),r.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Username (optional)"}),r.jsx("input",{type:"text",value:v,onChange:b=>$(b.target.value),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent",placeholder:"admin"})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Password (optional)"}),r.jsx("input",{type:"password",value:B,onChange:b=>Z(b.target.value),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent",placeholder:"••••••"})]})]})]}),r.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Width"}),r.jsx("input",{type:"number",value:d,onChange:b=>m(parseInt(b.target.value)),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent"})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"Height"}),r.jsx("input",{type:"number",value:p,onChange:b=>f(parseInt(b.target.value)),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent"})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium text-primary mb-1",children:"FPS"}),r.jsx("input",{type:"number",value:y,onChange:b=>g(parseInt(b.target.value)),className:"w-full px-3 py-2 bg-secondary/10 border border-secondary/20 rounded-md text-primary focus:outline-none focus:border-accent"})]})]})]})]}),n==="test"&&i&&r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{children:[r.jsx("h3",{className:"text-lg font-medium text-primary mb-2",children:"Test Connection"}),r.jsx("p",{className:"text-sm text-secondary",children:"Verify that the camera URL is accessible"})]}),r.jsx("div",{className:"p-4 bg-secondary/10 rounded-lg",children:r.jsx("div",{className:"text-sm font-mono text-primary break-all",children:w})}),W.isError&&r.jsx("div",{className:"p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-sm text-red-600 dark:text-red-400",children:"Connection test failed. Check the URL and credentials."}),W.isSuccess&&W.data.status==="ok"&&r.jsx("div",{className:"p-3 bg-green-500/10 border border-green-500/20 rounded-lg text-sm text-green-600 dark:text-green-400",children:"✓ Connection successful!"})]}),n==="complete"&&r.jsxs("div",{className:"text-center space-y-4 py-8",children:[r.jsx("div",{className:"text-6xl",children:"✓"}),r.jsx("h3",{className:"text-lg font-medium text-primary",children:"Camera Added!"}),r.jsxs("p",{className:"text-sm text-secondary",children:[l," has been added to your configuration."]})]})]}),n!=="complete"&&r.jsxs("div",{className:"p-6 border-t border-secondary/20 flex justify-between",children:[r.jsx("button",{onClick:()=>{n==="configure"?s("select"):n==="test"?s("configure"):e()},className:"px-4 py-2 text-sm bg-secondary/20 text-primary rounded-md hover:bg-secondary/30 transition-colors",children:"Back"}),r.jsx("button",{onClick:()=>{n==="configure"?i?s("test"):z():n==="test"&&C()},disabled:n==="configure"&&(!l||!d||!p||!y)||n==="configure"&&i&&!w||n==="test"&&W.isPending||D.isPending,className:"px-4 py-2 text-sm bg-accent text-white rounded-md hover:bg-accent/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:n==="test"&&W.isPending?"Testing...":n==="test"?"Test Connection":D.isPending?"Adding...":n==="configure"&&!i?"Add Camera":"Next"})]})]})})}function vi({camera:e,onAdd:t}){const n={libcam:"Pi Camera (libcamera)",v4l2:"USB Camera (V4L2)",netcam:"Network Camera",unknown:"Unknown Camera"}[e.type],s={libcam:"🎥",v4l2:"📹",netcam:"🌐",unknown:"❓"}[e.type];return r.jsx("div",{className:"p-4 bg-secondary/10 rounded-lg border border-secondary/20 hover:border-accent/50 transition-colors",children:r.jsxs("div",{className:"flex items-start justify-between",children:[r.jsxs("div",{className:"flex items-start gap-3 flex-1",children:[r.jsx("div",{className:"text-2xl",children:s}),r.jsxs("div",{className:"flex-1 min-w-0",children:[r.jsxs("div",{className:"flex items-center gap-2",children:[r.jsx("h5",{className:"font-medium text-primary",children:e.device_name}),r.jsx("span",{className:"text-xs px-2 py-0.5 bg-accent/20 text-accent rounded",children:n})]}),r.jsxs("dl",{className:"mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs",children:[e.sensor_model&&r.jsxs(r.Fragment,{children:[r.jsx("dt",{className:"text-secondary",children:"Sensor:"}),r.jsx("dd",{className:"text-primary font-mono",children:e.sensor_model})]}),r.jsx("dt",{className:"text-secondary",children:"Device:"}),r.jsx("dd",{className:"text-primary font-mono truncate",title:e.device_path,children:e.device_path}),r.jsx("dt",{className:"text-secondary",children:"Default:"}),r.jsxs("dd",{className:"text-primary",children:[e.default_width,"x",e.default_height," @ ",e.default_fps,"fps"]})]}),e.resolutions.length>0&&r.jsxs("details",{className:"mt-2",children:[r.jsxs("summary",{className:"text-xs text-secondary cursor-pointer hover:text-primary",children:["Available resolutions (",e.resolutions.length,")"]}),r.jsxs("div",{className:"mt-1 flex flex-wrap gap-1",children:[e.resolutions.slice(0,10).map(([a,o],i)=>r.jsxs("span",{className:"text-xs px-1.5 py-0.5 bg-secondary/10 rounded",children:[a,"x",o]},i)),e.resolutions.length>10&&r.jsxs("span",{className:"text-xs text-secondary",children:["+",e.resolutions.length-10," more"]})]})]})]})]}),r.jsx("button",{onClick:t,className:"ml-4 px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/90 transition-colors whitespace-nowrap",children:"Add Camera"})]})})}function yi({camera:e}){const[t,n]=x.useState(!1),s=br(),a=()=>{s.mutate({camId:e.id},{onSuccess:()=>{n(!1)}})};return r.jsx("div",{className:"p-4 bg-secondary/10 rounded-lg border border-secondary/20",children:r.jsxs("div",{className:"flex items-start justify-between",children:[r.jsxs("div",{className:"flex items-start gap-3 flex-1",children:[r.jsx("div",{className:"text-2xl",children:"✅"}),r.jsxs("div",{className:"flex-1 min-w-0",children:[r.jsxs("div",{className:"flex items-center gap-2",children:[r.jsx("h5",{className:"font-medium text-primary",children:e.name}),r.jsx("span",{className:"text-xs px-2 py-0.5 bg-green-500/20 text-green-600 dark:text-green-400 rounded",children:"Active"})]}),r.jsxs("dl",{className:"mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs",children:[r.jsx("dt",{className:"text-secondary",children:"Camera ID:"}),r.jsx("dd",{className:"text-primary font-mono",children:e.id}),e.width&&e.height&&r.jsxs(r.Fragment,{children:[r.jsx("dt",{className:"text-secondary",children:"Resolution:"}),r.jsxs("dd",{className:"text-primary",children:[e.width,"x",e.height]})]}),r.jsx("dt",{className:"text-secondary",children:"Stream URL:"}),r.jsx("dd",{className:"text-primary font-mono truncate",children:r.jsx("a",{href:e.url,target:"_blank",rel:"noopener noreferrer",className:"hover:text-accent",children:e.url})})]})]})]}),r.jsxs("div",{className:"ml-4 flex gap-2",children:[r.jsx("a",{href:`/camera/${e.id}`,className:"px-3 py-1.5 text-sm bg-secondary/20 text-primary rounded hover:bg-secondary/30 transition-colors",children:"View"}),t?r.jsxs("div",{className:"flex gap-1",children:[r.jsx("button",{onClick:a,disabled:s.isPending,className:"px-3 py-1.5 text-sm bg-red-500 text-white rounded hover:bg-red-600 transition-colors disabled:opacity-50",children:s.isPending?"Removing...":"Confirm"}),r.jsx("button",{onClick:()=>n(!1),className:"px-3 py-1.5 text-sm bg-secondary/20 text-primary rounded hover:bg-secondary/30 transition-colors",children:"Cancel"})]}):r.jsx("button",{onClick:()=>n(!0),className:"px-3 py-1.5 text-sm bg-red-500/20 text-red-600 dark:text-red-400 rounded hover:bg-red-500/30 transition-colors",children:"Remove"})]})]})})}function _i(){const[e,t]=x.useState(!1),{data:n=[],isLoading:s}=vr(),{data:a,isLoading:o}=yr(),{data:i,isLoading:c,refetch:l}=_r(),u=i?.cameras||[],d=u.length>0;return s||o?r.jsx("div",{className:"p-6 bg-secondary/20 rounded-lg",children:r.jsx("p",{className:"text-secondary",children:"Loading camera information..."})}):r.jsxs("div",{className:"space-y-6",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsxs("div",{children:[r.jsx("h3",{className:"text-lg font-semibold text-primary",children:"Camera Management"}),r.jsxs("p",{className:"text-sm text-secondary",children:["Add, remove, and configure cameras",a?.is_raspberry_pi&&` • ${a.pi_model}`]})]}),r.jsx("button",{onClick:()=>{l(),t(!0)},className:"px-4 py-2 bg-accent text-white rounded-md hover:bg-accent/90 transition-colors",children:"Add Camera"})]}),n.length===0&&r.jsxs("div",{className:"p-8 bg-primary/10 rounded-lg text-center space-y-4",children:[r.jsx("div",{className:"text-4xl",children:"📷"}),r.jsx("h3",{className:"text-xl font-semibold text-primary",children:"Welcome to Motion"}),r.jsx("p",{className:"text-secondary max-w-md mx-auto",children:"Get started by adding your first camera. Motion will automatically detect connected cameras or you can manually configure a network camera."}),r.jsx("button",{onClick:()=>{l(),t(!0)},className:"px-6 py-3 bg-accent text-white rounded-md hover:bg-accent/90 transition-colors font-medium",children:"Add Your First Camera"})]}),n.length>0&&r.jsxs("div",{className:"space-y-3",children:[r.jsx("h4",{className:"text-sm font-medium text-secondary",children:"Configured Cameras"}),r.jsx("div",{className:"grid gap-3",children:n.map(m=>r.jsx(yi,{camera:m},m.id))})]}),!c&&d&&r.jsxs("div",{className:"space-y-3",children:[r.jsxs("h4",{className:"text-sm font-medium text-secondary",children:["Available Cameras (",u.length,")"]}),r.jsx("p",{className:"text-xs text-secondary",children:"These cameras were detected but not yet configured"}),r.jsx("div",{className:"grid gap-3",children:u.map((m,p)=>r.jsx(vi,{camera:m,onAdd:()=>t(!0)},`${m.device_id}-${p}`))})]}),e&&r.jsx(bi,{onClose:()=>t(!1),detectedCameras:u,platformInfo:a})]})}function lr(e){const{data:t,isLoading:n}=Ft();return x.useMemo(()=>{if(n||!t?.status)return{isLoading:!0,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const s=`cam${e}`,a=t.status[s];if(!a)return{isLoading:!1,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const o=a.camera_type??"unknown";return{isLoading:!1,cameraType:o,cameraDevice:a.camera_device??"",isConnected:!a.lost_connection,features:{hasLibcamControls:o==="libcam",hasV4L2Controls:o==="v4l2",hasNetcamConfig:o==="netcam",hasDualStream:o==="netcam"&&a.has_high_stream===!0,supportsPassthrough:o==="netcam"},...o==="libcam"&&{libcamCapabilities:a.supportedControls},...o==="v4l2"&&{v4l2Controls:a.v4l2_controls},...o==="netcam"&&{netcamStatus:a.netcam_status}}},[t,e,n])}function ji({cameraId:e}){const{cameraType:t,cameraDevice:n,isConnected:s}=lr(e);return r.jsx(O,{title:"Camera Source",description:"Camera connection and type information",collapsible:!0,defaultOpen:!0,children:r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{className:"flex items-center gap-3",children:[r.jsx("span",{className:"text-sm text-gray-400",children:"Type:"}),r.jsx(wi,{type:t})]}),r.jsxs("div",{className:"flex items-start gap-3",children:[r.jsx("span",{className:"text-sm text-gray-400 pt-0.5",children:"Device:"}),n?r.jsx("div",{className:"flex-1",children:r.jsx("code",{className:"text-sm text-gray-200 bg-gray-800 px-2 py-1 rounded",children:n})}):r.jsx("span",{className:"text-sm text-gray-500 italic",children:"Not configured"})]}),r.jsxs("div",{className:"flex items-center gap-3",children:[r.jsx("span",{className:"text-sm text-gray-400",children:"Status:"}),r.jsx(Ni,{isConnected:s})]}),t==="unknown"&&r.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[r.jsx("p",{className:"text-sm text-gray-300 mb-2",children:r.jsx("strong",{children:"No camera configured"})}),r.jsx("p",{className:"text-sm text-gray-400 mb-3",children:"Configure a camera by setting one of the following:"}),r.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[r.jsxs("li",{children:[r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"libcam_device"})," - Raspberry Pi camera"]}),r.jsxs("li",{children:[r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"v4l2_device"})," - USB webcam (e.g., /dev/video0)"]}),r.jsxs("li",{children:[r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"netcam_url"})," - IP camera (RTSP/HTTP URL)"]})]})]})]})})}function wi({type:e}){const t={libcam:"bg-purple-500/20 text-purple-300 border-purple-500/30",v4l2:"bg-blue-500/20 text-blue-300 border-blue-500/30",netcam:"bg-green-500/20 text-green-300 border-green-500/30",unknown:"bg-gray-500/20 text-gray-400 border-gray-500/30"},n={libcam:"libcamera (Pi Camera)",v4l2:"V4L2 (USB Camera)",netcam:"Network Camera (IP)",unknown:"Not Configured"};return r.jsx("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${t[e]}`,children:n[e]})}function Ni({isConnected:e}){return r.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${e?"bg-green-500/20 text-green-300 border-green-500/30":"bg-red-500/20 text-red-300 border-red-500/30"}`,children:[r.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e?"bg-green-400":"bg-red-400"}`}),e?"Connected":"Disconnected"]})}function Si({config:e,onChange:t,getError:n,capabilities:s,originalConfig:a}){const o=(l,u="")=>e[l]?.value??u,i=(l,u="")=>a?.[l]?.value??u,c=!!o("libcam_awb_enable",!1);return r.jsxs(O,{title:"libcamera Controls",description:"Raspberry Pi camera controls (libcamera only)",collapsible:!0,defaultOpen:!1,children:[r.jsx(P,{label:"Brightness",value:Number(o("libcam_brightness",0)),onChange:l=>t("libcam_brightness",l),min:-1,max:1,step:.1,helpText:"Brightness adjustment (-1.0 to 1.0)",error:n?.("libcam_brightness")}),r.jsx(P,{label:"Contrast",value:Number(o("libcam_contrast",1)),onChange:l=>t("libcam_contrast",l),min:0,max:32,step:.5,helpText:"Contrast adjustment (0.0 to 32.0)",error:n?.("libcam_contrast")}),r.jsx(P,{label:"Gain (ISO)",value:Number(o("libcam_gain",1)),onChange:l=>t("libcam_gain",l),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)",error:n?.("libcam_gain")}),r.jsx(Y,{label:"Auto White Balance",value:c,onChange:l=>t("libcam_awb_enable",l),helpText:"Enable automatic white balance"}),c&&r.jsxs(r.Fragment,{children:[r.jsx(R,{label:"AWB Mode",value:String(o("libcam_awb_mode",0)),onChange:l=>t("libcam_awb_mode",Number(l)),options:$r.map(l=>({value:String(l.value),label:l.label})),helpText:"White balance mode"}),s?.AwbLocked!==!1&&r.jsx(Y,{label:"Lock AWB",value:!!o("libcam_awb_locked",!1),onChange:l=>t("libcam_awb_locked",l),helpText:"Lock white balance settings"})]}),!c&&r.jsxs(r.Fragment,{children:[s?.ColourTemperature!==!1&&r.jsx(P,{label:"Color Temperature",value:Number(o("libcam_colour_temp",0)),onChange:l=>t("libcam_colour_temp",l),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)",error:n?.("libcam_colour_temp")}),r.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[r.jsx(P,{label:"Red Gain",value:Number(o("libcam_colour_gain_r",1)),onChange:l=>t("libcam_colour_gain_r",l),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)",error:n?.("libcam_colour_gain_r")}),r.jsx(P,{label:"Blue Gain",value:Number(o("libcam_colour_gain_b",1)),onChange:l=>t("libcam_colour_gain_b",l),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)",error:n?.("libcam_colour_gain_b")})]}),s?.ColourTemperature===!1&&r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[r.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),s?.AfMode?r.jsxs(r.Fragment,{children:[r.jsx(R,{label:"Autofocus Mode",value:String(o("libcam_af_mode",0)),onChange:l=>t("libcam_af_mode",Number(l)),options:zr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus control mode"}),Number(o("libcam_af_mode",0))===0&&s?.LensPosition&&r.jsx(P,{label:"Lens Position",value:Number(o("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),Number(o("libcam_af_mode",0))>0&&r.jsxs(r.Fragment,{children:[s?.AfRange&&r.jsx(R,{label:"Autofocus Range",value:String(o("libcam_af_range",0)),onChange:l=>t("libcam_af_range",Number(l)),options:Mr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus range preference"}),s?.AfSpeed&&r.jsx(R,{label:"Autofocus Speed",value:String(o("libcam_af_speed",0)),onChange:l=>t("libcam_af_speed",Number(l)),options:Or.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus adjustment speed"})]})]}):s!==void 0?r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[r.jsx("strong",{children:"Autofocus:"})," Not supported on this camera.",s?.LensPosition?r.jsx("span",{children:" Manual focus (lens position) is available."}):r.jsx("span",{children:" This camera has fixed focus."})]}):null,!s?.AfMode&&s?.LensPosition&&r.jsx(P,{label:"Lens Position",value:Number(o("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:n?.("libcam_lens_position")}),r.jsx(S,{label:"Buffer Count",value:String(o("libcam_buffer_count",4)),onChange:l=>t("libcam_buffer_count",Number(l)),type:"number",helpText:"Frame buffers for capture (2-8). Higher values reduce frame drops under load but use more memory. Default 4 works for most setups; increase to 6-8 if seeing drops at high framerates.",error:n?.("libcam_buffer_count"),originalValue:String(i("libcam_buffer_count",4))})]})}function Ci({config:e,onChange:t,controls:n,getError:s}){if(!n||n.length===0)return r.jsx(O,{title:"Camera Controls",description:"USB camera settings",collapsible:!0,defaultOpen:!1,children:r.jsx("p",{className:"text-gray-400 text-sm",children:"No controls available for this camera."})});const a=Pi(n);return r.jsx(O,{title:"Camera Controls",description:"USB camera settings (V4L2)",collapsible:!0,defaultOpen:!1,children:Object.entries(a).map(([o,i])=>r.jsxs("div",{className:"space-y-4",children:[o!=="Other"&&r.jsx("h4",{className:"text-sm font-medium text-gray-300 mt-4 first:mt-0",children:o}),i.map(c=>r.jsx(ki,{control:c,value:Ti(e,c),onChange:l=>t(`v4l2_${c.id}`,l),error:s?.(`v4l2_${c.id}`)},c.id))]},o))})}function ki({control:e,value:t,onChange:n,error:s}){switch(e.type){case"boolean":return r.jsx(Y,{label:e.name,value:!!t,onChange:n,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`});case"menu":return r.jsx(R,{label:e.name,value:String(t),onChange:a=>n(Number(a)),options:e.menuItems?.map(a=>({value:String(a.value),label:a.label}))??[],helpText:`Default: ${e.default}`,error:s});default:return r.jsx(P,{label:e.name,value:Number(t),onChange:n,min:e.min,max:e.max,step:e.step??1,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`,error:s})}}function Ti(e,t){const n=`v4l2_${t.id}`,s=e[n]?.value;return s!==void 0?t.type==="boolean"?!!s:Number(s):t.type==="boolean"?!!t.current:t.current}function Pi(e){const t={"Image Quality":[],"Exposure & Gain":[],"White Balance":[],Focus:[],Other:[]},n={"Image Quality":["brightness","contrast","saturation","hue","sharpness","gamma"],"Exposure & Gain":["exposure","gain","iso","backlight"],"White Balance":["white","balance","color","colour","temperature"],Focus:["focus","zoom","pan","tilt","lens"]};for(const s of e){const a=s.name.toLowerCase();let o=!1;for(const[i,c]of Object.entries(n))if(c.some(l=>a.includes(l))){t[i].push(s),o=!0;break}o||t.Other.push(s)}return Object.fromEntries(Object.entries(t).filter(([s,a])=>a.length>0))}function $i({config:e,onChange:t,connectionStatus:n,hasDualStream:s,getError:a}){const o=(i,c="")=>e[i]?.value??c;return r.jsx(O,{title:"Network Camera",description:"IP camera connection settings (RTSP/HTTP)",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[n&&r.jsxs("div",{className:"flex items-center gap-3 pb-2",children:[r.jsx("span",{className:"text-sm text-gray-400",children:"Connection:"}),r.jsx(zi,{status:n})]}),r.jsx(S,{label:"Stream URL",value:String(o("netcam_url","")),onChange:i=>t("netcam_url",i),placeholder:"rtsp://192.168.1.100:554/stream",helpText:"RTSP, HTTP, HTTPS, or file:// URL for the camera stream",error:a?.("netcam_url")}),r.jsx(S,{label:"Credentials",value:String(o("netcam_userpass","")),onChange:i=>t("netcam_userpass",i),type:"password",placeholder:"username:password",helpText:"Leave empty if camera doesn't require authentication",error:a?.("netcam_userpass")}),r.jsx(S,{label:"FFmpeg Parameters",value:String(o("netcam_params","")),onChange:i=>t("netcam_params",i),placeholder:"-rtsp_transport tcp",helpText:"Advanced: Custom FFmpeg input options (e.g., -rtsp_transport tcp)",error:a?.("netcam_params")}),s&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"border-t border-gray-700 my-4 pt-4",children:[r.jsx("h4",{className:"text-sm font-medium text-gray-300 mb-3",children:"High Resolution Stream"}),r.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Optional secondary stream for higher quality recordings while using lower resolution for motion detection."})]}),r.jsx(S,{label:"High-Res URL",value:String(o("netcam_high_url","")),onChange:i=>t("netcam_high_url",i),placeholder:"rtsp://192.168.1.100:554/stream1",helpText:"Optional: Higher resolution stream for recordings",error:a?.("netcam_high_url")}),r.jsx(S,{label:"High-Res FFmpeg Parameters",value:String(o("netcam_high_params","")),onChange:i=>t("netcam_high_params",i),placeholder:"-rtsp_transport tcp",helpText:"FFmpeg parameters for high-resolution stream",error:a?.("netcam_high_params")})]}),r.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[r.jsx("p",{className:"text-sm text-gray-300 mb-2",children:r.jsx("strong",{children:"Supported Protocols"})}),r.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[r.jsxs("li",{children:[r.jsx("strong",{children:"RTSP:"})," ",r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"rtsp://"})," - Most IP cameras"]}),r.jsxs("li",{children:[r.jsx("strong",{children:"HTTP:"})," ",r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"http://"})," - MJPEG streams"]}),r.jsxs("li",{children:[r.jsx("strong",{children:"HTTPS:"})," ",r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"https://"})," - Secure streams"]}),r.jsxs("li",{children:[r.jsx("strong",{children:"File:"})," ",r.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"file://"})," - Local video files"]})]})]})]})})}function zi({status:e}){const t={connected:{color:"bg-green-500/20 text-green-300 border-green-500/30",text:"Connected"},reading:{color:"bg-blue-500/20 text-blue-300 border-blue-500/30",text:"Reading"},not_connected:{color:"bg-red-500/20 text-red-300 border-red-500/30",text:"Not Connected"},reconnecting:{color:"bg-yellow-500/20 text-yellow-300 border-yellow-500/30",text:"Reconnecting"},unknown:{color:"bg-gray-500/20 text-gray-400 border-gray-500/30",text:"Unknown"}},{color:n,text:s}=t[e]||t.unknown;return r.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${n}`,children:[r.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e==="connected"?"bg-green-400":e==="reading"?"bg-blue-400 animate-pulse":e==="reconnecting"?"bg-yellow-400 animate-pulse":"bg-red-400"}`}),s]})}function Mi({config:e,onChange:t,getError:n}){const s=(_,v="")=>e[_]?.value??v,a=String(s("text_left","")),o=String(s("text_right","")),i=ot(a),c=ot(o),[l,u]=x.useState(i==="custom"?a:""),[d,m]=x.useState(c==="custom"?o:""),p=_=>{_==="custom"?t("text_left",l):t("text_left",it(_))},f=_=>{_==="custom"?t("text_right",d):t("text_right",it(_))},y=_=>{u(_),t("text_left",_)},g=_=>{m(_),t("text_right",_)},w=[{value:"disabled",label:"Disabled"},{value:"camera-name",label:"Camera Name"},{value:"timestamp",label:"Timestamp"},{value:"custom",label:"Custom Text"}];return r.jsxs(O,{title:"Text Overlay",description:"Add text overlays to video frames",collapsible:!0,defaultOpen:!1,children:[r.jsx(R,{label:"Left Text",value:i,onChange:p,options:w,helpText:"Text displayed in top-left corner"}),i==="custom"&&r.jsx(S,{label:"Custom Left Text",value:l,onChange:y,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_left")}),r.jsx(R,{label:"Right Text",value:c,onChange:f,options:w,helpText:"Text displayed in top-right corner"}),c==="custom"&&r.jsx(S,{label:"Custom Right Text",value:d,onChange:g,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:n?.("text_right")}),r.jsx(P,{label:"Text Scale",value:Number(s("text_scale",1)),onChange:_=>t("text_scale",_),min:1,max:10,unit:"x",helpText:"Text size multiplier (1-10)",error:n?.("text_scale")})]})}const Oi=[{value:"100",label:"Full (100%)"},{value:"75",label:"High (75%)"},{value:"50",label:"Medium (50%)"},{value:"25",label:"Low (25%)"},{value:"10",label:"Minimal (10%)"}];function Ii({config:e,onChange:t,getError:n}){const s=(o,i="")=>e[o]?.value??i,a=!s("stream_localhost",!1);return r.jsxs(O,{title:"Video Streaming",description:"Live MJPEG stream configuration",collapsible:!0,defaultOpen:!1,children:[r.jsx(Y,{label:"Enable Video Streaming",value:a,onChange:o=>t("stream_localhost",!o),helpText:"Enable/disable live MJPEG streaming. When disabled, stream is only accessible from localhost."}),a&&r.jsxs(r.Fragment,{children:[r.jsx(R,{label:"Streaming Resolution",value:String(s("stream_preview_scale",100)),onChange:o=>t("stream_preview_scale",Number(o)),options:Oi,helpText:"Scale stream as percentage of source resolution. Lower = less bandwidth and CPU."}),r.jsx(P,{label:"Stream Quality",value:Number(s("stream_quality",50)),onChange:o=>t("stream_quality",o),min:1,max:100,unit:"%",helpText:"JPEG compression quality (1-100). Higher = better quality, more bandwidth.",error:n?.("stream_quality")}),r.jsx(P,{label:"Stream Max Framerate",value:Number(s("stream_maxrate",15)),onChange:o=>t("stream_maxrate",o),min:1,max:30,unit:" fps",helpText:"Maximum frames per second (lower = less bandwidth and CPU)",error:n?.("stream_maxrate")}),r.jsx(Y,{label:"Show Motion Boxes",value:!!s("stream_motion",!1),onChange:o=>t("stream_motion",o),helpText:"Display motion detection boxes in stream"}),r.jsx(R,{label:"Direct Stream Access Security",value:String(s("webcontrol_auth_method",0)),onChange:o=>t("webcontrol_auth_method",Number(o)),options:Ir.map(o=>({value:String(o.value),label:o.label})),helpText:"Authentication when streams are accessed directly (embedded in other websites, VLC, home automation). None = open access on trusted networks only. Basic = use with HTTPS. Digest = recommended."}),r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-4",children:[r.jsxs("p",{children:[r.jsx("strong",{children:"Stream URL:"})," ",r.jsx("code",{children:"http://[hostname]:[port]/[cam]/mjpg/stream"})]}),r.jsxs("p",{className:"mt-1",children:[r.jsx("strong",{children:"Note:"})," Streaming resolution scales the output to reduce bandwidth and CPU usage. Server-side resizing is always performed by Motion."]})]})]})]})}function Ai({config:e,onChange:t,getError:n}){const s=(d,m="")=>e[d]?.value??m,a=Number(s("width",640)),o=Number(s("height",480)),i=Number(s("threshold",1500)),c=Ar(i,a,o),l=d=>{const m=Number(d),p=Er(m,a,o);t("threshold",p)},u=[{value:"",label:"Off"},{value:"EedDl",label:"Light"},{value:"EedDl",label:"Medium (default)"},{value:"EedDl",label:"Heavy"}];return r.jsx(O,{title:"Motion Detection",description:"Configure motion detection sensitivity and behavior",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsx(P,{label:"Threshold",value:c,onChange:d=>l(String(d)),min:0,max:20,step:.1,unit:"%",helpText:`Percentage of frame that must change (${i} pixels at ${a}x${o}). Higher = less sensitive.`,error:n?.("threshold")}),r.jsx(S,{label:"Threshold Maximum",value:String(s("threshold_maximum",0)),onChange:d=>t("threshold_maximum",Number(d)),type:"number",helpText:"Maximum threshold for auto-tuning (0 = disabled)",error:n?.("threshold_maximum")}),r.jsx(Y,{label:"Auto-tune Threshold",value:s("threshold_tune",!1),onChange:d=>t("threshold_tune",d),helpText:"Automatically adjust threshold based on noise levels"}),r.jsx(Y,{label:"Auto-tune Noise Level",value:s("noise_tune",!1),onChange:d=>t("noise_tune",d),helpText:"Automatically determine optimal noise level"}),r.jsx(P,{label:"Noise Level",value:Number(s("noise_level",32)),onChange:d=>t("noise_level",d),min:1,max:255,helpText:"Noise tolerance (1-255). Lower values detect smaller motions.",error:n?.("noise_level")}),r.jsx(P,{label:"Light Switch Detection",value:Number(s("lightswitch_percent",0)),onChange:d=>t("lightswitch_percent",d),min:0,max:100,unit:"%",helpText:"Ignore sudden brightness changes (0 = disabled). Prevents false triggers from lights turning on/off.",error:n?.("lightswitch_percent")}),r.jsx(R,{label:"Despeckle Filter",value:String(s("despeckle_filter","")),onChange:d=>t("despeckle_filter",d),options:u,helpText:"Remove noise speckles from motion detection"}),r.jsx(P,{label:"Smart Mask Speed",value:Number(s("smart_mask_speed",0)),onChange:d=>t("smart_mask_speed",d),min:0,max:10,helpText:"Auto-mask static areas (0 = disabled, 1-10 = speed). Higher values adapt faster to static objects.",error:n?.("smart_mask_speed")}),r.jsx(R,{label:"Locate Motion Mode",value:String(s("locate_motion_mode","off")),onChange:d=>t("locate_motion_mode",d),options:Dr,helpText:"Draw box around motion area. 'Preview' = stream only, 'On' = saved images, 'Both' = both."}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"Event Timing"}),r.jsx(S,{label:"Event Gap (seconds)",value:String(s("event_gap",60)),onChange:d=>t("event_gap",Number(d)),type:"number",min:"0",helpText:"Seconds of no motion before ending an event. Prevents splitting continuous motion into multiple events.",error:n?.("event_gap")}),r.jsx(S,{label:"Pre-Capture (frames)",value:String(s("pre_capture",0)),onChange:d=>t("pre_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture before motion detected. Uses CPU/memory to buffer frames.",error:n?.("pre_capture")}),r.jsx(S,{label:"Post-Capture (frames)",value:String(s("post_capture",0)),onChange:d=>t("post_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture after motion stops",error:n?.("post_capture")}),r.jsx(S,{label:"Minimum Motion Frames",value:String(s("minimum_motion_frames",1)),onChange:d=>t("minimum_motion_frames",Number(d)),type:"number",min:"1",helpText:"Consecutive frames with motion required to trigger event. Filters brief false positives.",error:n?.("minimum_motion_frames")})]})]})})}function Di({config:e,onChange:t,getError:n}){const s=(p,f="")=>e[p]?.value??f,a=String(s("picture_output","off")),o=Number(s("snapshot_interval",0)),i=Rr(a,o),[c,l]=x.useState(i),u=p=>{l(p);const f=Fr(p);t("picture_output",f.picture_output),f.snapshot_interval!==void 0&&t("snapshot_interval",f.snapshot_interval)},d=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered (all frames)"},{value:"motion-triggered-one",label:"Motion Triggered (first frame only)"},{value:"best",label:"Best Quality Frame"},{value:"center",label:"Center Frame"},{value:"interval-snapshots",label:"Interval Snapshots"},{value:"manual",label:"Manual Only"}],m=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return r.jsx(O,{title:"Picture Settings",description:"Configure picture capture and snapshots",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsx(R,{label:"Capture Mode",value:c,onChange:u,options:d,helpText:"When to capture still images during motion events"}),r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[r.jsx("strong",{children:"Current settings:"})," picture_output=",a,o>0&&`, snapshot_interval=${o}s`]}),c==="motion-triggered"&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"text-xs text-yellow-300 bg-yellow-900/30 border border-yellow-700 p-3 rounded",children:[r.jsx("strong",{children:"Warning:"})," This mode captures every frame during motion. At 15fps, continuous motion can generate 900+ pictures per minute. Configure limits below to prevent runaway capture."]}),r.jsx(S,{label:"Max Pictures Per Event",value:String(s("picture_max_per_event",0)),onChange:p=>t("picture_max_per_event",Number(p)),type:"number",min:"0",max:"100000",helpText:"Maximum pictures per motion event (0 = unlimited)",error:n?.("picture_max_per_event")}),r.jsx(S,{label:"Min Interval Between Pictures (ms)",value:String(s("picture_min_interval",0)),onChange:p=>t("picture_min_interval",Number(p)),type:"number",min:"0",max:"60000",helpText:"Minimum milliseconds between captures (0 = no limit). 1000ms = 1 picture/second.",error:n?.("picture_min_interval")})]}),c==="interval-snapshots"&&r.jsx(S,{label:"Snapshot Interval (seconds)",value:String(s("snapshot_interval",60)),onChange:p=>t("snapshot_interval",Number(p)),type:"number",min:"1",helpText:"Seconds between snapshots (independent of motion)",error:n?.("snapshot_interval")}),r.jsx(P,{label:"Picture Quality",value:Number(s("picture_quality",75)),onChange:p=>t("picture_quality",p),min:1,max:100,unit:"%",helpText:"JPEG quality (1-100). Higher = better quality, larger files.",error:n?.("picture_quality")}),r.jsx(S,{label:"Picture Filename Pattern",value:String(s("picture_filename","%Y%m%d%H%M%S-%q")),onChange:p=>t("picture_filename",p),helpText:`Format codes: ${m}`,error:n?.("picture_filename")}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),r.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[r.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y-%m-%d/%H%M%S-%q"})," → ",r.jsx("code",{children:"2025-01-29/143022-05.jpg"})]}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y/%m/%d/%H%M%S"})," → ",r.jsx("code",{children:"2025/01/29/143022.jpg"})]}),r.jsxs("p",{children:[r.jsx("code",{children:"%$/%Y-%m-%d/%H%M%S"})," → ",r.jsx("code",{children:"Camera1/2025-01-29/143022.jpg"})]}),r.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y%m%d%H%M%S-%q"})," → ",r.jsx("code",{children:"20250129143022-05.jpg"})]}),r.jsxs("p",{className:"mt-2",children:["Available codes: ",m]}),r.jsxs("p",{className:"mt-2 text-yellow-200",children:[r.jsx("strong",{children:"Tip:"})," Using date-based folders like ",r.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]})]})})}function Ei(){return qe({queryKey:["deviceInfo"],queryFn:async()=>{const e=await fetch("/0/api/system/status");if(!e.ok)throw new Error("Failed to fetch device info");return e.json()},staleTime:6e4,retry:1})}function At(e){return e?.pi_generation===5}function Ri(e){return e?.pi_generation===4}function pe(e){return e?.hardware_encoders?.h264_v4l2m2m===!0}function Fi(e,t=70){return(e?.temperature?.celsius??0)>t}function Zi({config:e,onChange:t,getError:n,showPassthrough:s=!0}){const{data:a}=Ei(),o=(v,$="")=>e[v]?.value??$,i=o("movie_output",!1),c=o("movie_output_motion",!1),l=o("emulate_motion",!1),u=Zr(i,c,l),[d,m]=x.useState(u),p=v=>{m(v);const $=Ur(v);t("movie_output",$.movie_output),$.movie_output_motion!==void 0&&t("movie_output_motion",$.movie_output_motion),$.emulate_motion!==void 0&&t("emulate_motion",$.emulate_motion)},f=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered"},{value:"continuous",label:"Continuous Recording"}],y=["%Y - Year","%m - Month","%d - Day","%H - Hour","%M - Minute","%S - Second","%v - Event number","%$ - Camera name"].join(", "),g=String(o("movie_container","mp4")),w=()=>!a||pe(a)?ct:ct.filter(v=>!re(v.value)),_=()=>!(o("movie_passthrough",!1)||re(g)||g==="webm");return r.jsx(O,{title:"Movie Settings",description:"Configure video recording settings",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsx(R,{label:"Recording Mode",value:d,onChange:p,options:f,helpText:"When to record video. Motion Triggered = only during events, Continuous = always record."}),r.jsx(P,{label:"Movie Quality",value:Number(o("movie_quality",75)),onChange:v=>t("movie_quality",v),min:1,max:100,unit:"%",helpText:"Video encoding quality (1-100). Higher = better quality, larger files, more CPU.",error:n?.("movie_quality")}),r.jsx(S,{label:"Movie Filename Pattern",value:String(o("movie_filename","%Y%m%d%H%M%S")),onChange:v=>t("movie_filename",v),helpText:`Format codes: ${y}`,error:n?.("movie_filename")}),r.jsx(R,{label:"Container Format",value:String(o("movie_container","mp4")),onChange:v=>t("movie_container",v),options:w(),helpText:"Video container format. Hardware encoding requires v4l2m2m support."}),re(g)&&pe(a)&&r.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"Hardware Encoding Active:"})," Using h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%."]}),a&&!pe(a)&&r.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"Hardware Encoding Not Available:"})," This device does not have a hardware H.264 encoder.",At(a)&&" Pi 5 does not include a hardware encoder."," ","Hardware encoding options (h264_v4l2m2m) are hidden. Using software encoding (~40-70% CPU)."]}),re(g)&&!a&&r.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"Hardware Encoding:"})," Uses h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%. Only available on devices with v4l2m2m support."]}),Ie(g)&&r.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"High CPU Warning:"})," H.265/HEVC software encoding uses 80-100% CPU on Raspberry Pi. Not recommended for continuous recording. Consider H.264 for better performance."]}),pe(a)&&!re(g)&&!o("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&r.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"Hardware Encoding Available:"}),' This device has a hardware H.264 encoder. Select "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" to reduce CPU from ~40-70% to ~10%.']}),!a&&!re(g)&&!o("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-2",children:[r.jsx("strong",{children:"Tip:"}),' If your device has hardware encoding support (e.g., Raspberry Pi 4), consider selecting "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" for ~10% CPU instead of ~40-70% with software encoding.']}),g==="webm"&&r.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[r.jsx("strong",{children:"WebM Format:"})," Uses VP8 codec, optimized for web streaming. Encoder preset setting does not apply to VP8."]}),_()&&r.jsx(R,{label:"Encoder Preset",value:String(o("movie_encoder_preset","medium")),onChange:v=>t("movie_encoder_preset",v),options:Lr.map(v=>({value:v.value,label:v.label})),helpText:"Tradeoff between CPU usage and video quality. Lower presets use less CPU but produce lower quality video. Requires restart to take effect."}),r.jsx(S,{label:"Max Duration (seconds)",value:String(o("movie_max_time",0)),onChange:v=>t("movie_max_time",Number(v)),type:"number",min:"0",helpText:"Maximum movie length (0 = unlimited). Splits long events into multiple files.",error:n?.("movie_max_time")}),s&&r.jsx(Y,{label:"Passthrough Mode",value:o("movie_passthrough",!1),onChange:v=>t("movie_passthrough",v),helpText:"Copy codec without re-encoding (NETCAM only). Reduces CPU but may cause compatibility issues."}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),r.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[r.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y-%m-%d/%H%M%S"})," → ",r.jsx("code",{children:"2025-01-29/143022.mkv"})]}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y/%m/%d/%v-%H%M%S"})," → ",r.jsx("code",{children:"2025/01/29/42-143022.mkv"})]}),r.jsxs("p",{children:[r.jsx("code",{children:"%$/%Y-%m-%d/%v"})," → ",r.jsx("code",{children:"Camera1/2025-01-29/42.mkv"})]}),r.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),r.jsxs("p",{children:[r.jsx("code",{children:"%Y%m%d%H%M%S"})," → ",r.jsx("code",{children:"20250129143022.mkv"})]}),r.jsxs("p",{className:"mt-2",children:["Available codes: ",y]}),r.jsxs("p",{className:"mt-2 text-yellow-200",children:[r.jsx("strong",{children:"Tip:"})," Using date-based folders like ",r.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]}),d==="continuous"&&At(a)&&!o("movie_passthrough",!1)&&r.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded",children:[r.jsx("strong",{children:"Pi 5 CPU Warning:"})," Pi 5 does not have a hardware H.264 encoder. Continuous recording uses software encoding (~35-60% CPU constant).",r.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[r.jsx("li",{children:'Use encoder preset "Ultrafast" to reduce CPU by ~30%'}),r.jsx("li",{children:"Add active cooling (fan) to prevent thermal throttling"}),r.jsx("li",{children:"Enable passthrough if source is already H.264"})]})]}),d==="continuous"&&Ri(a)&&pe(a)&&r.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded",children:[r.jsx("strong",{children:"Continuous Recording on Pi 4:"})," Camera will record 24/7.",re(g)?r.jsx("span",{children:" Using hardware encoder - expect ~10% CPU usage."}):o("movie_passthrough",!1)?r.jsx("span",{children:" Passthrough mode enabled - expect ~5-10% CPU usage."}):r.jsx("span",{children:" Consider using hardware encoder (MKV/MP4 H.264 Hardware) for ~10% CPU instead of ~40-70%."})]}),d==="continuous"&&!a&&r.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[r.jsx("strong",{children:"Continuous Recording:"})," Camera will record 24/7 regardless of motion. Expected CPU usage on Raspberry Pi:",r.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[r.jsxs("li",{children:[r.jsx("strong",{children:"Pi 4 with hardware encoder:"})," ~10% CPU"]}),r.jsxs("li",{children:[r.jsx("strong",{children:"Pi 5 or Pi 4 software encoding:"})," ~35-60% CPU depending on preset"]}),r.jsxs("li",{children:[r.jsx("strong",{children:"Passthrough mode:"})," ~5-10% CPU (if source is H.264)"]})]})]}),Fi(a)&&r.jsxs("div",{className:"text-xs text-red-400 bg-red-950/30 p-3 rounded",children:[r.jsx("strong",{children:"High Temperature Warning:"})," Device is running at ",a?.temperature?.celsius.toFixed(1),"°C. Consider reducing encoding quality or adding active cooling."]})]})})}function Li({config:e,onChange:t,getError:n,originalConfig:s}){const a=(c,l="")=>e[c]?.value??l,o=(c,l="")=>s?.[c]?.value??l,i=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return r.jsx(O,{title:"Storage",description:"Base directory and periodic snapshot settings for this camera",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsx(S,{label:"Base Storage Directory",value:String(a("target_dir","/var/lib/motion")),onChange:c=>t("target_dir",c),helpText:"Root directory for ALL camera files. Picture and movie filename patterns (configured in their sections) create paths relative to this directory.",error:n?.("target_dir"),originalValue:String(o("target_dir","/var/lib/motion"))}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"Filename Patterns"}),r.jsx(S,{label:"Snapshot Filename",value:String(a("snapshot_filename","%Y%m%d%H%M%S-snapshot")),onChange:c=>t("snapshot_filename",c),helpText:"Format for periodic snapshot filenames (strftime syntax)",error:n?.("snapshot_filename")}),r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[r.jsxs("p",{children:[r.jsx("strong",{children:"Example:"})," ",r.jsx("code",{children:"%Y%m%d%H%M%S-snapshot"})," → ",r.jsx("code",{children:"20250129143022-snapshot.jpg"})]}),r.jsxs("p",{children:[r.jsx("strong",{children:"With subdirs:"})," ",r.jsx("code",{children:"%$/%Y-%m-%d/snapshot-%H%M%S"})," → ",r.jsx("code",{children:"Camera1/2025-01-29/snapshot-143022.jpg"})]})]})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Format Code Reference"}),r.jsxs("div",{className:"text-xs text-gray-400 space-y-1",children:[r.jsxs("p",{children:["Available codes: ",i]}),r.jsxs("p",{className:"mt-2 text-blue-200",children:[r.jsx("strong",{children:"How it works:"})," The Base Storage Directory above sets where files go. Picture and Movie sections set filename patterns (which can include subdirectories like ",r.jsx("code",{children:"%Y-%m-%d/"}),")."]})]})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"File Cleanup (Future)"}),r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[r.jsx("p",{children:"Automatic file retention and cleanup based on age/size will be available in a future update."}),r.jsxs("p",{className:"mt-2",children:["For now, use ",r.jsx("code",{children:"cleandir_params"})," in the Motion configuration file or manual cleanup scripts."]})]})]}),r.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[r.jsx("strong",{children:"💡 Network Storage:"})," For network shares (NFS, SMB), ensure target_dir points to a mounted directory. Test write permissions before starting recording."]})]})})}const te=["sun","mon","tue","wed","thu","fri","sat"],Dt={sun:{full:"Sunday",short:"Sun"},mon:{full:"Monday",short:"Mon"},tue:{full:"Tuesday",short:"Tue"},wed:{full:"Wednesday",short:"Wed"},thu:{full:"Thursday",short:"Thu"},fri:{full:"Friday",short:"Fri"},sat:{full:"Saturday",short:"Sat"}},ne=96,be=15;function ur(){return{sun:new Set,mon:new Set,tue:new Set,wed:new Set,thu:new Set,fri:new Set,sat:new Set}}function Ui(e){const t=new Map,n=e.trim().split(/\s+/);for(const s of n){const a=s.indexOf("=");if(a===-1)continue;const o=s.slice(0,a).toLowerCase(),i=s.slice(a+1);t.has(o)||t.set(o,[]),t.get(o).push(i)}return t}function Hi(e){if(e.length!==9||e[4]!=="-")return[];const t=parseInt(e.slice(0,2),10),n=parseInt(e.slice(2,4),10),s=parseInt(e.slice(5,7),10),a=parseInt(e.slice(7,9),10);if(isNaN(t)||isNaN(n)||isNaN(s)||isNaN(a)||t<0||t>23||n<0||n>59||s<0||s>23||a<0||a>59)return[];const o=t*4+Math.floor(n/be),i=s*4+Math.floor(a/be),c=[];for(let l=o;l<=i&&lc-l),n=[];let s=t[0],a=t[0];for(let c=1;cEt(e[i],e.sun))&&e.sun.size>0){const i=Ne(Array.from(e.sun));for(const c of i)s.push(`sun-sat=${Se(c)}`);return s.join(" ")}if(["mon","tue","wed","thu","fri"].every(i=>Et(e[i],e.mon))&&e.mon.size>0){const i=Ne(Array.from(e.mon));for(const c of i)s.push(`mon-fri=${Se(c)}`);for(const c of["sat","sun"])if(e[c].size>0){const l=Ne(Array.from(e[c]));for(const u of l)s.push(`${c}=${Se(u)}`)}return s.join(" ")}for(const i of te){if(e[i].size===0)continue;const c=Ne(Array.from(e[i]));for(const l of c)s.push(`${i}=${Se(l)}`)}return s.join(" ")}function Et(e,t){if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0}function Bi(e,t){const n=Be(e),s=Ve(t),a=(o,i)=>{const c=o===0?12:o>12?o-12:o,l=o<12?"am":"pm",u=i===0?"":`:${String(i).padStart(2,"0")}`;return`${c}${u}${l}`};return`${a(n.hour,n.min)} - ${a(s.hour,s.min)}`}function Wi(e){return te.every(t=>e[t].size===0)}function Yi(e){const t=te.filter(s=>e[s].size>0);return t.length===0?"No time ranges selected":t.length===7?"All days configured":t.map(s=>s.charAt(0).toUpperCase()+s.slice(1,3)).join(", ")}function qi({value:e,onChange:t}){const{schedule:n,defaultOn:s,action:a}=x.useMemo(()=>Vi(e),[e]),o=x.useCallback(f=>{const y=Re(f,s,a);t(y)},[t,s,a]),i=x.useCallback(f=>{const y=Fe(n);y[f].size===ne?y[f]=new Set:y[f]=new Set(Array.from({length:ne},(g,w)=>w)),o(y)},[n,o]),c=x.useCallback((f,y,g,w)=>{const _=Fe(n),v=new Set(n[f]),[$,B]=y<=g?[y,g]:[g,y];for(let Z=$;Z<=B;Z++)w?v.add(Z):v.delete(Z);_[f]=v,o(_)},[n,o]),l=x.useCallback(f=>{const y=Fe(n);y[f]=new Set,o(y)},[n,o]),u=x.useCallback(()=>{o(ur())},[o]),d=x.useCallback(f=>{const y=Re(n,f,a);t(y)},[n,a,t]),m=x.useCallback(f=>{const y=Re(n,s,f);t(y)},[n,s,t]),p=x.useCallback(f=>{t(f)},[t]);return{schedule:n,defaultOn:s,action:a,updateSchedule:o,toggleDay:i,setRange:c,clearDay:l,clearAll:u,setDefaultOn:d,setAction:m,applyPreset:p}}function Fe(e){return{sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)}}const Ji=x.memo(function({isSelected:t,isInDragRange:n,isDragSelect:s,isHourBoundary:a,onPointerDown:o,onPointerEnter:i}){let c;n?c=s?"bg-primary/70":"bg-surface-hover":t?c="bg-primary":c="bg-surface";const l=a?"border-t border-gray-700/50":"";return r.jsx("div",{className:`h-[6px] ${c} ${l} cursor-pointer transition-colors duration-75`,onPointerDown:o,onPointerEnter:i})}),Gi=x.memo(function({day:t,schedule:n,dragState:s,onPointerDown:a,onPointerMove:o}){const i=x.useMemo(()=>{if(!s.isDragging||s.startDay!==t)return null;const l=s.startIndex,u=s.currentIndex;return{from:Math.min(l,u),to:Math.max(l,u)}},[s.isDragging,s.startDay,s.startIndex,s.currentIndex,t]),c=x.useMemo(()=>Array.from({length:ne},(l,u)=>({isSelected:n.has(u),isInDragRange:i!==null&&u>=i.from&&u<=i.to,isHourBoundary:u%4===0})),[n,i]);return r.jsx("div",{className:"flex flex-col",children:c.map((l,u)=>r.jsx(Ji,{index:u,isSelected:l.isSelected,isInDragRange:l.isInDragRange,isDragSelect:s.selectMode,isHourBoundary:l.isHourBoundary,onPointerDown:()=>a(u),onPointerEnter:()=>o(u)},u))})}),Ki=[0,2,4,6,8,10,12,14,16,18,20,22];function Qi(e){return e===0?"12a":e===12?"12p":e<12?`${e}a`:`${e-12}p`}const Xi=x.memo(function(){return r.jsx("div",{className:"flex flex-col pr-1 text-xs text-gray-500 select-none",children:Ki.map(t=>r.jsx("div",{className:"flex items-start justify-end",style:{height:"48px"},children:r.jsx("span",{className:"-mt-1.5",children:Qi(t)})},t))})}),Ce={isDragging:!1,startDay:null,startIndex:null,currentIndex:null,selectMode:!0};function ec({schedule:e,onScheduleChange:t,disabled:n=!1}){const[s,a]=x.useState(Ce),o=x.useRef(null),i=x.useCallback((p,f)=>{if(n)return;const y=e[p].has(f);a({isDragging:!0,startDay:p,startIndex:f,currentIndex:f,selectMode:!y})},[e,n]),c=x.useCallback((p,f)=>{!s.isDragging||p!==s.startDay||a(y=>({...y,currentIndex:f}))},[s.isDragging,s.startDay]),l=x.useCallback(()=>{if(!s.isDragging||s.startDay===null||s.startIndex===null||s.currentIndex===null){a(Ce);return}const p={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)},f=new Set(e[s.startDay]),[y,g]=s.startIndex<=s.currentIndex?[s.startIndex,s.currentIndex]:[s.currentIndex,s.startIndex];for(let w=y;w<=g;w++)s.selectMode?f.add(w):f.delete(w);p[s.startDay]=f,t(p),a(Ce)},[s,e,t]);x.useEffect(()=>{const p=f=>{f.key==="Escape"&&s.isDragging&&a(Ce)};return window.addEventListener("keydown",p),()=>window.removeEventListener("keydown",p)},[s.isDragging]),x.useEffect(()=>(s.isDragging?(document.body.style.touchAction="none",document.body.style.userSelect="none"):(document.body.style.touchAction="",document.body.style.userSelect=""),()=>{document.body.style.touchAction="",document.body.style.userSelect=""}),[s.isDragging]);const u=x.useCallback(p=>{if(n)return;const f={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)};f[p].size===ne?f[p]=new Set:f[p]=new Set(Array.from({length:ne},(y,g)=>g)),t(f)},[e,t,n]),m=(()=>{if(!s.isDragging||s.startIndex===null||s.currentIndex===null)return null;const p=Math.min(s.startIndex,s.currentIndex),f=Math.max(s.startIndex,s.currentIndex);return Bi(p,f)})();return r.jsxs("div",{className:"select-none",children:[r.jsxs("div",{className:"flex mb-1",children:[r.jsx("div",{className:"w-8 shrink-0"}),r.jsx("div",{className:"grid grid-cols-7 gap-px flex-1",children:te.map(p=>{const f=e[p].size===ne,y=e[p].size>0;return r.jsxs("button",{type:"button",onClick:()=>u(p),disabled:n,className:`text-xs font-medium py-1 rounded-t transition-colors ${f?"bg-primary text-white":y?"bg-primary/30 text-primary":"bg-surface-elevated text-gray-400 hover:bg-surface-hover"} ${n?"cursor-not-allowed opacity-50":"cursor-pointer"}`,title:`Click to ${f?"clear":"select all"} ${Dt[p].full}`,children:[r.jsx("span",{className:"hidden sm:inline",children:Dt[p].short}),r.jsx("span",{className:"sm:hidden",children:p.charAt(0).toUpperCase()})]},p)})})]}),r.jsxs("div",{className:"flex",children:[r.jsx("div",{className:"w-8 shrink-0",children:r.jsx(Xi,{})}),r.jsx("div",{ref:o,className:`grid grid-cols-7 gap-px bg-surface-elevated flex-1 rounded ${n?"opacity-50":""}`,style:{touchAction:"none"},onPointerUp:l,onPointerLeave:l,onPointerCancel:l,children:te.map(p=>r.jsx(Gi,{day:p,schedule:e[p],dragState:s,onPointerDown:f=>i(p,f),onPointerMove:f=>c(p,f)},p))})]}),m&&r.jsx("div",{className:"mt-2 text-center",children:r.jsxs("span",{className:"text-xs bg-surface-elevated px-2 py-1 rounded text-gray-300",children:[s.selectMode?"Selecting":"Deselecting",":"," ",r.jsx("span",{className:"text-white font-medium",children:m})]})}),r.jsx("div",{className:"mt-2 text-xs text-gray-500 text-center",children:"Click and drag to select time ranges. Click day header to toggle entire day."})]})}const tc=[{label:"Business Hours",value:"default=true action=pause mon-fri=0900-1700",description:"Pause Mon-Fri 9am-5pm"},{label:"Night Watch",value:"default=false action=pause sun-sat=1800-2359 sun-sat=0000-0600",description:"Active 6pm-6am only"},{label:"Weekends Only",value:"default=false action=pause sat=0000-2359 sun=0000-2359",description:"Active Sat & Sun only"},{label:"Always On",value:"default=true action=pause",description:"No schedule restrictions"}],rc=x.memo(function({defaultOn:t,action:n,onDefaultOnChange:s,onActionChange:a,onApplyPreset:o,onClearAll:i,disabled:c=!1}){return r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Detection Default"}),r.jsxs("select",{value:t?"on":"off",onChange:l=>s(l.target.value==="on"),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[r.jsx("option",{value:"on",children:"On by default"}),r.jsx("option",{value:"off",children:"Off by default"})]}),r.jsx("p",{className:"text-xs text-gray-500 mt-1",children:t?"Schedule defines when detection is paused":"Schedule defines when detection is active"})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Schedule Action"}),r.jsxs("select",{value:n,onChange:l=>a(l.target.value),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[r.jsx("option",{value:"pause",children:"Pause detection"}),r.jsx("option",{value:"stop",children:"Stop camera"})]}),r.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n==="pause"?"Camera runs but ignores motion":"Camera completely stops during schedule"})]})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-xs text-gray-400 mb-2",children:"Quick Presets"}),r.jsxs("div",{className:"flex flex-wrap gap-2",children:[tc.map(l=>r.jsx("button",{type:"button",onClick:()=>o(l.value),disabled:c,className:"px-3 py-1.5 text-xs bg-surface-elevated hover:bg-surface-hover border border-gray-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:l.description,children:l.label},l.label)),r.jsx("button",{type:"button",onClick:i,disabled:c,className:"px-3 py-1.5 text-xs bg-danger/20 hover:bg-danger/30 text-danger border border-danger/30 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:"Clear all time ranges",children:"Clear All"})]})]})]})});function Rt({value:e,onChange:t,helpText:n,error:s,disabled:a=!1}){const{schedule:o,defaultOn:i,action:c,updateSchedule:l,setDefaultOn:u,setAction:d,applyPreset:m,clearAll:p}=qi({value:e,onChange:t}),f=Wi(o),y=Yi(o);return r.jsxs("div",{className:"space-y-4",children:[r.jsx(rc,{defaultOn:i,action:c,onDefaultOnChange:u,onActionChange:d,onApplyPreset:m,onClearAll:p,disabled:a}),r.jsx("div",{className:"border border-gray-700 rounded-lg p-4 bg-surface",children:r.jsx(ec,{schedule:o,onScheduleChange:l,disabled:a})}),r.jsxs("div",{className:"flex items-center justify-between text-sm",children:[r.jsx("span",{className:"text-gray-400",children:f?r.jsx("span",{className:"text-yellow-400",children:"No time ranges configured"}):r.jsxs(r.Fragment,{children:["Schedule: ",r.jsx("span",{className:"text-white",children:y})]})}),i?r.jsx("span",{className:"text-xs text-gray-500",children:"Detection paused during selected times"}):r.jsx("span",{className:"text-xs text-gray-500",children:"Detection active during selected times"})]}),n&&!s&&r.jsx("p",{className:"text-sm text-gray-400",children:n}),s&&r.jsx("p",{className:"text-sm text-red-400",role:"alert",children:s}),r.jsxs("details",{className:"text-xs",children:[r.jsx("summary",{className:"cursor-pointer text-gray-500 hover:text-gray-400",children:"Show raw schedule format"}),r.jsx("code",{className:"block mt-2 p-2 bg-surface-elevated rounded text-gray-400 break-all",children:e||"(empty)"})]})]})}function nc({config:e,onChange:t,getError:n}){const s=(d,m="")=>e[d]?.value??m,a=String(s("schedule_params","")),o=a.trim()!=="",i=d=>{d?t("schedule_params","default=true action=pause mon-fri=0900-1700"):t("schedule_params","")},c=String(s("picture_schedule_params","")),l=c.trim()!=="",u=d=>{d?t("picture_schedule_params","default=false action=pause mon-fri=0900-1700"):t("picture_schedule_params","")};return r.jsx(O,{title:"Schedules",description:"Configure when motion detection and continuous recording are active",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-6",children:[r.jsxs("div",{className:"space-y-4",children:[r.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Motion Detection Schedule"}),r.jsx("p",{className:"text-xs text-gray-400",children:"Control when motion detection is active or paused"}),r.jsx(Y,{label:"Enable Motion Detection Schedule",value:o,onChange:i,helpText:"When enabled, motion detection follows the schedule below"}),o&&r.jsx(Rt,{value:a,onChange:d=>t("schedule_params",d),error:n?.("schedule_params")})]}),r.jsx("div",{className:"border-t border-gray-700"}),r.jsxs("div",{className:"space-y-4",children:[r.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Continuous Recording Schedule"}),r.jsx("p",{className:"text-xs text-gray-400",children:"Control when continuous picture capture (timelapse) is active"}),r.jsx(Y,{label:"Enable Continuous Recording Schedule",value:l,onChange:u,helpText:"When enabled, continuous recording follows the schedule below"}),l&&r.jsx(Rt,{value:c,onChange:d=>t("picture_schedule_params",d),error:n?.("picture_schedule_params")})]})]})})}const We={gridColumns:2,gridRows:2,fitFramesVertically:!1,playbackFramerateFactor:1,playbackResolutionFactor:1,theme:"dark"};function sc(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{...We,...t}}catch(t){console.error("Failed to parse preferences:",t)}return We}function ac(){const[e,t]=x.useState(sc),n=(s,a)=>{const o={...e,[s]:a};t(o),localStorage.setItem("motion-ui-preferences",JSON.stringify(o))};return r.jsx(O,{title:"UI Preferences",description:"User interface preferences (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[r.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"Dashboard Layout"}),r.jsx(P,{label:"Grid Columns",value:e.gridColumns,onChange:s=>n("gridColumns",s),min:1,max:4,helpText:"Number of camera columns in dashboard grid (1-4)"}),r.jsx(P,{label:"Grid Rows",value:e.gridRows,onChange:s=>n("gridRows",s),min:1,max:4,helpText:"Number of camera rows in dashboard grid (1-4)"}),r.jsx(Y,{label:"Fit Frames Vertically",value:e.fitFramesVertically,onChange:s=>n("fitFramesVertically",s),helpText:"Fit camera frames to viewport height instead of width"})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"Playback Settings"}),r.jsx(P,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:s=>n("playbackFramerateFactor",s),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),r.jsx(P,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:s=>n("playbackResolutionFactor",s),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),r.jsx("div",{className:"text-xs text-gray-400",children:r.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("h4",{className:"font-medium mb-3",children:"Appearance"}),r.jsx(R,{label:"Theme",value:e.theme,onChange:s=>n("theme",s),options:[{value:"dark",label:"Dark"},{value:"light",label:"Light (Coming Soon)"},{value:"auto",label:"Auto (System Preference)"}],helpText:"UI color theme",disabled:!0}),r.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[r.jsx("strong",{children:"Note:"})," Light theme and auto theme switching will be available in a future update."]})]}),r.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[r.jsx("button",{onClick:()=>{localStorage.removeItem("motion-ui-preferences"),t(We)},className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors text-sm",children:"Reset to Defaults"}),r.jsx("p",{className:"text-xs text-gray-400 mt-2",children:"Clears all saved preferences and returns to default settings"})]})]})})}const Ze={playbackFramerateFactor:1,playbackResolutionFactor:1};function oc(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{playbackFramerateFactor:t.playbackFramerateFactor??Ze.playbackFramerateFactor,playbackResolutionFactor:t.playbackResolutionFactor??Ze.playbackResolutionFactor}}catch(t){console.error("Failed to parse preferences:",t)}return Ze}function ic(){const[e,t]=x.useState(oc),n=(s,a)=>{const o={...e,[s]:a};t(o);const i=localStorage.getItem("motion-ui-preferences");let c={};if(i)try{c=JSON.parse(i)}catch(l){console.error("Failed to parse existing preferences:",l)}localStorage.setItem("motion-ui-preferences",JSON.stringify({...c,...o}))};return r.jsx(O,{title:"Playback Settings",description:"Video playback preferences for this camera (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:r.jsxs("div",{className:"space-y-4",children:[r.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[r.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),r.jsx(P,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:s=>n("playbackFramerateFactor",s),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),r.jsx(P,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:s=>n("playbackResolutionFactor",s),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),r.jsx("div",{className:"text-xs text-gray-400",children:r.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]})})}function cc({cameraId:e}){const{addToast:t}=Ye(),n=Zt(),s=x.useRef(null),a=x.useRef(null),[o,i]=x.useState("motion"),[c,l]=x.useState("rectangle"),[u,d]=x.useState(!1),[m,p]=x.useState(null),[f,y]=x.useState(null),[g,w]=x.useState([]),[_,v]=x.useState([]),[$,B]=x.useState(!1),[Z,D]=x.useState(!1),{data:W,isLoading:de}=qe({queryKey:["mask",e,o],queryFn:()=>Lt(`/${e}/api/mask/${o}`)}),[T,C]=x.useState({width:640,height:480}),z=nt({mutationFn:k=>Pe(`/${e}/api/mask/${o}`,k),onSuccess:()=>{t(`${o==="motion"?"Motion":"Privacy"} mask saved`,"success"),n.invalidateQueries({queryKey:["mask",e,o]})},onError:()=>{t("Failed to save mask","error")}}),b=nt({mutationFn:()=>wr(`/${e}/api/mask/${o}`),onSuccess:()=>{t("Mask deleted","success"),w([]),n.invalidateQueries({queryKey:["mask",e,o]})},onError:()=>{t("Failed to delete mask","error")}}),F=x.useCallback(k=>{const N=s.current;if(!N)return{x:0,y:0};const M=N.getBoundingClientRect(),X=T.width/M.width,oe=T.height/M.height;return{x:Math.round((k.clientX-M.left)*X),y:Math.round((k.clientY-M.top)*oe)}},[T]),Q=x.useCallback(()=>{const k=s.current;if(!k)return;const N=k.getContext("2d");if(N&&(N.clearRect(0,0,k.width,k.height),N.fillStyle="rgba(255, 0, 0, 0.4)",N.strokeStyle="rgba(255, 0, 0, 0.8)",N.lineWidth=2,g.forEach(M=>{M.length<3||(N.beginPath(),N.moveTo(M[0].x,M[0].y),M.slice(1).forEach(X=>N.lineTo(X.x,X.y)),N.closePath(),N.fill(),N.stroke())}),_.length>0&&(N.beginPath(),N.moveTo(_[0].x,_[0].y),_.slice(1).forEach(M=>N.lineTo(M.x,M.y)),f&&N.lineTo(f.x,f.y),N.stroke(),N.fillStyle="rgba(255, 255, 0, 0.8)",_.forEach(M=>{N.beginPath(),N.arc(M.x,M.y,4,0,Math.PI*2),N.fill()})),c==="rectangle"&&u&&m&&f)){N.fillStyle="rgba(255, 0, 0, 0.4)",N.strokeStyle="rgba(255, 0, 0, 0.8)";const M=Math.min(m.x,f.x),X=Math.min(m.y,f.y),oe=Math.abs(f.x-m.x),ye=Math.abs(f.y-m.y);N.fillRect(M,X,oe,ye),N.strokeRect(M,X,oe,ye)}},[g,_,f,m,u,c]);x.useEffect(()=>{Q()},[Q]);const J=x.useCallback(k=>{const N=F(k);c==="rectangle"?(d(!0),p(N),y(N)):v(M=>[...M,N])},[c,F]),Oe=x.useCallback(k=>{const N=F(k);y(N)},[F]),me=x.useCallback(()=>{if(c==="rectangle"&&u&&m&&f){const k=Math.min(m.x,f.x),N=Math.min(m.y,f.y),M=Math.max(m.x,f.x),X=Math.max(m.y,f.y);if(M-k>5&&X-N>5){const oe=[{x:k,y:N},{x:M,y:N},{x:M,y:X},{x:k,y:X}];w(ye=>[...ye,oe])}}d(!1),p(null)},[c,u,m,f]),G=x.useCallback(()=>{c==="polygon"&&_.length>=3&&(w(k=>[...k,_]),v([]))},[c,_]),he=x.useCallback(()=>{w([]),v([]),p(null),y(null),d(!1)},[]),dr=x.useCallback(()=>{_.length>0?v(k=>k.slice(0,-1)):g.length>0&&w(k=>k.slice(0,-1))},[_.length,g.length]),mr=x.useCallback(()=>{if(g.length===0){t("Draw at least one mask area first","warning");return}z.mutate({polygons:g,width:T.width,height:T.height,invert:$})},[g,T,$,z,t]),hr=x.useCallback(()=>{window.confirm(`Delete the ${o} mask? This cannot be undone.`)&&b.mutate()},[o,b]),pr=x.useCallback(k=>{const N=k.currentTarget;C({width:N.naturalWidth,height:N.naturalHeight}),D(!1)},[]),fr=x.useMemo(()=>{const k=jr(),N=`/${e}/mjpg/stream`;return k?`${N}?token=${encodeURIComponent(k)}`:N},[e]);return r.jsxs(O,{title:"Mask Editor",description:"Draw mask areas on the camera feed to define motion detection or privacy zones",collapsible:!0,defaultOpen:!1,children:[r.jsxs("div",{className:"flex gap-4 mb-4",children:[r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium mb-2",children:"Mask Type"}),r.jsxs("div",{className:"flex gap-2",children:[r.jsx("button",{onClick:()=>{i("motion"),he()},className:`px-3 py-1.5 rounded text-sm transition-colors ${o==="motion"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Motion"}),r.jsx("button",{onClick:()=>{i("privacy"),he()},className:`px-3 py-1.5 rounded text-sm transition-colors ${o==="privacy"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Privacy"})]})]}),r.jsxs("div",{children:[r.jsx("label",{className:"block text-sm font-medium mb-2",children:"Draw Mode"}),r.jsxs("div",{className:"flex gap-2",children:[r.jsx("button",{onClick:()=>l("rectangle"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="rectangle"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Rectangle"}),r.jsx("button",{onClick:()=>l("polygon"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="polygon"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Polygon"})]})]}),r.jsx("div",{className:"flex items-end",children:r.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[r.jsx("input",{type:"checkbox",checked:$,onChange:k=>B(k.target.checked),className:"rounded"}),"Invert (detect outside areas)"]})})]}),de?r.jsx("div",{className:"text-sm text-gray-400 mb-4",children:"Loading mask info..."}):W?.exists?r.jsxs("div",{className:"text-sm text-green-500 mb-4",children:["Current mask: ",W.path," (",W.width,"x",W.height,")"]}):r.jsxs("div",{className:"text-sm text-gray-400 mb-4",children:["No ",o," mask configured"]}),r.jsxs("div",{ref:a,className:"relative bg-black rounded-lg overflow-hidden mb-4",children:[Z?r.jsx("div",{className:"aspect-video bg-surface flex items-center justify-center text-gray-400",children:r.jsxs("div",{className:"text-center",children:[r.jsx("p",{children:"Camera stream unavailable"}),r.jsx("p",{className:"text-xs mt-1",children:"Draw on blank canvas (640x480)"})]})}):r.jsx("img",{src:fr,alt:"Camera stream",onLoad:pr,onError:()=>D(!0),className:"w-full h-auto",style:{display:"block"}}),r.jsx("canvas",{ref:s,width:T.width,height:T.height,onMouseDown:J,onMouseMove:Oe,onMouseUp:me,onMouseLeave:me,onDoubleClick:G,className:"absolute inset-0 w-full h-full cursor-crosshair",style:{touchAction:"none"}})]}),r.jsx("div",{className:"text-xs text-gray-400 mb-4",children:c==="rectangle"?r.jsxs("p",{children:["Click and drag to draw rectangles. Red areas will be ",o==="motion"?"ignored for motion detection":"blacked out for privacy","."]}):r.jsx("p",{children:"Click to add polygon points. Double-click to complete the polygon."})}),r.jsxs("div",{className:"flex gap-3 flex-wrap",children:[r.jsx("button",{onClick:dr,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Undo"}),r.jsx("button",{onClick:he,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Clear All"}),r.jsx("button",{onClick:mr,disabled:z.isPending||g.length===0,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:z.isPending?"Saving...":"Save Mask"}),W?.exists&&r.jsx("button",{onClick:hr,disabled:b.isPending,className:"px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 rounded transition-colors disabled:opacity-50",children:b.isPending?"Deleting...":"Delete Mask"})]}),g.length>0&&r.jsxs("div",{className:"text-xs text-gray-400 mt-3",children:[g.length," mask area",g.length!==1?"s":""," drawn"]})]})}const fe={custom:{label:"Custom Command",description:"Enter your own shell command",command:""},webhook:{label:"Webhook (HTTP POST)",description:"Send HTTP POST to a URL",command:`curl -s -X POST -H "Content-Type: application/json" -d '{"camera":"%t","event":"%v","time":"%Y-%m-%d %T"}' "YOUR_WEBHOOK_URL"`},telegram:{label:"Telegram Bot",description:"Send message via Telegram Bot API",command:'curl -s -X POST "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage" -d "chat_id=YOUR_CHAT_ID&text=Motion detected on %t at %Y-%m-%d %T"'},email:{label:"Email (msmtp)",description:"Send email using msmtp",command:'echo -e "Subject: Motion Alert\\n\\nMotion detected on camera %t at %Y-%m-%d %T" | msmtp recipient@example.com'},pushover:{label:"Pushover",description:"Send push notification via Pushover",command:'curl -s -F "token=YOUR_APP_TOKEN" -F "user=YOUR_USER_KEY" -F "message=Motion on %t at %T" https://api.pushover.net/1/messages.json'}};function lc({config:e,onChange:t,getError:n}){const[s,a]=x.useState("custom"),o=(c,l="")=>e[c]?.value??l,i=c=>{const l=fe[s];l.command&&t(c,l.command)};return r.jsxs(O,{title:"Notifications & Scripts",description:"Configure commands that run on motion events. Use script hooks to send notifications via webhooks, Telegram, email, or custom scripts.",collapsible:!0,defaultOpen:!1,children:[r.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[r.jsx("h4",{className:"font-medium mb-2",children:"Available Variables"}),r.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-gray-400",children:[r.jsx("code",{children:"%f"})," ",r.jsx("span",{children:"Filename"}),r.jsx("code",{children:"%t"})," ",r.jsx("span",{children:"Camera name"}),r.jsx("code",{children:"%v"})," ",r.jsx("span",{children:"Event number"}),r.jsx("code",{children:"%Y"})," ",r.jsx("span",{children:"Year (4 digit)"}),r.jsx("code",{children:"%m"})," ",r.jsx("span",{children:"Month (01-12)"}),r.jsx("code",{children:"%d"})," ",r.jsx("span",{children:"Day (01-31)"}),r.jsx("code",{children:"%H"})," ",r.jsx("span",{children:"Hour (00-23)"}),r.jsx("code",{children:"%M"})," ",r.jsx("span",{children:"Minute (00-59)"}),r.jsx("code",{children:"%S"})," ",r.jsx("span",{children:"Second (00-59)"}),r.jsx("code",{children:"%T"})," ",r.jsx("span",{children:"Time HH:MM:SS"})]})]}),r.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[r.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Templates"}),r.jsx("div",{className:"flex flex-wrap gap-2 mb-3",children:Object.keys(fe).map(c=>r.jsx("button",{onClick:()=>a(c),className:`px-3 py-1.5 text-sm rounded transition-colors ${s===c?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:fe[c].label},c))}),s!=="custom"&&r.jsxs("div",{className:"text-xs text-gray-400 mb-3",children:[r.jsx("p",{children:fe[s].description}),r.jsx("pre",{className:"mt-2 p-2 bg-surface rounded text-xs overflow-x-auto whitespace-pre-wrap break-all",children:fe[s].command})]}),r.jsxs("div",{className:"flex gap-2",children:[r.jsx("button",{onClick:()=>i("on_event_start"),disabled:s==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Event Start"}),r.jsx("button",{onClick:()=>i("on_picture_save"),disabled:s==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Picture Save"}),r.jsx("button",{onClick:()=>i("on_movie_end"),disabled:s==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Movie End"})]})]}),r.jsx(S,{label:"On Event Start",value:String(o("on_event_start","")),onChange:c=>t("on_event_start",c),placeholder:"Command to run when motion event starts",helpText:"Executed when a new motion event begins",error:n?.("on_event_start")}),r.jsx(S,{label:"On Event End",value:String(o("on_event_end","")),onChange:c=>t("on_event_end",c),placeholder:"Command to run when motion event ends",helpText:"Executed when motion event ends (after gap timeout)",error:n?.("on_event_end")}),r.jsx(S,{label:"On Motion Detected",value:String(o("on_motion_detected","")),onChange:c=>t("on_motion_detected",c),placeholder:"Command to run on each motion frame",helpText:"Executed on every frame with motion (can be frequent!)",error:n?.("on_motion_detected")}),r.jsx(S,{label:"On Picture Save",value:String(o("on_picture_save","")),onChange:c=>t("on_picture_save",c),placeholder:"Command to run when picture is saved",helpText:"Executed after a snapshot is saved (%f = filename)",error:n?.("on_picture_save")}),r.jsx(S,{label:"On Movie Start",value:String(o("on_movie_start","")),onChange:c=>t("on_movie_start",c),placeholder:"Command to run when recording starts",helpText:"Executed when video recording begins",error:n?.("on_movie_start")}),r.jsx(S,{label:"On Movie End",value:String(o("on_movie_end","")),onChange:c=>t("on_movie_end",c),placeholder:"Command to run when recording ends",helpText:"Executed when video recording is complete (%f = filename)",error:n?.("on_movie_end")}),r.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[r.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Example: Send Picture via Telegram"}),r.jsx("pre",{className:"text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap",children:`# On Picture Save: +curl -F chat_id="YOUR_CHAT_ID" \\ + -F photo=@"%f" \\ + -F caption="Motion on %t at %T" \\ + "https://api.telegram.org/botYOUR_TOKEN/sendPhoto"`})]})]})}const ie={rclone:{label:"Rclone",description:"Universal cloud storage sync (supports 40+ providers)",pictureCmd:'rclone copy "%f" remote:motion/pictures/%Y%m%d/',movieCmd:'rclone copy "%f" remote:motion/movies/%Y%m%d/'},s3:{label:"AWS S3",description:"Amazon S3 or compatible storage (MinIO, DigitalOcean Spaces)",pictureCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/pictures/%Y%m%d/',movieCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/movies/%Y%m%d/'},gdrive:{label:"Google Drive",description:"Upload via gdrive CLI",pictureCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"',movieCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"'},dropbox:{label:"Dropbox",description:"Upload via Dropbox-Uploader script",pictureCmd:'dropbox_uploader.sh upload "%f" /motion/pictures/',movieCmd:'dropbox_uploader.sh upload "%f" /motion/movies/'},sftp:{label:"SFTP/SCP",description:"Upload to remote server via SSH",pictureCmd:'scp "%f" user@server:/backup/motion/pictures/',movieCmd:'scp "%f" user@server:/backup/motion/movies/'},custom:{label:"Custom",description:"Enter your own upload command",pictureCmd:"",movieCmd:""}};function uc({config:e,onChange:t,getError:n}){const[s,a]=x.useState("rclone"),o=(u,d="")=>e[u]?.value??d,i=String(o("on_picture_save","")),c=String(o("on_movie_end","")),l=()=>{const u=ie[s];u.pictureCmd&&t("on_picture_save",u.pictureCmd),u.movieCmd&&t("on_movie_end",u.movieCmd)};return r.jsxs(O,{title:"Cloud Upload",description:"Configure automatic upload of pictures and videos to cloud storage. Uses the same event hooks as notifications.",collapsible:!0,defaultOpen:!1,children:[r.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[r.jsxs("p",{className:"text-gray-400 mb-2",children:["Cloud uploads are triggered by the ",r.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_picture_save"})," and ",r.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_movie_end"})," event hooks. Select a provider template below or enter a custom command."]}),r.jsxs("p",{className:"text-xs text-gray-500",children:[r.jsx("strong",{children:"Note:"})," You must install the required CLI tools (rclone, aws-cli, etc.) on your Pi before uploads will work."]})]}),r.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[r.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Cloud Provider Templates"}),r.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:Object.keys(ie).map(u=>r.jsx("button",{onClick:()=>a(u),className:`px-3 py-1.5 text-sm rounded transition-colors ${s===u?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:ie[u].label},u))}),s!=="custom"&&r.jsxs("div",{className:"text-xs text-gray-400 mb-4",children:[r.jsx("p",{className:"mb-2",children:ie[s].description}),r.jsxs("div",{className:"space-y-2",children:[r.jsxs("div",{children:[r.jsx("span",{className:"text-gray-500",children:"Picture:"}),r.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[s].pictureCmd})]}),r.jsxs("div",{children:[r.jsx("span",{className:"text-gray-500",children:"Movie:"}),r.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[s].movieCmd})]})]})]}),r.jsx("button",{onClick:l,disabled:s==="custom",className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:"Apply Template"})]}),r.jsx(S,{label:"Picture Upload Command",value:i,onChange:u=>t("on_picture_save",u),placeholder:"Command to upload pictures",helpText:"Runs after each picture is saved. %f = filename, %Y%m%d = date",error:n?.("on_picture_save")}),r.jsx(S,{label:"Movie Upload Command",value:c,onChange:u=>t("on_movie_end",u),placeholder:"Command to upload videos",helpText:"Runs after each video recording completes. %f = filename",error:n?.("on_movie_end")}),r.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[r.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Guides"}),r.jsxs("div",{className:"space-y-4 text-xs text-gray-400",children:[r.jsxs("div",{children:[r.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"Rclone Setup"}),r.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install rclone +sudo apt install rclone + +# Configure a remote (interactive) +rclone config + +# Test upload +rclone copy /path/to/file remote:folder/`})]}),r.jsxs("div",{children:[r.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"AWS S3 Setup"}),r.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install AWS CLI +sudo apt install awscli + +# Configure credentials +aws configure + +# Test upload +aws s3 cp /path/to/file s3://bucket/`})]})]})]})]})}function hc(){const{role:e}=Nr(),{addToast:t}=Ye(),[n,s]=x.useState("0"),[a,o]=x.useState({}),[i,c]=x.useState({}),[l,u]=x.useState(!1),d=Zt(),{data:m,isLoading:p,error:f}=qe({queryKey:["config"],queryFn:async()=>{const C=await Lt("/0/api/config");return C.csrf_token&&kr(C.csrf_token),C}}),y=Sr(),{data:g}=Ft(),{data:w}=Hr(Number(n)),_=lr(Number(n));x.useEffect(()=>{o({}),c({})},[n]);const v=x.useCallback((C,z)=>{o(F=>({...F,[C]:z}));const b=mi(C,String(z));c(F=>{if(b.success){const{[C]:Q,...J}=F;return J}else return{...F,[C]:b.error??"Invalid value"}})},[]),$=Object.keys(a).length>0,B=Object.keys(i).length>0,Z=x.useMemo(()=>{if(!m)return{};const C=m.configuration.default||{};if(n==="0")return C;{const z=m.configuration[`cam${n}`]||{};return{...C,...z}}},[m,n]),D=x.useMemo(()=>{if(!m)return{};const C={...Z};for(const[z,b]of Object.entries(a))C[z]?C[z]={...C[z],value:b}:C[z]={value:b,enabled:!0,category:0,type:"string"};return C},[m,Z,a]),W=async()=>{if(!$){t("No changes to save","info");return}if(B){t("Please fix validation errors before saving","error");return}u(!0);const C=parseInt(n,10);try{const z=await y.mutateAsync({camId:C,changes:a});await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]});const b=z?.summary,F=z?.applied||[];if(!b)t(`Saved ${Object.keys(a).length} setting(s)`,"success"),o({});else{const Q=F.filter(J=>!J.error&&J.hot_reload===!1).map(J=>J.param);if(Q.length>0){t(`Restarting camera to apply ${Q.length} setting(s): ${Q.join(", ")}...`,"info"),o({});const J=await Cr(C);Br(C),window.dispatchEvent(new CustomEvent(Wr,{detail:{cameraId:C}})),await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]}),J?t(`Applied ${Q.length} setting(s). Camera restarted successfully.`,"success"):t("Settings saved. Camera is restarting - refresh page if stream doesn't recover.","warning")}else if(b.errors>0){const J=F.filter(G=>G.error).map(G=>G.param),Oe=F.filter(G=>!G.error).map(G=>G.param),me={};for(const[G,he]of Object.entries(a))Oe.includes(G)||(me[G]=he);o(me),b.success>0?t(`Saved ${b.success} setting(s). ${b.errors} failed: ${J.join(", ")}`,"warning"):t(`Failed to save settings: ${J.join(", ")}`,"error")}else t(`Successfully saved ${b.success} setting(s)`,"success"),o({})}}catch(z){console.error("Failed to save settings:",z),t("Failed to save settings. Check browser console for details.","error")}finally{u(!1)}},de=()=>{o({}),c({}),t("Changes discarded","info")},T=C=>i[C];return e!=="admin"?r.jsx("div",{className:"p-4 sm:p-6",children:r.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[r.jsx("svg",{className:"w-16 h-16 mx-auto text-yellow-500 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:r.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),r.jsx("h1",{className:"text-2xl font-bold mb-2",children:"Admin Access Required"}),r.jsx("p",{className:"text-gray-400",children:"You must be logged in as an administrator to access settings."})]})}):p?r.jsxs("div",{className:"p-6",children:[r.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),r.jsxs("div",{className:"animate-pulse",children:[r.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"}),r.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"})]})]}):f?r.jsxs("div",{className:"p-6",children:[r.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),r.jsx("div",{className:"bg-danger/10 border border-danger rounded-lg p-4",children:r.jsx("p",{className:"text-danger",children:"Failed to load configuration"})})]}):m?r.jsxs("div",{className:"p-6",children:[r.jsx("div",{className:"sticky top-[73px] z-40 -mx-6 px-6 py-3 bg-surface/95 backdrop-blur border-b border-gray-800 mb-6",children:r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsxs("div",{className:"flex items-center gap-4",children:[r.jsx("h2",{className:"text-2xl font-bold",children:"Settings"}),r.jsx("div",{children:r.jsxs("select",{value:n,onChange:C=>s(C.target.value),className:"px-3 py-1.5 bg-surface-elevated border border-gray-700 rounded-lg text-sm",children:[r.jsx("option",{value:"0",children:"Global Settings"}),m.cameras&&Object.entries(m.cameras).map(([C,z])=>{if(C==="count"||typeof z=="number")return null;const b=z;return r.jsx("option",{value:String(b.id),children:b.name||`Camera ${b.id}`},b.id)})]})})]}),r.jsxs("div",{className:"flex items-center gap-3",children:[$&&!B&&r.jsx("span",{className:"text-yellow-200 text-sm",children:"Unsaved changes"}),B&&r.jsx("span",{className:"text-red-200 text-sm",children:"Fix errors below"}),$&&r.jsx("button",{onClick:de,disabled:l,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded-lg transition-colors disabled:opacity-50",children:"Discard"}),r.jsx("button",{onClick:W,disabled:!$||l||B,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:l?"Saving...":$?"Save Changes":"Saved"})]})]})}),n==="0"&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-6",children:r.jsx("p",{className:"text-sm text-blue-200",children:"Global settings apply to the Motion daemon and web server. To configure camera-specific settings, select a camera from the dropdown above."})}),r.jsx(gi,{config:D,onChange:v,getError:T,originalConfig:Z,systemStatus:g}),r.jsx(O,{title:"Camera Management",description:"Add, remove, and configure cameras",collapsible:!0,defaultOpen:!0,children:r.jsx(_i,{})}),r.jsx(ac,{}),r.jsx(O,{title:"About",description:"Motion version information",collapsible:!0,defaultOpen:!1,children:r.jsxs("p",{className:"text-sm text-gray-400",children:["Motion Version: ",m.version]})})]}),n!=="0"&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"bg-surface-elevated rounded-lg p-4 mb-6",children:r.jsx(Vr,{cameraId:Number(n),readOnly:!1})}),r.jsx(ji,{cameraId:Number(n)}),_.features.hasLibcamControls&&r.jsx(Si,{config:D,onChange:v,getError:T,capabilities:w,originalConfig:Z}),_.features.hasV4L2Controls&&r.jsx(Ci,{config:D,onChange:v,controls:_.v4l2Controls,getError:T}),_.features.hasNetcamConfig&&r.jsx($i,{config:D,onChange:v,connectionStatus:_.netcamStatus,hasDualStream:_.features.hasDualStream,getError:T}),r.jsx(xi,{config:D,onChange:v,getError:T}),r.jsx(Zi,{config:D,onChange:v,getError:T,showPassthrough:_.features.supportsPassthrough}),r.jsx(Ii,{config:D,onChange:v,getError:T}),r.jsx(Di,{config:D,onChange:v,getError:T}),r.jsx(Ai,{config:D,onChange:v,getError:T}),r.jsx(cc,{cameraId:parseInt(n,10)}),r.jsx(nc,{config:D,onChange:v,getError:T}),r.jsx(Li,{config:D,onChange:v,getError:T,originalConfig:Z}),r.jsx(Mi,{config:D,onChange:v,getError:T}),r.jsx(lc,{config:D,onChange:v,getError:T}),r.jsx(uc,{config:D,onChange:v,getError:T}),r.jsx(ic,{})]})]}):null}export{hc as Settings}; diff --git a/data/webui/assets/Settings-JwhcLbnw.js b/data/webui/assets/Settings-JwhcLbnw.js deleted file mode 100644 index 7d588470..00000000 --- a/data/webui/assets/Settings-JwhcLbnw.js +++ /dev/null @@ -1,22 +0,0 @@ -import{r as x,j as s,h as Te,a as Ye,i as Zt,e as qe,k as Ft,l as nt,m as gr,n as xr,f as Lt,c as br,u as vr,o as _r,g as yr}from"./index-tiawrtsp.js";import{c as D,b as V,R as st,f as ot,F as C,g as wr,h as jr,A as Sr,d as Nr,i as kr,j as Cr,m as at,k as it,l as Tr,p as Pr,L as $r,a as zr,n as Or,o as Mr,q as Ir,r as re,s as Ie,E as Ar,t as Er,M as ct,u as Dr,C as Rr,v as Zr,w as Fr}from"./parameterMappings-BmLxmuw_.js";function j({label:e,value:t,onChange:r,type:n="text",placeholder:o,disabled:a=!1,required:i=!1,helpText:c,error:l,min:u,max:d,step:h,originalValue:f,showVisibilityToggle:p}){const[v,g]=x.useState(!1),S=R=>{r(R.target.value)},_=!!l,b=f!==void 0&&String(t)!==String(f),I=n==="password",G=p??I,H=I&&v?"text":n;return s.jsxs("div",{className:"mb-4",children:[s.jsxs("label",{className:"block text-sm font-medium mb-1",children:[e,i&&s.jsx("span",{className:"text-red-500 ml-1",children:"*"}),b&&s.jsx("span",{className:"ml-2 text-xs text-yellow-400",children:"(modified)"})]}),s.jsxs("div",{className:"relative",children:[s.jsx("input",{type:H,value:t,onChange:S,placeholder:o,disabled:a,required:i,min:u,max:d,step:h,className:`w-full px-3 py-2 bg-surface border rounded-lg focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed ${G?"pr-10":""} ${_?"border-red-500 focus:ring-red-500":b?"border-yellow-500/50 focus:ring-yellow-500":"border-surface-elevated focus:ring-primary"}`,"aria-invalid":_,"aria-describedby":_?`${e}-error`:void 0}),G&&s.jsx("button",{type:"button",onClick:()=>g(!v),className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-200 transition-colors","aria-label":v?"Hide password":"Show password",children:v?s.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"})}):s.jsxs("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})]})})]}),_&&s.jsx("p",{id:`${e}-error`,className:"mt-1 text-sm text-red-400",role:"alert",children:l}),c&&!_&&s.jsx("p",{className:"mt-1 text-sm text-gray-400",children:c})]})}function O({title:e,description:t,children:r,collapsible:n=!1,defaultOpen:o=!0}){const[a,i]=x.useState(o),c=()=>{n&&i(!a)};return s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-6 mb-6",children:[s.jsxs("div",{className:`flex items-center justify-between ${n?"cursor-pointer":""}`,onClick:c,children:[s.jsxs("div",{children:[s.jsx("h3",{className:"text-lg font-semibold",children:e}),t&&s.jsx("p",{className:"text-sm text-gray-400 mt-1",children:t})]}),n&&s.jsx("svg",{className:`w-5 h-5 transition-transform ${a?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),a&&s.jsx("div",{className:"mt-4",children:r})]})}function m(e,t,r){function n(c,l){if(c._zod||Object.defineProperty(c,"_zod",{value:{def:l,constr:i,traits:new Set},enumerable:!1}),c._zod.traits.has(e))return;c._zod.traits.add(e),t(c,l);const u=i.prototype,d=Object.keys(u);for(let h=0;hr?.Parent&&c instanceof r.Parent?!0:c?._zod?.traits?.has(e)}),Object.defineProperty(i,"name",{value:e}),i}class le extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class Ut extends Error{constructor(t){super(`Encountered unidirectional transform during encode: ${t}`),this.name="ZodEncodeError"}}const Lr={};function se(e){return Lr}function Le(e,t){return typeof t=="bigint"?t.toString():t}function Je(e){return e==null}function Ge(e){const t=e.startsWith("^")?1:0,r=e.endsWith("$")?e.length-1:e.length;return e.slice(t,r)}function Ur(e,t){const r=(e.toString().split(".")[1]||"").length,n=t.toString();let o=(n.split(".")[1]||"").length;if(o===0&&/\d?e-\d?/.test(n)){const l=n.match(/\d?e-(\d?)/);l?.[1]&&(o=Number.parseInt(l[1]))}const a=r>o?r:o,i=Number.parseInt(e.toFixed(a).replace(".","")),c=Number.parseInt(t.toFixed(a).replace(".",""));return i%c/10**a}const lt=Symbol("evaluating");function P(e,t,r){let n;Object.defineProperty(e,t,{get(){if(n!==lt)return n===void 0&&(n=lt,n=r()),n},set(o){Object.defineProperty(e,t,{value:o})},configurable:!0})}function Hr(...e){const t={};for(const r of e){const n=Object.getOwnPropertyDescriptors(r);Object.assign(t,n)}return Object.defineProperties({},t)}function Vr(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const Ht="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function ut(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ue(e){if(ut(e)===!1)return!1;const t=e.constructor;if(t===void 0||typeof t!="function")return!0;const r=t.prototype;return!(ut(r)===!1||Object.prototype.hasOwnProperty.call(r,"isPrototypeOf")===!1)}function Vt(e){return Ue(e)?{...e}:Array.isArray(e)?[...e]:e}function Ke(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Br(e,t,r){const n=new e._zod.constr(t??e._zod.def);return(!t||r?.parent)&&(n._zod.parent=e),n}function y(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}const Wr={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ce(e,t=0){if(e.aborted===!0)return!0;for(let r=t;r{var n;return(n=r).path??(n.path=[]),r.path.unshift(e),r})}function _e(e){return typeof e=="string"?e:e?.message}function oe(e,t,r){const n={...e,path:e.path??[]};if(!e.message){const o=_e(e.inst?._zod.def?.error?.(e))??_e(t?.error?.(e))??_e(r.customError?.(e))??_e(r.localeError?.(e))??"Invalid input";n.message=o}return delete n.inst,delete n.continue,t?.reportInput||delete n.input,n}function Qe(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function ge(...e){const[t,r,n]=e;return typeof t=="string"?{message:t,code:"custom",input:r,inst:n}:{...t}}const Bt=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),e.message=JSON.stringify(t,Le,2),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},Wt=m("$ZodError",Bt),Yt=m("$ZodError",Bt,{Parent:Error});function qr(e,t=r=>r.message){const r={},n=[];for(const o of e.issues)o.path.length>0?(r[o.path[0]]=r[o.path[0]]||[],r[o.path[0]].push(t(o))):n.push(t(o));return{formErrors:n,fieldErrors:r}}function Jr(e,t=r=>r.message){const r={_errors:[]},n=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(i=>n({issues:i}));else if(a.code==="invalid_key")n({issues:a.issues});else if(a.code==="invalid_element")n({issues:a.issues});else if(a.path.length===0)r._errors.push(t(a));else{let i=r,c=0;for(;c(t,r,n,o)=>{const a=n?Object.assign(n,{async:!1}):{async:!1},i=t._zod.run({value:r,issues:[]},a);if(i instanceof Promise)throw new le;if(i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>oe(l,a,se())));throw Ht(c,o?.callee),c}return i.value},et=e=>async(t,r,n,o)=>{const a=n?Object.assign(n,{async:!0}):{async:!0};let i=t._zod.run({value:r,issues:[]},a);if(i instanceof Promise&&(i=await i),i.issues.length){const c=new(o?.Err??e)(i.issues.map(l=>oe(l,a,se())));throw Ht(c,o?.callee),c}return i.value},Pe=e=>(t,r,n)=>{const o=n?{...n,async:!1}:{async:!1},a=t._zod.run({value:r,issues:[]},o);if(a instanceof Promise)throw new le;return a.issues.length?{success:!1,error:new(e??Wt)(a.issues.map(i=>oe(i,o,se())))}:{success:!0,data:a.value}},Gr=Pe(Yt),$e=e=>async(t,r,n)=>{const o=n?Object.assign(n,{async:!0}):{async:!0};let a=t._zod.run({value:r,issues:[]},o);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(i=>oe(i,o,se())))}:{success:!0,data:a.value}},Kr=$e(Yt),Qr=e=>(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return Xe(e)(t,r,o)},Xr=e=>(t,r,n)=>Xe(e)(t,r,n),en=e=>async(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return et(e)(t,r,o)},tn=e=>async(t,r,n)=>et(e)(t,r,n),rn=e=>(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return Pe(e)(t,r,o)},nn=e=>(t,r,n)=>Pe(e)(t,r,n),sn=e=>async(t,r,n)=>{const o=n?Object.assign(n,{direction:"backward"}):{direction:"backward"};return $e(e)(t,r,o)},on=e=>async(t,r,n)=>$e(e)(t,r,n),an=/^[cC][^\s-]{8,}$/,cn=/^[0-9a-z]+$/,ln=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,un=/^[0-9a-vA-V]{20}$/,dn=/^[A-Za-z0-9]{27}$/,mn=/^[a-zA-Z0-9_-]{21}$/,hn=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,pn=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,dt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,fn=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,gn="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function xn(){return new RegExp(gn,"u")}const bn=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,vn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,_n=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,yn=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,wn=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,qt=/^[A-Za-z0-9_-]*$/,jn=/^\+(?:[0-9]){6,14}[0-9]$/,Jt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",Sn=new RegExp(`^${Jt}$`);function Gt(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function Nn(e){return new RegExp(`^${Gt(e)}$`)}function kn(e){const t=Gt({precision:e.precision}),r=["Z"];e.local&&r.push(""),e.offset&&r.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const n=`${t}(?:${r.join("|")})`;return new RegExp(`^${Jt}T(?:${n})$`)}const Cn=e=>{const t=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},Tn=/^-?\d+$/,Pn=/^-?\d+(?:\.\d+)?/,$n=/^[^A-Z]*$/,zn=/^[^a-z]*$/,B=m("$ZodCheck",(e,t)=>{var r;e._zod??(e._zod={}),e._zod.def=t,(r=e._zod).onattach??(r.onattach=[])}),Kt={number:"number",bigint:"bigint",object:"date"},Qt=m("$ZodCheckLessThan",(e,t)=>{B.init(e,t);const r=Kt[typeof t.value];e._zod.onattach.push(n=>{const o=n._zod.bag,a=(t.inclusive?o.maximum:o.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?n.value<=t.value:n.value{B.init(e,t);const r=Kt[typeof t.value];e._zod.onattach.push(n=>{const o=n._zod.bag,a=(t.inclusive?o.minimum:o.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>a&&(t.inclusive?o.minimum=t.value:o.exclusiveMinimum=t.value)}),e._zod.check=n=>{(t.inclusive?n.value>=t.value:n.value>t.value)||n.issues.push({origin:r,code:"too_small",minimum:t.value,input:n.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),On=m("$ZodCheckMultipleOf",(e,t)=>{B.init(e,t),e._zod.onattach.push(r=>{var n;(n=r._zod.bag).multipleOf??(n.multipleOf=t.value)}),e._zod.check=r=>{if(typeof r.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof r.value=="bigint"?r.value%t.value===BigInt(0):Ur(r.value,t.value)===0)||r.issues.push({origin:typeof r.value,code:"not_multiple_of",divisor:t.value,input:r.value,inst:e,continue:!t.abort})}}),Mn=m("$ZodCheckNumberFormat",(e,t)=>{B.init(e,t),t.format=t.format||"float64";const r=t.format?.includes("int"),n=r?"int":"number",[o,a]=Wr[t.format];e._zod.onattach.push(i=>{const c=i._zod.bag;c.format=t.format,c.minimum=o,c.maximum=a,r&&(c.pattern=Tn)}),e._zod.check=i=>{const c=i.value;if(r){if(!Number.isInteger(c)){i.issues.push({expected:n,format:t.format,code:"invalid_type",continue:!1,input:c,inst:e});return}if(!Number.isSafeInteger(c)){c>0?i.issues.push({input:c,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort}):i.issues.push({input:c,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:n,continue:!t.abort});return}}ca&&i.issues.push({origin:"number",input:c,code:"too_big",maximum:a,inst:e})}}),In=m("$ZodCheckMaxLength",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const o=n.value;if(o.length<=t.maximum)return;const i=Qe(o);n.issues.push({origin:i,code:"too_big",maximum:t.maximum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),An=m("$ZodCheckMinLength",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>o&&(n._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{const o=n.value;if(o.length>=t.minimum)return;const i=Qe(o);n.issues.push({origin:i,code:"too_small",minimum:t.minimum,inclusive:!0,input:o,inst:e,continue:!t.abort})}}),En=m("$ZodCheckLengthEquals",(e,t)=>{var r;B.init(e,t),(r=e._zod.def).when??(r.when=n=>{const o=n.value;return!Je(o)&&o.length!==void 0}),e._zod.onattach.push(n=>{const o=n._zod.bag;o.minimum=t.length,o.maximum=t.length,o.length=t.length}),e._zod.check=n=>{const o=n.value,a=o.length;if(a===t.length)return;const i=Qe(o),c=a>t.length;n.issues.push({origin:i,...c?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:n.value,inst:e,continue:!t.abort})}}),ze=m("$ZodCheckStringFormat",(e,t)=>{var r,n;B.init(e,t),e._zod.onattach.push(o=>{const a=o._zod.bag;a.format=t.format,t.pattern&&(a.patterns??(a.patterns=new Set),a.patterns.add(t.pattern))}),t.pattern?(r=e._zod).check??(r.check=o=>{t.pattern.lastIndex=0,!t.pattern.test(o.value)&&o.issues.push({origin:"string",code:"invalid_format",format:t.format,input:o.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(n=e._zod).check??(n.check=()=>{})}),Dn=m("$ZodCheckRegex",(e,t)=>{ze.init(e,t),e._zod.check=r=>{t.pattern.lastIndex=0,!t.pattern.test(r.value)&&r.issues.push({origin:"string",code:"invalid_format",format:"regex",input:r.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),Rn=m("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=$n),ze.init(e,t)}),Zn=m("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=zn),ze.init(e,t)}),Fn=m("$ZodCheckIncludes",(e,t)=>{B.init(e,t);const r=Ke(t.includes),n=new RegExp(typeof t.position=="number"?`^.{${t.position}}${r}`:r);t.pattern=n,e._zod.onattach.push(o=>{const a=o._zod.bag;a.patterns??(a.patterns=new Set),a.patterns.add(n)}),e._zod.check=o=>{o.value.includes(t.includes,t.position)||o.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:o.value,inst:e,continue:!t.abort})}}),Ln=m("$ZodCheckStartsWith",(e,t)=>{B.init(e,t);const r=new RegExp(`^${Ke(t.prefix)}.*`);t.pattern??(t.pattern=r),e._zod.onattach.push(n=>{const o=n._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=n=>{n.value.startsWith(t.prefix)||n.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:n.value,inst:e,continue:!t.abort})}}),Un=m("$ZodCheckEndsWith",(e,t)=>{B.init(e,t);const r=new RegExp(`.*${Ke(t.suffix)}$`);t.pattern??(t.pattern=r),e._zod.onattach.push(n=>{const o=n._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=n=>{n.value.endsWith(t.suffix)||n.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:n.value,inst:e,continue:!t.abort})}}),Hn=m("$ZodCheckOverwrite",(e,t)=>{B.init(e,t),e._zod.check=r=>{r.value=t.tx(r.value)}}),Vn={major:4,minor:2,patch:1},Z=m("$ZodType",(e,t)=>{var r;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=Vn;const n=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&n.unshift(e);for(const o of n)for(const a of o._zod.onattach)a(e);if(n.length===0)(r=e._zod).deferred??(r.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{const o=(i,c,l)=>{let u=ce(i),d;for(const h of c){if(h._zod.def.when){if(!h._zod.def.when(i))continue}else if(u)continue;const f=i.issues.length,p=h._zod.check(i);if(p instanceof Promise&&l?.async===!1)throw new le;if(d||p instanceof Promise)d=(d??Promise.resolve()).then(async()=>{await p,i.issues.length!==f&&(u||(u=ce(i,f)))});else{if(i.issues.length===f)continue;u||(u=ce(i,f))}}return d?d.then(()=>i):i},a=(i,c,l)=>{if(ce(i))return i.aborted=!0,i;const u=o(c,n,l);if(u instanceof Promise){if(l.async===!1)throw new le;return u.then(d=>e._zod.parse(d,l))}return e._zod.parse(u,l)};e._zod.run=(i,c)=>{if(c.skipChecks)return e._zod.parse(i,c);if(c.direction==="backward"){const u=e._zod.parse({value:i.value,issues:[]},{...c,skipChecks:!0});return u instanceof Promise?u.then(d=>a(d,i,c)):a(u,i,c)}const l=e._zod.parse(i,c);if(l instanceof Promise){if(c.async===!1)throw new le;return l.then(u=>o(u,n,c))}return o(l,n,c)}}e["~standard"]={validate:o=>{try{const a=Gr(e,o);return a.success?{value:a.data}:{issues:a.error?.issues}}catch{return Kr(e,o).then(i=>i.success?{value:i.data}:{issues:i.error?.issues})}},vendor:"zod",version:1}}),tt=m("$ZodString",(e,t)=>{Z.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??Cn(e._zod.bag),e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=String(r.value)}catch{}return typeof r.value=="string"||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),$=m("$ZodStringFormat",(e,t)=>{ze.init(e,t),tt.init(e,t)}),Bn=m("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=pn),$.init(e,t)}),Wn=m("$ZodUUID",(e,t)=>{if(t.version){const n={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(n===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=dt(n))}else t.pattern??(t.pattern=dt());$.init(e,t)}),Yn=m("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=fn),$.init(e,t)}),qn=m("$ZodURL",(e,t)=>{$.init(e,t),e._zod.check=r=>{try{const n=r.value.trim(),o=new URL(n);t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(o.hostname)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:t.hostname.source,input:r.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(o.protocol.endsWith(":")?o.protocol.slice(0,-1):o.protocol)||r.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:r.value,inst:e,continue:!t.abort})),t.normalize?r.value=o.href:r.value=n;return}catch{r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!t.abort})}}}),Jn=m("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=xn()),$.init(e,t)}),Gn=m("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=mn),$.init(e,t)}),Kn=m("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=an),$.init(e,t)}),Qn=m("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=cn),$.init(e,t)}),Xn=m("$ZodULID",(e,t)=>{t.pattern??(t.pattern=ln),$.init(e,t)}),es=m("$ZodXID",(e,t)=>{t.pattern??(t.pattern=un),$.init(e,t)}),ts=m("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=dn),$.init(e,t)}),rs=m("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=kn(t)),$.init(e,t)}),ns=m("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=Sn),$.init(e,t)}),ss=m("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=Nn(t)),$.init(e,t)}),os=m("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=hn),$.init(e,t)}),as=m("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=bn),$.init(e,t),e._zod.bag.format="ipv4"}),is=m("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=vn),$.init(e,t),e._zod.bag.format="ipv6",e._zod.check=r=>{try{new URL(`http://[${r.value}]`)}catch{r.issues.push({code:"invalid_format",format:"ipv6",input:r.value,inst:e,continue:!t.abort})}}}),cs=m("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=_n),$.init(e,t)}),ls=m("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=yn),$.init(e,t),e._zod.check=r=>{const n=r.value.split("/");try{if(n.length!==2)throw new Error;const[o,a]=n;if(!a)throw new Error;const i=Number(a);if(`${i}`!==a)throw new Error;if(i<0||i>128)throw new Error;new URL(`http://[${o}]`)}catch{r.issues.push({code:"invalid_format",format:"cidrv6",input:r.value,inst:e,continue:!t.abort})}}});function er(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const us=m("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=wn),$.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=r=>{er(r.value)||r.issues.push({code:"invalid_format",format:"base64",input:r.value,inst:e,continue:!t.abort})}});function ds(e){if(!qt.test(e))return!1;const t=e.replace(/[-_]/g,n=>n==="-"?"+":"/"),r=t.padEnd(Math.ceil(t.length/4)*4,"=");return er(r)}const ms=m("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=qt),$.init(e,t),e._zod.bag.contentEncoding="base64url",e._zod.check=r=>{ds(r.value)||r.issues.push({code:"invalid_format",format:"base64url",input:r.value,inst:e,continue:!t.abort})}}),hs=m("$ZodE164",(e,t)=>{t.pattern??(t.pattern=jn),$.init(e,t)});function ps(e,t=null){try{const r=e.split(".");if(r.length!==3)return!1;const[n]=r;if(!n)return!1;const o=JSON.parse(atob(n));return!("typ"in o&&o?.typ!=="JWT"||!o.alg||t&&(!("alg"in o)||o.alg!==t))}catch{return!1}}const fs=m("$ZodJWT",(e,t)=>{$.init(e,t),e._zod.check=r=>{ps(r.value,t.alg)||r.issues.push({code:"invalid_format",format:"jwt",input:r.value,inst:e,continue:!t.abort})}}),tr=m("$ZodNumber",(e,t)=>{Z.init(e,t),e._zod.pattern=e._zod.bag.pattern??Pn,e._zod.parse=(r,n)=>{if(t.coerce)try{r.value=Number(r.value)}catch{}const o=r.value;if(typeof o=="number"&&!Number.isNaN(o)&&Number.isFinite(o))return r;const a=typeof o=="number"?Number.isNaN(o)?"NaN":Number.isFinite(o)?void 0:"Infinity":void 0;return r.issues.push({expected:"number",code:"invalid_type",input:o,inst:e,...a?{received:a}:{}}),r}}),gs=m("$ZodNumberFormat",(e,t)=>{Mn.init(e,t),tr.init(e,t)});function mt(e,t,r){e.issues.length&&t.issues.push(...Yr(r,e.issues)),t.value[r]=e.value}const xs=m("$ZodArray",(e,t)=>{Z.init(e,t),e._zod.parse=(r,n)=>{const o=r.value;if(!Array.isArray(o))return r.issues.push({expected:"array",code:"invalid_type",input:o,inst:e}),r;r.value=Array(o.length);const a=[];for(let i=0;imt(u,r,i))):mt(l,r,i)}return a.length?Promise.all(a).then(()=>r):r}});function ht(e,t,r,n){for(const a of e)if(a.issues.length===0)return t.value=a.value,t;const o=e.filter(a=>!ce(a));return o.length===1?(t.value=o[0].value,o[0]):(t.issues.push({code:"invalid_union",input:t.value,inst:r,errors:e.map(a=>a.issues.map(i=>oe(i,n,se())))}),t)}const bs=m("$ZodUnion",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.options.some(o=>o._zod.optin==="optional")?"optional":void 0),P(e._zod,"optout",()=>t.options.some(o=>o._zod.optout==="optional")?"optional":void 0),P(e._zod,"values",()=>{if(t.options.every(o=>o._zod.values))return new Set(t.options.flatMap(o=>Array.from(o._zod.values)))}),P(e._zod,"pattern",()=>{if(t.options.every(o=>o._zod.pattern)){const o=t.options.map(a=>a._zod.pattern);return new RegExp(`^(${o.map(a=>Ge(a.source)).join("|")})$`)}});const r=t.options.length===1,n=t.options[0]._zod.run;e._zod.parse=(o,a)=>{if(r)return n(o,a);let i=!1;const c=[];for(const l of t.options){const u=l._zod.run({value:o.value,issues:[]},a);if(u instanceof Promise)c.push(u),i=!0;else{if(u.issues.length===0)return u;c.push(u)}}return i?Promise.all(c).then(l=>ht(l,o,e,a)):ht(c,o,e,a)}}),vs=m("$ZodIntersection",(e,t)=>{Z.init(e,t),e._zod.parse=(r,n)=>{const o=r.value,a=t.left._zod.run({value:o,issues:[]},n),i=t.right._zod.run({value:o,issues:[]},n);return a instanceof Promise||i instanceof Promise?Promise.all([a,i]).then(([l,u])=>pt(r,l,u)):pt(r,a,i)}});function He(e,t){if(e===t)return{valid:!0,data:e};if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(Ue(e)&&Ue(t)){const r=Object.keys(t),n=Object.keys(e).filter(a=>r.indexOf(a)!==-1),o={...e,...t};for(const a of n){const i=He(e[a],t[a]);if(!i.valid)return{valid:!1,mergeErrorPath:[a,...i.mergeErrorPath]};o[a]=i.data}return{valid:!0,data:o}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};const r=[];for(let n=0;n{Z.init(e,t),e._zod.parse=(r,n)=>{if(n.direction==="backward")throw new Ut(e.constructor.name);const o=t.transform(r.value,r);if(n.async)return(o instanceof Promise?o:Promise.resolve(o)).then(i=>(r.value=i,r));if(o instanceof Promise)throw new le;return r.value=o,r}});function ft(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const ys=m("$ZodOptional",(e,t)=>{Z.init(e,t),e._zod.optin="optional",e._zod.optout="optional",P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),P(e._zod,"pattern",()=>{const r=t.innerType._zod.pattern;return r?new RegExp(`^(${Ge(r.source)})?$`):void 0}),e._zod.parse=(r,n)=>{if(t.innerType._zod.optin==="optional"){const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>ft(a,r.value)):ft(o,r.value)}return r.value===void 0?r:t.innerType._zod.run(r,n)}}),ws=m("$ZodNullable",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"pattern",()=>{const r=t.innerType._zod.pattern;return r?new RegExp(`^(${Ge(r.source)}|null)$`):void 0}),P(e._zod,"values",()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(r,n)=>r.value===null?r:t.innerType._zod.run(r,n)}),js=m("$ZodDefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);if(r.value===void 0)return r.value=t.defaultValue,r;const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>gt(a,t)):gt(o,t)}});function gt(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}const Ss=m("$ZodPrefault",(e,t)=>{Z.init(e,t),e._zod.optin="optional",P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>(n.direction==="backward"||r.value===void 0&&(r.value=t.defaultValue),t.innerType._zod.run(r,n))}),Ns=m("$ZodNonOptional",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>{const r=t.innerType._zod.values;return r?new Set([...r].filter(n=>n!==void 0)):void 0}),e._zod.parse=(r,n)=>{const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>xt(a,e)):xt(o,e)}});function xt(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:t}),e}const ks=m("$ZodCatch",(e,t)=>{Z.init(e,t),P(e._zod,"optin",()=>t.innerType._zod.optin),P(e._zod,"optout",()=>t.innerType._zod.optout),P(e._zod,"values",()=>t.innerType._zod.values),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(a=>(r.value=a.value,a.issues.length&&(r.value=t.catchValue({...r,error:{issues:a.issues.map(i=>oe(i,n,se()))},input:r.value}),r.issues=[]),r)):(r.value=o.value,o.issues.length&&(r.value=t.catchValue({...r,error:{issues:o.issues.map(a=>oe(a,n,se()))},input:r.value}),r.issues=[]),r)}}),Cs=m("$ZodPipe",(e,t)=>{Z.init(e,t),P(e._zod,"values",()=>t.in._zod.values),P(e._zod,"optin",()=>t.in._zod.optin),P(e._zod,"optout",()=>t.out._zod.optout),P(e._zod,"propValues",()=>t.in._zod.propValues),e._zod.parse=(r,n)=>{if(n.direction==="backward"){const a=t.out._zod.run(r,n);return a instanceof Promise?a.then(i=>ye(i,t.in,n)):ye(a,t.in,n)}const o=t.in._zod.run(r,n);return o instanceof Promise?o.then(a=>ye(a,t.out,n)):ye(o,t.out,n)}});function ye(e,t,r){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},r)}const Ts=m("$ZodReadonly",(e,t)=>{Z.init(e,t),P(e._zod,"propValues",()=>t.innerType._zod.propValues),P(e._zod,"values",()=>t.innerType._zod.values),P(e._zod,"optin",()=>t.innerType?._zod?.optin),P(e._zod,"optout",()=>t.innerType?._zod?.optout),e._zod.parse=(r,n)=>{if(n.direction==="backward")return t.innerType._zod.run(r,n);const o=t.innerType._zod.run(r,n);return o instanceof Promise?o.then(bt):bt(o)}});function bt(e){return e.value=Object.freeze(e.value),e}const Ps=m("$ZodCustom",(e,t)=>{B.init(e,t),Z.init(e,t),e._zod.parse=(r,n)=>r,e._zod.check=r=>{const n=r.value,o=t.fn(n);if(o instanceof Promise)return o.then(a=>vt(a,r,n,e));vt(o,r,n,e)}});function vt(e,t,r,n){if(!e){const o={code:"custom",input:r,inst:n,path:[...n._zod.def.path??[]],continue:!n._zod.def.abort};n._zod.def.params&&(o.params=n._zod.def.params),t.issues.push(ge(o))}}var _t;class $s{constructor(){this._map=new WeakMap,this._idmap=new Map}add(t,...r){const n=r[0];if(this._map.set(t,n),n&&typeof n=="object"&&"id"in n){if(this._idmap.has(n.id))throw new Error(`ID ${n.id} already exists in the registry`);this._idmap.set(n.id,t)}return this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(t){const r=this._map.get(t);return r&&typeof r=="object"&&"id"in r&&this._idmap.delete(r.id),this._map.delete(t),this}get(t){const r=t._zod.parent;if(r){const n={...this.get(r)??{}};delete n.id;const o={...n,...this._map.get(t)};return Object.keys(o).length?o:void 0}return this._map.get(t)}has(t){return this._map.has(t)}}function zs(){return new $s}(_t=globalThis).__zod_globalRegistry??(_t.__zod_globalRegistry=zs());const fe=globalThis.__zod_globalRegistry;function Os(e,t){return new e({type:"string",...y(t)})}function Ms(e,t){return new e({type:"string",format:"email",check:"string_format",abort:!1,...y(t)})}function yt(e,t){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...y(t)})}function Is(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...y(t)})}function As(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...y(t)})}function Es(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...y(t)})}function Ds(e,t){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...y(t)})}function Rs(e,t){return new e({type:"string",format:"url",check:"string_format",abort:!1,...y(t)})}function Zs(e,t){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...y(t)})}function Fs(e,t){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...y(t)})}function Ls(e,t){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...y(t)})}function Us(e,t){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...y(t)})}function Hs(e,t){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...y(t)})}function Vs(e,t){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...y(t)})}function Bs(e,t){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...y(t)})}function Ws(e,t){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...y(t)})}function Ys(e,t){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...y(t)})}function qs(e,t){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...y(t)})}function Js(e,t){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...y(t)})}function Gs(e,t){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...y(t)})}function Ks(e,t){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...y(t)})}function Qs(e,t){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...y(t)})}function Xs(e,t){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...y(t)})}function eo(e,t){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...y(t)})}function to(e,t){return new e({type:"string",format:"date",check:"string_format",...y(t)})}function ro(e,t){return new e({type:"string",format:"time",check:"string_format",precision:null,...y(t)})}function no(e,t){return new e({type:"string",format:"duration",check:"string_format",...y(t)})}function so(e,t){return new e({type:"number",coerce:!0,checks:[],...y(t)})}function oo(e,t){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...y(t)})}function wt(e,t){return new Qt({check:"less_than",...y(t),value:e,inclusive:!1})}function Ae(e,t){return new Qt({check:"less_than",...y(t),value:e,inclusive:!0})}function jt(e,t){return new Xt({check:"greater_than",...y(t),value:e,inclusive:!1})}function Ee(e,t){return new Xt({check:"greater_than",...y(t),value:e,inclusive:!0})}function St(e,t){return new On({check:"multiple_of",...y(t),value:e})}function rr(e,t){return new In({check:"max_length",...y(t),maximum:e})}function ke(e,t){return new An({check:"min_length",...y(t),minimum:e})}function nr(e,t){return new En({check:"length_equals",...y(t),length:e})}function ao(e,t){return new Dn({check:"string_format",format:"regex",...y(t),pattern:e})}function io(e){return new Rn({check:"string_format",format:"lowercase",...y(e)})}function co(e){return new Zn({check:"string_format",format:"uppercase",...y(e)})}function lo(e,t){return new Fn({check:"string_format",format:"includes",...y(t),includes:e})}function uo(e,t){return new Ln({check:"string_format",format:"starts_with",...y(t),prefix:e})}function mo(e,t){return new Un({check:"string_format",format:"ends_with",...y(t),suffix:e})}function ue(e){return new Hn({check:"overwrite",tx:e})}function ho(e){return ue(t=>t.normalize(e))}function po(){return ue(e=>e.trim())}function fo(){return ue(e=>e.toLowerCase())}function go(){return ue(e=>e.toUpperCase())}function xo(){return ue(e=>Vr(e))}function bo(e,t,r){return new e({type:"array",element:t,...y(r)})}function vo(e,t,r){return new e({type:"custom",check:"custom",fn:t,...y(r)})}function _o(e){const t=yo(r=>(r.addIssue=n=>{if(typeof n=="string")r.issues.push(ge(n,r.value,t._zod.def));else{const o=n;o.fatal&&(o.continue=!1),o.code??(o.code="custom"),o.input??(o.input=r.value),o.inst??(o.inst=t),o.continue??(o.continue=!t._zod.def.abort),r.issues.push(ge(o))}},e(r.value,r)));return t}function yo(e,t){const r=new B({check:"custom",...y(t)});return r._zod.check=e,r}function sr(e){let t=e?.target??"draft-2020-12";return t==="draft-4"&&(t="draft-04"),t==="draft-7"&&(t="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??fe,target:t,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref",reused:e?.reused??"inline",external:e?.external??void 0}}function L(e,t,r={path:[],schemaPath:[]}){var n;const o=e._zod.def,a=t.seen.get(e);if(a)return a.count++,r.schemaPath.includes(e)&&(a.cycle=r.path),a.schema;const i={schema:{},count:1,cycle:void 0,path:r.path};t.seen.set(e,i);const c=e._zod.toJSONSchema?.();if(c)i.schema=c;else{const d={...r,schemaPath:[...r.schemaPath,e],path:r.path},h=e._zod.parent;if(h)i.ref=h,L(h,t,d),t.seen.get(h).isParent=!0;else if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,d);else{const f=i.schema,p=t.processors[o.type];if(!p)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${o.type}`);p(e,t,f,d)}}const l=t.metadataRegistry.get(e);return l&&Object.assign(i.schema,l),t.io==="input"&&F(e)&&(delete i.schema.examples,delete i.schema.default),t.io==="input"&&i.schema._prefault&&((n=i.schema).default??(n.default=i.schema._prefault)),delete i.schema._prefault,t.seen.get(e).schema}function or(e,t){const r=e.seen.get(t);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");const n=a=>{const i=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const d=e.external.registry.get(a[0])?.id,h=e.external.uri??(p=>p);if(d)return{ref:h(d)};const f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${h("__shared")}#/${i}/${f}`}}if(a[1]===r)return{ref:"#"};const l=`#/${i}/`,u=a[1].schema.id??`__schema${e.counter++}`;return{defId:u,ref:l+u}},o=a=>{if(a[1].schema.$ref)return;const i=a[1],{ref:c,defId:l}=n(a);i.def={...i.schema},l&&(i.defId=l);const u=i.schema;for(const d in u)delete u[d];u.$ref=c};if(e.cycles==="throw")for(const a of e.seen.entries()){const i=a[1];if(i.cycle)throw new Error(`Cycle detected: #/${i.cycle?.join("/")}/ - -Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const a of e.seen.entries()){const i=a[1];if(t===a[0]){o(a);continue}if(e.external){const l=e.external.registry.get(a[0])?.id;if(t!==a[0]&&l){o(a);continue}}if(e.metadataRegistry.get(a[0])?.id){o(a);continue}if(i.cycle){o(a);continue}if(i.count>1&&e.reused==="ref"){o(a);continue}}}function ar(e,t){const r=e.seen.get(t);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");const n=i=>{const c=e.seen.get(i),l=c.def??c.schema,u={...l};if(c.ref===null)return;const d=c.ref;if(c.ref=null,d){n(d);const h=e.seen.get(d).schema;h.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(l.allOf=l.allOf??[],l.allOf.push(h)):(Object.assign(l,h),Object.assign(l,u))}c.isParent||e.override({zodSchema:i,jsonSchema:l,path:c.path??[]})};for(const i of[...e.seen.entries()].reverse())n(i[0]);const o={};if(e.target==="draft-2020-12"?o.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?o.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?o.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){const i=e.external.registry.get(t)?.id;if(!i)throw new Error("Schema is missing an `id` property");o.$id=e.external.uri(i)}Object.assign(o,r.def??r.schema);const a=e.external?.defs??{};for(const i of e.seen.entries()){const c=i[1];c.def&&c.defId&&(a[c.defId]=c.def)}e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?o.$defs=a:o.definitions=a);try{const i=JSON.parse(JSON.stringify(o));return Object.defineProperty(i,"~standard",{value:{...t["~standard"],jsonSchema:{input:Ce(t,"input"),output:Ce(t,"output")}},enumerable:!1,writable:!1}),i}catch{throw new Error("Error converting schema to JSON.")}}function F(e,t){const r=t??{seen:new Set};if(r.seen.has(e))return!1;r.seen.add(e);const n=e._zod.def;if(n.type==="transform")return!0;if(n.type==="array")return F(n.element,r);if(n.type==="set")return F(n.valueType,r);if(n.type==="lazy")return F(n.getter(),r);if(n.type==="promise"||n.type==="optional"||n.type==="nonoptional"||n.type==="nullable"||n.type==="readonly"||n.type==="default"||n.type==="prefault")return F(n.innerType,r);if(n.type==="intersection")return F(n.left,r)||F(n.right,r);if(n.type==="record"||n.type==="map")return F(n.keyType,r)||F(n.valueType,r);if(n.type==="pipe")return F(n.in,r)||F(n.out,r);if(n.type==="object"){for(const o in n.shape)if(F(n.shape[o],r))return!0;return!1}if(n.type==="union"){for(const o of n.options)if(F(o,r))return!0;return!1}if(n.type==="tuple"){for(const o of n.items)if(F(o,r))return!0;return!!(n.rest&&F(n.rest,r))}return!1}const wo=(e,t={})=>r=>{const n=sr({...r,processors:t});return L(e,n),or(n,e),ar(n,e)},Ce=(e,t)=>r=>{const{libraryOptions:n,target:o}=r??{},a=sr({...n??{},target:o,io:t,processors:{}});return L(e,a),or(a,e),ar(a,e)},jo={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},So=(e,t,r,n)=>{const o=r;o.type="string";const{minimum:a,maximum:i,format:c,patterns:l,contentEncoding:u}=e._zod.bag;if(typeof a=="number"&&(o.minLength=a),typeof i=="number"&&(o.maxLength=i),c&&(o.format=jo[c]??c,o.format===""&&delete o.format),u&&(o.contentEncoding=u),l&&l.size>0){const d=[...l];d.length===1?o.pattern=d[0].source:d.length>1&&(o.allOf=[...d.map(h=>({...t.target==="draft-07"||t.target==="draft-04"||t.target==="openapi-3.0"?{type:"string"}:{},pattern:h.source}))])}},No=(e,t,r,n)=>{const o=r,{minimum:a,maximum:i,format:c,multipleOf:l,exclusiveMaximum:u,exclusiveMinimum:d}=e._zod.bag;typeof c=="string"&&c.includes("int")?o.type="integer":o.type="number",typeof d=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.minimum=d,o.exclusiveMinimum=!0):o.exclusiveMinimum=d),typeof a=="number"&&(o.minimum=a,typeof d=="number"&&t.target!=="draft-04"&&(d>=a?delete o.minimum:delete o.exclusiveMinimum)),typeof u=="number"&&(t.target==="draft-04"||t.target==="openapi-3.0"?(o.maximum=u,o.exclusiveMaximum=!0):o.exclusiveMaximum=u),typeof i=="number"&&(o.maximum=i,typeof u=="number"&&t.target!=="draft-04"&&(u<=i?delete o.maximum:delete o.exclusiveMaximum)),typeof l=="number"&&(o.multipleOf=l)},ko=(e,t,r,n)=>{if(t.unrepresentable==="throw")throw new Error("Custom types cannot be represented in JSON Schema")},Co=(e,t,r,n)=>{if(t.unrepresentable==="throw")throw new Error("Transforms cannot be represented in JSON Schema")},To=(e,t,r,n)=>{const o=r,a=e._zod.def,{minimum:i,maximum:c}=e._zod.bag;typeof i=="number"&&(o.minItems=i),typeof c=="number"&&(o.maxItems=c),o.type="array",o.items=L(a.element,t,{...n,path:[...n.path,"items"]})},Po=(e,t,r,n)=>{const o=e._zod.def,a=o.inclusive===!1,i=o.options.map((c,l)=>L(c,t,{...n,path:[...n.path,a?"oneOf":"anyOf",l]}));a?r.oneOf=i:r.anyOf=i},$o=(e,t,r,n)=>{const o=e._zod.def,a=L(o.left,t,{...n,path:[...n.path,"allOf",0]}),i=L(o.right,t,{...n,path:[...n.path,"allOf",1]}),c=u=>"allOf"in u&&Object.keys(u).length===1,l=[...c(a)?a.allOf:[a],...c(i)?i.allOf:[i]];r.allOf=l},zo=(e,t,r,n)=>{const o=e._zod.def,a=L(o.innerType,t,n),i=t.seen.get(e);t.target==="openapi-3.0"?(i.ref=o.innerType,r.nullable=!0):r.anyOf=[a,{type:"null"}]},Oo=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType},Mo=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,r.default=JSON.parse(JSON.stringify(o.defaultValue))},Io=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,t.io==="input"&&(r._prefault=JSON.parse(JSON.stringify(o.defaultValue)))},Ao=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType;let i;try{i=o.catchValue(void 0)}catch{throw new Error("Dynamic catch values are not supported in JSON Schema")}r.default=i},Eo=(e,t,r,n)=>{const o=e._zod.def,a=t.io==="input"?o.in._zod.def.type==="transform"?o.out:o.in:o.out;L(a,t,n);const i=t.seen.get(e);i.ref=a},Do=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType,r.readOnly=!0},Ro=(e,t,r,n)=>{const o=e._zod.def;L(o.innerType,t,n);const a=t.seen.get(e);a.ref=o.innerType},Zo=m("ZodISODateTime",(e,t)=>{rs.init(e,t),M.init(e,t)});function Fo(e){return eo(Zo,e)}const Lo=m("ZodISODate",(e,t)=>{ns.init(e,t),M.init(e,t)});function Uo(e){return to(Lo,e)}const Ho=m("ZodISOTime",(e,t)=>{ss.init(e,t),M.init(e,t)});function Vo(e){return ro(Ho,e)}const Bo=m("ZodISODuration",(e,t)=>{os.init(e,t),M.init(e,t)});function Wo(e){return no(Bo,e)}const Yo=(e,t)=>{Wt.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{value:r=>Jr(e,r)},flatten:{value:r=>qr(e,r)},addIssue:{value:r=>{e.issues.push(r),e.message=JSON.stringify(e.issues,Le,2)}},addIssues:{value:r=>{e.issues.push(...r),e.message=JSON.stringify(e.issues,Le,2)}},isEmpty:{get(){return e.issues.length===0}}})},J=m("ZodError",Yo,{Parent:Error}),qo=Xe(J),Jo=et(J),Go=Pe(J),Ko=$e(J),Qo=Qr(J),Xo=Xr(J),ea=en(J),ta=tn(J),ra=rn(J),na=nn(J),sa=sn(J),oa=on(J),U=m("ZodType",(e,t)=>(Z.init(e,t),Object.assign(e["~standard"],{jsonSchema:{input:Ce(e,"input"),output:Ce(e,"output")}}),e.toJSONSchema=wo(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{value:t}),e.check=(...r)=>e.clone(Hr(t,{checks:[...t.checks??[],...r.map(n=>typeof n=="function"?{_zod:{check:n,def:{check:"custom"},onattach:[]}}:n)]})),e.clone=(r,n)=>Br(e,r,n),e.brand=()=>e,e.register=((r,n)=>(r.add(e,n),e)),e.parse=(r,n)=>qo(e,r,n,{callee:e.parse}),e.safeParse=(r,n)=>Go(e,r,n),e.parseAsync=async(r,n)=>Jo(e,r,n,{callee:e.parseAsync}),e.safeParseAsync=async(r,n)=>Ko(e,r,n),e.spa=e.safeParseAsync,e.encode=(r,n)=>Qo(e,r,n),e.decode=(r,n)=>Xo(e,r,n),e.encodeAsync=async(r,n)=>ea(e,r,n),e.decodeAsync=async(r,n)=>ta(e,r,n),e.safeEncode=(r,n)=>ra(e,r,n),e.safeDecode=(r,n)=>na(e,r,n),e.safeEncodeAsync=async(r,n)=>sa(e,r,n),e.safeDecodeAsync=async(r,n)=>oa(e,r,n),e.refine=(r,n)=>e.check(Ya(r,n)),e.superRefine=r=>e.check(qa(r)),e.overwrite=r=>e.check(ue(r)),e.optional=()=>Ct(e),e.nullable=()=>Tt(e),e.nullish=()=>Ct(Tt(e)),e.nonoptional=r=>Fa(e,r),e.array=()=>ka(e),e.or=r=>Ta([e,r]),e.and=r=>$a(e,r),e.transform=r=>Pt(e,Oa(r)),e.default=r=>Ea(e,r),e.prefault=r=>Ra(e,r),e.catch=r=>Ua(e,r),e.pipe=r=>Pt(e,r),e.readonly=()=>Ba(e),e.describe=r=>{const n=e.clone();return fe.add(n,{description:r}),n},Object.defineProperty(e,"description",{get(){return fe.get(e)?.description},configurable:!0}),e.meta=(...r)=>{if(r.length===0)return fe.get(e);const n=e.clone();return fe.add(n,r[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e)),ir=m("_ZodString",(e,t)=>{tt.init(e,t),U.init(e,t),e._zod.processJSONSchema=(n,o,a)=>So(e,n,o);const r=e._zod.bag;e.format=r.format??null,e.minLength=r.minimum??null,e.maxLength=r.maximum??null,e.regex=(...n)=>e.check(ao(...n)),e.includes=(...n)=>e.check(lo(...n)),e.startsWith=(...n)=>e.check(uo(...n)),e.endsWith=(...n)=>e.check(mo(...n)),e.min=(...n)=>e.check(ke(...n)),e.max=(...n)=>e.check(rr(...n)),e.length=(...n)=>e.check(nr(...n)),e.nonempty=(...n)=>e.check(ke(1,...n)),e.lowercase=n=>e.check(io(n)),e.uppercase=n=>e.check(co(n)),e.trim=()=>e.check(po()),e.normalize=(...n)=>e.check(ho(...n)),e.toLowerCase=()=>e.check(fo()),e.toUpperCase=()=>e.check(go()),e.slugify=()=>e.check(xo())}),aa=m("ZodString",(e,t)=>{tt.init(e,t),ir.init(e,t),e.email=r=>e.check(Ms(ia,r)),e.url=r=>e.check(Rs(ca,r)),e.jwt=r=>e.check(Xs(ja,r)),e.emoji=r=>e.check(Zs(la,r)),e.guid=r=>e.check(yt(Nt,r)),e.uuid=r=>e.check(Is(we,r)),e.uuidv4=r=>e.check(As(we,r)),e.uuidv6=r=>e.check(Es(we,r)),e.uuidv7=r=>e.check(Ds(we,r)),e.nanoid=r=>e.check(Fs(ua,r)),e.guid=r=>e.check(yt(Nt,r)),e.cuid=r=>e.check(Ls(da,r)),e.cuid2=r=>e.check(Us(ma,r)),e.ulid=r=>e.check(Hs(ha,r)),e.base64=r=>e.check(Gs(_a,r)),e.base64url=r=>e.check(Ks(ya,r)),e.xid=r=>e.check(Vs(pa,r)),e.ksuid=r=>e.check(Bs(fa,r)),e.ipv4=r=>e.check(Ws(ga,r)),e.ipv6=r=>e.check(Ys(xa,r)),e.cidrv4=r=>e.check(qs(ba,r)),e.cidrv6=r=>e.check(Js(va,r)),e.e164=r=>e.check(Qs(wa,r)),e.datetime=r=>e.check(Fo(r)),e.date=r=>e.check(Uo(r)),e.time=r=>e.check(Vo(r)),e.duration=r=>e.check(Wo(r))});function be(e){return Os(aa,e)}const M=m("ZodStringFormat",(e,t)=>{$.init(e,t),ir.init(e,t)}),ia=m("ZodEmail",(e,t)=>{Yn.init(e,t),M.init(e,t)}),Nt=m("ZodGUID",(e,t)=>{Bn.init(e,t),M.init(e,t)}),we=m("ZodUUID",(e,t)=>{Wn.init(e,t),M.init(e,t)}),ca=m("ZodURL",(e,t)=>{qn.init(e,t),M.init(e,t)}),la=m("ZodEmoji",(e,t)=>{Jn.init(e,t),M.init(e,t)}),ua=m("ZodNanoID",(e,t)=>{Gn.init(e,t),M.init(e,t)}),da=m("ZodCUID",(e,t)=>{Kn.init(e,t),M.init(e,t)}),ma=m("ZodCUID2",(e,t)=>{Qn.init(e,t),M.init(e,t)}),ha=m("ZodULID",(e,t)=>{Xn.init(e,t),M.init(e,t)}),pa=m("ZodXID",(e,t)=>{es.init(e,t),M.init(e,t)}),fa=m("ZodKSUID",(e,t)=>{ts.init(e,t),M.init(e,t)}),ga=m("ZodIPv4",(e,t)=>{as.init(e,t),M.init(e,t)}),xa=m("ZodIPv6",(e,t)=>{is.init(e,t),M.init(e,t)}),ba=m("ZodCIDRv4",(e,t)=>{cs.init(e,t),M.init(e,t)}),va=m("ZodCIDRv6",(e,t)=>{ls.init(e,t),M.init(e,t)}),_a=m("ZodBase64",(e,t)=>{us.init(e,t),M.init(e,t)}),ya=m("ZodBase64URL",(e,t)=>{ms.init(e,t),M.init(e,t)}),wa=m("ZodE164",(e,t)=>{hs.init(e,t),M.init(e,t)}),ja=m("ZodJWT",(e,t)=>{fs.init(e,t),M.init(e,t)}),cr=m("ZodNumber",(e,t)=>{tr.init(e,t),U.init(e,t),e._zod.processJSONSchema=(n,o,a)=>No(e,n,o),e.gt=(n,o)=>e.check(jt(n,o)),e.gte=(n,o)=>e.check(Ee(n,o)),e.min=(n,o)=>e.check(Ee(n,o)),e.lt=(n,o)=>e.check(wt(n,o)),e.lte=(n,o)=>e.check(Ae(n,o)),e.max=(n,o)=>e.check(Ae(n,o)),e.int=n=>e.check(kt(n)),e.safe=n=>e.check(kt(n)),e.positive=n=>e.check(jt(0,n)),e.nonnegative=n=>e.check(Ee(0,n)),e.negative=n=>e.check(wt(0,n)),e.nonpositive=n=>e.check(Ae(0,n)),e.multipleOf=(n,o)=>e.check(St(n,o)),e.step=(n,o)=>e.check(St(n,o)),e.finite=()=>e;const r=e._zod.bag;e.minValue=Math.max(r.minimum??Number.NEGATIVE_INFINITY,r.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null,e.maxValue=Math.min(r.maximum??Number.POSITIVE_INFINITY,r.exclusiveMaximum??Number.POSITIVE_INFINITY)??null,e.isInt=(r.format??"").includes("int")||Number.isSafeInteger(r.multipleOf??.5),e.isFinite=!0,e.format=r.format??null}),Sa=m("ZodNumberFormat",(e,t)=>{gs.init(e,t),cr.init(e,t)});function kt(e){return oo(Sa,e)}const Na=m("ZodArray",(e,t)=>{xs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>To(e,r,n,o),e.element=t.element,e.min=(r,n)=>e.check(ke(r,n)),e.nonempty=r=>e.check(ke(1,r)),e.max=(r,n)=>e.check(rr(r,n)),e.length=(r,n)=>e.check(nr(r,n)),e.unwrap=()=>e.element});function ka(e,t){return bo(Na,e,t)}const Ca=m("ZodUnion",(e,t)=>{bs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Po(e,r,n,o),e.options=t.options});function Ta(e,t){return new Ca({type:"union",options:e,...y(t)})}const Pa=m("ZodIntersection",(e,t)=>{vs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>$o(e,r,n,o)});function $a(e,t){return new Pa({type:"intersection",left:e,right:t})}const za=m("ZodTransform",(e,t)=>{_s.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Co(e,r),e._zod.parse=(r,n)=>{if(n.direction==="backward")throw new Ut(e.constructor.name);r.addIssue=a=>{if(typeof a=="string")r.issues.push(ge(a,r.value,t));else{const i=a;i.fatal&&(i.continue=!1),i.code??(i.code="custom"),i.input??(i.input=r.value),i.inst??(i.inst=e),r.issues.push(ge(i))}};const o=t.transform(r.value,r);return o instanceof Promise?o.then(a=>(r.value=a,r)):(r.value=o,r)}});function Oa(e){return new za({type:"transform",transform:e})}const Ma=m("ZodOptional",(e,t)=>{ys.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Ro(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Ma({type:"optional",innerType:e})}const Ia=m("ZodNullable",(e,t)=>{ws.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>zo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Tt(e){return new Ia({type:"nullable",innerType:e})}const Aa=m("ZodDefault",(e,t)=>{js.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Mo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Ea(e,t){return new Aa({type:"default",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Da=m("ZodPrefault",(e,t)=>{Ss.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Io(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ra(e,t){return new Da({type:"prefault",innerType:e,get defaultValue(){return typeof t=="function"?t():Vt(t)}})}const Za=m("ZodNonOptional",(e,t)=>{Ns.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Oo(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Fa(e,t){return new Za({type:"nonoptional",innerType:e,...y(t)})}const La=m("ZodCatch",(e,t)=>{ks.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Ao(e,r,n,o),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Ua(e,t){return new La({type:"catch",innerType:e,catchValue:typeof t=="function"?t:()=>t})}const Ha=m("ZodPipe",(e,t)=>{Cs.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Eo(e,r,n,o),e.in=t.in,e.out=t.out});function Pt(e,t){return new Ha({type:"pipe",in:e,out:t})}const Va=m("ZodReadonly",(e,t)=>{Ts.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>Do(e,r,n,o),e.unwrap=()=>e._zod.def.innerType});function Ba(e){return new Va({type:"readonly",innerType:e})}const Wa=m("ZodCustom",(e,t)=>{Ps.init(e,t),U.init(e,t),e._zod.processJSONSchema=(r,n,o)=>ko(e,r)});function Ya(e,t={}){return vo(Wa,e,t)}function qa(e){return _o(e)}function Q(e){return so(cr,e)}const Ja=/^[A-Za-z0-9\-_+ ]*$/,Ga=/^([A-Za-z0-9 ()/._-]|%\d*[CYmdHMSqvQtwhf$]|%\d*\{[a-z_]+\})*$/,Ka=/^[A-Za-z0-9 ()/._-]*$/,Qa=/^[A-Za-z0-9 _+.@^~<>,-]*$/;function rt(e){return e.includes("..")||e.includes("~/")}const $t=be().max(64,"Name must be 64 characters or less").regex(Ja,"Name can only contain letters, numbers, hyphens, underscores, plus signs, and spaces"),De=be().max(255,"Filename must be 255 characters or less").regex(Ga,"Filename contains invalid characters. Use letters, numbers, underscores, hyphens, strftime codes (%Y, %m, %d), and Motion tokens (%{movienbr}, %v, etc.)").refine(e=>!rt(e),{message:"Filename cannot contain directory traversal sequences (.. or ~/)"}),zt=be().max(4096,"Path must be 4096 characters or less").regex(Ka,"Path contains invalid characters").refine(e=>!rt(e),{message:"Path cannot contain directory traversal sequences (.. or ~/)"});be().max(255,"Email must be 255 characters or less").regex(Qa,"Email contains invalid characters");const Ot=Q().int("Framerate must be a whole number").min(1,"Framerate must be at least 1").max(100,"Framerate cannot exceed 100"),Mt=Q().int("Quality must be a whole number").min(1,"Quality must be at least 1%").max(100,"Quality cannot exceed 100%"),Xa=Q().int("Width must be a whole number").min(160,"Width must be at least 160 pixels").max(4096,"Width cannot exceed 4096 pixels"),ei=Q().int("Height must be a whole number").min(120,"Height must be at least 120 pixels").max(2160,"Height cannot exceed 2160 pixels"),It=Q().int("Port must be a whole number").min(1,"Port must be at least 1").max(65535,"Port cannot exceed 65535"),ti=Q().int("Threshold must be a whole number").min(1,"Threshold must be at least 1").max(2147483647,"Threshold is too large"),ri=Q().int("Noise level must be a whole number").min(0,"Noise level must be at least 0").max(255,"Noise level cannot exceed 255"),ni=Q().int("Log level must be a whole number").min(1,"Log level must be at least 1").max(9,"Log level cannot exceed 9"),si=Q().int("Device ID must be a whole number").min(1,"Device ID must be at least 1").max(999,"Device ID cannot exceed 999"),oi=Q().int("Must be a whole number").min(0,"Must be 0 or greater");be().transform(e=>{const t=e.toLowerCase();return t==="on"||t==="true"||t==="1"});function ai(e,t){const n={device_name:$t,camera_name:$t,device_id:si,target_dir:zt,snapshot_filename:De,picture_filename:De,movie_filename:De,log_file:zt,framerate:Ot,stream_maxrate:Ot,width:Xa,height:ei,stream_quality:Mt,picture_quality:Mt,stream_port:It,webcontrol_port:It,threshold:ti,noise_level:ri,minimum_motion_frames:oi,log_level:ni}[e];if(!n)return typeof t=="string"&&rt(t)?{success:!1,error:"Value cannot contain directory traversal sequences (.. or ~/)"}:{success:!0};const o=n.safeParse(t);return o.success?{success:!0}:{success:!1,error:o.error.issues[0]?.message??"Invalid value"}}async function ii(){return await Te("/0/api/system/reboot",{})}async function ci(){return await Te("/0/api/system/shutdown",{})}async function li(){return await Te("/0/api/system/service-restart",{})}function ui({config:e,onChange:t,getError:r,originalConfig:n,systemStatus:o}){const{addToast:a}=Ye(),i=o?.actions?.service??!1,c=o?.actions?.power??!1,l=(g,S="")=>e[g]?.value??S,u=(g,S="")=>n?.[g]?.value??S,d=g=>e[g]?.password_set===!0,h=g=>n?.[g]?.password_set===!0,f=async()=>{if(window.confirm("Are you sure you want to reboot the Pi? The system will restart and be unavailable for about a minute."))try{await ii(),a("Rebooting... The system will be back online shortly.","info")}catch(g){a(g.message||"Failed to reboot. Power control may be disabled in config.","error")}},p=async()=>{if(window.confirm("Are you sure you want to shutdown the Pi? You will need to physically power it back on."))try{await ci(),a("Shutting down... The system will power off.","warning")}catch(g){a(g.message||"Failed to shutdown. Power control may be disabled in config.","error")}},v=async()=>{if(window.confirm("Are you sure you want to restart the Motion service? Active streams will be interrupted briefly."))try{await li(),a("Restarting Motion... Streams will resume shortly.","info")}catch(g){a(g.message||"Failed to restart service. Service control may be disabled in config.","error")}};return s.jsxs(s.Fragment,{children:[s.jsx(O,{title:"Device Controls",description:"Service and system power management",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"flex flex-col gap-4",children:[s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"Service Control"}),s.jsx("button",{onClick:v,disabled:!i,className:`px-4 py-2 rounded-lg text-sm transition-colors ${i?"bg-blue-600/20 text-blue-300 hover:bg-blue-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:i?void 0:"Enable with webcontrol_actions service=on",children:"Restart Motion"}),!i&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions service=on"})," to enable"]})]}),s.jsxs("div",{children:[s.jsx("p",{className:"text-xs text-gray-400 mb-2",children:"System Power"}),s.jsxs("div",{className:"flex gap-3",children:[s.jsx("button",{onClick:f,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-yellow-600/20 text-yellow-300 hover:bg-yellow-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Restart Pi"}),s.jsx("button",{onClick:p,disabled:!c,className:`px-4 py-2 rounded-lg text-sm transition-colors ${c?"bg-red-600/20 text-red-300 hover:bg-red-600/30":"bg-gray-600/20 text-gray-500 cursor-not-allowed"}`,title:c?void 0:"Enable with webcontrol_actions power=on",children:"Shutdown Pi"})]}),!c&&s.jsxs("p",{className:"text-xs text-amber-500 mt-1",children:["Disabled - add ",s.jsx("code",{className:"text-xs bg-surface-base px-1 rounded",children:"webcontrol_actions power=on"})," to enable"]})]})]})}),s.jsxs(O,{title:"Authentication",description:"Web interface and stream access credentials",collapsible:!0,defaultOpen:!0,children:[s.jsxs("div",{className:"mb-6",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Web Interface"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Credentials for logging into this web interface. Format: username:password"}),u("webcontrol_authentication","")===""&&u("webcontrol_user_authentication","")===""&&s.jsx("div",{className:"mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg",children:s.jsxs("div",{className:"flex items-start gap-2",children:[s.jsx("svg",{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})}),s.jsxs("div",{className:"flex-1",children:[s.jsx("p",{className:"text-sm font-medium text-blue-300 mb-1",children:"Initial Setup Available"}),s.jsxs("p",{className:"text-xs text-blue-300/80",children:["Configure authentication now to secure your Motion installation. During initial setup, you can set credentials without changing"," ",s.jsx("code",{className:"text-xs bg-surface px-1 rounded",children:"webcontrol_parms"})," in the config file. Once authentication is configured, it will require restart to apply."]})]})]})}),s.jsxs("div",{className:"mb-4",children:[s.jsx("label",{className:"block text-sm font-medium mb-1 text-gray-300",children:"Admin Username"}),s.jsx("input",{type:"text",value:"admin",disabled:!0,className:`w-full px-3 py-2 bg-surface-elevated border border-gray-700 rounded-lg - text-gray-500 cursor-not-allowed`}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:"Admin username is fixed for security"})]}),s.jsx(j,{label:"Admin Password",value:String(l("webcontrol_authentication","")).split(":")[1]||"",onChange:g=>{const S=String(l("webcontrol_authentication","")).split(":")[0]||"admin";t("webcontrol_authentication",`${S}:${g}`)},type:"password",placeholder:d("webcontrol_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_authentication")?"Password is configured. Enter a new password to change it.":"Administrator password (click eye icon to reveal)",originalValue:h("webcontrol_authentication")?"[set]":""}),s.jsx(j,{label:"Viewer Username",value:String(l("webcontrol_user_authentication","")).split(":")[0]||"",onChange:g=>{const S=String(l("webcontrol_user_authentication","")).split(":")[1]||"";t("webcontrol_user_authentication",g?`${g}:${S}`:"")},helpText:"View-only username (can view streams but not change settings)",error:r?.("webcontrol_user_authentication"),originalValue:String(u("webcontrol_user_authentication","")).split(":")[0]||"",showVisibilityToggle:!1}),s.jsx(j,{label:"Viewer Password",value:String(l("webcontrol_user_authentication","")).split(":")[1]||"",onChange:g=>{const S=String(l("webcontrol_user_authentication","")).split(":")[0]||"";t("webcontrol_user_authentication",S?`${S}:${g}`:"")},type:"password",placeholder:d("webcontrol_user_authentication")?"Password set (enter new to change)":"Enter password",helpText:d("webcontrol_user_authentication")?"Password is configured. Enter a new password to change it.":"View-only password (click eye icon to reveal)",originalValue:h("webcontrol_user_authentication")?"[set]":""})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium text-sm mb-3 text-gray-300",children:"Direct Stream Access"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Authentication for direct stream URLs (embedded in websites, VLC, home automation)"}),s.jsx(D,{label:"Authentication Mode",value:String(l("webcontrol_auth_method","0")),onChange:g=>t("webcontrol_auth_method",g),options:[{value:"0",label:"None - No authentication required"},{value:"1",label:"Basic - Simple username/password (use with HTTPS)"},{value:"2",label:"Digest - Secure hash-based (recommended)"}],helpText:"Controls authentication for direct stream access and external API clients. The web UI uses session-based login instead.",error:r?.("webcontrol_auth_method")})]})]}),s.jsxs(O,{title:"Daemon",description:"Motion process settings",collapsible:!0,defaultOpen:!1,children:[s.jsx(V,{label:"Run as Daemon",value:l("daemon",!1),onChange:g=>t("daemon",g),helpText:"Run Motion in background mode"}),s.jsx(j,{label:"PID File",value:String(l("pid_file","")),onChange:g=>t("pid_file",g),helpText:"Path to process ID file. Leave empty to let systemd manage the PID.",error:r?.("pid_file"),originalValue:String(u("pid_file",""))}),s.jsx(j,{label:"Log File",value:String(l("log_file","")),onChange:g=>t("log_file",g),helpText:"Path to log file. Leave empty to use journald (view with: journalctl -u motion).",error:r?.("log_file"),originalValue:String(u("log_file",""))}),s.jsx(D,{label:"Log Level",value:String(l("log_level","6")),onChange:g=>t("log_level",g),options:[{value:"1",label:"Emergency"},{value:"2",label:"Alert"},{value:"3",label:"Critical"},{value:"4",label:"Error"},{value:"5",label:"Warning"},{value:"6",label:"Notice"},{value:"7",label:"Info"},{value:"8",label:"Debug"},{value:"9",label:"All"}],helpText:"Verbosity level for logging",error:r?.("log_level")})]}),s.jsxs(O,{title:"Web Server",description:"API server configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(j,{label:"Port",value:String(l("webcontrol_port","8080")),onChange:g=>t("webcontrol_port",g),type:"number",helpText:"Primary web server port",error:r?.("webcontrol_port"),originalValue:String(u("webcontrol_port","8080"))}),s.jsx(V,{label:"Localhost Only",value:l("webcontrol_localhost",!1),onChange:g=>t("webcontrol_localhost",g),helpText:"Restrict access to localhost only (127.0.0.1)"}),s.jsx(V,{label:"TLS/HTTPS",value:l("webcontrol_tls",!1),onChange:g=>t("webcontrol_tls",g),helpText:"Enable HTTPS encryption"}),l("webcontrol_tls",!1)&&s.jsxs(s.Fragment,{children:[s.jsx(j,{label:"TLS Certificate",value:String(l("webcontrol_cert","")),onChange:g=>t("webcontrol_cert",g),helpText:"Path to TLS certificate file (.crt or .pem)",error:r?.("webcontrol_cert")}),s.jsx(j,{label:"TLS Private Key",value:String(l("webcontrol_key","")),onChange:g=>t("webcontrol_key",g),helpText:"Path to TLS private key file (.key or .pem)",error:r?.("webcontrol_key")})]})]})]})}function di({config:e,onChange:t,getError:r}){const n=(h,f="")=>e[h]?.value??f,o=Number(n("width",640)),a=Number(n("height",480)),i=ot(o,a),c=st.some(h=>h.width===o&&h.height===a),l=h=>{if(h==="custom")return;const{width:f,height:p}=jr(h);t("width",f),t("height",p)},u=h=>{t("width",Number(h))},d=h=>{t("height",Number(h))};return s.jsxs(O,{title:"Device Settings",description:"Basic camera configuration and identification",collapsible:!0,defaultOpen:!1,children:[s.jsx(j,{label:"Camera Name",value:String(n("device_name","")),onChange:h=>t("device_name",h),placeholder:"My Camera",helpText:"Friendly name for this camera",error:r?.("device_name")}),s.jsx(D,{label:"Resolution",value:c?i:"custom",onChange:l,options:[...st.map(h=>({value:ot(h.width,h.height),label:h.label})),{value:"custom",label:"Custom"}],helpText:"Video resolution (width x height)"}),!c&&s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(j,{label:"Width",value:String(o),onChange:u,type:"number",helpText:"Custom width in pixels",error:r?.("width")}),s.jsx(j,{label:"Height",value:String(a),onChange:d,type:"number",helpText:"Custom height in pixels",error:r?.("height")})]}),s.jsx(C,{label:"Framerate",value:Number(n("framerate",15)),onChange:h=>t("framerate",h),min:2,max:30,unit:" fps",helpText:"Frames per second (higher uses more CPU)",error:r?.("framerate")}),s.jsx(D,{label:"Rotation",value:String(n("rotate",0)),onChange:h=>t("rotate",Number(h)),options:wr.map(h=>({value:String(h.value),label:h.label})),helpText:"Rotate camera image"})]})}function lr(e){const{data:t,isLoading:r}=Zt();return x.useMemo(()=>{if(r||!t?.status)return{isLoading:!0,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const n=`cam${e}`,o=t.status[n];if(!o)return{isLoading:!1,cameraType:"unknown",cameraDevice:"",isConnected:!1,features:{hasLibcamControls:!1,hasV4L2Controls:!1,hasNetcamConfig:!1,hasDualStream:!1,supportsPassthrough:!1}};const a=o.camera_type??"unknown";return{isLoading:!1,cameraType:a,cameraDevice:o.camera_device??"",isConnected:!o.lost_connection,features:{hasLibcamControls:a==="libcam",hasV4L2Controls:a==="v4l2",hasNetcamConfig:a==="netcam",hasDualStream:a==="netcam"&&o.has_high_stream===!0,supportsPassthrough:a==="netcam"},...a==="libcam"&&{libcamCapabilities:o.supportedControls},...a==="v4l2"&&{v4l2Controls:o.v4l2_controls},...a==="netcam"&&{netcamStatus:o.netcam_status}}},[t,e,r])}function mi({cameraId:e}){const{cameraType:t,cameraDevice:r,isConnected:n}=lr(e);return s.jsx(O,{title:"Camera Source",description:"Camera connection and type information",collapsible:!0,defaultOpen:!0,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Type:"}),s.jsx(hi,{type:t})]}),s.jsxs("div",{className:"flex items-start gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400 pt-0.5",children:"Device:"}),r?s.jsx("div",{className:"flex-1",children:s.jsx("code",{className:"text-sm text-gray-200 bg-gray-800 px-2 py-1 rounded",children:r})}):s.jsx("span",{className:"text-sm text-gray-500 italic",children:"Not configured"})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Status:"}),s.jsx(pi,{isConnected:n})]}),t==="unknown"&&s.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[s.jsx("p",{className:"text-sm text-gray-300 mb-2",children:s.jsx("strong",{children:"No camera configured"})}),s.jsx("p",{className:"text-sm text-gray-400 mb-3",children:"Configure a camera by setting one of the following:"}),s.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"libcam_device"})," - Raspberry Pi camera"]}),s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"v4l2_device"})," - USB webcam (e.g., /dev/video0)"]}),s.jsxs("li",{children:[s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"netcam_url"})," - IP camera (RTSP/HTTP URL)"]})]})]})]})})}function hi({type:e}){const t={libcam:"bg-purple-500/20 text-purple-300 border-purple-500/30",v4l2:"bg-blue-500/20 text-blue-300 border-blue-500/30",netcam:"bg-green-500/20 text-green-300 border-green-500/30",unknown:"bg-gray-500/20 text-gray-400 border-gray-500/30"},r={libcam:"libcamera (Pi Camera)",v4l2:"V4L2 (USB Camera)",netcam:"Network Camera (IP)",unknown:"Not Configured"};return s.jsx("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${t[e]}`,children:r[e]})}function pi({isConnected:e}){return s.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${e?"bg-green-500/20 text-green-300 border-green-500/30":"bg-red-500/20 text-red-300 border-red-500/30"}`,children:[s.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e?"bg-green-400":"bg-red-400"}`}),e?"Connected":"Disconnected"]})}function fi({config:e,onChange:t,getError:r,capabilities:n,originalConfig:o}){const a=(l,u="")=>e[l]?.value??u,i=(l,u="")=>o?.[l]?.value??u,c=!!a("libcam_awb_enable",!1);return s.jsxs(O,{title:"libcamera Controls",description:"Raspberry Pi camera controls (libcamera only)",collapsible:!0,defaultOpen:!1,children:[s.jsx(C,{label:"Brightness",value:Number(a("libcam_brightness",0)),onChange:l=>t("libcam_brightness",l),min:-1,max:1,step:.1,helpText:"Brightness adjustment (-1.0 to 1.0)",error:r?.("libcam_brightness")}),s.jsx(C,{label:"Contrast",value:Number(a("libcam_contrast",1)),onChange:l=>t("libcam_contrast",l),min:0,max:32,step:.5,helpText:"Contrast adjustment (0.0 to 32.0)",error:r?.("libcam_contrast")}),s.jsx(C,{label:"Gain (ISO)",value:Number(a("libcam_gain",1)),onChange:l=>t("libcam_gain",l),min:0,max:10,step:.1,helpText:"Analog gain (0=auto, 1.0-10.0) (Gain 1.0 ~ ISO 100)",error:r?.("libcam_gain")}),s.jsx(V,{label:"Auto White Balance",value:c,onChange:l=>t("libcam_awb_enable",l),helpText:"Enable automatic white balance"}),c&&s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"AWB Mode",value:String(a("libcam_awb_mode",0)),onChange:l=>t("libcam_awb_mode",Number(l)),options:Sr.map(l=>({value:String(l.value),label:l.label})),helpText:"White balance mode"}),n?.AwbLocked!==!1&&s.jsx(V,{label:"Lock AWB",value:!!a("libcam_awb_locked",!1),onChange:l=>t("libcam_awb_locked",l),helpText:"Lock white balance settings"})]}),!c&&s.jsxs(s.Fragment,{children:[n?.ColourTemperature!==!1&&s.jsx(C,{label:"Color Temperature",value:Number(a("libcam_colour_temp",0)),onChange:l=>t("libcam_colour_temp",l),min:0,max:1e4,step:100,unit:" K",helpText:"Manual color temperature in Kelvin (0-10000)",error:r?.("libcam_colour_temp")}),s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsx(C,{label:"Red Gain",value:Number(a("libcam_colour_gain_r",1)),onChange:l=>t("libcam_colour_gain_r",l),min:0,max:8,step:.1,helpText:"Red color gain (0.0-8.0)",error:r?.("libcam_colour_gain_r")}),s.jsx(C,{label:"Blue Gain",value:Number(a("libcam_colour_gain_b",1)),onChange:l=>t("libcam_colour_gain_b",l),min:0,max:8,step:.1,helpText:"Blue color gain (0.0-8.0)",error:r?.("libcam_colour_gain_b")})]}),n?.ColourTemperature===!1&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Color Temperature control is not available on this camera. NoIR cameras and some other sensors don't support this feature. Use Red/Blue Gain for manual white balance."]})]}),n?.AfMode?s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"Autofocus Mode",value:String(a("libcam_af_mode",0)),onChange:l=>t("libcam_af_mode",Number(l)),options:Nr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus control mode"}),Number(a("libcam_af_mode",0))===0&&n?.LensPosition&&s.jsx(C,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:r?.("libcam_lens_position")}),Number(a("libcam_af_mode",0))>0&&s.jsxs(s.Fragment,{children:[n?.AfRange&&s.jsx(D,{label:"Autofocus Range",value:String(a("libcam_af_range",0)),onChange:l=>t("libcam_af_range",Number(l)),options:kr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus range preference"}),n?.AfSpeed&&s.jsx(D,{label:"Autofocus Speed",value:String(a("libcam_af_speed",0)),onChange:l=>t("libcam_af_speed",Number(l)),options:Cr.map(l=>({value:String(l.value),label:l.label})),helpText:"Focus adjustment speed"})]})]}):n!==void 0?s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Autofocus:"})," Not supported on this camera.",n?.LensPosition?s.jsx("span",{children:" Manual focus (lens position) is available."}):s.jsx("span",{children:" This camera has fixed focus."})]}):null,!n?.AfMode&&n?.LensPosition&&s.jsx(C,{label:"Lens Position",value:Number(a("libcam_lens_position",0)),onChange:l=>t("libcam_lens_position",l),min:0,max:15,step:.5,unit:" dioptres",helpText:"Manual focus position (0.0-15.0 dioptres)",error:r?.("libcam_lens_position")}),s.jsx(j,{label:"Buffer Count",value:String(a("libcam_buffer_count",4)),onChange:l=>t("libcam_buffer_count",Number(l)),type:"number",helpText:"Frame buffers for capture (2-8). Higher values reduce frame drops under load but use more memory. Default 4 works for most setups; increase to 6-8 if seeing drops at high framerates.",error:r?.("libcam_buffer_count"),originalValue:String(i("libcam_buffer_count",4))})]})}function gi({config:e,onChange:t,controls:r,getError:n}){if(!r||r.length===0)return s.jsx(O,{title:"Camera Controls",description:"USB camera settings",collapsible:!0,defaultOpen:!1,children:s.jsx("p",{className:"text-gray-400 text-sm",children:"No controls available for this camera."})});const o=vi(r);return s.jsx(O,{title:"Camera Controls",description:"USB camera settings (V4L2)",collapsible:!0,defaultOpen:!1,children:Object.entries(o).map(([a,i])=>s.jsxs("div",{className:"space-y-4",children:[a!=="Other"&&s.jsx("h4",{className:"text-sm font-medium text-gray-300 mt-4 first:mt-0",children:a}),i.map(c=>s.jsx(xi,{control:c,value:bi(e,c),onChange:l=>t(`v4l2_${c.id}`,l),error:n?.(`v4l2_${c.id}`)},c.id))]},a))})}function xi({control:e,value:t,onChange:r,error:n}){switch(e.type){case"boolean":return s.jsx(V,{label:e.name,value:!!t,onChange:r,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`});case"menu":return s.jsx(D,{label:e.name,value:String(t),onChange:o=>r(Number(o)),options:e.menuItems?.map(o=>({value:String(o.value),label:o.label}))??[],helpText:`Default: ${e.default}`,error:n});default:return s.jsx(C,{label:e.name,value:Number(t),onChange:r,min:e.min,max:e.max,step:e.step??1,helpText:`Range: ${e.min}-${e.max}, Default: ${e.default}`,error:n})}}function bi(e,t){const r=`v4l2_${t.id}`,n=e[r]?.value;return n!==void 0?t.type==="boolean"?!!n:Number(n):t.type==="boolean"?!!t.current:t.current}function vi(e){const t={"Image Quality":[],"Exposure & Gain":[],"White Balance":[],Focus:[],Other:[]},r={"Image Quality":["brightness","contrast","saturation","hue","sharpness","gamma"],"Exposure & Gain":["exposure","gain","iso","backlight"],"White Balance":["white","balance","color","colour","temperature"],Focus:["focus","zoom","pan","tilt","lens"]};for(const n of e){const o=n.name.toLowerCase();let a=!1;for(const[i,c]of Object.entries(r))if(c.some(l=>o.includes(l))){t[i].push(n),a=!0;break}a||t.Other.push(n)}return Object.fromEntries(Object.entries(t).filter(([n,o])=>o.length>0))}function _i({config:e,onChange:t,connectionStatus:r,hasDualStream:n,getError:o}){const a=(i,c="")=>e[i]?.value??c;return s.jsx(O,{title:"Network Camera",description:"IP camera connection settings (RTSP/HTTP)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[r&&s.jsxs("div",{className:"flex items-center gap-3 pb-2",children:[s.jsx("span",{className:"text-sm text-gray-400",children:"Connection:"}),s.jsx(yi,{status:r})]}),s.jsx(j,{label:"Stream URL",value:String(a("netcam_url","")),onChange:i=>t("netcam_url",i),placeholder:"rtsp://192.168.1.100:554/stream",helpText:"RTSP, HTTP, HTTPS, or file:// URL for the camera stream",error:o?.("netcam_url")}),s.jsx(j,{label:"Credentials",value:String(a("netcam_userpass","")),onChange:i=>t("netcam_userpass",i),type:"password",placeholder:"username:password",helpText:"Leave empty if camera doesn't require authentication",error:o?.("netcam_userpass")}),s.jsx(j,{label:"FFmpeg Parameters",value:String(a("netcam_params","")),onChange:i=>t("netcam_params",i),placeholder:"-rtsp_transport tcp",helpText:"Advanced: Custom FFmpeg input options (e.g., -rtsp_transport tcp)",error:o?.("netcam_params")}),n&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"border-t border-gray-700 my-4 pt-4",children:[s.jsx("h4",{className:"text-sm font-medium text-gray-300 mb-3",children:"High Resolution Stream"}),s.jsx("p",{className:"text-xs text-gray-400 mb-4",children:"Optional secondary stream for higher quality recordings while using lower resolution for motion detection."})]}),s.jsx(j,{label:"High-Res URL",value:String(a("netcam_high_url","")),onChange:i=>t("netcam_high_url",i),placeholder:"rtsp://192.168.1.100:554/stream1",helpText:"Optional: Higher resolution stream for recordings",error:o?.("netcam_high_url")}),s.jsx(j,{label:"High-Res FFmpeg Parameters",value:String(a("netcam_high_params","")),onChange:i=>t("netcam_high_params",i),placeholder:"-rtsp_transport tcp",helpText:"FFmpeg parameters for high-resolution stream",error:o?.("netcam_high_params")})]}),s.jsxs("div",{className:"mt-4 p-4 bg-gray-800 border border-gray-700 rounded",children:[s.jsx("p",{className:"text-sm text-gray-300 mb-2",children:s.jsx("strong",{children:"Supported Protocols"})}),s.jsxs("ul",{className:"text-sm text-gray-400 list-disc list-inside space-y-1 ml-2",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"RTSP:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"rtsp://"})," - Most IP cameras"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"HTTP:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"http://"})," - MJPEG streams"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"HTTPS:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"https://"})," - Secure streams"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"File:"})," ",s.jsx("code",{className:"text-xs bg-gray-900 px-1 py-0.5 rounded",children:"file://"})," - Local video files"]})]})]})]})})}function yi({status:e}){const t={connected:{color:"bg-green-500/20 text-green-300 border-green-500/30",text:"Connected"},reading:{color:"bg-blue-500/20 text-blue-300 border-blue-500/30",text:"Reading"},not_connected:{color:"bg-red-500/20 text-red-300 border-red-500/30",text:"Not Connected"},reconnecting:{color:"bg-yellow-500/20 text-yellow-300 border-yellow-500/30",text:"Reconnecting"},unknown:{color:"bg-gray-500/20 text-gray-400 border-gray-500/30",text:"Unknown"}},{color:r,text:n}=t[e]||t.unknown;return s.jsxs("span",{className:`inline-flex items-center px-3 py-1 rounded border text-xs font-medium ${r}`,children:[s.jsx("span",{className:`w-2 h-2 rounded-full mr-2 ${e==="connected"?"bg-green-400":e==="reading"?"bg-blue-400 animate-pulse":e==="reconnecting"?"bg-yellow-400 animate-pulse":"bg-red-400"}`}),n]})}function wi({config:e,onChange:t,getError:r}){const n=(_,b="")=>e[_]?.value??b,o=String(n("text_left","")),a=String(n("text_right","")),i=at(o),c=at(a),[l,u]=x.useState(i==="custom"?o:""),[d,h]=x.useState(c==="custom"?a:""),f=_=>{_==="custom"?t("text_left",l):t("text_left",it(_))},p=_=>{_==="custom"?t("text_right",d):t("text_right",it(_))},v=_=>{u(_),t("text_left",_)},g=_=>{h(_),t("text_right",_)},S=[{value:"disabled",label:"Disabled"},{value:"camera-name",label:"Camera Name"},{value:"timestamp",label:"Timestamp"},{value:"custom",label:"Custom Text"}];return s.jsxs(O,{title:"Text Overlay",description:"Add text overlays to video frames",collapsible:!0,defaultOpen:!1,children:[s.jsx(D,{label:"Left Text",value:i,onChange:f,options:S,helpText:"Text displayed in top-left corner"}),i==="custom"&&s.jsx(j,{label:"Custom Left Text",value:l,onChange:v,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:r?.("text_left")}),s.jsx(D,{label:"Right Text",value:c,onChange:p,options:S,helpText:"Text displayed in top-right corner"}),c==="custom"&&s.jsx(j,{label:"Custom Right Text",value:d,onChange:g,placeholder:"Enter custom text",helpText:"Use Motion format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %$ (camera name)",error:r?.("text_right")}),s.jsx(C,{label:"Text Scale",value:Number(n("text_scale",1)),onChange:_=>t("text_scale",_),min:1,max:10,unit:"x",helpText:"Text size multiplier (1-10)",error:r?.("text_scale")})]})}const ji=[{value:"100",label:"Full (100%)"},{value:"75",label:"High (75%)"},{value:"50",label:"Medium (50%)"},{value:"25",label:"Low (25%)"},{value:"10",label:"Minimal (10%)"}];function Si({config:e,onChange:t,getError:r}){const n=(a,i="")=>e[a]?.value??i,o=!n("stream_localhost",!1);return s.jsxs(O,{title:"Video Streaming",description:"Live MJPEG stream configuration",collapsible:!0,defaultOpen:!1,children:[s.jsx(V,{label:"Enable Video Streaming",value:o,onChange:a=>t("stream_localhost",!a),helpText:"Enable/disable live MJPEG streaming. When disabled, stream is only accessible from localhost."}),o&&s.jsxs(s.Fragment,{children:[s.jsx(D,{label:"Streaming Resolution",value:String(n("stream_preview_scale",100)),onChange:a=>t("stream_preview_scale",Number(a)),options:ji,helpText:"Scale stream as percentage of source resolution. Lower = less bandwidth and CPU."}),s.jsx(C,{label:"Stream Quality",value:Number(n("stream_quality",50)),onChange:a=>t("stream_quality",a),min:1,max:100,unit:"%",helpText:"JPEG compression quality (1-100). Higher = better quality, more bandwidth.",error:r?.("stream_quality")}),s.jsx(C,{label:"Stream Max Framerate",value:Number(n("stream_maxrate",15)),onChange:a=>t("stream_maxrate",a),min:1,max:30,unit:" fps",helpText:"Maximum frames per second (lower = less bandwidth and CPU)",error:r?.("stream_maxrate")}),s.jsx(V,{label:"Show Motion Boxes",value:!!n("stream_motion",!1),onChange:a=>t("stream_motion",a),helpText:"Display motion detection boxes in stream"}),s.jsx(D,{label:"Direct Stream Access Security",value:String(n("webcontrol_auth_method",0)),onChange:a=>t("webcontrol_auth_method",Number(a)),options:Tr.map(a=>({value:String(a.value),label:a.label})),helpText:"Authentication when streams are accessed directly (embedded in other websites, VLC, home automation). None = open access on trusted networks only. Basic = use with HTTPS. Digest = recommended."}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Stream URL:"})," ",s.jsx("code",{children:"http://[hostname]:[port]/[cam]/mjpg/stream"})]}),s.jsxs("p",{className:"mt-1",children:[s.jsx("strong",{children:"Note:"})," Streaming resolution scales the output to reduce bandwidth and CPU usage. Server-side resizing is always performed by Motion."]})]})]})]})}function Ni({config:e,onChange:t,getError:r}){const n=(d,h="")=>e[d]?.value??h,o=Number(n("width",640)),a=Number(n("height",480)),i=Number(n("threshold",1500)),c=Pr(i,o,a),l=d=>{const h=Number(d),f=zr(h,o,a);t("threshold",f)},u=[{value:"",label:"Off"},{value:"EedDl",label:"Light"},{value:"EedDl",label:"Medium (default)"},{value:"EedDl",label:"Heavy"}];return s.jsx(O,{title:"Motion Detection",description:"Configure motion detection sensitivity and behavior",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(C,{label:"Threshold",value:c,onChange:d=>l(String(d)),min:0,max:20,step:.1,unit:"%",helpText:`Percentage of frame that must change (${i} pixels at ${o}x${a}). Higher = less sensitive.`,error:r?.("threshold")}),s.jsx(j,{label:"Threshold Maximum",value:String(n("threshold_maximum",0)),onChange:d=>t("threshold_maximum",Number(d)),type:"number",helpText:"Maximum threshold for auto-tuning (0 = disabled)",error:r?.("threshold_maximum")}),s.jsx(V,{label:"Auto-tune Threshold",value:n("threshold_tune",!1),onChange:d=>t("threshold_tune",d),helpText:"Automatically adjust threshold based on noise levels"}),s.jsx(V,{label:"Auto-tune Noise Level",value:n("noise_tune",!1),onChange:d=>t("noise_tune",d),helpText:"Automatically determine optimal noise level"}),s.jsx(C,{label:"Noise Level",value:Number(n("noise_level",32)),onChange:d=>t("noise_level",d),min:1,max:255,helpText:"Noise tolerance (1-255). Lower values detect smaller motions.",error:r?.("noise_level")}),s.jsx(C,{label:"Light Switch Detection",value:Number(n("lightswitch_percent",0)),onChange:d=>t("lightswitch_percent",d),min:0,max:100,unit:"%",helpText:"Ignore sudden brightness changes (0 = disabled). Prevents false triggers from lights turning on/off.",error:r?.("lightswitch_percent")}),s.jsx(D,{label:"Despeckle Filter",value:String(n("despeckle_filter","")),onChange:d=>t("despeckle_filter",d),options:u,helpText:"Remove noise speckles from motion detection"}),s.jsx(C,{label:"Smart Mask Speed",value:Number(n("smart_mask_speed",0)),onChange:d=>t("smart_mask_speed",d),min:0,max:10,helpText:"Auto-mask static areas (0 = disabled, 1-10 = speed). Higher values adapt faster to static objects.",error:r?.("smart_mask_speed")}),s.jsx(D,{label:"Locate Motion Mode",value:String(n("locate_motion_mode","off")),onChange:d=>t("locate_motion_mode",d),options:$r,helpText:"Draw box around motion area. 'Preview' = stream only, 'On' = saved images, 'Both' = both."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Event Timing"}),s.jsx(j,{label:"Event Gap (seconds)",value:String(n("event_gap",60)),onChange:d=>t("event_gap",Number(d)),type:"number",min:"0",helpText:"Seconds of no motion before ending an event. Prevents splitting continuous motion into multiple events.",error:r?.("event_gap")}),s.jsx(j,{label:"Pre-Capture (frames)",value:String(n("pre_capture",0)),onChange:d=>t("pre_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture before motion detected. Uses CPU/memory to buffer frames.",error:r?.("pre_capture")}),s.jsx(j,{label:"Post-Capture (frames)",value:String(n("post_capture",0)),onChange:d=>t("post_capture",Number(d)),type:"number",min:"0",helpText:"Frames to capture after motion stops",error:r?.("post_capture")}),s.jsx(j,{label:"Minimum Motion Frames",value:String(n("minimum_motion_frames",1)),onChange:d=>t("minimum_motion_frames",Number(d)),type:"number",min:"1",helpText:"Consecutive frames with motion required to trigger event. Filters brief false positives.",error:r?.("minimum_motion_frames")})]})]})})}function ki({config:e,onChange:t,getError:r}){const n=(f,p="")=>e[f]?.value??p,o=String(n("picture_output","off")),a=Number(n("snapshot_interval",0)),i=Or(o,a),[c,l]=x.useState(i),u=f=>{l(f);const p=Mr(f);t("picture_output",p.picture_output),p.snapshot_interval!==void 0&&t("snapshot_interval",p.snapshot_interval)},d=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered (all frames)"},{value:"motion-triggered-one",label:"Motion Triggered (first frame only)"},{value:"best",label:"Best Quality Frame"},{value:"center",label:"Center Frame"},{value:"interval-snapshots",label:"Interval Snapshots"},{value:"manual",label:"Manual Only"}],h=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(O,{title:"Picture Settings",description:"Configure picture capture and snapshots",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(D,{label:"Capture Mode",value:c,onChange:u,options:d,helpText:"When to capture still images during motion events"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("strong",{children:"Current settings:"})," picture_output=",o,a>0&&`, snapshot_interval=${a}s`]}),c==="motion-triggered"&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"text-xs text-yellow-300 bg-yellow-900/30 border border-yellow-700 p-3 rounded",children:[s.jsx("strong",{children:"Warning:"})," This mode captures every frame during motion. At 15fps, continuous motion can generate 900+ pictures per minute. Configure limits below to prevent runaway capture."]}),s.jsx(j,{label:"Max Pictures Per Event",value:String(n("picture_max_per_event",0)),onChange:f=>t("picture_max_per_event",Number(f)),type:"number",min:"0",max:"100000",helpText:"Maximum pictures per motion event (0 = unlimited)",error:r?.("picture_max_per_event")}),s.jsx(j,{label:"Min Interval Between Pictures (ms)",value:String(n("picture_min_interval",0)),onChange:f=>t("picture_min_interval",Number(f)),type:"number",min:"0",max:"60000",helpText:"Minimum milliseconds between captures (0 = no limit). 1000ms = 1 picture/second.",error:r?.("picture_min_interval")})]}),c==="interval-snapshots"&&s.jsx(j,{label:"Snapshot Interval (seconds)",value:String(n("snapshot_interval",60)),onChange:f=>t("snapshot_interval",Number(f)),type:"number",min:"1",helpText:"Seconds between snapshots (independent of motion)",error:r?.("snapshot_interval")}),s.jsx(C,{label:"Picture Quality",value:Number(n("picture_quality",75)),onChange:f=>t("picture_quality",f),min:1,max:100,unit:"%",helpText:"JPEG quality (1-100). Higher = better quality, larger files.",error:r?.("picture_quality")}),s.jsx(j,{label:"Picture Filename Pattern",value:String(n("picture_filename","%Y%m%d%H%M%S-%q")),onChange:f=>t("picture_filename",f),helpText:`Format codes: ${h}`,error:r?.("picture_filename")}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S-%q"})," → ",s.jsx("code",{children:"2025-01-29/143022-05.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/143022.jpg"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/143022.jpg"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S-%q"})," → ",s.jsx("code",{children:"20250129143022-05.jpg"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",h]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]})]})})}function Ci(){return qe({queryKey:["deviceInfo"],queryFn:async()=>{const e=await fetch("/0/api/system/status");if(!e.ok)throw new Error("Failed to fetch device info");return e.json()},staleTime:6e4,retry:1})}function At(e){return e?.pi_generation===5}function Ti(e){return e?.pi_generation===4}function he(e){return e?.hardware_encoders?.h264_v4l2m2m===!0}function Pi(e,t=70){return(e?.temperature?.celsius??0)>t}function $i({config:e,onChange:t,getError:r,showPassthrough:n=!0}){const{data:o}=Ci(),a=(b,I="")=>e[b]?.value??I,i=a("movie_output",!1),c=a("movie_output_motion",!1),l=a("emulate_motion",!1),u=Ir(i,c,l),[d,h]=x.useState(u),f=b=>{h(b);const I=Er(b);t("movie_output",I.movie_output),I.movie_output_motion!==void 0&&t("movie_output_motion",I.movie_output_motion),I.emulate_motion!==void 0&&t("emulate_motion",I.emulate_motion)},p=[{value:"off",label:"Off"},{value:"motion-triggered",label:"Motion Triggered"},{value:"continuous",label:"Continuous Recording"}],v=["%Y - Year","%m - Month","%d - Day","%H - Hour","%M - Minute","%S - Second","%v - Event number","%$ - Camera name"].join(", "),g=String(a("movie_container","mp4")),S=()=>!o||he(o)?ct:ct.filter(b=>!re(b.value)),_=()=>!(a("movie_passthrough",!1)||re(g)||g==="webm");return s.jsx(O,{title:"Movie Settings",description:"Configure video recording settings",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(D,{label:"Recording Mode",value:d,onChange:f,options:p,helpText:"When to record video. Motion Triggered = only during events, Continuous = always record."}),s.jsx(C,{label:"Movie Quality",value:Number(a("movie_quality",75)),onChange:b=>t("movie_quality",b),min:1,max:100,unit:"%",helpText:"Video encoding quality (1-100). Higher = better quality, larger files, more CPU.",error:r?.("movie_quality")}),s.jsx(j,{label:"Movie Filename Pattern",value:String(a("movie_filename","%Y%m%d%H%M%S")),onChange:b=>t("movie_filename",b),helpText:`Format codes: ${v}`,error:r?.("movie_filename")}),s.jsx(D,{label:"Container Format",value:String(a("movie_container","mp4")),onChange:b=>t("movie_container",b),options:S(),helpText:"Video container format. Hardware encoding requires v4l2m2m support."}),re(g)&&he(o)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Active:"})," Using h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%."]}),o&&!he(o)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Not Available:"})," This device does not have a hardware H.264 encoder.",At(o)&&" Pi 5 does not include a hardware encoder."," ","Hardware encoding options (h264_v4l2m2m) are hidden. Using software encoding (~40-70% CPU)."]}),re(g)&&!o&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding:"})," Uses h264_v4l2m2m hardware encoder for ~10% CPU usage instead of 40-70%. Only available on devices with v4l2m2m support."]}),Ie(g)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"High CPU Warning:"})," H.265/HEVC software encoding uses 80-100% CPU on Raspberry Pi. Not recommended for continuous recording. Consider H.264 for better performance."]}),he(o)&&!re(g)&&!a("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"Hardware Encoding Available:"}),' This device has a hardware H.264 encoder. Select "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" to reduce CPU from ~40-70% to ~10%.']}),!o&&!re(g)&&!a("movie_passthrough",!1)&&!g.includes("webm")&&!Ie(g)&&s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mt-2",children:[s.jsx("strong",{children:"Tip:"}),' If your device has hardware encoding support (e.g., Raspberry Pi 4), consider selecting "MKV - H.264 Hardware" or "MP4 - H.264 Hardware" for ~10% CPU instead of ~40-70% with software encoding.']}),g==="webm"&&s.jsxs("div",{className:"text-xs text-blue-400 bg-blue-950/30 p-3 rounded mt-2",children:[s.jsx("strong",{children:"WebM Format:"})," Uses VP8 codec, optimized for web streaming. Encoder preset setting does not apply to VP8."]}),_()&&s.jsx(D,{label:"Encoder Preset",value:String(a("movie_encoder_preset","medium")),onChange:b=>t("movie_encoder_preset",b),options:Ar.map(b=>({value:b.value,label:b.label})),helpText:"Tradeoff between CPU usage and video quality. Lower presets use less CPU but produce lower quality video. Requires restart to take effect."}),s.jsx(j,{label:"Max Duration (seconds)",value:String(a("movie_max_time",0)),onChange:b=>t("movie_max_time",Number(b)),type:"number",min:"0",helpText:"Maximum movie length (0 = unlimited). Splits long events into multiple files.",error:r?.("movie_max_time")}),n&&s.jsx(V,{label:"Passthrough Mode",value:a("movie_passthrough",!1),onChange:b=>t("movie_passthrough",b),helpText:"Copy codec without re-encoding (NETCAM only). Reduces CPU but may cause compatibility issues."}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-2",children:[s.jsx("p",{className:"font-medium text-gray-300",children:"Dynamic Folder Examples (Recommended):"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y-%m-%d/%H%M%S"})," → ",s.jsx("code",{children:"2025-01-29/143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y/%m/%d/%v-%H%M%S"})," → ",s.jsx("code",{children:"2025/01/29/42-143022.mkv"})]}),s.jsxs("p",{children:[s.jsx("code",{children:"%$/%Y-%m-%d/%v"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/42.mkv"})]}),s.jsx("p",{className:"mt-2 font-medium text-gray-300",children:"Flat Structure:"}),s.jsxs("p",{children:[s.jsx("code",{children:"%Y%m%d%H%M%S"})," → ",s.jsx("code",{children:"20250129143022.mkv"})]}),s.jsxs("p",{className:"mt-2",children:["Available codes: ",v]}),s.jsxs("p",{className:"mt-2 text-yellow-200",children:[s.jsx("strong",{children:"Tip:"})," Using date-based folders like ",s.jsx("code",{children:"%Y-%m-%d/"})," keeps files organized and makes browsing faster."]})]})]}),d==="continuous"&&At(o)&&!a("movie_passthrough",!1)&&s.jsxs("div",{className:"text-xs text-amber-400 bg-amber-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Pi 5 CPU Warning:"})," Pi 5 does not have a hardware H.264 encoder. Continuous recording uses software encoding (~35-60% CPU constant).",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsx("li",{children:'Use encoder preset "Ultrafast" to reduce CPU by ~30%'}),s.jsx("li",{children:"Add active cooling (fan) to prevent thermal throttling"}),s.jsx("li",{children:"Enable passthrough if source is already H.264"})]})]}),d==="continuous"&&Ti(o)&&he(o)&&s.jsxs("div",{className:"text-xs text-green-400 bg-green-950/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording on Pi 4:"})," Camera will record 24/7.",re(g)?s.jsx("span",{children:" Using hardware encoder - expect ~10% CPU usage."}):a("movie_passthrough",!1)?s.jsx("span",{children:" Passthrough mode enabled - expect ~5-10% CPU usage."}):s.jsx("span",{children:" Consider using hardware encoder (MKV/MP4 H.264 Hardware) for ~10% CPU instead of ~40-70%."})]}),d==="continuous"&&!o&&s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Continuous Recording:"})," Camera will record 24/7 regardless of motion. Expected CPU usage on Raspberry Pi:",s.jsxs("ul",{className:"list-disc ml-4 mt-1",children:[s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 4 with hardware encoder:"})," ~10% CPU"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Pi 5 or Pi 4 software encoding:"})," ~35-60% CPU depending on preset"]}),s.jsxs("li",{children:[s.jsx("strong",{children:"Passthrough mode:"})," ~5-10% CPU (if source is H.264)"]})]})]}),Pi(o)&&s.jsxs("div",{className:"text-xs text-red-400 bg-red-950/30 p-3 rounded",children:[s.jsx("strong",{children:"High Temperature Warning:"})," Device is running at ",o?.temperature?.celsius.toFixed(1),"°C. Consider reducing encoding quality or adding active cooling."]})]})})}function zi({config:e,onChange:t,getError:r,originalConfig:n}){const o=(c,l="")=>e[c]?.value??l,a=(c,l="")=>n?.[c]?.value??l,i=["%Y - Year (4 digits)","%m - Month (01-12)","%d - Day (01-31)","%H - Hour (00-23)","%M - Minute (00-59)","%S - Second (00-59)","%q - Frame number","%v - Event number","%$ - Camera name"].join(", ");return s.jsx(O,{title:"Storage",description:"Base directory and periodic snapshot settings for this camera",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsx(j,{label:"Base Storage Directory",value:String(o("target_dir","/var/lib/motion")),onChange:c=>t("target_dir",c),helpText:"Root directory for ALL camera files. Picture and movie filename patterns (configured in their sections) create paths relative to this directory.",error:r?.("target_dir"),originalValue:String(a("target_dir","/var/lib/motion"))}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Filename Patterns"}),s.jsx(j,{label:"Snapshot Filename",value:String(o("snapshot_filename","%Y%m%d%H%M%S-snapshot")),onChange:c=>t("snapshot_filename",c),helpText:"Format for periodic snapshot filenames (strftime syntax)",error:r?.("snapshot_filename")}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsxs("p",{children:[s.jsx("strong",{children:"Example:"})," ",s.jsx("code",{children:"%Y%m%d%H%M%S-snapshot"})," → ",s.jsx("code",{children:"20250129143022-snapshot.jpg"})]}),s.jsxs("p",{children:[s.jsx("strong",{children:"With subdirs:"})," ",s.jsx("code",{children:"%$/%Y-%m-%d/snapshot-%H%M%S"})," → ",s.jsx("code",{children:"Camera1/2025-01-29/snapshot-143022.jpg"})]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Format Code Reference"}),s.jsxs("div",{className:"text-xs text-gray-400 space-y-1",children:[s.jsxs("p",{children:["Available codes: ",i]}),s.jsxs("p",{className:"mt-2 text-blue-200",children:[s.jsx("strong",{children:"How it works:"})," The Base Storage Directory above sets where files go. Picture and Movie sections set filename patterns (which can include subdirectories like ",s.jsx("code",{children:"%Y-%m-%d/"}),")."]})]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"File Cleanup (Future)"}),s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded",children:[s.jsx("p",{children:"Automatic file retention and cleanup based on age/size will be available in a future update."}),s.jsxs("p",{className:"mt-2",children:["For now, use ",s.jsx("code",{children:"cleandir_params"})," in the Motion configuration file or manual cleanup scripts."]})]})]}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"💡 Network Storage:"})," For network shares (NFS, SMB), ensure target_dir points to a mounted directory. Test write permissions before starting recording."]})]})})}const ee=["sun","mon","tue","wed","thu","fri","sat"],Et={sun:{full:"Sunday",short:"Sun"},mon:{full:"Monday",short:"Mon"},tue:{full:"Tuesday",short:"Tue"},wed:{full:"Wednesday",short:"Wed"},thu:{full:"Thursday",short:"Thu"},fri:{full:"Friday",short:"Fri"},sat:{full:"Saturday",short:"Sat"}},ne=96,xe=15;function ur(){return{sun:new Set,mon:new Set,tue:new Set,wed:new Set,thu:new Set,fri:new Set,sat:new Set}}function Oi(e){const t=new Map,r=e.trim().split(/\s+/);for(const n of r){const o=n.indexOf("=");if(o===-1)continue;const a=n.slice(0,o).toLowerCase(),i=n.slice(o+1);t.has(a)||t.set(a,[]),t.get(a).push(i)}return t}function Mi(e){if(e.length!==9||e[4]!=="-")return[];const t=parseInt(e.slice(0,2),10),r=parseInt(e.slice(2,4),10),n=parseInt(e.slice(5,7),10),o=parseInt(e.slice(7,9),10);if(isNaN(t)||isNaN(r)||isNaN(n)||isNaN(o)||t<0||t>23||r<0||r>59||n<0||n>23||o<0||o>59)return[];const a=t*4+Math.floor(r/xe),i=n*4+Math.floor(o/xe),c=[];for(let l=a;l<=i&&lc-l),r=[];let n=t[0],o=t[0];for(let c=1;cDt(e[i],e.sun))&&e.sun.size>0){const i=je(Array.from(e.sun));for(const c of i)n.push(`sun-sat=${Se(c)}`);return n.join(" ")}if(["mon","tue","wed","thu","fri"].every(i=>Dt(e[i],e.mon))&&e.mon.size>0){const i=je(Array.from(e.mon));for(const c of i)n.push(`mon-fri=${Se(c)}`);for(const c of["sat","sun"])if(e[c].size>0){const l=je(Array.from(e[c]));for(const u of l)n.push(`${c}=${Se(u)}`)}return n.join(" ")}for(const i of ee){if(e[i].size===0)continue;const c=je(Array.from(e[i]));for(const l of c)n.push(`${i}=${Se(l)}`)}return n.join(" ")}function Dt(e,t){if(e.size!==t.size)return!1;for(const r of e)if(!t.has(r))return!1;return!0}function Ai(e,t){const r=Be(e),n=Ve(t),o=(a,i)=>{const c=a===0?12:a>12?a-12:a,l=a<12?"am":"pm",u=i===0?"":`:${String(i).padStart(2,"0")}`;return`${c}${u}${l}`};return`${o(r.hour,r.min)} - ${o(n.hour,n.min)}`}function Ei(e){return ee.every(t=>e[t].size===0)}function Di(e){const t=ee.filter(n=>e[n].size>0);return t.length===0?"No time ranges selected":t.length===7?"All days configured":t.map(n=>n.charAt(0).toUpperCase()+n.slice(1,3)).join(", ")}function Ri({value:e,onChange:t}){const{schedule:r,defaultOn:n,action:o}=x.useMemo(()=>Ii(e),[e]),a=x.useCallback(p=>{const v=Re(p,n,o);t(v)},[t,n,o]),i=x.useCallback(p=>{const v=Ze(r);v[p].size===ne?v[p]=new Set:v[p]=new Set(Array.from({length:ne},(g,S)=>S)),a(v)},[r,a]),c=x.useCallback((p,v,g,S)=>{const _=Ze(r),b=new Set(r[p]),[I,G]=v<=g?[v,g]:[g,v];for(let H=I;H<=G;H++)S?b.add(H):b.delete(H);_[p]=b,a(_)},[r,a]),l=x.useCallback(p=>{const v=Ze(r);v[p]=new Set,a(v)},[r,a]),u=x.useCallback(()=>{a(ur())},[a]),d=x.useCallback(p=>{const v=Re(r,p,o);t(v)},[r,o,t]),h=x.useCallback(p=>{const v=Re(r,n,p);t(v)},[r,n,t]),f=x.useCallback(p=>{t(p)},[t]);return{schedule:r,defaultOn:n,action:o,updateSchedule:a,toggleDay:i,setRange:c,clearDay:l,clearAll:u,setDefaultOn:d,setAction:h,applyPreset:f}}function Ze(e){return{sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)}}const Zi=x.memo(function({isSelected:t,isInDragRange:r,isDragSelect:n,isHourBoundary:o,onPointerDown:a,onPointerEnter:i}){let c;r?c=n?"bg-primary/70":"bg-surface-hover":t?c="bg-primary":c="bg-surface";const l=o?"border-t border-gray-700/50":"";return s.jsx("div",{className:`h-[6px] ${c} ${l} cursor-pointer transition-colors duration-75`,onPointerDown:a,onPointerEnter:i})}),Fi=x.memo(function({day:t,schedule:r,dragState:n,onPointerDown:o,onPointerMove:a}){const i=x.useMemo(()=>{if(!n.isDragging||n.startDay!==t)return null;const l=n.startIndex,u=n.currentIndex;return{from:Math.min(l,u),to:Math.max(l,u)}},[n.isDragging,n.startDay,n.startIndex,n.currentIndex,t]),c=x.useMemo(()=>Array.from({length:ne},(l,u)=>({isSelected:r.has(u),isInDragRange:i!==null&&u>=i.from&&u<=i.to,isHourBoundary:u%4===0})),[r,i]);return s.jsx("div",{className:"flex flex-col",children:c.map((l,u)=>s.jsx(Zi,{index:u,isSelected:l.isSelected,isInDragRange:l.isInDragRange,isDragSelect:n.selectMode,isHourBoundary:l.isHourBoundary,onPointerDown:()=>o(u),onPointerEnter:()=>a(u)},u))})}),Li=[0,2,4,6,8,10,12,14,16,18,20,22];function Ui(e){return e===0?"12a":e===12?"12p":e<12?`${e}a`:`${e-12}p`}const Hi=x.memo(function(){return s.jsx("div",{className:"flex flex-col pr-1 text-xs text-gray-500 select-none",children:Li.map(t=>s.jsx("div",{className:"flex items-start justify-end",style:{height:"48px"},children:s.jsx("span",{className:"-mt-1.5",children:Ui(t)})},t))})}),Ne={isDragging:!1,startDay:null,startIndex:null,currentIndex:null,selectMode:!0};function Vi({schedule:e,onScheduleChange:t,disabled:r=!1}){const[n,o]=x.useState(Ne),a=x.useRef(null),i=x.useCallback((f,p)=>{if(r)return;const v=e[f].has(p);o({isDragging:!0,startDay:f,startIndex:p,currentIndex:p,selectMode:!v})},[e,r]),c=x.useCallback((f,p)=>{!n.isDragging||f!==n.startDay||o(v=>({...v,currentIndex:p}))},[n.isDragging,n.startDay]),l=x.useCallback(()=>{if(!n.isDragging||n.startDay===null||n.startIndex===null||n.currentIndex===null){o(Ne);return}const f={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)},p=new Set(e[n.startDay]),[v,g]=n.startIndex<=n.currentIndex?[n.startIndex,n.currentIndex]:[n.currentIndex,n.startIndex];for(let S=v;S<=g;S++)n.selectMode?p.add(S):p.delete(S);f[n.startDay]=p,t(f),o(Ne)},[n,e,t]);x.useEffect(()=>{const f=p=>{p.key==="Escape"&&n.isDragging&&o(Ne)};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)},[n.isDragging]),x.useEffect(()=>(n.isDragging?(document.body.style.touchAction="none",document.body.style.userSelect="none"):(document.body.style.touchAction="",document.body.style.userSelect=""),()=>{document.body.style.touchAction="",document.body.style.userSelect=""}),[n.isDragging]);const u=x.useCallback(f=>{if(r)return;const p={sun:new Set(e.sun),mon:new Set(e.mon),tue:new Set(e.tue),wed:new Set(e.wed),thu:new Set(e.thu),fri:new Set(e.fri),sat:new Set(e.sat)};p[f].size===ne?p[f]=new Set:p[f]=new Set(Array.from({length:ne},(v,g)=>g)),t(p)},[e,t,r]),h=(()=>{if(!n.isDragging||n.startIndex===null||n.currentIndex===null)return null;const f=Math.min(n.startIndex,n.currentIndex),p=Math.max(n.startIndex,n.currentIndex);return Ai(f,p)})();return s.jsxs("div",{className:"select-none",children:[s.jsxs("div",{className:"flex mb-1",children:[s.jsx("div",{className:"w-8 shrink-0"}),s.jsx("div",{className:"grid grid-cols-7 gap-px flex-1",children:ee.map(f=>{const p=e[f].size===ne,v=e[f].size>0;return s.jsxs("button",{type:"button",onClick:()=>u(f),disabled:r,className:`text-xs font-medium py-1 rounded-t transition-colors ${p?"bg-primary text-white":v?"bg-primary/30 text-primary":"bg-surface-elevated text-gray-400 hover:bg-surface-hover"} ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,title:`Click to ${p?"clear":"select all"} ${Et[f].full}`,children:[s.jsx("span",{className:"hidden sm:inline",children:Et[f].short}),s.jsx("span",{className:"sm:hidden",children:f.charAt(0).toUpperCase()})]},f)})})]}),s.jsxs("div",{className:"flex",children:[s.jsx("div",{className:"w-8 shrink-0",children:s.jsx(Hi,{})}),s.jsx("div",{ref:a,className:`grid grid-cols-7 gap-px bg-surface-elevated flex-1 rounded ${r?"opacity-50":""}`,style:{touchAction:"none"},onPointerUp:l,onPointerLeave:l,onPointerCancel:l,children:ee.map(f=>s.jsx(Fi,{day:f,schedule:e[f],dragState:n,onPointerDown:p=>i(f,p),onPointerMove:p=>c(f,p)},f))})]}),h&&s.jsx("div",{className:"mt-2 text-center",children:s.jsxs("span",{className:"text-xs bg-surface-elevated px-2 py-1 rounded text-gray-300",children:[n.selectMode?"Selecting":"Deselecting",":"," ",s.jsx("span",{className:"text-white font-medium",children:h})]})}),s.jsx("div",{className:"mt-2 text-xs text-gray-500 text-center",children:"Click and drag to select time ranges. Click day header to toggle entire day."})]})}const Bi=[{label:"Business Hours",value:"default=true action=pause mon-fri=0900-1700",description:"Pause Mon-Fri 9am-5pm"},{label:"Night Watch",value:"default=false action=pause sun-sat=1800-2359 sun-sat=0000-0600",description:"Active 6pm-6am only"},{label:"Weekends Only",value:"default=false action=pause sat=0000-2359 sun=0000-2359",description:"Active Sat & Sun only"},{label:"Always On",value:"default=true action=pause",description:"No schedule restrictions"}],Wi=x.memo(function({defaultOn:t,action:r,onDefaultOnChange:n,onActionChange:o,onApplyPreset:a,onClearAll:i,disabled:c=!1}){return s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Detection Default"}),s.jsxs("select",{value:t?"on":"off",onChange:l=>n(l.target.value==="on"),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"on",children:"On by default"}),s.jsx("option",{value:"off",children:"Off by default"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:t?"Schedule defines when detection is paused":"Schedule defines when detection is active"})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-1",children:"Schedule Action"}),s.jsxs("select",{value:r,onChange:l=>o(l.target.value),disabled:c,className:"w-full bg-surface-elevated border border-gray-700 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed",children:[s.jsx("option",{value:"pause",children:"Pause detection"}),s.jsx("option",{value:"stop",children:"Stop camera"})]}),s.jsx("p",{className:"text-xs text-gray-500 mt-1",children:r==="pause"?"Camera runs but ignores motion":"Camera completely stops during schedule"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-xs text-gray-400 mb-2",children:"Quick Presets"}),s.jsxs("div",{className:"flex flex-wrap gap-2",children:[Bi.map(l=>s.jsx("button",{type:"button",onClick:()=>a(l.value),disabled:c,className:"px-3 py-1.5 text-xs bg-surface-elevated hover:bg-surface-hover border border-gray-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:l.description,children:l.label},l.label)),s.jsx("button",{type:"button",onClick:i,disabled:c,className:"px-3 py-1.5 text-xs bg-danger/20 hover:bg-danger/30 text-danger border border-danger/30 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",title:"Clear all time ranges",children:"Clear All"})]})]})]})});function Rt({value:e,onChange:t,helpText:r,error:n,disabled:o=!1}){const{schedule:a,defaultOn:i,action:c,updateSchedule:l,setDefaultOn:u,setAction:d,applyPreset:h,clearAll:f}=Ri({value:e,onChange:t}),p=Ei(a),v=Di(a);return s.jsxs("div",{className:"space-y-4",children:[s.jsx(Wi,{defaultOn:i,action:c,onDefaultOnChange:u,onActionChange:d,onApplyPreset:h,onClearAll:f,disabled:o}),s.jsx("div",{className:"border border-gray-700 rounded-lg p-4 bg-surface",children:s.jsx(Vi,{schedule:a,onScheduleChange:l,disabled:o})}),s.jsxs("div",{className:"flex items-center justify-between text-sm",children:[s.jsx("span",{className:"text-gray-400",children:p?s.jsx("span",{className:"text-yellow-400",children:"No time ranges configured"}):s.jsxs(s.Fragment,{children:["Schedule: ",s.jsx("span",{className:"text-white",children:v})]})}),i?s.jsx("span",{className:"text-xs text-gray-500",children:"Detection paused during selected times"}):s.jsx("span",{className:"text-xs text-gray-500",children:"Detection active during selected times"})]}),r&&!n&&s.jsx("p",{className:"text-sm text-gray-400",children:r}),n&&s.jsx("p",{className:"text-sm text-red-400",role:"alert",children:n}),s.jsxs("details",{className:"text-xs",children:[s.jsx("summary",{className:"cursor-pointer text-gray-500 hover:text-gray-400",children:"Show raw schedule format"}),s.jsx("code",{className:"block mt-2 p-2 bg-surface-elevated rounded text-gray-400 break-all",children:e||"(empty)"})]})]})}function Yi({config:e,onChange:t,getError:r}){const n=(d,h="")=>e[d]?.value??h,o=String(n("schedule_params","")),a=o.trim()!=="",i=d=>{d?t("schedule_params","default=true action=pause mon-fri=0900-1700"):t("schedule_params","")},c=String(n("picture_schedule_params","")),l=c.trim()!=="",u=d=>{d?t("picture_schedule_params","default=false action=pause mon-fri=0900-1700"):t("picture_schedule_params","")};return s.jsx(O,{title:"Schedules",description:"Configure when motion detection and continuous recording are active",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-6",children:[s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Motion Detection Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when motion detection is active or paused"}),s.jsx(V,{label:"Enable Motion Detection Schedule",value:a,onChange:i,helpText:"When enabled, motion detection follows the schedule below"}),a&&s.jsx(Rt,{value:o,onChange:d=>t("schedule_params",d),error:r?.("schedule_params")})]}),s.jsx("div",{className:"border-t border-gray-700"}),s.jsxs("div",{className:"space-y-4",children:[s.jsx("h4",{className:"text-sm font-medium border-b border-gray-700 pb-2",children:"Continuous Recording Schedule"}),s.jsx("p",{className:"text-xs text-gray-400",children:"Control when continuous picture capture (timelapse) is active"}),s.jsx(V,{label:"Enable Continuous Recording Schedule",value:l,onChange:u,helpText:"When enabled, continuous recording follows the schedule below"}),l&&s.jsx(Rt,{value:c,onChange:d=>t("picture_schedule_params",d),error:r?.("picture_schedule_params")})]})]})})}const We={gridColumns:2,gridRows:2,fitFramesVertically:!1,playbackFramerateFactor:1,playbackResolutionFactor:1,theme:"dark"};function qi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{...We,...t}}catch(t){console.error("Failed to parse preferences:",t)}return We}function Ji(){const[e,t]=x.useState(qi),r=(n,o)=>{const a={...e,[n]:o};t(a),localStorage.setItem("motion-ui-preferences",JSON.stringify(a))};return s.jsx(O,{title:"UI Preferences",description:"User interface preferences (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Dashboard Layout"}),s.jsx(C,{label:"Grid Columns",value:e.gridColumns,onChange:n=>r("gridColumns",n),min:1,max:4,helpText:"Number of camera columns in dashboard grid (1-4)"}),s.jsx(C,{label:"Grid Rows",value:e.gridRows,onChange:n=>r("gridRows",n),min:1,max:4,helpText:"Number of camera rows in dashboard grid (1-4)"}),s.jsx(V,{label:"Fit Frames Vertically",value:e.fitFramesVertically,onChange:n=>r("fitFramesVertically",n),helpText:"Fit camera frames to viewport height instead of width"})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Playback Settings"}),s.jsx(C,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:n=>r("playbackFramerateFactor",n),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(C,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:n=>r("playbackResolutionFactor",n),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("h4",{className:"font-medium mb-3",children:"Appearance"}),s.jsx(D,{label:"Theme",value:e.theme,onChange:n=>r("theme",n),options:[{value:"dark",label:"Dark"},{value:"light",label:"Light (Coming Soon)"},{value:"auto",label:"Auto (System Preference)"}],helpText:"UI color theme",disabled:!0}),s.jsxs("div",{className:"text-xs text-yellow-200 bg-yellow-600/10 border border-yellow-600/30 p-3 rounded",children:[s.jsx("strong",{children:"Note:"})," Light theme and auto theme switching will be available in a future update."]})]}),s.jsxs("div",{className:"border-t border-surface-elevated pt-4",children:[s.jsx("button",{onClick:()=>{localStorage.removeItem("motion-ui-preferences"),t(We)},className:"px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors text-sm",children:"Reset to Defaults"}),s.jsx("p",{className:"text-xs text-gray-400 mt-2",children:"Clears all saved preferences and returns to default settings"})]})]})})}const Fe={playbackFramerateFactor:1,playbackResolutionFactor:1};function Gi(){const e=localStorage.getItem("motion-ui-preferences");if(e)try{const t=JSON.parse(e);return{playbackFramerateFactor:t.playbackFramerateFactor??Fe.playbackFramerateFactor,playbackResolutionFactor:t.playbackResolutionFactor??Fe.playbackResolutionFactor}}catch(t){console.error("Failed to parse preferences:",t)}return Fe}function Ki(){const[e,t]=x.useState(Gi),r=(n,o)=>{const a={...e,[n]:o};t(a);const i=localStorage.getItem("motion-ui-preferences");let c={};if(i)try{c=JSON.parse(i)}catch(l){console.error("Failed to parse existing preferences:",l)}localStorage.setItem("motion-ui-preferences",JSON.stringify({...c,...a}))};return s.jsx(O,{title:"Playback Settings",description:"Video playback preferences for this camera (stored locally in browser)",collapsible:!0,defaultOpen:!1,children:s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{className:"text-xs text-gray-400 bg-surface-elevated p-3 rounded mb-4",children:[s.jsx("strong",{children:"Note:"})," These preferences are stored in your browser's localStorage and are not saved to the Motion server. They are specific to this browser/device."]}),s.jsx(C,{label:"Framerate Factor",value:e.playbackFramerateFactor,onChange:n=>r("playbackFramerateFactor",n),min:.1,max:4,step:.1,unit:"x",helpText:"Playback speed multiplier (0.5 = half speed, 2.0 = double speed)"}),s.jsx(C,{label:"Resolution Factor",value:e.playbackResolutionFactor,onChange:n=>r("playbackResolutionFactor",n),min:.25,max:1,step:.25,unit:"x",helpText:"Playback resolution scaling (0.5 = half resolution, 1.0 = full)"}),s.jsx("div",{className:"text-xs text-gray-400",children:s.jsx("p",{children:"Lower resolution factors reduce bandwidth and improve performance on slow connections."})})]})})}function Qi({cameraId:e}){const{addToast:t}=Ye(),r=Ft(),n=x.useRef(null),o=x.useRef(null),[a,i]=x.useState("motion"),[c,l]=x.useState("rectangle"),[u,d]=x.useState(!1),[h,f]=x.useState(null),[p,v]=x.useState(null),[g,S]=x.useState([]),[_,b]=x.useState([]),[I,G]=x.useState(!1),[H,R]=x.useState(!1),{data:te,isLoading:Oe}=qe({queryKey:["mask",e,a],queryFn:()=>Lt(`/${e}/api/mask/${a}`)}),[z,N]=x.useState({width:640,height:480}),A=nt({mutationFn:k=>Te(`/${e}/api/mask/${a}`,k),onSuccess:()=>{t(`${a==="motion"?"Motion":"Privacy"} mask saved`,"success"),r.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to save mask","error")}}),E=nt({mutationFn:()=>xr(`/${e}/api/mask/${a}`),onSuccess:()=>{t("Mask deleted","success"),S([]),r.invalidateQueries({queryKey:["mask",e,a]})},onError:()=>{t("Failed to delete mask","error")}}),W=x.useCallback(k=>{const w=n.current;if(!w)return{x:0,y:0};const T=w.getBoundingClientRect(),K=z.width/T.width,ae=z.height/T.height;return{x:Math.round((k.clientX-T.left)*K),y:Math.round((k.clientY-T.top)*ae)}},[z]),X=x.useCallback(()=>{const k=n.current;if(!k)return;const w=k.getContext("2d");if(w&&(w.clearRect(0,0,k.width,k.height),w.fillStyle="rgba(255, 0, 0, 0.4)",w.strokeStyle="rgba(255, 0, 0, 0.8)",w.lineWidth=2,g.forEach(T=>{T.length<3||(w.beginPath(),w.moveTo(T[0].x,T[0].y),T.slice(1).forEach(K=>w.lineTo(K.x,K.y)),w.closePath(),w.fill(),w.stroke())}),_.length>0&&(w.beginPath(),w.moveTo(_[0].x,_[0].y),_.slice(1).forEach(T=>w.lineTo(T.x,T.y)),p&&w.lineTo(p.x,p.y),w.stroke(),w.fillStyle="rgba(255, 255, 0, 0.8)",_.forEach(T=>{w.beginPath(),w.arc(T.x,T.y,4,0,Math.PI*2),w.fill()})),c==="rectangle"&&u&&h&&p)){w.fillStyle="rgba(255, 0, 0, 0.4)",w.strokeStyle="rgba(255, 0, 0, 0.8)";const T=Math.min(h.x,p.x),K=Math.min(h.y,p.y),ae=Math.abs(p.x-h.x),ve=Math.abs(p.y-h.y);w.fillRect(T,K,ae,ve),w.strokeRect(T,K,ae,ve)}},[g,_,p,h,u,c]);x.useEffect(()=>{X()},[X]);const Y=x.useCallback(k=>{const w=W(k);c==="rectangle"?(d(!0),f(w),v(w)):b(T=>[...T,w])},[c,W]),Me=x.useCallback(k=>{const w=W(k);v(w)},[W]),de=x.useCallback(()=>{if(c==="rectangle"&&u&&h&&p){const k=Math.min(h.x,p.x),w=Math.min(h.y,p.y),T=Math.max(h.x,p.x),K=Math.max(h.y,p.y);if(T-k>5&&K-w>5){const ae=[{x:k,y:w},{x:T,y:w},{x:T,y:K},{x:k,y:K}];S(ve=>[...ve,ae])}}d(!1),f(null)},[c,u,h,p]),q=x.useCallback(()=>{c==="polygon"&&_.length>=3&&(S(k=>[...k,_]),b([]))},[c,_]),me=x.useCallback(()=>{S([]),b([]),f(null),v(null),d(!1)},[]),dr=x.useCallback(()=>{_.length>0?b(k=>k.slice(0,-1)):g.length>0&&S(k=>k.slice(0,-1))},[_.length,g.length]),mr=x.useCallback(()=>{if(g.length===0){t("Draw at least one mask area first","warning");return}A.mutate({polygons:g,width:z.width,height:z.height,invert:I})},[g,z,I,A,t]),hr=x.useCallback(()=>{window.confirm(`Delete the ${a} mask? This cannot be undone.`)&&E.mutate()},[a,E]),pr=x.useCallback(k=>{const w=k.currentTarget;N({width:w.naturalWidth,height:w.naturalHeight}),R(!1)},[]),fr=x.useMemo(()=>{const k=gr(),w=`/${e}/mjpg/stream`;return k?`${w}?token=${encodeURIComponent(k)}`:w},[e]);return s.jsxs(O,{title:"Mask Editor",description:"Draw mask areas on the camera feed to define motion detection or privacy zones",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"flex gap-4 mb-4",children:[s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Mask Type"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>{i("motion"),me()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="motion"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Motion"}),s.jsx("button",{onClick:()=>{i("privacy"),me()},className:`px-3 py-1.5 rounded text-sm transition-colors ${a==="privacy"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Privacy"})]})]}),s.jsxs("div",{children:[s.jsx("label",{className:"block text-sm font-medium mb-2",children:"Draw Mode"}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>l("rectangle"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="rectangle"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Rectangle"}),s.jsx("button",{onClick:()=>l("polygon"),className:`px-3 py-1.5 rounded text-sm transition-colors ${c==="polygon"?"bg-primary text-white":"bg-surface-elevated hover:bg-surface"}`,children:"Polygon"})]})]}),s.jsx("div",{className:"flex items-end",children:s.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[s.jsx("input",{type:"checkbox",checked:I,onChange:k=>G(k.target.checked),className:"rounded"}),"Invert (detect outside areas)"]})})]}),Oe?s.jsx("div",{className:"text-sm text-gray-400 mb-4",children:"Loading mask info..."}):te?.exists?s.jsxs("div",{className:"text-sm text-green-500 mb-4",children:["Current mask: ",te.path," (",te.width,"x",te.height,")"]}):s.jsxs("div",{className:"text-sm text-gray-400 mb-4",children:["No ",a," mask configured"]}),s.jsxs("div",{ref:o,className:"relative bg-black rounded-lg overflow-hidden mb-4",children:[H?s.jsx("div",{className:"aspect-video bg-surface flex items-center justify-center text-gray-400",children:s.jsxs("div",{className:"text-center",children:[s.jsx("p",{children:"Camera stream unavailable"}),s.jsx("p",{className:"text-xs mt-1",children:"Draw on blank canvas (640x480)"})]})}):s.jsx("img",{src:fr,alt:"Camera stream",onLoad:pr,onError:()=>R(!0),className:"w-full h-auto",style:{display:"block"}}),s.jsx("canvas",{ref:n,width:z.width,height:z.height,onMouseDown:Y,onMouseMove:Me,onMouseUp:de,onMouseLeave:de,onDoubleClick:q,className:"absolute inset-0 w-full h-full cursor-crosshair",style:{touchAction:"none"}})]}),s.jsx("div",{className:"text-xs text-gray-400 mb-4",children:c==="rectangle"?s.jsxs("p",{children:["Click and drag to draw rectangles. Red areas will be ",a==="motion"?"ignored for motion detection":"blacked out for privacy","."]}):s.jsx("p",{children:"Click to add polygon points. Double-click to complete the polygon."})}),s.jsxs("div",{className:"flex gap-3 flex-wrap",children:[s.jsx("button",{onClick:dr,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Undo"}),s.jsx("button",{onClick:me,disabled:g.length===0&&_.length===0,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded transition-colors disabled:opacity-50",children:"Clear All"}),s.jsx("button",{onClick:mr,disabled:A.isPending||g.length===0,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:A.isPending?"Saving...":"Save Mask"}),te?.exists&&s.jsx("button",{onClick:hr,disabled:E.isPending,className:"px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 rounded transition-colors disabled:opacity-50",children:E.isPending?"Deleting...":"Delete Mask"})]}),g.length>0&&s.jsxs("div",{className:"text-xs text-gray-400 mt-3",children:[g.length," mask area",g.length!==1?"s":""," drawn"]})]})}const pe={custom:{label:"Custom Command",description:"Enter your own shell command",command:""},webhook:{label:"Webhook (HTTP POST)",description:"Send HTTP POST to a URL",command:`curl -s -X POST -H "Content-Type: application/json" -d '{"camera":"%t","event":"%v","time":"%Y-%m-%d %T"}' "YOUR_WEBHOOK_URL"`},telegram:{label:"Telegram Bot",description:"Send message via Telegram Bot API",command:'curl -s -X POST "https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage" -d "chat_id=YOUR_CHAT_ID&text=Motion detected on %t at %Y-%m-%d %T"'},email:{label:"Email (msmtp)",description:"Send email using msmtp",command:'echo -e "Subject: Motion Alert\\n\\nMotion detected on camera %t at %Y-%m-%d %T" | msmtp recipient@example.com'},pushover:{label:"Pushover",description:"Send push notification via Pushover",command:'curl -s -F "token=YOUR_APP_TOKEN" -F "user=YOUR_USER_KEY" -F "message=Motion on %t at %T" https://api.pushover.net/1/messages.json'}};function Xi({config:e,onChange:t,getError:r}){const[n,o]=x.useState("custom"),a=(c,l="")=>e[c]?.value??l,i=c=>{const l=pe[n];l.command&&t(c,l.command)};return s.jsxs(O,{title:"Notifications & Scripts",description:"Configure commands that run on motion events. Use script hooks to send notifications via webhooks, Telegram, email, or custom scripts.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsx("h4",{className:"font-medium mb-2",children:"Available Variables"}),s.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-gray-400",children:[s.jsx("code",{children:"%f"})," ",s.jsx("span",{children:"Filename"}),s.jsx("code",{children:"%t"})," ",s.jsx("span",{children:"Camera name"}),s.jsx("code",{children:"%v"})," ",s.jsx("span",{children:"Event number"}),s.jsx("code",{children:"%Y"})," ",s.jsx("span",{children:"Year (4 digit)"}),s.jsx("code",{children:"%m"})," ",s.jsx("span",{children:"Month (01-12)"}),s.jsx("code",{children:"%d"})," ",s.jsx("span",{children:"Day (01-31)"}),s.jsx("code",{children:"%H"})," ",s.jsx("span",{children:"Hour (00-23)"}),s.jsx("code",{children:"%M"})," ",s.jsx("span",{children:"Minute (00-59)"}),s.jsx("code",{children:"%S"})," ",s.jsx("span",{children:"Second (00-59)"}),s.jsx("code",{children:"%T"})," ",s.jsx("span",{children:"Time HH:MM:SS"})]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-3",children:Object.keys(pe).map(c=>s.jsx("button",{onClick:()=>o(c),className:`px-3 py-1.5 text-sm rounded transition-colors ${n===c?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:pe[c].label},c))}),n!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-3",children:[s.jsx("p",{children:pe[n].description}),s.jsx("pre",{className:"mt-2 p-2 bg-surface rounded text-xs overflow-x-auto whitespace-pre-wrap break-all",children:pe[n].command})]}),s.jsxs("div",{className:"flex gap-2",children:[s.jsx("button",{onClick:()=>i("on_event_start"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Event Start"}),s.jsx("button",{onClick:()=>i("on_picture_save"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Picture Save"}),s.jsx("button",{onClick:()=>i("on_movie_end"),disabled:n==="custom",className:"px-3 py-1.5 text-xs bg-surface hover:bg-surface-elevated rounded transition-colors disabled:opacity-50",children:"Apply to Movie End"})]})]}),s.jsx(j,{label:"On Event Start",value:String(a("on_event_start","")),onChange:c=>t("on_event_start",c),placeholder:"Command to run when motion event starts",helpText:"Executed when a new motion event begins",error:r?.("on_event_start")}),s.jsx(j,{label:"On Event End",value:String(a("on_event_end","")),onChange:c=>t("on_event_end",c),placeholder:"Command to run when motion event ends",helpText:"Executed when motion event ends (after gap timeout)",error:r?.("on_event_end")}),s.jsx(j,{label:"On Motion Detected",value:String(a("on_motion_detected","")),onChange:c=>t("on_motion_detected",c),placeholder:"Command to run on each motion frame",helpText:"Executed on every frame with motion (can be frequent!)",error:r?.("on_motion_detected")}),s.jsx(j,{label:"On Picture Save",value:String(a("on_picture_save","")),onChange:c=>t("on_picture_save",c),placeholder:"Command to run when picture is saved",helpText:"Executed after a snapshot is saved (%f = filename)",error:r?.("on_picture_save")}),s.jsx(j,{label:"On Movie Start",value:String(a("on_movie_start","")),onChange:c=>t("on_movie_start",c),placeholder:"Command to run when recording starts",helpText:"Executed when video recording begins",error:r?.("on_movie_start")}),s.jsx(j,{label:"On Movie End",value:String(a("on_movie_end","")),onChange:c=>t("on_movie_end",c),placeholder:"Command to run when recording ends",helpText:"Executed when video recording is complete (%f = filename)",error:r?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-2 text-sm",children:"Example: Send Picture via Telegram"}),s.jsx("pre",{className:"text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap",children:`# On Picture Save: -curl -F chat_id="YOUR_CHAT_ID" \\ - -F photo=@"%f" \\ - -F caption="Motion on %t at %T" \\ - "https://api.telegram.org/botYOUR_TOKEN/sendPhoto"`})]})]})}const ie={rclone:{label:"Rclone",description:"Universal cloud storage sync (supports 40+ providers)",pictureCmd:'rclone copy "%f" remote:motion/pictures/%Y%m%d/',movieCmd:'rclone copy "%f" remote:motion/movies/%Y%m%d/'},s3:{label:"AWS S3",description:"Amazon S3 or compatible storage (MinIO, DigitalOcean Spaces)",pictureCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/pictures/%Y%m%d/',movieCmd:'aws s3 cp "%f" s3://YOUR_BUCKET/motion/movies/%Y%m%d/'},gdrive:{label:"Google Drive",description:"Upload via gdrive CLI",pictureCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"',movieCmd:'gdrive files upload --parent YOUR_FOLDER_ID "%f"'},dropbox:{label:"Dropbox",description:"Upload via Dropbox-Uploader script",pictureCmd:'dropbox_uploader.sh upload "%f" /motion/pictures/',movieCmd:'dropbox_uploader.sh upload "%f" /motion/movies/'},sftp:{label:"SFTP/SCP",description:"Upload to remote server via SSH",pictureCmd:'scp "%f" user@server:/backup/motion/pictures/',movieCmd:'scp "%f" user@server:/backup/motion/movies/'},custom:{label:"Custom",description:"Enter your own upload command",pictureCmd:"",movieCmd:""}};function ec({config:e,onChange:t,getError:r}){const[n,o]=x.useState("rclone"),a=(u,d="")=>e[u]?.value??d,i=String(a("on_picture_save","")),c=String(a("on_movie_end","")),l=()=>{const u=ie[n];u.pictureCmd&&t("on_picture_save",u.pictureCmd),u.movieCmd&&t("on_movie_end",u.movieCmd)};return s.jsxs(O,{title:"Cloud Upload",description:"Configure automatic upload of pictures and videos to cloud storage. Uses the same event hooks as notifications.",collapsible:!0,defaultOpen:!1,children:[s.jsxs("div",{className:"bg-surface rounded-lg p-4 mb-6 text-sm",children:[s.jsxs("p",{className:"text-gray-400 mb-2",children:["Cloud uploads are triggered by the ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_picture_save"})," and ",s.jsx("code",{className:"bg-surface-elevated px-1 rounded",children:"on_movie_end"})," event hooks. Select a provider template below or enter a custom command."]}),s.jsxs("p",{className:"text-xs text-gray-500",children:[s.jsx("strong",{children:"Note:"})," You must install the required CLI tools (rclone, aws-cli, etc.) on your Pi before uploads will work."]})]}),s.jsxs("div",{className:"mb-6 p-4 bg-surface-elevated rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Cloud Provider Templates"}),s.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:Object.keys(ie).map(u=>s.jsx("button",{onClick:()=>o(u),className:`px-3 py-1.5 text-sm rounded transition-colors ${n===u?"bg-primary text-white":"bg-surface hover:bg-surface-elevated"}`,children:ie[u].label},u))}),n!=="custom"&&s.jsxs("div",{className:"text-xs text-gray-400 mb-4",children:[s.jsx("p",{className:"mb-2",children:ie[n].description}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Picture:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[n].pictureCmd})]}),s.jsxs("div",{children:[s.jsx("span",{className:"text-gray-500",children:"Movie:"}),s.jsx("pre",{className:"mt-1 p-2 bg-surface rounded overflow-x-auto whitespace-pre-wrap break-all",children:ie[n].movieCmd})]})]})]}),s.jsx("button",{onClick:l,disabled:n==="custom",className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded transition-colors disabled:opacity-50",children:"Apply Template"})]}),s.jsx(j,{label:"Picture Upload Command",value:i,onChange:u=>t("on_picture_save",u),placeholder:"Command to upload pictures",helpText:"Runs after each picture is saved. %f = filename, %Y%m%d = date",error:r?.("on_picture_save")}),s.jsx(j,{label:"Movie Upload Command",value:c,onChange:u=>t("on_movie_end",u),placeholder:"Command to upload videos",helpText:"Runs after each video recording completes. %f = filename",error:r?.("on_movie_end")}),s.jsxs("div",{className:"mt-6 p-4 bg-surface rounded-lg",children:[s.jsx("h4",{className:"font-medium mb-3 text-sm",children:"Quick Setup Guides"}),s.jsxs("div",{className:"space-y-4 text-xs text-gray-400",children:[s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"Rclone Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install rclone -sudo apt install rclone - -# Configure a remote (interactive) -rclone config - -# Test upload -rclone copy /path/to/file remote:folder/`})]}),s.jsxs("div",{children:[s.jsx("h5",{className:"font-medium text-gray-300 mb-1",children:"AWS S3 Setup"}),s.jsx("pre",{className:"p-2 bg-surface-elevated rounded overflow-x-auto",children:`# Install AWS CLI -sudo apt install awscli - -# Configure credentials -aws configure - -# Test upload -aws s3 cp /path/to/file s3://bucket/`})]})]})]})]})}function nc(){const{role:e}=br(),{addToast:t}=Ye(),[r,n]=x.useState("0"),[o,a]=x.useState({}),[i,c]=x.useState({}),[l,u]=x.useState(!1),d=Ft(),{data:h,isLoading:f,error:p}=qe({queryKey:["config"],queryFn:async()=>{const N=await Lt("/0/api/config");return N.csrf_token&&yr(N.csrf_token),N}}),v=vr(),{data:g}=Zt(),{data:S}=Dr(Number(r)),_=lr(Number(r));x.useEffect(()=>{a({}),c({})},[r]);const b=x.useCallback((N,A)=>{a(W=>({...W,[N]:A}));const E=ai(N,String(A));c(W=>{if(E.success){const{[N]:X,...Y}=W;return Y}else return{...W,[N]:E.error??"Invalid value"}})},[]),I=Object.keys(o).length>0,G=Object.keys(i).length>0,H=x.useMemo(()=>{if(!h)return{};const N=h.configuration.default||{};if(r==="0")return N;{const A=h.configuration[`cam${r}`]||{};return{...N,...A}}},[h,r]),R=x.useMemo(()=>{if(!h)return{};const N={...H};for(const[A,E]of Object.entries(o))N[A]?N[A]={...N[A],value:E}:N[A]={value:E,enabled:!0,category:0,type:"string"};return N},[h,H,o]),te=async()=>{if(!I){t("No changes to save","info");return}if(G){t("Please fix validation errors before saving","error");return}u(!0);const N=parseInt(r,10);try{const A=await v.mutateAsync({camId:N,changes:o});await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]});const E=A?.summary,W=A?.applied||[];if(!E)t(`Saved ${Object.keys(o).length} setting(s)`,"success"),a({});else{const X=W.filter(Y=>!Y.error&&Y.hot_reload===!1).map(Y=>Y.param);if(X.length>0){t(`Restarting camera to apply ${X.length} setting(s): ${X.join(", ")}...`,"info"),a({});const Y=await _r(N);Zr(N),window.dispatchEvent(new CustomEvent(Fr,{detail:{cameraId:N}})),await d.invalidateQueries({queryKey:["config"]}),await d.refetchQueries({queryKey:["config"]}),Y?t(`Applied ${X.length} setting(s). Camera restarted successfully.`,"success"):t("Settings saved. Camera is restarting - refresh page if stream doesn't recover.","warning")}else if(E.errors>0){const Y=W.filter(q=>q.error).map(q=>q.param),Me=W.filter(q=>!q.error).map(q=>q.param),de={};for(const[q,me]of Object.entries(o))Me.includes(q)||(de[q]=me);a(de),E.success>0?t(`Saved ${E.success} setting(s). ${E.errors} failed: ${Y.join(", ")}`,"warning"):t(`Failed to save settings: ${Y.join(", ")}`,"error")}else t(`Successfully saved ${E.success} setting(s)`,"success"),a({})}}catch(A){console.error("Failed to save settings:",A),t("Failed to save settings. Check browser console for details.","error")}finally{u(!1)}},Oe=()=>{a({}),c({}),t("Changes discarded","info")},z=N=>i[N];return e!=="admin"?s.jsx("div",{className:"p-4 sm:p-6",children:s.jsxs("div",{className:"bg-surface-elevated rounded-lg p-8 text-center max-w-2xl mx-auto",children:[s.jsx("svg",{className:"w-16 h-16 mx-auto text-yellow-500 mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:s.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),s.jsx("h1",{className:"text-2xl font-bold mb-2",children:"Admin Access Required"}),s.jsx("p",{className:"text-gray-400",children:"You must be logged in as an administrator to access settings."})]})}):f?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsxs("div",{className:"animate-pulse",children:[s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"}),s.jsx("div",{className:"h-32 bg-surface-elevated rounded-lg mb-4"})]})]}):p?s.jsxs("div",{className:"p-6",children:[s.jsx("h2",{className:"text-3xl font-bold mb-6",children:"Settings"}),s.jsx("div",{className:"bg-danger/10 border border-danger rounded-lg p-4",children:s.jsx("p",{className:"text-danger",children:"Failed to load configuration"})})]}):h?s.jsxs("div",{className:"p-6",children:[s.jsx("div",{className:"sticky top-[73px] z-40 -mx-6 px-6 py-3 bg-surface/95 backdrop-blur border-b border-gray-800 mb-6",children:s.jsxs("div",{className:"flex items-center justify-between",children:[s.jsxs("div",{className:"flex items-center gap-4",children:[s.jsx("h2",{className:"text-2xl font-bold",children:"Settings"}),s.jsx("div",{children:s.jsxs("select",{value:r,onChange:N=>n(N.target.value),className:"px-3 py-1.5 bg-surface-elevated border border-gray-700 rounded-lg text-sm",children:[s.jsx("option",{value:"0",children:"Global Settings"}),h.cameras&&Object.entries(h.cameras).map(([N,A])=>{if(N==="count"||typeof A=="number")return null;const E=A;return s.jsx("option",{value:String(E.id),children:E.name||`Camera ${E.id}`},E.id)})]})})]}),s.jsxs("div",{className:"flex items-center gap-3",children:[I&&!G&&s.jsx("span",{className:"text-yellow-200 text-sm",children:"Unsaved changes"}),G&&s.jsx("span",{className:"text-red-200 text-sm",children:"Fix errors below"}),I&&s.jsx("button",{onClick:Oe,disabled:l,className:"px-3 py-1.5 text-sm bg-surface-elevated hover:bg-surface rounded-lg transition-colors disabled:opacity-50",children:"Discard"}),s.jsx("button",{onClick:te,disabled:!I||l||G,className:"px-4 py-1.5 text-sm bg-primary hover:bg-primary-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:l?"Saving...":I?"Save Changes":"Saved"})]})]})}),r==="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-6",children:s.jsx("p",{className:"text-sm text-blue-200",children:"Global settings apply to the Motion daemon and web server. To configure camera-specific settings, select a camera from the dropdown above."})}),s.jsx(ui,{config:R,onChange:b,getError:z,originalConfig:H,systemStatus:g}),s.jsx(Ji,{}),s.jsx(O,{title:"About",description:"Motion version information",collapsible:!0,defaultOpen:!1,children:s.jsxs("p",{className:"text-sm text-gray-400",children:["Motion Version: ",h.version]})})]}),r!=="0"&&s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"bg-surface-elevated rounded-lg p-4 mb-6",children:s.jsx(Rr,{cameraId:Number(r),readOnly:!1})}),s.jsx(mi,{cameraId:Number(r)}),_.features.hasLibcamControls&&s.jsx(fi,{config:R,onChange:b,getError:z,capabilities:S,originalConfig:H}),_.features.hasV4L2Controls&&s.jsx(gi,{config:R,onChange:b,controls:_.v4l2Controls,getError:z}),_.features.hasNetcamConfig&&s.jsx(_i,{config:R,onChange:b,connectionStatus:_.netcamStatus,hasDualStream:_.features.hasDualStream,getError:z}),s.jsx(di,{config:R,onChange:b,getError:z}),s.jsx($i,{config:R,onChange:b,getError:z,showPassthrough:_.features.supportsPassthrough}),s.jsx(Si,{config:R,onChange:b,getError:z}),s.jsx(ki,{config:R,onChange:b,getError:z}),s.jsx(Ni,{config:R,onChange:b,getError:z}),s.jsx(Qi,{cameraId:parseInt(r,10)}),s.jsx(Yi,{config:R,onChange:b,getError:z}),s.jsx(zi,{config:R,onChange:b,getError:z,originalConfig:H}),s.jsx(wi,{config:R,onChange:b,getError:z}),s.jsx(Xi,{config:R,onChange:b,getError:z}),s.jsx(ec,{config:R,onChange:b,getError:z}),s.jsx(Ki,{})]})]}):null}export{nc as Settings}; diff --git a/data/webui/assets/index-CVdKRgH6.css b/data/webui/assets/index-CVdKRgH6.css deleted file mode 100644 index 6249b3c9..00000000 --- a/data/webui/assets/index-CVdKRgH6.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}body{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-1{left:.25rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-4{top:1rem}.top-\[73px\]{top:73px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[101\]{z-index:101}.z-\[150\]{z-index:150}.z-\[60\]{z-index:60}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.-mt-1\.5{margin-top:-.375rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-\[6px\]{height:6px}.h-auto{height:auto}.h-full{height:100%}.max-h-\[70vh\]{max-height:70vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[250px\]{min-width:250px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-grab{cursor:grab}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-px{gap:1px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-blue-500\/20{border-color:#3b82f633}.border-blue-500\/30{border-color:#3b82f64d}.border-danger{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-danger\/30{border-color:#ef44444d}.border-gray-500\/30{border-color:#6b72804d}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-700\/50{border-color:#37415180}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-green-500\/30{border-color:#22c55e4d}.border-purple-500\/30{border-color:#a855f74d}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-500\/30{border-color:#ef44444d}.border-red-600\/30{border-color:#dc26264d}.border-surface{--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.border-surface-elevated{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-yellow-500\/30{border-color:#eab3084d}.border-yellow-500\/50{border-color:#eab30880}.border-yellow-600\/30{border-color:#ca8a044d}.border-yellow-700{--tw-border-opacity: 1;border-color:rgb(161 98 7 / var(--tw-border-opacity, 1))}.bg-amber-950\/30{background-color:#451a034d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-500\/20{background-color:#3b82f633}.bg-blue-600\/20{background-color:#2563eb33}.bg-blue-950\/30{background-color:#1725544d}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-danger\/10{background-color:#ef44441a}.bg-danger\/20{background-color:#ef444433}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-500\/20{background-color:#6b728033}.bg-gray-600\/20{background-color:#4b556333}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-950\/30{background-color:#052e164d}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-primary\/20{background-color:#3b82f633}.bg-primary\/30{background-color:#3b82f64d}.bg-primary\/70{background-color:#3b82f6b3}.bg-purple-500\/20{background-color:#a855f733}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/20{background-color:#ef444433}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/10{background-color:#dc26261a}.bg-red-600\/20{background-color:#dc262633}.bg-red-600\/80{background-color:#dc2626cc}.bg-red-950\/30{background-color:#450a0a4d}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.bg-surface-elevated{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-surface-hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.bg-surface\/95{background-color:#1a1a1af2}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-500\/20{background-color:#eab30833}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.bg-yellow-600\/10{background-color:#ca8a041a}.bg-yellow-600\/20{background-color:#ca8a0433}.bg-yellow-900\/30{background-color:#713f124d}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-4{padding-left:1rem}.pr-1{padding-right:.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-300\/80{color:#93c5fdcc}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in{animation:slide-in .3s ease-out}.first\:mt-0:first-child{margin-top:0}.hover\:border-danger:hover{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.hover\:bg-blue-600\/30:hover{background-color:#2563eb4d}.hover\:bg-danger\/20:hover{background-color:#ef444433}.hover\:bg-danger\/30:hover{background-color:#ef44444d}.hover\:bg-danger\/80:hover{background-color:#ef4444cc}.hover\:bg-primary-hover:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-red-600\/40:hover{background-color:#dc262666}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-elevated:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-hover:hover{--tw-bg-opacity: 1;background-color:rgb(46 46 46 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-600\/30:hover{background-color:#ca8a044d}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-primary:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-2:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-primary:hover{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-yellow-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(234 179 8 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width: 0px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-primary\/30:disabled{background-color:#3b82f64d}.disabled\:bg-surface\/30:disabled{background-color:#1a1a1a4d}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group:focus-within .group-focus-within\:opacity-100{opacity:1}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:mb-6{margin-bottom:1.5rem}.sm\:inline{display:inline}.sm\:hidden{display:none}.sm\:gap-6{gap:1.5rem}.sm\:p-6{padding:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:gap-4{gap:1rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:py-8{padding-top:2rem;padding-bottom:2rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/data/webui/assets/index-tiawrtsp.js b/data/webui/assets/index-DEo73YRp.js similarity index 53% rename from data/webui/assets/index-tiawrtsp.js rename to data/webui/assets/index-DEo73YRp.js index 32e85df7..b4c04b69 100644 --- a/data/webui/assets/index-tiawrtsp.js +++ b/data/webui/assets/index-DEo73YRp.js @@ -1,17 +1,17 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Dashboard-D7oUR9hz.js","assets/parameterMappings-BmLxmuw_.js","assets/Settings-JwhcLbnw.js"])))=>i.map(i=>d[i]); -(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))r(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const m of h.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function c(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function r(d){if(d.ep)return;d.ep=!0;const h=c(d);fetch(d.href,h)}})();function om(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var tr={exports:{}},kn={};var Hd;function E0(){if(Hd)return kn;Hd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function c(r,d,h){var m=null;if(h!==void 0&&(m=""+h),d.key!==void 0&&(m=""+d.key),"key"in d){h={};for(var v in d)v!=="key"&&(h[v]=d[v])}else h=d;return d=h.ref,{$$typeof:n,type:r,key:m,ref:d!==void 0?d:null,props:h}}return kn.Fragment=s,kn.jsx=c,kn.jsxs=c,kn}var Bd;function T0(){return Bd||(Bd=1,tr.exports=E0()),tr.exports}var _=T0(),er={exports:{}},P={};var Qd;function x0(){if(Qd)return P;Qd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),m=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),D=Symbol.iterator;function q(b){return b===null||typeof b!="object"?null:(b=D&&b[D]||b["@@iterator"],typeof b=="function"?b:null)}var H={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,Y={};function Q(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}Q.prototype.isReactComponent={},Q.prototype.setState=function(b,w){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,w,"setState")},Q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function X(){}X.prototype=Q.prototype;function J(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}var ht=J.prototype=new X;ht.constructor=J,B(ht,Q.prototype),ht.isPureReactComponent=!0;var ct=Array.isArray;function Rt(){}var F={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(b,w,G){var K=G.ref;return{$$typeof:n,type:b,key:w,ref:K!==void 0?K:null,props:G}}function Qt(b,w){return zt(b.type,w,b.props)}function $t(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function te(b){var w={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(G){return w[G]})}var Yl=/\/+/g;function Ze(b,w){return typeof b=="object"&&b!==null&&b.key!=null?te(""+b.key):w.toString(36)}function Ne(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Rt,Rt):(b.status="pending",b.then(function(w){b.status==="pending"&&(b.status="fulfilled",b.value=w)},function(w){b.status==="pending"&&(b.status="rejected",b.reason=w)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function U(b,w,G,K,I){var lt=typeof b;(lt==="undefined"||lt==="boolean")&&(b=null);var yt=!1;if(b===null)yt=!0;else switch(lt){case"bigint":case"string":case"number":yt=!0;break;case"object":switch(b.$$typeof){case n:case s:yt=!0;break;case T:return yt=b._init,U(yt(b._payload),w,G,K,I)}}if(yt)return I=I(b),yt=K===""?"."+Ze(b,0):K,ct(I)?(G="",yt!=null&&(G=yt.replace(Yl,"$&/")+"/"),U(I,w,G,"",function(tn){return tn})):I!=null&&($t(I)&&(I=Qt(I,G+(I.key==null||b&&b.key===I.key?"":(""+I.key).replace(Yl,"$&/")+"/")+yt)),w.push(I)),1;yt=0;var Wt=K===""?".":K+":";if(ct(b))for(var Ut=0;Ut>>1,Tt=U[gt];if(0>>1;gtd(G,W))Kd(I,G)?(U[gt]=I,U[K]=W,gt=K):(U[gt]=G,U[w]=W,gt=w);else if(Kd(I,W))U[gt]=I,U[K]=W,gt=K;else break t}}return L}function d(U,L){var W=U.sortIndex-L.sortIndex;return W!==0?W:U.id-L.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;n.unstable_now=function(){return h.now()}}else{var m=Date,v=m.now();n.unstable_now=function(){return m.now()-v}}var p=[],y=[],T=1,x=null,D=3,q=!1,H=!1,B=!1,Y=!1,Q=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,J=typeof setImmediate<"u"?setImmediate:null;function ht(U){for(var L=c(y);L!==null;){if(L.callback===null)r(y);else if(L.startTime<=U)r(y),L.sortIndex=L.expirationTime,s(p,L);else break;L=c(y)}}function ct(U){if(B=!1,ht(U),!H)if(c(p)!==null)H=!0,Rt||(Rt=!0,te());else{var L=c(y);L!==null&&Ne(ct,L.startTime-U)}}var Rt=!1,F=-1,rt=5,zt=-1;function Qt(){return Y?!0:!(n.unstable_now()-ztU&&Qt());){var gt=x.callback;if(typeof gt=="function"){x.callback=null,D=x.priorityLevel;var Tt=gt(x.expirationTime<=U);if(U=n.unstable_now(),typeof Tt=="function"){x.callback=Tt,ht(U),L=!0;break e}x===c(p)&&r(p),ht(U)}else r(p);x=c(p)}if(x!==null)L=!0;else{var b=c(y);b!==null&&Ne(ct,b.startTime-U),L=!1}}break t}finally{x=null,D=W,q=!1}L=void 0}}finally{L?te():Rt=!1}}}var te;if(typeof J=="function")te=function(){J($t)};else if(typeof MessageChannel<"u"){var Yl=new MessageChannel,Ze=Yl.port2;Yl.port1.onmessage=$t,te=function(){Ze.postMessage(null)}}else te=function(){Q($t,0)};function Ne(U,L){F=Q(function(){U(n.unstable_now())},L)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(U){U.callback=null},n.unstable_forceFrameRate=function(U){0>U||125gt?(U.sortIndex=W,s(y,U),c(p)===null&&U===c(y)&&(B?(X(F),F=-1):B=!0,Ne(ct,W-gt))):(U.sortIndex=Tt,s(p,U),H||q||(H=!0,Rt||(Rt=!0,te()))),U},n.unstable_shouldYield=Qt,n.unstable_wrapCallback=function(U){var L=D;return function(){var W=D;D=L;try{return U.apply(this,arguments)}finally{D=W}}}})(nr)),nr}var Gd;function A0(){return Gd||(Gd=1,ar.exports=O0()),ar.exports}var ur={exports:{}},Ft={};var Xd;function C0(){if(Xd)return Ft;Xd=1;var n=gr();function s(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),ur.exports=C0(),ur.exports}var Kd;function z0(){if(Kd)return Fn;Kd=1;var n=A0(),s=gr(),c=M0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=gt[Tt],gt[Tt]=null,Tt--)}function G(t,e){Tt++,gt[Tt]=t.current,t.current=e}var K=b(null),I=b(null),lt=b(null),yt=b(null);function Wt(t,e){switch(G(lt,e),G(I,t),G(K,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?id(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=id(e),t=sd(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}w(K),G(K,t)}function Ut(){w(K),w(I),w(lt)}function tn(t){t.memoizedState!==null&&G(yt,t);var e=K.current,l=sd(e,t.type);e!==l&&(G(I,t),G(K,l))}function uu(t){I.current===t&&(w(K),w(I)),yt.current===t&&(w(yt),Zn._currentValue=W)}var qi,jr;function Gl(t){if(qi===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);qi=e&&e[1]||"",jr=-1i.map(i=>d[i]); +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))r(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const m of h.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function c(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function r(d){if(d.ep)return;d.ep=!0;const h=c(d);fetch(d.href,h)}})();function hm(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var lr={exports:{}},Fn={};var Bd;function E0(){if(Bd)return Fn;Bd=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function c(r,d,h){var m=null;if(h!==void 0&&(m=""+h),d.key!==void 0&&(m=""+d.key),"key"in d){h={};for(var v in d)v!=="key"&&(h[v]=d[v])}else h=d;return d=h.ref,{$$typeof:n,type:r,key:m,ref:d!==void 0?d:null,props:h}}return Fn.Fragment=s,Fn.jsx=c,Fn.jsxs=c,Fn}var Qd;function T0(){return Qd||(Qd=1,lr.exports=E0()),lr.exports}var _=T0(),ar={exports:{}},P={};var Ld;function x0(){if(Ld)return P;Ld=1;var n=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),m=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),x=Symbol.for("react.activity"),D=Symbol.iterator;function q(b){return b===null||typeof b!="object"?null:(b=D&&b[D]||b["@@iterator"],typeof b=="function"?b:null)}var H={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,Y={};function Q(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}Q.prototype.isReactComponent={},Q.prototype.setState=function(b,w){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,w,"setState")},Q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function X(){}X.prototype=Q.prototype;function J(b,w,G){this.props=b,this.context=w,this.refs=Y,this.updater=G||H}var ht=J.prototype=new X;ht.constructor=J,B(ht,Q.prototype),ht.isPureReactComponent=!0;var ct=Array.isArray;function Rt(){}var F={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(b,w,G){var Z=G.ref;return{$$typeof:n,type:b,key:w,ref:Z!==void 0?Z:null,props:G}}function Lt(b,w){return zt(b.type,w,b.props)}function Wt(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function ee(b){var w={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(G){return w[G]})}var Gl=/\/+/g;function Ve(b,w){return typeof b=="object"&&b!==null&&b.key!=null?ee(""+b.key):w.toString(36)}function Ne(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Rt,Rt):(b.status="pending",b.then(function(w){b.status==="pending"&&(b.status="fulfilled",b.value=w)},function(w){b.status==="pending"&&(b.status="rejected",b.reason=w)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function U(b,w,G,Z,I){var lt=typeof b;(lt==="undefined"||lt==="boolean")&&(b=null);var yt=!1;if(b===null)yt=!0;else switch(lt){case"bigint":case"string":case"number":yt=!0;break;case"object":switch(b.$$typeof){case n:case s:yt=!0;break;case T:return yt=b._init,U(yt(b._payload),w,G,Z,I)}}if(yt)return I=I(b),yt=Z===""?"."+Ve(b,0):Z,ct(I)?(G="",yt!=null&&(G=yt.replace(Gl,"$&/")+"/"),U(I,w,G,"",function(en){return en})):I!=null&&(Wt(I)&&(I=Lt(I,G+(I.key==null||b&&b.key===I.key?"":(""+I.key).replace(Gl,"$&/")+"/")+yt)),w.push(I)),1;yt=0;var Pt=Z===""?".":Z+":";if(ct(b))for(var Nt=0;Nt>>1,Tt=U[gt];if(0>>1;gtd(G,W))Zd(I,G)?(U[gt]=I,U[Z]=W,gt=Z):(U[gt]=G,U[w]=W,gt=w);else if(Zd(I,W))U[gt]=I,U[Z]=W,gt=Z;else break t}}return L}function d(U,L){var W=U.sortIndex-L.sortIndex;return W!==0?W:U.id-L.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;n.unstable_now=function(){return h.now()}}else{var m=Date,v=m.now();n.unstable_now=function(){return m.now()-v}}var p=[],y=[],T=1,x=null,D=3,q=!1,H=!1,B=!1,Y=!1,Q=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,J=typeof setImmediate<"u"?setImmediate:null;function ht(U){for(var L=c(y);L!==null;){if(L.callback===null)r(y);else if(L.startTime<=U)r(y),L.sortIndex=L.expirationTime,s(p,L);else break;L=c(y)}}function ct(U){if(B=!1,ht(U),!H)if(c(p)!==null)H=!0,Rt||(Rt=!0,ee());else{var L=c(y);L!==null&&Ne(ct,L.startTime-U)}}var Rt=!1,F=-1,rt=5,zt=-1;function Lt(){return Y?!0:!(n.unstable_now()-ztU&&Lt());){var gt=x.callback;if(typeof gt=="function"){x.callback=null,D=x.priorityLevel;var Tt=gt(x.expirationTime<=U);if(U=n.unstable_now(),typeof Tt=="function"){x.callback=Tt,ht(U),L=!0;break e}x===c(p)&&r(p),ht(U)}else r(p);x=c(p)}if(x!==null)L=!0;else{var b=c(y);b!==null&&Ne(ct,b.startTime-U),L=!1}}break t}finally{x=null,D=W,q=!1}L=void 0}}finally{L?ee():Rt=!1}}}var ee;if(typeof J=="function")ee=function(){J(Wt)};else if(typeof MessageChannel<"u"){var Gl=new MessageChannel,Ve=Gl.port2;Gl.port1.onmessage=Wt,ee=function(){Ve.postMessage(null)}}else ee=function(){Q(Wt,0)};function Ne(U,L){F=Q(function(){U(n.unstable_now())},L)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(U){U.callback=null},n.unstable_forceFrameRate=function(U){0>U||125gt?(U.sortIndex=W,s(y,U),c(p)===null&&U===c(y)&&(B?(X(F),F=-1):B=!0,Ne(ct,W-gt))):(U.sortIndex=Tt,s(p,U),H||q||(H=!0,Rt||(Rt=!0,ee()))),U},n.unstable_shouldYield=Lt,n.unstable_wrapCallback=function(U){var L=D;return function(){var W=D;D=L;try{return U.apply(this,arguments)}finally{D=W}}}})(ir)),ir}var Xd;function A0(){return Xd||(Xd=1,ur.exports=O0()),ur.exports}var sr={exports:{}},$t={};var Kd;function C0(){if(Kd)return $t;Kd=1;var n=br();function s(p){var y="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),sr.exports=C0(),sr.exports}var Vd;function z0(){if(Vd)return $n;Vd=1;var n=A0(),s=br(),c=M0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=gt[Tt],gt[Tt]=null,Tt--)}function G(t,e){Tt++,gt[Tt]=t.current,t.current=e}var Z=b(null),I=b(null),lt=b(null),yt=b(null);function Pt(t,e){switch(G(lt,e),G(I,t),G(Z,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?sd(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=sd(e),t=cd(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}w(Z),G(Z,t)}function Nt(){w(Z),w(I),w(lt)}function en(t){t.memoizedState!==null&&G(yt,t);var e=Z.current,l=cd(e,t.type);e!==l&&(G(I,t),G(Z,l))}function iu(t){I.current===t&&(w(Z),w(I)),yt.current===t&&(w(yt),Zn._currentValue=W)}var Bi,wr;function Xl(t){if(Bi===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);Bi=e&&e[1]||"",wr=-1)":-1u||g[a]!==A[u]){var z=` -`+g[a].replace(" at new "," at ");return t.displayName&&z.includes("")&&(z=z.replace("",t.displayName)),z}while(1<=a&&0<=u);break}}}finally{Hi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Gl(l):""}function Pm(t,e){switch(t.tag){case 26:case 27:case 5:return Gl(t.type);case 16:return Gl("Lazy");case 13:return t.child!==e&&e!==null?Gl("Suspense Fallback"):Gl("Suspense");case 19:return Gl("SuspenseList");case 0:case 15:return Bi(t.type,!1);case 11:return Bi(t.type.render,!1);case 1:return Bi(t.type,!0);case 31:return Gl("Activity");default:return""}}function wr(t){try{var e="",l=null;do e+=Pm(t,l),l=t,t=t.return;while(t);return e}catch(a){return` +`+g[a].replace(" at new "," at ");return t.displayName&&z.includes("")&&(z=z.replace("",t.displayName)),z}while(1<=a&&0<=u);break}}}finally{Qi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Xl(l):""}function Pm(t,e){switch(t.tag){case 26:case 27:case 5:return Xl(t.type);case 16:return Xl("Lazy");case 13:return t.child!==e&&e!==null?Xl("Suspense Fallback"):Xl("Suspense");case 19:return Xl("SuspenseList");case 0:case 15:return Li(t.type,!1);case 11:return Li(t.type.render,!1);case 1:return Li(t.type,!0);case 31:return Xl("Activity");default:return""}}function qr(t){try{var e="",l=null;do e+=Pm(t,l),l=t,t=t.return;while(t);return e}catch(a){return` Error generating stack: `+a.message+` -`+a.stack}}var Qi=Object.prototype.hasOwnProperty,Li=n.unstable_scheduleCallback,Yi=n.unstable_cancelCallback,Im=n.unstable_shouldYield,ty=n.unstable_requestPaint,re=n.unstable_now,ey=n.unstable_getCurrentPriorityLevel,qr=n.unstable_ImmediatePriority,Hr=n.unstable_UserBlockingPriority,iu=n.unstable_NormalPriority,ly=n.unstable_LowPriority,Br=n.unstable_IdlePriority,ay=n.log,ny=n.unstable_setDisableYieldValue,en=null,fe=null;function hl(t){if(typeof ay=="function"&&ny(t),fe&&typeof fe.setStrictMode=="function")try{fe.setStrictMode(en,t)}catch{}}var oe=Math.clz32?Math.clz32:sy,uy=Math.log,iy=Math.LN2;function sy(t){return t>>>=0,t===0?32:31-(uy(t)/iy|0)|0}var su=256,cu=262144,ru=4194304;function Xl(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function fu(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,i=t.suspendedLanes,f=t.pingedLanes;t=t.warmLanes;var o=a&134217727;return o!==0?(a=o&~i,a!==0?u=Xl(a):(f&=o,f!==0?u=Xl(f):l||(l=o&~t,l!==0&&(u=Xl(l))))):(o=a&~i,o!==0?u=Xl(o):f!==0?u=Xl(f):l||(l=a&~t,l!==0&&(u=Xl(l)))),u===0?0:e!==0&&e!==u&&(e&i)===0&&(i=u&-u,l=e&-e,i>=l||i===32&&(l&4194048)!==0)?e:u}function ln(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function cy(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Qr(){var t=ru;return ru<<=1,(ru&62914560)===0&&(ru=4194304),t}function Gi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function an(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ry(t,e,l,a,u,i){var f=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var o=t.entanglements,g=t.expirationTimes,A=t.hiddenUpdates;for(l=f&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var yy=/[\n"\\]/g;function Te(t){return t.replace(yy,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function ki(t,e,l,a,u,i,f,o){t.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?t.type=f:t.removeAttribute("type"),e!=null?f==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Ee(e)):t.value!==""+Ee(e)&&(t.value=""+Ee(e)):f!=="submit"&&f!=="reset"||t.removeAttribute("value"),e!=null?Fi(t,f,Ee(e)):l!=null?Fi(t,f,Ee(l)):a!=null&&t.removeAttribute("value"),u==null&&i!=null&&(t.defaultChecked=!!i),u!=null&&(t.checked=u&&typeof u!="function"&&typeof u!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?t.name=""+Ee(o):t.removeAttribute("name")}function Pr(t,e,l,a,u,i,f,o){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.type=i),e!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||e!=null)){Ji(t);return}l=l!=null?""+Ee(l):"",e=e!=null?""+Ee(e):l,o||e===t.value||(t.value=e),t.defaultValue=e}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=o?t.checked:!!a,t.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(t.name=f),Ji(t)}function Fi(t,e,l){e==="number"&&du(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function pa(t,e,l,a){if(t=t.options,e){e={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ts=!1;if(Je)try{var cn={};Object.defineProperty(cn,"passive",{get:function(){ts=!0}}),window.addEventListener("test",cn,cn),window.removeEventListener("test",cn,cn)}catch{ts=!1}var ml=null,es=null,yu=null;function uf(){if(yu)return yu;var t,e=es,l=e.length,a,u="value"in ml?ml.value:ml.textContent,i=u.length;for(t=0;t=on),hf=" ",df=!1;function mf(t,e){switch(t){case"keyup":return Xy.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function yf(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ea=!1;function Ky(t,e){switch(t){case"compositionend":return yf(e);case"keypress":return e.which!==32?null:(df=!0,hf);case"textInput":return t=e.data,t===hf&&df?null:t;default:return null}}function Vy(t,e){if(Ea)return t==="compositionend"||!is&&mf(t,e)?(t=uf(),yu=es=ml=null,Ea=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=xf(l)}}function Of(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?Of(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function Af(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=du(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=du(t.document)}return e}function rs(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var tv=Je&&"documentMode"in document&&11>=document.documentMode,Ta=null,fs=null,yn=null,os=!1;function Cf(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;os||Ta==null||Ta!==du(a)||(a=Ta,"selectionStart"in a&&rs(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),yn&&mn(yn,a)||(yn=a,a=ci(fs,"onSelect"),0>=f,u-=f,Be=1<<32-oe(e)+u|l<et?(it=V,V=null):it=V.sibling;var ot=C(E,V,O[et],N);if(ot===null){V===null&&(V=it);break}t&&V&&ot.alternate===null&&e(E,V),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot,V=it}if(et===O.length)return l(E,V),st&&Fe(E,et),k;if(V===null){for(;etet?(it=V,V=null):it=V.sibling;var ql=C(E,V,ot.value,N);if(ql===null){V===null&&(V=it);break}t&&V&&ql.alternate===null&&e(E,V),S=i(ql,S,et),ft===null?k=ql:ft.sibling=ql,ft=ql,V=it}if(ot.done)return l(E,V),st&&Fe(E,et),k;if(V===null){for(;!ot.done;et++,ot=O.next())ot=j(E,ot.value,N),ot!==null&&(S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return st&&Fe(E,et),k}for(V=a(V);!ot.done;et++,ot=O.next())ot=M(V,E,et,ot.value,N),ot!==null&&(t&&ot.alternate!==null&&V.delete(ot.key===null?et:ot.key),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return t&&V.forEach(function(b0){return e(E,b0)}),st&&Fe(E,et),k}function Et(E,S,O,N){if(typeof O=="object"&&O!==null&&O.type===B&&O.key===null&&(O=O.props.children),typeof O=="object"&&O!==null){switch(O.$$typeof){case q:t:{for(var k=O.key;S!==null;){if(S.key===k){if(k=O.type,k===B){if(S.tag===7){l(E,S.sibling),N=u(S,O.props.children),N.return=E,E=N;break t}}else if(S.elementType===k||typeof k=="object"&&k!==null&&k.$$typeof===rt&&ta(k)===S.type){l(E,S.sibling),N=u(S,O.props),En(N,O),N.return=E,E=N;break t}l(E,S);break}else e(E,S);S=S.sibling}O.type===B?(N=Fl(O.props.children,E.mode,N,O.key),N.return=E,E=N):(N=Ou(O.type,O.key,O.props,null,E.mode,N),En(N,O),N.return=E,E=N)}return f(E);case H:t:{for(k=O.key;S!==null;){if(S.key===k)if(S.tag===4&&S.stateNode.containerInfo===O.containerInfo&&S.stateNode.implementation===O.implementation){l(E,S.sibling),N=u(S,O.children||[]),N.return=E,E=N;break t}else{l(E,S);break}else e(E,S);S=S.sibling}N=gs(O,E.mode,N),N.return=E,E=N}return f(E);case rt:return O=ta(O),Et(E,S,O,N)}if(Ne(O))return Z(E,S,O,N);if(te(O)){if(k=te(O),typeof k!="function")throw Error(r(150));return O=k.call(O),$(E,S,O,N)}if(typeof O.then=="function")return Et(E,S,Uu(O),N);if(O.$$typeof===J)return Et(E,S,Mu(E,O),N);Nu(E,O)}return typeof O=="string"&&O!==""||typeof O=="number"||typeof O=="bigint"?(O=""+O,S!==null&&S.tag===6?(l(E,S.sibling),N=u(S,O),N.return=E,E=N):(l(E,S),N=ps(O,E.mode,N),N.return=E,E=N),f(E)):l(E,S)}return function(E,S,O,N){try{bn=0;var k=Et(E,S,O,N);return Na=null,k}catch(V){if(V===Ua||V===_u)throw V;var ft=de(29,V,null,E.mode);return ft.lanes=N,ft.return=E,ft}}}var la=$f(!0),Wf=$f(!1),Sl=!1;function _s(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ds(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function bl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function El(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(dt&2)!==0){var u=a.pending;return u===null?e.next=e:(e.next=u.next,u.next=e),a.pending=e,e=Ru(t),jf(t,null,l),e}return xu(t,a,e,l),Ru(t)}function Tn(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Yr(t,l)}}function Us(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var u=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var f={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?u=i=f:i=i.next=f,l=l.next}while(l!==null);i===null?u=i=e:i=i.next=e}else u=i=e;l={baseState:a.baseState,firstBaseUpdate:u,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var Ns=!1;function xn(){if(Ns){var t=Da;if(t!==null)throw t}}function Rn(t,e,l,a){Ns=!1;var u=t.updateQueue;Sl=!1;var i=u.firstBaseUpdate,f=u.lastBaseUpdate,o=u.shared.pending;if(o!==null){u.shared.pending=null;var g=o,A=g.next;g.next=null,f===null?i=A:f.next=A,f=g;var z=t.alternate;z!==null&&(z=z.updateQueue,o=z.lastBaseUpdate,o!==f&&(o===null?z.firstBaseUpdate=A:o.next=A,z.lastBaseUpdate=g))}if(i!==null){var j=u.baseState;f=0,z=A=g=null,o=i;do{var C=o.lane&-536870913,M=C!==o.lane;if(M?(ut&C)===C:(a&C)===C){C!==0&&C===_a&&(Ns=!0),z!==null&&(z=z.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});t:{var Z=t,$=o;C=e;var Et=l;switch($.tag){case 1:if(Z=$.payload,typeof Z=="function"){j=Z.call(Et,j,C);break t}j=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=$.payload,C=typeof Z=="function"?Z.call(Et,j,C):Z,C==null)break t;j=x({},j,C);break t;case 2:Sl=!0}}C=o.callback,C!==null&&(t.flags|=64,M&&(t.flags|=8192),M=u.callbacks,M===null?u.callbacks=[C]:M.push(C))}else M={lane:C,tag:o.tag,payload:o.payload,callback:o.callback,next:null},z===null?(A=z=M,g=j):z=z.next=M,f|=C;if(o=o.next,o===null){if(o=u.shared.pending,o===null)break;M=o,o=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);z===null&&(g=j),u.baseState=g,u.firstBaseUpdate=A,u.lastBaseUpdate=z,i===null&&(u.shared.lanes=0),Al|=f,t.lanes=f,t.memoizedState=j}}function Pf(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function If(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;ti?i:8;var f=U.T,o={};U.T=o,Ps(t,!1,e,l);try{var g=u(),A=U.S;if(A!==null&&A(o,g),g!==null&&typeof g=="object"&&typeof g.then=="function"){var z=rv(g,a);Cn(t,e,z,ge(t))}else Cn(t,e,a,ge(t))}catch(j){Cn(t,e,{then:function(){},status:"rejected",reason:j},ge())}finally{L.p=i,f!==null&&o.types!==null&&(f.types=o.types),U.T=f}}function yv(){}function $s(t,e,l,a){if(t.tag!==5)throw Error(r(476));var u=Uo(t).queue;Do(t,u,e,W,l===null?yv:function(){return No(t),l(a)})}function Uo(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ie,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ie,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function No(t){var e=Uo(t);e.next===null&&(e=t.alternate.memoizedState),Cn(t,e.next.queue,{},ge())}function Ws(){return Vt(Zn)}function jo(){return jt().memoizedState}function wo(){return jt().memoizedState}function vv(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ge();t=bl(l);var a=El(e,t,l);a!==null&&(se(a,e,l),Tn(a,e,l)),e={cache:As()},t.payload=e;return}e=e.return}}function pv(t,e,l){var a=ge();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Xu(t)?Ho(e,l):(l=ys(t,e,l,a),l!==null&&(se(l,t,a),Bo(l,e,a)))}function qo(t,e,l){var a=ge();Cn(t,e,l,a)}function Cn(t,e,l,a){var u={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Xu(t))Ho(e,u);else{var i=t.alternate;if(t.lanes===0&&(i===null||i.lanes===0)&&(i=e.lastRenderedReducer,i!==null))try{var f=e.lastRenderedState,o=i(f,l);if(u.hasEagerState=!0,u.eagerState=o,he(o,f))return xu(t,e,u,0),xt===null&&Tu(),!1}catch{}if(l=ys(t,e,u,a),l!==null)return se(l,t,a),Bo(l,e,a),!0}return!1}function Ps(t,e,l,a){if(a={lane:2,revertLane:_c(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Xu(t)){if(e)throw Error(r(479))}else e=ys(t,l,a,2),e!==null&&se(e,t,2)}function Xu(t){var e=t.alternate;return t===tt||e!==null&&e===tt}function Ho(t,e){wa=qu=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function Bo(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Yr(t,l)}}var Mn={readContext:Vt,use:Qu,useCallback:_t,useContext:_t,useEffect:_t,useImperativeHandle:_t,useLayoutEffect:_t,useInsertionEffect:_t,useMemo:_t,useReducer:_t,useRef:_t,useState:_t,useDebugValue:_t,useDeferredValue:_t,useTransition:_t,useSyncExternalStore:_t,useId:_t,useHostTransitionStatus:_t,useFormState:_t,useActionState:_t,useOptimistic:_t,useMemoCache:_t,useCacheRefresh:_t};Mn.useEffectEvent=_t;var Qo={readContext:Vt,use:Qu,useCallback:function(t,e){return Pt().memoizedState=[t,e===void 0?null:e],t},useContext:Vt,useEffect:To,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Yu(4194308,4,Ao.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Yu(4194308,4,t,e)},useInsertionEffect:function(t,e){Yu(4,2,t,e)},useMemo:function(t,e){var l=Pt();e=e===void 0?null:e;var a=t();if(aa){hl(!0);try{t()}finally{hl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=Pt();if(l!==void 0){var u=l(e);if(aa){hl(!0);try{l(e)}finally{hl(!1)}}}else u=e;return a.memoizedState=a.baseState=u,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:u},a.queue=t,t=t.dispatch=pv.bind(null,tt,t),[a.memoizedState,t]},useRef:function(t){var e=Pt();return t={current:t},e.memoizedState=t},useState:function(t){t=Ks(t);var e=t.queue,l=qo.bind(null,tt,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:ks,useDeferredValue:function(t,e){var l=Pt();return Fs(l,t,e)},useTransition:function(){var t=Ks(!1);return t=Do.bind(null,tt,t.queue,!0,!1),Pt().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=tt,u=Pt();if(st){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),xt===null)throw Error(r(349));(ut&127)!==0||uo(a,e,l)}u.memoizedState=l;var i={value:l,getSnapshot:e};return u.queue=i,To(so.bind(null,a,i,t),[t]),a.flags|=2048,Ha(9,{destroy:void 0},io.bind(null,a,i,l,e),null),l},useId:function(){var t=Pt(),e=xt.identifierPrefix;if(st){var l=Qe,a=Be;l=(a&~(1<<32-oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=Hu++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?f.createElement(u,{is:a.is}):f.createElement(u)}}i[Zt]=e,i[ee]=a;t:for(f=e.child;f!==null;){if(f.tag===5||f.tag===6)i.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break t;for(;f.sibling===null;){if(f.return===null||f.return===e)break t;f=f.return}f.sibling.return=f.return,f=f.sibling}e.stateNode=i;t:switch(kt(i,u,a),u){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&el(e)}}return At(e),hc(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&el(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=lt.current,Ma(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,u=Kt,u!==null)switch(u.tag){case 27:case 5:a=u.memoizedProps}t[Zt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||nd(t.nodeValue,l)),t||pl(e,!0)}else t=ri(t).createTextNode(a),t[Zt]=e,e.stateNode=t}return At(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=Ma(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Zt]=e}else $l(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),t=!1}else l=Ts(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ye(e),e):(ye(e),null);if((e.flags&128)!==0)throw Error(r(558))}return At(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(u=Ma(e),a!==null&&a.dehydrated!==null){if(t===null){if(!u)throw Error(r(318));if(u=e.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(r(317));u[Zt]=e}else $l(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),u=!1}else u=Ts(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=u),u=!0;if(!u)return e.flags&256?(ye(e),e):(ye(e),null)}return ye(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,u=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(u=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==u&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),ku(e,e.updateQueue),At(e),null);case 4:return Ut(),t===null&&jc(e.stateNode.containerInfo),At(e),null;case 10:return We(e.type),At(e),null;case 19:if(w(Nt),a=e.memoizedState,a===null)return At(e),null;if(u=(e.flags&128)!==0,i=a.rendering,i===null)if(u)_n(a,!1);else{if(Dt!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(i=wu(t),i!==null){for(e.flags|=128,_n(a,!1),t=i.updateQueue,e.updateQueue=t,ku(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)wf(l,t),l=l.sibling;return G(Nt,Nt.current&1|2),st&&Fe(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&re()>Iu&&(e.flags|=128,u=!0,_n(a,!1),e.lanes=4194304)}else{if(!u)if(t=wu(i),t!==null){if(e.flags|=128,u=!0,t=t.updateQueue,e.updateQueue=t,ku(e,t),_n(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!st)return At(e),null}else 2*re()-a.renderingStartTime>Iu&&l!==536870912&&(e.flags|=128,u=!0,_n(a,!1),e.lanes=4194304);a.isBackwards?(i.sibling=e.child,e.child=i):(t=a.last,t!==null?t.sibling=i:e.child=i,a.last=i)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=re(),t.sibling=null,l=Nt.current,G(Nt,u?l&1|2:l&1),st&&Fe(e,a.treeForkCount),t):(At(e),null);case 22:case 23:return ye(e),ws(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(At(e),e.subtreeFlags&6&&(e.flags|=8192)):At(e),l=e.updateQueue,l!==null&&ku(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&w(Il),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),We(wt),At(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function Tv(t,e){switch(bs(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return We(wt),Ut(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return uu(e),null;case 31:if(e.memoizedState!==null){if(ye(e),e.alternate===null)throw Error(r(340));$l()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ye(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));$l()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return w(Nt),null;case 4:return Ut(),null;case 10:return We(e.type),null;case 22:case 23:return ye(e),ws(),t!==null&&w(Il),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return We(wt),null;case 25:return null;default:return null}}function rh(t,e){switch(bs(e),e.tag){case 3:We(wt),Ut();break;case 26:case 27:case 5:uu(e);break;case 4:Ut();break;case 31:e.memoizedState!==null&&ye(e);break;case 13:ye(e);break;case 19:w(Nt);break;case 10:We(e.type);break;case 22:case 23:ye(e),ws(),t!==null&&w(Il);break;case 24:We(wt)}}function Dn(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var u=a.next;l=u;do{if((l.tag&t)===t){a=void 0;var i=l.create,f=l.inst;a=i(),f.destroy=a}l=l.next}while(l!==u)}}catch(o){pt(e,e.return,o)}}function Rl(t,e,l){try{var a=e.updateQueue,u=a!==null?a.lastEffect:null;if(u!==null){var i=u.next;a=i;do{if((a.tag&t)===t){var f=a.inst,o=f.destroy;if(o!==void 0){f.destroy=void 0,u=e;var g=l,A=o;try{A()}catch(z){pt(u,g,z)}}}a=a.next}while(a!==i)}}catch(z){pt(e,e.return,z)}}function fh(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{If(e,l)}catch(a){pt(t,t.return,a)}}}function oh(t,e,l){l.props=na(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){pt(t,e,a)}}function Un(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(u){pt(t,e,u)}}function Le(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(u){pt(t,e,u)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(u){pt(t,e,u)}else l.current=null}function hh(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(u){pt(t,t.return,u)}}function dc(t,e,l){try{var a=t.stateNode;Zv(a,t.type,l,e),a[ee]=e}catch(u){pt(t,t.return,u)}}function dh(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&Dl(t.type)||t.tag===4}function mc(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||dh(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&Dl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function yc(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=Ve));else if(a!==4&&(a===27&&Dl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(yc(t,e,l),t=t.sibling;t!==null;)yc(t,e,l),t=t.sibling}function Fu(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&Dl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Fu(t,e,l),t=t.sibling;t!==null;)Fu(t,e,l),t=t.sibling}function mh(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,u=e.attributes;u.length;)e.removeAttributeNode(u[0]);kt(e,a,l),e[Zt]=t,e[ee]=l}catch(i){pt(t,t.return,i)}}var ll=!1,Bt=!1,vc=!1,yh=typeof WeakSet=="function"?WeakSet:Set,Xt=null;function xv(t,e){if(t=t.containerInfo,Hc=vi,t=Af(t),rs(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var u=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break t}var f=0,o=-1,g=-1,A=0,z=0,j=t,C=null;e:for(;;){for(var M;j!==l||u!==0&&j.nodeType!==3||(o=f+u),j!==i||a!==0&&j.nodeType!==3||(g=f+a),j.nodeType===3&&(f+=j.nodeValue.length),(M=j.firstChild)!==null;)C=j,j=M;for(;;){if(j===t)break e;if(C===l&&++A===u&&(o=f),C===i&&++z===a&&(g=f),(M=j.nextSibling)!==null)break;j=C,C=j.parentNode}j=M}l=o===-1||g===-1?null:{start:o,end:g}}else l=null}l=l||{start:0,end:0}}else l=null;for(Bc={focusedElem:t,selectionRange:l},vi=!1,Xt=e;Xt!==null;)if(e=Xt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Xt=t;else for(;Xt!==null;){switch(e=Xt,i=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),kt(i,a,l),i[Zt]=t,Gt(i),a=i;break t;case"link":var f=Ed("link","href",u).get(a+(l.href||""));if(f){for(var o=0;oEt&&(f=Et,Et=$,$=f);var E=Rf(o,$),S=Rf(o,Et);if(E&&S&&(M.rangeCount!==1||M.anchorNode!==E.node||M.anchorOffset!==E.offset||M.focusNode!==S.node||M.focusOffset!==S.offset)){var O=j.createRange();O.setStart(E.node,E.offset),M.removeAllRanges(),$>Et?(M.addRange(O),M.extend(S.node,S.offset)):(O.setEnd(S.node,S.offset),M.addRange(O))}}}}for(j=[],M=o;M=M.parentNode;)M.nodeType===1&&j.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,U.T=null,l=xc,xc=null;var i=Ml,f=sl;if(Lt=0,Ga=Ml=null,sl=0,(dt&6)!==0)throw Error(r(331));var o=dt;if(dt|=4,Ah(i.current),xh(i,i.current,f,l),dt=o,Bn(0,!1),fe&&typeof fe.onPostCommitFiberRoot=="function")try{fe.onPostCommitFiberRoot(en,i)}catch{}return!0}finally{L.p=u,U.T=a,Zh(t,e)}}function Vh(t,e,l){e=Re(l,e),e=lc(t.stateNode,e,2),t=El(t,e,2),t!==null&&(an(t,2),Ye(t))}function pt(t,e,l){if(t.tag===3)Vh(t,t,l);else for(;e!==null;){if(e.tag===3){Vh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Cl===null||!Cl.has(a))){t=Re(l,t),l=Jo(2),a=El(e,l,2),a!==null&&(ko(l,a,e,t),an(a,2),Ye(a));break}}e=e.return}}function Cc(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new Av;var u=new Set;a.set(e,u)}else u=a.get(e),u===void 0&&(u=new Set,a.set(e,u));u.has(l)||(Sc=!0,u.add(l),t=Dv.bind(null,t,e,l),e.then(t,t))}function Dv(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,xt===t&&(ut&l)===l&&(Dt===4||Dt===3&&(ut&62914560)===ut&&300>re()-Pu?(dt&2)===0&&Xa(t,0):bc|=l,Ya===ut&&(Ya=0)),Ye(t)}function Jh(t,e){e===0&&(e=Qr()),t=kl(t,e),t!==null&&(an(t,e),Ye(t))}function Uv(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Jh(t,l)}function Nv(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,u=t.memoizedState;u!==null&&(l=u.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),Jh(t,l)}function jv(t,e){return Li(t,e)}var ui=null,Ka=null,Mc=!1,ii=!1,zc=!1,_l=0;function Ye(t){t!==Ka&&t.next===null&&(Ka===null?ui=Ka=t:Ka=Ka.next=t),ii=!0,Mc||(Mc=!0,qv())}function Bn(t,e){if(!zc&&ii){zc=!0;do for(var l=!1,a=ui;a!==null;){if(t!==0){var u=a.pendingLanes;if(u===0)var i=0;else{var f=a.suspendedLanes,o=a.pingedLanes;i=(1<<31-oe(42|t)+1)-1,i&=u&~(f&~o),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Wh(a,i))}else i=ut,i=fu(a,a===xt?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||ln(a,i)||(l=!0,Wh(a,i));a=a.next}while(l);zc=!1}}function wv(){kh()}function kh(){ii=Mc=!1;var t=0;_l!==0&&Vv()&&(t=_l);for(var e=re(),l=null,a=ui;a!==null;){var u=a.next,i=Fh(a,e);i===0?(a.next=null,l===null?ui=u:l.next=u,u===null&&(Ka=l)):(l=a,(t!==0||(i&3)!==0)&&(ii=!0)),a=u}Lt!==0&&Lt!==5||Bn(t),_l!==0&&(_l=0)}function Fh(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,u=t.expirationTimes,i=t.pendingLanes&-62914561;0o)break;var z=g.transferSize,j=g.initiatorType;z&&ud(j)&&(g=g.responseEnd,f+=z*(g"u"?null:document;function pd(t,e,l){var a=Va;if(a&&typeof e=="string"&&e){var u=Te(e);u='link[rel="'+t+'"][href="'+u+'"]',typeof l=="string"&&(u+='[crossorigin="'+l+'"]'),vd.has(u)||(vd.add(u),t={rel:t,crossOrigin:l,href:e},a.querySelector(u)===null&&(e=a.createElement("link"),kt(e,"link",t),Gt(e),a.head.appendChild(e)))}}function e0(t){cl.D(t),pd("dns-prefetch",t,null)}function l0(t,e){cl.C(t,e),pd("preconnect",t,e)}function a0(t,e,l){cl.L(t,e,l);var a=Va;if(a&&t&&e){var u='link[rel="preload"][as="'+Te(e)+'"]';e==="image"&&l&&l.imageSrcSet?(u+='[imagesrcset="'+Te(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(u+='[imagesizes="'+Te(l.imageSizes)+'"]')):u+='[href="'+Te(t)+'"]';var i=u;switch(e){case"style":i=Ja(t);break;case"script":i=ka(t)}_e.has(i)||(t=x({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),_e.set(i,t),a.querySelector(u)!==null||e==="style"&&a.querySelector(Gn(i))||e==="script"&&a.querySelector(Xn(i))||(e=a.createElement("link"),kt(e,"link",t),Gt(e),a.head.appendChild(e)))}}function n0(t,e){cl.m(t,e);var l=Va;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",u='link[rel="modulepreload"][as="'+Te(a)+'"][href="'+Te(t)+'"]',i=u;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=ka(t)}if(!_e.has(i)&&(t=x({rel:"modulepreload",href:t},e),_e.set(i,t),l.querySelector(u)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Xn(i)))return}a=l.createElement("link"),kt(a,"link",t),Gt(a),l.head.appendChild(a)}}}function u0(t,e,l){cl.S(t,e,l);var a=Va;if(a&&t){var u=ya(a).hoistableStyles,i=Ja(t);e=e||"default";var f=u.get(i);if(!f){var o={loading:0,preload:null};if(f=a.querySelector(Gn(i)))o.loading=5;else{t=x({rel:"stylesheet",href:t,"data-precedence":e},l),(l=_e.get(i))&&Kc(t,l);var g=f=a.createElement("link");Gt(g),kt(g,"link",t),g._p=new Promise(function(A,z){g.onload=A,g.onerror=z}),g.addEventListener("load",function(){o.loading|=1}),g.addEventListener("error",function(){o.loading|=2}),o.loading|=4,oi(f,e,a)}f={type:"stylesheet",instance:f,count:1,state:o},u.set(i,f)}}}function i0(t,e){cl.X(t,e);var l=Va;if(l&&t){var a=ya(l).hoistableScripts,u=ka(t),i=a.get(u);i||(i=l.querySelector(Xn(u)),i||(t=x({src:t,async:!0},e),(e=_e.get(u))&&Vc(t,e),i=l.createElement("script"),Gt(i),kt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function s0(t,e){cl.M(t,e);var l=Va;if(l&&t){var a=ya(l).hoistableScripts,u=ka(t),i=a.get(u);i||(i=l.querySelector(Xn(u)),i||(t=x({src:t,async:!0,type:"module"},e),(e=_e.get(u))&&Vc(t,e),i=l.createElement("script"),Gt(i),kt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function gd(t,e,l,a){var u=(u=lt.current)?fi(u):null;if(!u)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=Ja(l.href),l=ya(u).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=Ja(l.href);var i=ya(u).hoistableStyles,f=i.get(t);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(t,f),(i=u.querySelector(Gn(t)))&&!i._p&&(f.instance=i,f.state.loading=5),_e.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},_e.set(t,l),i||c0(u,t,l,f.state))),e&&a===null)throw Error(r(528,""));return f}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=ka(l),l=ya(u).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function Ja(t){return'href="'+Te(t)+'"'}function Gn(t){return'link[rel="stylesheet"]['+t+"]"}function Sd(t){return x({},t,{"data-precedence":t.precedence,precedence:null})}function c0(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),kt(e,"link",l),Gt(e),t.head.appendChild(e))}function ka(t){return'[src="'+Te(t)+'"]'}function Xn(t){return"script[async]"+t}function bd(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Te(l.href)+'"]');if(a)return e.instance=a,Gt(a),a;var u=x({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),Gt(a),kt(a,"style",u),oi(a,l.precedence,t),e.instance=a;case"stylesheet":u=Ja(l.href);var i=t.querySelector(Gn(u));if(i)return e.state.loading|=4,e.instance=i,Gt(i),i;a=Sd(l),(u=_e.get(u))&&Kc(a,u),i=(t.ownerDocument||t).createElement("link"),Gt(i);var f=i;return f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),kt(i,"link",a),e.state.loading|=4,oi(i,l.precedence,t),e.instance=i;case"script":return i=ka(l.src),(u=t.querySelector(Xn(i)))?(e.instance=u,Gt(u),u):(a=l,(u=_e.get(i))&&(a=x({},l),Vc(a,u)),t=t.ownerDocument||t,u=t.createElement("script"),Gt(u),kt(u,"link",a),t.head.appendChild(u),e.instance=u);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,oi(a,l.precedence,t));return e.instance}function oi(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=a.length?a[a.length-1]:null,i=u,f=0;f title"):null)}function r0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function xd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function f0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var u=Ja(a.href),i=e.querySelector(Gn(u));if(i){e=i._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=di.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=i,Gt(i);return}i=e.ownerDocument||e,a=Sd(a),(u=_e.get(u))&&Kc(a,u),i=i.createElement("link"),Gt(i);var f=i;f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),kt(i,"link",a),l.instance=i}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=di.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var Jc=0;function o0(t,e){return t.stylesheets&&t.count===0&&yi(t,t.stylesheets),0Jc?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(u)}}:null}function di(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)yi(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var mi=null;function yi(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,mi=new Map,e.forEach(h0,t),mi=null,di.call(t))}function h0(t,e){if(!(e.state.loading&4)){var l=mi.get(t);if(l)var a=l.get(null);else{l=new Map,mi.set(t,l);for(var u=t.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),lr.exports=z0(),lr.exports}var D0=_0();const U0=om(D0);var Jd="popstate";function N0(n={}){function s(r,d){let{pathname:h,search:m,hash:v}=r.location;return fr("",{pathname:h,search:m,hash:v},d.state&&d.state.usr||null,d.state&&d.state.key||"default")}function c(r,d){return typeof d=="string"?d:Pn(d)}return w0(s,c,null,n)}function Mt(n,s){if(n===!1||n===null||typeof n>"u")throw new Error(s)}function He(n,s){if(!n){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function j0(){return Math.random().toString(36).substring(2,10)}function kd(n,s){return{usr:n.state,key:n.key,idx:s}}function fr(n,s,c=null,r){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof s=="string"?Wa(s):s,state:c,key:s&&s.key||r||j0()}}function Pn({pathname:n="/",search:s="",hash:c=""}){return s&&s!=="?"&&(n+=s.charAt(0)==="?"?s:"?"+s),c&&c!=="#"&&(n+=c.charAt(0)==="#"?c:"#"+c),n}function Wa(n){let s={};if(n){let c=n.indexOf("#");c>=0&&(s.hash=n.substring(c),n=n.substring(0,c));let r=n.indexOf("?");r>=0&&(s.search=n.substring(r),n=n.substring(0,r)),n&&(s.pathname=n)}return s}function w0(n,s,c,r={}){let{window:d=document.defaultView,v5Compat:h=!1}=r,m=d.history,v="POP",p=null,y=T();y==null&&(y=0,m.replaceState({...m.state,idx:y},""));function T(){return(m.state||{idx:null}).idx}function x(){v="POP";let Y=T(),Q=Y==null?null:Y-y;y=Y,p&&p({action:v,location:B.location,delta:Q})}function D(Y,Q){v="PUSH";let X=fr(B.location,Y,Q);y=T()+1;let J=kd(X,y),ht=B.createHref(X);try{m.pushState(J,"",ht)}catch(ct){if(ct instanceof DOMException&&ct.name==="DataCloneError")throw ct;d.location.assign(ht)}h&&p&&p({action:v,location:B.location,delta:1})}function q(Y,Q){v="REPLACE";let X=fr(B.location,Y,Q);y=T();let J=kd(X,y),ht=B.createHref(X);m.replaceState(J,"",ht),h&&p&&p({action:v,location:B.location,delta:0})}function H(Y){return q0(Y)}let B={get action(){return v},get location(){return n(d,m)},listen(Y){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(Jd,x),p=Y,()=>{d.removeEventListener(Jd,x),p=null}},createHref(Y){return s(d,Y)},createURL:H,encodeLocation(Y){let Q=H(Y);return{pathname:Q.pathname,search:Q.search,hash:Q.hash}},push:D,replace:q,go(Y){return m.go(Y)}};return B}function q0(n,s=!1){let c="http://localhost";typeof window<"u"&&(c=window.location.origin!=="null"?window.location.origin:window.location.href),Mt(c,"No window.location.(origin|href) available to create URL");let r=typeof n=="string"?n:Pn(n);return r=r.replace(/ $/,"%20"),!s&&r.startsWith("//")&&(r=c+r),new URL(r,c)}function hm(n,s,c="/"){return H0(n,s,c,!1)}function H0(n,s,c,r){let d=typeof s=="string"?Wa(s):s,h=fl(d.pathname||"/",c);if(h==null)return null;let m=dm(n);B0(m);let v=null;for(let p=0;v==null&&p{let T={relativePath:y===void 0?m.path||"":y,caseSensitive:m.caseSensitive===!0,childrenIndex:v,route:m};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&p)return;Mt(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let x=rl([r,T.relativePath]),D=c.concat(T);m.children&&m.children.length>0&&(Mt(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),dm(m.children,s,D,x,p)),!(m.path==null&&!m.index)&&s.push({path:x,score:K0(x,m.index),routesMeta:D})};return n.forEach((m,v)=>{if(m.path===""||!m.path?.includes("?"))h(m,v);else for(let p of mm(m.path))h(m,v,!0,p)}),s}function mm(n){let s=n.split("/");if(s.length===0)return[];let[c,...r]=s,d=c.endsWith("?"),h=c.replace(/\?$/,"");if(r.length===0)return d?[h,""]:[h];let m=mm(r.join("/")),v=[];return v.push(...m.map(p=>p===""?h:[h,p].join("/"))),d&&v.push(...m),v.map(p=>n.startsWith("/")&&p===""?"/":p)}function B0(n){n.sort((s,c)=>s.score!==c.score?c.score-s.score:V0(s.routesMeta.map(r=>r.childrenIndex),c.routesMeta.map(r=>r.childrenIndex)))}var Q0=/^:[\w-]+$/,L0=3,Y0=2,G0=1,X0=10,Z0=-2,Fd=n=>n==="*";function K0(n,s){let c=n.split("/"),r=c.length;return c.some(Fd)&&(r+=Z0),s&&(r+=Y0),c.filter(d=>!Fd(d)).reduce((d,h)=>d+(Q0.test(h)?L0:h===""?G0:X0),r)}function V0(n,s){return n.length===s.length&&n.slice(0,-1).every((r,d)=>r===s[d])?n[n.length-1]-s[s.length-1]:0}function J0(n,s,c=!1){let{routesMeta:r}=n,d={},h="/",m=[];for(let v=0;v{if(T==="*"){let H=v[D]||"";m=h.slice(0,h.length-H.length).replace(/(.)\/+$/,"$1")}const q=v[D];return x&&!q?y[T]=void 0:y[T]=(q||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:m,pattern:n}}function k0(n,s=!1,c=!0){He(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],d="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(m,v,p)=>(r.push({paramName:v,isOptional:p!=null}),p?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),d+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):c?d+="\\/*$":n!==""&&n!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,s?void 0:"i"),r]}function F0(n){try{return n.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return He(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),n}}function fl(n,s){if(s==="/")return n;if(!n.toLowerCase().startsWith(s.toLowerCase()))return null;let c=s.endsWith("/")?s.length-1:s.length,r=n.charAt(c);return r&&r!=="/"?null:n.slice(c)||"/"}var ym=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,$0=n=>ym.test(n);function W0(n,s="/"){let{pathname:c,search:r="",hash:d=""}=typeof n=="string"?Wa(n):n,h;if(c)if($0(c))h=c;else{if(c.includes("//")){let m=c;c=c.replace(/\/\/+/g,"/"),He(!1,`Pathnames cannot have embedded double slashes - normalizing ${m} -> ${c}`)}c.startsWith("/")?h=$d(c.substring(1),"/"):h=$d(c,s)}else h=s;return{pathname:h,search:tp(r),hash:ep(d)}}function $d(n,s){let c=s.replace(/\/+$/,"").split("/");return n.split("/").forEach(d=>{d===".."?c.length>1&&c.pop():d!=="."&&c.push(d)}),c.length>1?c.join("/"):"/"}function ir(n,s,c,r){return`Cannot include a '${n}' character in a manually specified \`to.${s}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${c}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function P0(n){return n.filter((s,c)=>c===0||s.route.path&&s.route.path.length>0)}function vm(n){let s=P0(n);return s.map((c,r)=>r===s.length-1?c.pathname:c.pathnameBase)}function pm(n,s,c,r=!1){let d;typeof n=="string"?d=Wa(n):(d={...n},Mt(!d.pathname||!d.pathname.includes("?"),ir("?","pathname","search",d)),Mt(!d.pathname||!d.pathname.includes("#"),ir("#","pathname","hash",d)),Mt(!d.search||!d.search.includes("#"),ir("#","search","hash",d)));let h=n===""||d.pathname==="",m=h?"/":d.pathname,v;if(m==null)v=c;else{let x=s.length-1;if(!r&&m.startsWith("..")){let D=m.split("/");for(;D[0]==="..";)D.shift(),x-=1;d.pathname=D.join("/")}v=x>=0?s[x]:"/"}let p=W0(d,v),y=m&&m!=="/"&&m.endsWith("/"),T=(h||m===".")&&c.endsWith("/");return!p.pathname.endsWith("/")&&(y||T)&&(p.pathname+="/"),p}var rl=n=>n.join("/").replace(/\/\/+/g,"/"),I0=n=>n.replace(/\/+$/,"").replace(/^\/*/,"/"),tp=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,ep=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,lp=class{constructor(n,s,c,r=!1){this.status=n,this.statusText=s||"",this.internal=r,c instanceof Error?(this.data=c.toString(),this.error=c):this.data=c}};function ap(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function np(n){return n.map(s=>s.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var gm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Sm(n,s){let c=n;if(typeof c!="string"||!ym.test(c))return{absoluteURL:void 0,isExternal:!1,to:c};let r=c,d=!1;if(gm)try{let h=new URL(window.location.href),m=c.startsWith("//")?new URL(h.protocol+c):new URL(c),v=fl(m.pathname,s);m.origin===h.origin&&v!=null?c=v+m.search+m.hash:d=!0}catch{He(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:d,to:c}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var bm=["POST","PUT","PATCH","DELETE"];new Set(bm);var up=["GET",...bm];new Set(up);var Pa=R.createContext(null);Pa.displayName="DataRouter";var _i=R.createContext(null);_i.displayName="DataRouterState";var ip=R.createContext(!1),Em=R.createContext({isTransitioning:!1});Em.displayName="ViewTransition";var sp=R.createContext(new Map);sp.displayName="Fetchers";var cp=R.createContext(null);cp.displayName="Await";var Ue=R.createContext(null);Ue.displayName="Navigation";var eu=R.createContext(null);eu.displayName="Location";var Xe=R.createContext({outlet:null,matches:[],isDataRoute:!1});Xe.displayName="Route";var Sr=R.createContext(null);Sr.displayName="RouteError";var Tm="REACT_ROUTER_ERROR",rp="REDIRECT",fp="ROUTE_ERROR_RESPONSE";function op(n){if(n.startsWith(`${Tm}:${rp}:{`))try{let s=JSON.parse(n.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function hp(n){if(n.startsWith(`${Tm}:${fp}:{`))try{let s=JSON.parse(n.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new lp(s.status,s.statusText,s.data)}catch{}}function dp(n,{relative:s}={}){Mt(lu(),"useHref() may be used only in the context of a component.");let{basename:c,navigator:r}=R.useContext(Ue),{hash:d,pathname:h,search:m}=au(n,{relative:s}),v=h;return c!=="/"&&(v=h==="/"?c:rl([c,h])),r.createHref({pathname:v,search:m,hash:d})}function lu(){return R.useContext(eu)!=null}function fa(){return Mt(lu(),"useLocation() may be used only in the context of a component."),R.useContext(eu).location}var xm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Rm(n){R.useContext(Ue).static||R.useLayoutEffect(n)}function Om(){let{isDataRoute:n}=R.useContext(Xe);return n?Mp():mp()}function mp(){Mt(lu(),"useNavigate() may be used only in the context of a component.");let n=R.useContext(Pa),{basename:s,navigator:c}=R.useContext(Ue),{matches:r}=R.useContext(Xe),{pathname:d}=fa(),h=JSON.stringify(vm(r)),m=R.useRef(!1);return Rm(()=>{m.current=!0}),R.useCallback((p,y={})=>{if(He(m.current,xm),!m.current)return;if(typeof p=="number"){c.go(p);return}let T=pm(p,JSON.parse(h),d,y.relative==="path");n==null&&s!=="/"&&(T.pathname=T.pathname==="/"?s:rl([s,T.pathname])),(y.replace?c.replace:c.push)(T,y.state,y)},[s,c,h,d,n])}var yp=R.createContext(null);function vp(n){let s=R.useContext(Xe).outlet;return R.useMemo(()=>s&&R.createElement(yp.Provider,{value:n},s),[s,n])}function au(n,{relative:s}={}){let{matches:c}=R.useContext(Xe),{pathname:r}=fa(),d=JSON.stringify(vm(c));return R.useMemo(()=>pm(n,JSON.parse(d),r,s==="path"),[n,d,r,s])}function pp(n,s){return Am(n,s)}function Am(n,s,c,r,d){Mt(lu(),"useRoutes() may be used only in the context of a component.");let{navigator:h}=R.useContext(Ue),{matches:m}=R.useContext(Xe),v=m[m.length-1],p=v?v.params:{},y=v?v.pathname:"/",T=v?v.pathnameBase:"/",x=v&&v.route;{let X=x&&x.path||"";Mm(y,!x||X.endsWith("*")||X.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${y}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. +`+a.stack}}var Yi=Object.prototype.hasOwnProperty,Gi=n.unstable_scheduleCallback,Xi=n.unstable_cancelCallback,Im=n.unstable_shouldYield,ty=n.unstable_requestPaint,re=n.unstable_now,ey=n.unstable_getCurrentPriorityLevel,Hr=n.unstable_ImmediatePriority,Br=n.unstable_UserBlockingPriority,su=n.unstable_NormalPriority,ly=n.unstable_LowPriority,Qr=n.unstable_IdlePriority,ay=n.log,ny=n.unstable_setDisableYieldValue,ln=null,fe=null;function yl(t){if(typeof ay=="function"&&ny(t),fe&&typeof fe.setStrictMode=="function")try{fe.setStrictMode(ln,t)}catch{}}var oe=Math.clz32?Math.clz32:sy,uy=Math.log,iy=Math.LN2;function sy(t){return t>>>=0,t===0?32:31-(uy(t)/iy|0)|0}var cu=256,ru=262144,fu=4194304;function Kl(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function ou(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,i=t.suspendedLanes,f=t.pingedLanes;t=t.warmLanes;var o=a&134217727;return o!==0?(a=o&~i,a!==0?u=Kl(a):(f&=o,f!==0?u=Kl(f):l||(l=o&~t,l!==0&&(u=Kl(l))))):(o=a&~i,o!==0?u=Kl(o):f!==0?u=Kl(f):l||(l=a&~t,l!==0&&(u=Kl(l)))),u===0?0:e!==0&&e!==u&&(e&i)===0&&(i=u&-u,l=e&-e,i>=l||i===32&&(l&4194048)!==0)?e:u}function an(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function cy(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Lr(){var t=fu;return fu<<=1,(fu&62914560)===0&&(fu=4194304),t}function Ki(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function nn(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ry(t,e,l,a,u,i){var f=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var o=t.entanglements,g=t.expirationTimes,A=t.hiddenUpdates;for(l=f&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var yy=/[\n"\\]/g;function Te(t){return t.replace(yy,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function $i(t,e,l,a,u,i,f,o){t.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?t.type=f:t.removeAttribute("type"),e!=null?f==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Ee(e)):t.value!==""+Ee(e)&&(t.value=""+Ee(e)):f!=="submit"&&f!=="reset"||t.removeAttribute("value"),e!=null?Wi(t,f,Ee(e)):l!=null?Wi(t,f,Ee(l)):a!=null&&t.removeAttribute("value"),u==null&&i!=null&&(t.defaultChecked=!!i),u!=null&&(t.checked=u&&typeof u!="function"&&typeof u!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?t.name=""+Ee(o):t.removeAttribute("name")}function Ir(t,e,l,a,u,i,f,o){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.type=i),e!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||e!=null)){Fi(t);return}l=l!=null?""+Ee(l):"",e=e!=null?""+Ee(e):l,o||e===t.value||(t.value=e),t.defaultValue=e}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=o?t.checked:!!a,t.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(t.name=f),Fi(t)}function Wi(t,e,l){e==="number"&&mu(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function ga(t,e,l,a){if(t=t.options,e){e={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ls=!1;if(Fe)try{var rn={};Object.defineProperty(rn,"passive",{get:function(){ls=!0}}),window.addEventListener("test",rn,rn),window.removeEventListener("test",rn,rn)}catch{ls=!1}var pl=null,as=null,vu=null;function sf(){if(vu)return vu;var t,e=as,l=e.length,a,u="value"in pl?pl.value:pl.textContent,i=u.length;for(t=0;t=hn),df=" ",mf=!1;function yf(t,e){switch(t){case"keyup":return Xy.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function vf(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ta=!1;function Zy(t,e){switch(t){case"compositionend":return vf(e);case"keypress":return e.which!==32?null:(mf=!0,df);case"textInput":return t=e.data,t===df&&mf?null:t;default:return null}}function Vy(t,e){if(Ta)return t==="compositionend"||!cs&&yf(t,e)?(t=sf(),vu=as=pl=null,Ta=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=Rf(l)}}function Af(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?Af(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function Cf(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=mu(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=mu(t.document)}return e}function os(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var tv=Fe&&"documentMode"in document&&11>=document.documentMode,xa=null,hs=null,vn=null,ds=!1;function Mf(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;ds||xa==null||xa!==mu(a)||(a=xa,"selectionStart"in a&&os(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),vn&&yn(vn,a)||(vn=a,a=ri(hs,"onSelect"),0>=f,u-=f,Be=1<<32-oe(e)+u|l<et?(it=V,V=null):it=V.sibling;var ot=C(E,V,O[et],N);if(ot===null){V===null&&(V=it);break}t&&V&&ot.alternate===null&&e(E,V),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot,V=it}if(et===O.length)return l(E,V),st&&We(E,et),k;if(V===null){for(;etet?(it=V,V=null):it=V.sibling;var Ql=C(E,V,ot.value,N);if(Ql===null){V===null&&(V=it);break}t&&V&&Ql.alternate===null&&e(E,V),S=i(Ql,S,et),ft===null?k=Ql:ft.sibling=Ql,ft=Ql,V=it}if(ot.done)return l(E,V),st&&We(E,et),k;if(V===null){for(;!ot.done;et++,ot=O.next())ot=j(E,ot.value,N),ot!==null&&(S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return st&&We(E,et),k}for(V=a(V);!ot.done;et++,ot=O.next())ot=M(V,E,et,ot.value,N),ot!==null&&(t&&ot.alternate!==null&&V.delete(ot.key===null?et:ot.key),S=i(ot,S,et),ft===null?k=ot:ft.sibling=ot,ft=ot);return t&&V.forEach(function(b0){return e(E,b0)}),st&&We(E,et),k}function Et(E,S,O,N){if(typeof O=="object"&&O!==null&&O.type===B&&O.key===null&&(O=O.props.children),typeof O=="object"&&O!==null){switch(O.$$typeof){case q:t:{for(var k=O.key;S!==null;){if(S.key===k){if(k=O.type,k===B){if(S.tag===7){l(E,S.sibling),N=u(S,O.props.children),N.return=E,E=N;break t}}else if(S.elementType===k||typeof k=="object"&&k!==null&&k.$$typeof===rt&&ea(k)===S.type){l(E,S.sibling),N=u(S,O.props),Tn(N,O),N.return=E,E=N;break t}l(E,S);break}else e(E,S);S=S.sibling}O.type===B?(N=$l(O.props.children,E.mode,N,O.key),N.return=E,E=N):(N=Au(O.type,O.key,O.props,null,E.mode,N),Tn(N,O),N.return=E,E=N)}return f(E);case H:t:{for(k=O.key;S!==null;){if(S.key===k)if(S.tag===4&&S.stateNode.containerInfo===O.containerInfo&&S.stateNode.implementation===O.implementation){l(E,S.sibling),N=u(S,O.children||[]),N.return=E,E=N;break t}else{l(E,S);break}else e(E,S);S=S.sibling}N=bs(O,E.mode,N),N.return=E,E=N}return f(E);case rt:return O=ea(O),Et(E,S,O,N)}if(Ne(O))return K(E,S,O,N);if(ee(O)){if(k=ee(O),typeof k!="function")throw Error(r(150));return O=k.call(O),$(E,S,O,N)}if(typeof O.then=="function")return Et(E,S,Nu(O),N);if(O.$$typeof===J)return Et(E,S,zu(E,O),N);ju(E,O)}return typeof O=="string"&&O!==""||typeof O=="number"||typeof O=="bigint"?(O=""+O,S!==null&&S.tag===6?(l(E,S.sibling),N=u(S,O),N.return=E,E=N):(l(E,S),N=Ss(O,E.mode,N),N.return=E,E=N),f(E)):l(E,S)}return function(E,S,O,N){try{En=0;var k=Et(E,S,O,N);return ja=null,k}catch(V){if(V===Na||V===Du)throw V;var ft=de(29,V,null,E.mode);return ft.lanes=N,ft.return=E,ft}}}var aa=Wf(!0),Pf=Wf(!1),Tl=!1;function Us(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ns(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function xl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Rl(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(dt&2)!==0){var u=a.pending;return u===null?e.next=e:(e.next=u.next,u.next=e),a.pending=e,e=Ou(t),wf(t,null,l),e}return Ru(t,a,e,l),Ou(t)}function xn(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Gr(t,l)}}function js(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var u=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var f={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?u=i=f:i=i.next=f,l=l.next}while(l!==null);i===null?u=i=e:i=i.next=e}else u=i=e;l={baseState:a.baseState,firstBaseUpdate:u,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var ws=!1;function Rn(){if(ws){var t=Ua;if(t!==null)throw t}}function On(t,e,l,a){ws=!1;var u=t.updateQueue;Tl=!1;var i=u.firstBaseUpdate,f=u.lastBaseUpdate,o=u.shared.pending;if(o!==null){u.shared.pending=null;var g=o,A=g.next;g.next=null,f===null?i=A:f.next=A,f=g;var z=t.alternate;z!==null&&(z=z.updateQueue,o=z.lastBaseUpdate,o!==f&&(o===null?z.firstBaseUpdate=A:o.next=A,z.lastBaseUpdate=g))}if(i!==null){var j=u.baseState;f=0,z=A=g=null,o=i;do{var C=o.lane&-536870913,M=C!==o.lane;if(M?(ut&C)===C:(a&C)===C){C!==0&&C===Da&&(ws=!0),z!==null&&(z=z.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});t:{var K=t,$=o;C=e;var Et=l;switch($.tag){case 1:if(K=$.payload,typeof K=="function"){j=K.call(Et,j,C);break t}j=K;break t;case 3:K.flags=K.flags&-65537|128;case 0:if(K=$.payload,C=typeof K=="function"?K.call(Et,j,C):K,C==null)break t;j=x({},j,C);break t;case 2:Tl=!0}}C=o.callback,C!==null&&(t.flags|=64,M&&(t.flags|=8192),M=u.callbacks,M===null?u.callbacks=[C]:M.push(C))}else M={lane:C,tag:o.tag,payload:o.payload,callback:o.callback,next:null},z===null?(A=z=M,g=j):z=z.next=M,f|=C;if(o=o.next,o===null){if(o=u.shared.pending,o===null)break;M=o,o=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);z===null&&(g=j),u.baseState=g,u.firstBaseUpdate=A,u.lastBaseUpdate=z,i===null&&(u.shared.lanes=0),zl|=f,t.lanes=f,t.memoizedState=j}}function If(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function to(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;ti?i:8;var f=U.T,o={};U.T=o,tc(t,!1,e,l);try{var g=u(),A=U.S;if(A!==null&&A(o,g),g!==null&&typeof g=="object"&&typeof g.then=="function"){var z=rv(g,a);Mn(t,e,z,ge(t))}else Mn(t,e,a,ge(t))}catch(j){Mn(t,e,{then:function(){},status:"rejected",reason:j},ge())}finally{L.p=i,f!==null&&o.types!==null&&(f.types=o.types),U.T=f}}function yv(){}function Ps(t,e,l,a){if(t.tag!==5)throw Error(r(476));var u=No(t).queue;Uo(t,u,e,W,l===null?yv:function(){return jo(t),l(a)})}function No(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:el,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:el,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function jo(t){var e=No(t);e.next===null&&(e=t.alternate.memoizedState),Mn(t,e.next.queue,{},ge())}function Is(){return Jt(Zn)}function wo(){return wt().memoizedState}function qo(){return wt().memoizedState}function vv(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ge();t=xl(l);var a=Rl(e,t,l);a!==null&&(ce(a,e,l),xn(a,e,l)),e={cache:Ms()},t.payload=e;return}e=e.return}}function pv(t,e,l){var a=ge();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Ku(t)?Bo(e,l):(l=ps(t,e,l,a),l!==null&&(ce(l,t,a),Qo(l,e,a)))}function Ho(t,e,l){var a=ge();Mn(t,e,l,a)}function Mn(t,e,l,a){var u={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Ku(t))Bo(e,u);else{var i=t.alternate;if(t.lanes===0&&(i===null||i.lanes===0)&&(i=e.lastRenderedReducer,i!==null))try{var f=e.lastRenderedState,o=i(f,l);if(u.hasEagerState=!0,u.eagerState=o,he(o,f))return Ru(t,e,u,0),xt===null&&xu(),!1}catch{}if(l=ps(t,e,u,a),l!==null)return ce(l,t,a),Qo(l,e,a),!0}return!1}function tc(t,e,l,a){if(a={lane:2,revertLane:Uc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Ku(t)){if(e)throw Error(r(479))}else e=ps(t,l,a,2),e!==null&&ce(e,t,2)}function Ku(t){var e=t.alternate;return t===tt||e!==null&&e===tt}function Bo(t,e){qa=Hu=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function Qo(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Gr(t,l)}}var zn={readContext:Jt,use:Lu,useCallback:_t,useContext:_t,useEffect:_t,useImperativeHandle:_t,useLayoutEffect:_t,useInsertionEffect:_t,useMemo:_t,useReducer:_t,useRef:_t,useState:_t,useDebugValue:_t,useDeferredValue:_t,useTransition:_t,useSyncExternalStore:_t,useId:_t,useHostTransitionStatus:_t,useFormState:_t,useActionState:_t,useOptimistic:_t,useMemoCache:_t,useCacheRefresh:_t};zn.useEffectEvent=_t;var Lo={readContext:Jt,use:Lu,useCallback:function(t,e){return It().memoizedState=[t,e===void 0?null:e],t},useContext:Jt,useEffect:xo,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Gu(4194308,4,Co.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Gu(4194308,4,t,e)},useInsertionEffect:function(t,e){Gu(4,2,t,e)},useMemo:function(t,e){var l=It();e=e===void 0?null:e;var a=t();if(na){yl(!0);try{t()}finally{yl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=It();if(l!==void 0){var u=l(e);if(na){yl(!0);try{l(e)}finally{yl(!1)}}}else u=e;return a.memoizedState=a.baseState=u,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:u},a.queue=t,t=t.dispatch=pv.bind(null,tt,t),[a.memoizedState,t]},useRef:function(t){var e=It();return t={current:t},e.memoizedState=t},useState:function(t){t=Js(t);var e=t.queue,l=Ho.bind(null,tt,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:$s,useDeferredValue:function(t,e){var l=It();return Ws(l,t,e)},useTransition:function(){var t=Js(!1);return t=Uo.bind(null,tt,t.queue,!0,!1),It().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=tt,u=It();if(st){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),xt===null)throw Error(r(349));(ut&127)!==0||io(a,e,l)}u.memoizedState=l;var i={value:l,getSnapshot:e};return u.queue=i,xo(co.bind(null,a,i,t),[t]),a.flags|=2048,Ba(9,{destroy:void 0},so.bind(null,a,i,l,e),null),l},useId:function(){var t=It(),e=xt.identifierPrefix;if(st){var l=Qe,a=Be;l=(a&~(1<<32-oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=Bu++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?f.createElement(u,{is:a.is}):f.createElement(u)}}i[Zt]=e,i[le]=a;t:for(f=e.child;f!==null;){if(f.tag===5||f.tag===6)i.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break t;for(;f.sibling===null;){if(f.return===null||f.return===e)break t;f=f.return}f.sibling.return=f.return,f=f.sibling}e.stateNode=i;t:switch(Ft(i,u,a),u){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&al(e)}}return At(e),mc(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&al(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=lt.current,za(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,u=Vt,u!==null)switch(u.tag){case 27:case 5:a=u.memoizedProps}t[Zt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||ud(t.nodeValue,l)),t||bl(e,!0)}else t=fi(t).createTextNode(a),t[Zt]=e,e.stateNode=t}return At(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=za(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Zt]=e}else Wl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),t=!1}else l=Rs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ye(e),e):(ye(e),null);if((e.flags&128)!==0)throw Error(r(558))}return At(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(u=za(e),a!==null&&a.dehydrated!==null){if(t===null){if(!u)throw Error(r(318));if(u=e.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(r(317));u[Zt]=e}else Wl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),u=!1}else u=Rs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=u),u=!0;if(!u)return e.flags&256?(ye(e),e):(ye(e),null)}return ye(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,u=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(u=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==u&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),Fu(e,e.updateQueue),At(e),null);case 4:return Nt(),t===null&&qc(e.stateNode.containerInfo),At(e),null;case 10:return Ie(e.type),At(e),null;case 19:if(w(jt),a=e.memoizedState,a===null)return At(e),null;if(u=(e.flags&128)!==0,i=a.rendering,i===null)if(u)Dn(a,!1);else{if(Dt!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(i=qu(t),i!==null){for(e.flags|=128,Dn(a,!1),t=i.updateQueue,e.updateQueue=t,Fu(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)qf(l,t),l=l.sibling;return G(jt,jt.current&1|2),st&&We(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&re()>ti&&(e.flags|=128,u=!0,Dn(a,!1),e.lanes=4194304)}else{if(!u)if(t=qu(i),t!==null){if(e.flags|=128,u=!0,t=t.updateQueue,e.updateQueue=t,Fu(e,t),Dn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!st)return At(e),null}else 2*re()-a.renderingStartTime>ti&&l!==536870912&&(e.flags|=128,u=!0,Dn(a,!1),e.lanes=4194304);a.isBackwards?(i.sibling=e.child,e.child=i):(t=a.last,t!==null?t.sibling=i:e.child=i,a.last=i)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=re(),t.sibling=null,l=jt.current,G(jt,u?l&1|2:l&1),st&&We(e,a.treeForkCount),t):(At(e),null);case 22:case 23:return ye(e),Hs(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(At(e),e.subtreeFlags&6&&(e.flags|=8192)):At(e),l=e.updateQueue,l!==null&&Fu(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&w(ta),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),Ie(qt),At(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function Tv(t,e){switch(Ts(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return Ie(qt),Nt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return iu(e),null;case 31:if(e.memoizedState!==null){if(ye(e),e.alternate===null)throw Error(r(340));Wl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ye(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));Wl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return w(jt),null;case 4:return Nt(),null;case 10:return Ie(e.type),null;case 22:case 23:return ye(e),Hs(),t!==null&&w(ta),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return Ie(qt),null;case 25:return null;default:return null}}function fh(t,e){switch(Ts(e),e.tag){case 3:Ie(qt),Nt();break;case 26:case 27:case 5:iu(e);break;case 4:Nt();break;case 31:e.memoizedState!==null&&ye(e);break;case 13:ye(e);break;case 19:w(jt);break;case 10:Ie(e.type);break;case 22:case 23:ye(e),Hs(),t!==null&&w(ta);break;case 24:Ie(qt)}}function Un(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var u=a.next;l=u;do{if((l.tag&t)===t){a=void 0;var i=l.create,f=l.inst;a=i(),f.destroy=a}l=l.next}while(l!==u)}}catch(o){pt(e,e.return,o)}}function Cl(t,e,l){try{var a=e.updateQueue,u=a!==null?a.lastEffect:null;if(u!==null){var i=u.next;a=i;do{if((a.tag&t)===t){var f=a.inst,o=f.destroy;if(o!==void 0){f.destroy=void 0,u=e;var g=l,A=o;try{A()}catch(z){pt(u,g,z)}}}a=a.next}while(a!==i)}}catch(z){pt(e,e.return,z)}}function oh(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{to(e,l)}catch(a){pt(t,t.return,a)}}}function hh(t,e,l){l.props=ua(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){pt(t,e,a)}}function Nn(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(u){pt(t,e,u)}}function Le(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(u){pt(t,e,u)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(u){pt(t,e,u)}else l.current=null}function dh(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(u){pt(t,t.return,u)}}function yc(t,e,l){try{var a=t.stateNode;Kv(a,t.type,l,e),a[le]=e}catch(u){pt(t,t.return,u)}}function mh(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&jl(t.type)||t.tag===4}function vc(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||mh(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&jl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function pc(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=ke));else if(a!==4&&(a===27&&jl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(pc(t,e,l),t=t.sibling;t!==null;)pc(t,e,l),t=t.sibling}function $u(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&jl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for($u(t,e,l),t=t.sibling;t!==null;)$u(t,e,l),t=t.sibling}function yh(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,u=e.attributes;u.length;)e.removeAttributeNode(u[0]);Ft(e,a,l),e[Zt]=t,e[le]=l}catch(i){pt(t,t.return,i)}}var nl=!1,Qt=!1,gc=!1,vh=typeof WeakSet=="function"?WeakSet:Set,Kt=null;function xv(t,e){if(t=t.containerInfo,Qc=pi,t=Cf(t),os(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var u=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break t}var f=0,o=-1,g=-1,A=0,z=0,j=t,C=null;e:for(;;){for(var M;j!==l||u!==0&&j.nodeType!==3||(o=f+u),j!==i||a!==0&&j.nodeType!==3||(g=f+a),j.nodeType===3&&(f+=j.nodeValue.length),(M=j.firstChild)!==null;)C=j,j=M;for(;;){if(j===t)break e;if(C===l&&++A===u&&(o=f),C===i&&++z===a&&(g=f),(M=j.nextSibling)!==null)break;j=C,C=j.parentNode}j=M}l=o===-1||g===-1?null:{start:o,end:g}}else l=null}l=l||{start:0,end:0}}else l=null;for(Lc={focusedElem:t,selectionRange:l},pi=!1,Kt=e;Kt!==null;)if(e=Kt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Kt=t;else for(;Kt!==null;){switch(e=Kt,i=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),Ft(i,a,l),i[Zt]=t,Xt(i),a=i;break t;case"link":var f=Td("link","href",u).get(a+(l.href||""));if(f){for(var o=0;oEt&&(f=Et,Et=$,$=f);var E=Of(o,$),S=Of(o,Et);if(E&&S&&(M.rangeCount!==1||M.anchorNode!==E.node||M.anchorOffset!==E.offset||M.focusNode!==S.node||M.focusOffset!==S.offset)){var O=j.createRange();O.setStart(E.node,E.offset),M.removeAllRanges(),$>Et?(M.addRange(O),M.extend(S.node,S.offset)):(O.setEnd(S.node,S.offset),M.addRange(O))}}}}for(j=[],M=o;M=M.parentNode;)M.nodeType===1&&j.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,U.T=null,l=Oc,Oc=null;var i=Dl,f=rl;if(Yt=0,Xa=Dl=null,rl=0,(dt&6)!==0)throw Error(r(331));var o=dt;if(dt|=4,Ch(i.current),Rh(i,i.current,f,l),dt=o,Qn(0,!1),fe&&typeof fe.onPostCommitFiberRoot=="function")try{fe.onPostCommitFiberRoot(ln,i)}catch{}return!0}finally{L.p=u,U.T=a,Zh(t,e)}}function Jh(t,e,l){e=Re(l,e),e=nc(t.stateNode,e,2),t=Rl(t,e,2),t!==null&&(nn(t,2),Ye(t))}function pt(t,e,l){if(t.tag===3)Jh(t,t,l);else for(;e!==null;){if(e.tag===3){Jh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(_l===null||!_l.has(a))){t=Re(l,t),l=ko(2),a=Rl(e,l,2),a!==null&&(Fo(l,a,e,t),nn(a,2),Ye(a));break}}e=e.return}}function zc(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new Av;var u=new Set;a.set(e,u)}else u=a.get(e),u===void 0&&(u=new Set,a.set(e,u));u.has(l)||(Ec=!0,u.add(l),t=Dv.bind(null,t,e,l),e.then(t,t))}function Dv(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,xt===t&&(ut&l)===l&&(Dt===4||Dt===3&&(ut&62914560)===ut&&300>re()-Iu?(dt&2)===0&&Ka(t,0):Tc|=l,Ga===ut&&(Ga=0)),Ye(t)}function kh(t,e){e===0&&(e=Lr()),t=Fl(t,e),t!==null&&(nn(t,e),Ye(t))}function Uv(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),kh(t,l)}function Nv(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,u=t.memoizedState;u!==null&&(l=u.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),kh(t,l)}function jv(t,e){return Gi(t,e)}var ii=null,Va=null,_c=!1,si=!1,Dc=!1,Nl=0;function Ye(t){t!==Va&&t.next===null&&(Va===null?ii=Va=t:Va=Va.next=t),si=!0,_c||(_c=!0,qv())}function Qn(t,e){if(!Dc&&si){Dc=!0;do for(var l=!1,a=ii;a!==null;){if(t!==0){var u=a.pendingLanes;if(u===0)var i=0;else{var f=a.suspendedLanes,o=a.pingedLanes;i=(1<<31-oe(42|t)+1)-1,i&=u&~(f&~o),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Ph(a,i))}else i=ut,i=ou(a,a===xt?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||an(a,i)||(l=!0,Ph(a,i));a=a.next}while(l);Dc=!1}}function wv(){Fh()}function Fh(){si=_c=!1;var t=0;Nl!==0&&Vv()&&(t=Nl);for(var e=re(),l=null,a=ii;a!==null;){var u=a.next,i=$h(a,e);i===0?(a.next=null,l===null?ii=u:l.next=u,u===null&&(Va=l)):(l=a,(t!==0||(i&3)!==0)&&(si=!0)),a=u}Yt!==0&&Yt!==5||Qn(t),Nl!==0&&(Nl=0)}function $h(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,u=t.expirationTimes,i=t.pendingLanes&-62914561;0o)break;var z=g.transferSize,j=g.initiatorType;z&&id(j)&&(g=g.responseEnd,f+=z*(g"u"?null:document;function gd(t,e,l){var a=Ja;if(a&&typeof e=="string"&&e){var u=Te(e);u='link[rel="'+t+'"][href="'+u+'"]',typeof l=="string"&&(u+='[crossorigin="'+l+'"]'),pd.has(u)||(pd.add(u),t={rel:t,crossOrigin:l,href:e},a.querySelector(u)===null&&(e=a.createElement("link"),Ft(e,"link",t),Xt(e),a.head.appendChild(e)))}}function e0(t){fl.D(t),gd("dns-prefetch",t,null)}function l0(t,e){fl.C(t,e),gd("preconnect",t,e)}function a0(t,e,l){fl.L(t,e,l);var a=Ja;if(a&&t&&e){var u='link[rel="preload"][as="'+Te(e)+'"]';e==="image"&&l&&l.imageSrcSet?(u+='[imagesrcset="'+Te(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(u+='[imagesizes="'+Te(l.imageSizes)+'"]')):u+='[href="'+Te(t)+'"]';var i=u;switch(e){case"style":i=ka(t);break;case"script":i=Fa(t)}_e.has(i)||(t=x({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),_e.set(i,t),a.querySelector(u)!==null||e==="style"&&a.querySelector(Xn(i))||e==="script"&&a.querySelector(Kn(i))||(e=a.createElement("link"),Ft(e,"link",t),Xt(e),a.head.appendChild(e)))}}function n0(t,e){fl.m(t,e);var l=Ja;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",u='link[rel="modulepreload"][as="'+Te(a)+'"][href="'+Te(t)+'"]',i=u;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=Fa(t)}if(!_e.has(i)&&(t=x({rel:"modulepreload",href:t},e),_e.set(i,t),l.querySelector(u)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Kn(i)))return}a=l.createElement("link"),Ft(a,"link",t),Xt(a),l.head.appendChild(a)}}}function u0(t,e,l){fl.S(t,e,l);var a=Ja;if(a&&t){var u=va(a).hoistableStyles,i=ka(t);e=e||"default";var f=u.get(i);if(!f){var o={loading:0,preload:null};if(f=a.querySelector(Xn(i)))o.loading=5;else{t=x({rel:"stylesheet",href:t,"data-precedence":e},l),(l=_e.get(i))&&Jc(t,l);var g=f=a.createElement("link");Xt(g),Ft(g,"link",t),g._p=new Promise(function(A,z){g.onload=A,g.onerror=z}),g.addEventListener("load",function(){o.loading|=1}),g.addEventListener("error",function(){o.loading|=2}),o.loading|=4,hi(f,e,a)}f={type:"stylesheet",instance:f,count:1,state:o},u.set(i,f)}}}function i0(t,e){fl.X(t,e);var l=Ja;if(l&&t){var a=va(l).hoistableScripts,u=Fa(t),i=a.get(u);i||(i=l.querySelector(Kn(u)),i||(t=x({src:t,async:!0},e),(e=_e.get(u))&&kc(t,e),i=l.createElement("script"),Xt(i),Ft(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function s0(t,e){fl.M(t,e);var l=Ja;if(l&&t){var a=va(l).hoistableScripts,u=Fa(t),i=a.get(u);i||(i=l.querySelector(Kn(u)),i||(t=x({src:t,async:!0,type:"module"},e),(e=_e.get(u))&&kc(t,e),i=l.createElement("script"),Xt(i),Ft(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function Sd(t,e,l,a){var u=(u=lt.current)?oi(u):null;if(!u)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=ka(l.href),l=va(u).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=ka(l.href);var i=va(u).hoistableStyles,f=i.get(t);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(t,f),(i=u.querySelector(Xn(t)))&&!i._p&&(f.instance=i,f.state.loading=5),_e.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},_e.set(t,l),i||c0(u,t,l,f.state))),e&&a===null)throw Error(r(528,""));return f}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=Fa(l),l=va(u).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function ka(t){return'href="'+Te(t)+'"'}function Xn(t){return'link[rel="stylesheet"]['+t+"]"}function bd(t){return x({},t,{"data-precedence":t.precedence,precedence:null})}function c0(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),Ft(e,"link",l),Xt(e),t.head.appendChild(e))}function Fa(t){return'[src="'+Te(t)+'"]'}function Kn(t){return"script[async]"+t}function Ed(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Te(l.href)+'"]');if(a)return e.instance=a,Xt(a),a;var u=x({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),Xt(a),Ft(a,"style",u),hi(a,l.precedence,t),e.instance=a;case"stylesheet":u=ka(l.href);var i=t.querySelector(Xn(u));if(i)return e.state.loading|=4,e.instance=i,Xt(i),i;a=bd(l),(u=_e.get(u))&&Jc(a,u),i=(t.ownerDocument||t).createElement("link"),Xt(i);var f=i;return f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),Ft(i,"link",a),e.state.loading|=4,hi(i,l.precedence,t),e.instance=i;case"script":return i=Fa(l.src),(u=t.querySelector(Kn(i)))?(e.instance=u,Xt(u),u):(a=l,(u=_e.get(i))&&(a=x({},l),kc(a,u)),t=t.ownerDocument||t,u=t.createElement("script"),Xt(u),Ft(u,"link",a),t.head.appendChild(u),e.instance=u);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,hi(a,l.precedence,t));return e.instance}function hi(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=a.length?a[a.length-1]:null,i=u,f=0;f title"):null)}function r0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function Rd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function f0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var u=ka(a.href),i=e.querySelector(Xn(u));if(i){e=i._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=mi.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=i,Xt(i);return}i=e.ownerDocument||e,a=bd(a),(u=_e.get(u))&&Jc(a,u),i=i.createElement("link"),Xt(i);var f=i;f._p=new Promise(function(o,g){f.onload=o,f.onerror=g}),Ft(i,"link",a),l.instance=i}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=mi.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var Fc=0;function o0(t,e){return t.stylesheets&&t.count===0&&vi(t,t.stylesheets),0Fc?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(u)}}:null}function mi(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)vi(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var yi=null;function vi(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,yi=new Map,e.forEach(h0,t),yi=null,mi.call(t))}function h0(t,e){if(!(e.state.loading&4)){var l=yi.get(t);if(l)var a=l.get(null);else{l=new Map,yi.set(t,l);for(var u=t.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(s){console.error(s)}}return n(),nr.exports=z0(),nr.exports}var D0=_0();const U0=hm(D0);var kd="popstate";function N0(n={}){function s(r,d){let{pathname:h,search:m,hash:v}=r.location;return hr("",{pathname:h,search:m,hash:v},d.state&&d.state.usr||null,d.state&&d.state.key||"default")}function c(r,d){return typeof d=="string"?d:In(d)}return w0(s,c,null,n)}function Mt(n,s){if(n===!1||n===null||typeof n>"u")throw new Error(s)}function He(n,s){if(!n){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function j0(){return Math.random().toString(36).substring(2,10)}function Fd(n,s){return{usr:n.state,key:n.key,idx:s}}function hr(n,s,c=null,r){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof s=="string"?Pa(s):s,state:c,key:s&&s.key||r||j0()}}function In({pathname:n="/",search:s="",hash:c=""}){return s&&s!=="?"&&(n+=s.charAt(0)==="?"?s:"?"+s),c&&c!=="#"&&(n+=c.charAt(0)==="#"?c:"#"+c),n}function Pa(n){let s={};if(n){let c=n.indexOf("#");c>=0&&(s.hash=n.substring(c),n=n.substring(0,c));let r=n.indexOf("?");r>=0&&(s.search=n.substring(r),n=n.substring(0,r)),n&&(s.pathname=n)}return s}function w0(n,s,c,r={}){let{window:d=document.defaultView,v5Compat:h=!1}=r,m=d.history,v="POP",p=null,y=T();y==null&&(y=0,m.replaceState({...m.state,idx:y},""));function T(){return(m.state||{idx:null}).idx}function x(){v="POP";let Y=T(),Q=Y==null?null:Y-y;y=Y,p&&p({action:v,location:B.location,delta:Q})}function D(Y,Q){v="PUSH";let X=hr(B.location,Y,Q);y=T()+1;let J=Fd(X,y),ht=B.createHref(X);try{m.pushState(J,"",ht)}catch(ct){if(ct instanceof DOMException&&ct.name==="DataCloneError")throw ct;d.location.assign(ht)}h&&p&&p({action:v,location:B.location,delta:1})}function q(Y,Q){v="REPLACE";let X=hr(B.location,Y,Q);y=T();let J=Fd(X,y),ht=B.createHref(X);m.replaceState(J,"",ht),h&&p&&p({action:v,location:B.location,delta:0})}function H(Y){return q0(Y)}let B={get action(){return v},get location(){return n(d,m)},listen(Y){if(p)throw new Error("A history only accepts one active listener");return d.addEventListener(kd,x),p=Y,()=>{d.removeEventListener(kd,x),p=null}},createHref(Y){return s(d,Y)},createURL:H,encodeLocation(Y){let Q=H(Y);return{pathname:Q.pathname,search:Q.search,hash:Q.hash}},push:D,replace:q,go(Y){return m.go(Y)}};return B}function q0(n,s=!1){let c="http://localhost";typeof window<"u"&&(c=window.location.origin!=="null"?window.location.origin:window.location.href),Mt(c,"No window.location.(origin|href) available to create URL");let r=typeof n=="string"?n:In(n);return r=r.replace(/ $/,"%20"),!s&&r.startsWith("//")&&(r=c+r),new URL(r,c)}function dm(n,s,c="/"){return H0(n,s,c,!1)}function H0(n,s,c,r){let d=typeof s=="string"?Pa(s):s,h=hl(d.pathname||"/",c);if(h==null)return null;let m=mm(n);B0(m);let v=null;for(let p=0;v==null&&p{let T={relativePath:y===void 0?m.path||"":y,caseSensitive:m.caseSensitive===!0,childrenIndex:v,route:m};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&p)return;Mt(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let x=ol([r,T.relativePath]),D=c.concat(T);m.children&&m.children.length>0&&(Mt(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${x}".`),mm(m.children,s,D,x,p)),!(m.path==null&&!m.index)&&s.push({path:x,score:Z0(x,m.index),routesMeta:D})};return n.forEach((m,v)=>{if(m.path===""||!m.path?.includes("?"))h(m,v);else for(let p of ym(m.path))h(m,v,!0,p)}),s}function ym(n){let s=n.split("/");if(s.length===0)return[];let[c,...r]=s,d=c.endsWith("?"),h=c.replace(/\?$/,"");if(r.length===0)return d?[h,""]:[h];let m=ym(r.join("/")),v=[];return v.push(...m.map(p=>p===""?h:[h,p].join("/"))),d&&v.push(...m),v.map(p=>n.startsWith("/")&&p===""?"/":p)}function B0(n){n.sort((s,c)=>s.score!==c.score?c.score-s.score:V0(s.routesMeta.map(r=>r.childrenIndex),c.routesMeta.map(r=>r.childrenIndex)))}var Q0=/^:[\w-]+$/,L0=3,Y0=2,G0=1,X0=10,K0=-2,$d=n=>n==="*";function Z0(n,s){let c=n.split("/"),r=c.length;return c.some($d)&&(r+=K0),s&&(r+=Y0),c.filter(d=>!$d(d)).reduce((d,h)=>d+(Q0.test(h)?L0:h===""?G0:X0),r)}function V0(n,s){return n.length===s.length&&n.slice(0,-1).every((r,d)=>r===s[d])?n[n.length-1]-s[s.length-1]:0}function J0(n,s,c=!1){let{routesMeta:r}=n,d={},h="/",m=[];for(let v=0;v{if(T==="*"){let H=v[D]||"";m=h.slice(0,h.length-H.length).replace(/(.)\/+$/,"$1")}const q=v[D];return x&&!q?y[T]=void 0:y[T]=(q||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:m,pattern:n}}function k0(n,s=!1,c=!0){He(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],d="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(m,v,p)=>(r.push({paramName:v,isOptional:p!=null}),p?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),d+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):c?d+="\\/*$":n!==""&&n!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,s?void 0:"i"),r]}function F0(n){try{return n.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return He(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),n}}function hl(n,s){if(s==="/")return n;if(!n.toLowerCase().startsWith(s.toLowerCase()))return null;let c=s.endsWith("/")?s.length-1:s.length,r=n.charAt(c);return r&&r!=="/"?null:n.slice(c)||"/"}var vm=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,$0=n=>vm.test(n);function W0(n,s="/"){let{pathname:c,search:r="",hash:d=""}=typeof n=="string"?Pa(n):n,h;if(c)if($0(c))h=c;else{if(c.includes("//")){let m=c;c=c.replace(/\/\/+/g,"/"),He(!1,`Pathnames cannot have embedded double slashes - normalizing ${m} -> ${c}`)}c.startsWith("/")?h=Wd(c.substring(1),"/"):h=Wd(c,s)}else h=s;return{pathname:h,search:tp(r),hash:ep(d)}}function Wd(n,s){let c=s.replace(/\/+$/,"").split("/");return n.split("/").forEach(d=>{d===".."?c.length>1&&c.pop():d!=="."&&c.push(d)}),c.length>1?c.join("/"):"/"}function cr(n,s,c,r){return`Cannot include a '${n}' character in a manually specified \`to.${s}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${c}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function P0(n){return n.filter((s,c)=>c===0||s.route.path&&s.route.path.length>0)}function pm(n){let s=P0(n);return s.map((c,r)=>r===s.length-1?c.pathname:c.pathnameBase)}function gm(n,s,c,r=!1){let d;typeof n=="string"?d=Pa(n):(d={...n},Mt(!d.pathname||!d.pathname.includes("?"),cr("?","pathname","search",d)),Mt(!d.pathname||!d.pathname.includes("#"),cr("#","pathname","hash",d)),Mt(!d.search||!d.search.includes("#"),cr("#","search","hash",d)));let h=n===""||d.pathname==="",m=h?"/":d.pathname,v;if(m==null)v=c;else{let x=s.length-1;if(!r&&m.startsWith("..")){let D=m.split("/");for(;D[0]==="..";)D.shift(),x-=1;d.pathname=D.join("/")}v=x>=0?s[x]:"/"}let p=W0(d,v),y=m&&m!=="/"&&m.endsWith("/"),T=(h||m===".")&&c.endsWith("/");return!p.pathname.endsWith("/")&&(y||T)&&(p.pathname+="/"),p}var ol=n=>n.join("/").replace(/\/\/+/g,"/"),I0=n=>n.replace(/\/+$/,"").replace(/^\/*/,"/"),tp=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,ep=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,lp=class{constructor(n,s,c,r=!1){this.status=n,this.statusText=s||"",this.internal=r,c instanceof Error?(this.data=c.toString(),this.error=c):this.data=c}};function ap(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function np(n){return n.map(s=>s.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var Sm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function bm(n,s){let c=n;if(typeof c!="string"||!vm.test(c))return{absoluteURL:void 0,isExternal:!1,to:c};let r=c,d=!1;if(Sm)try{let h=new URL(window.location.href),m=c.startsWith("//")?new URL(h.protocol+c):new URL(c),v=hl(m.pathname,s);m.origin===h.origin&&v!=null?c=v+m.search+m.hash:d=!0}catch{He(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:d,to:c}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Em=["POST","PUT","PATCH","DELETE"];new Set(Em);var up=["GET",...Em];new Set(up);var Ia=R.createContext(null);Ia.displayName="DataRouter";var Di=R.createContext(null);Di.displayName="DataRouterState";var ip=R.createContext(!1),Tm=R.createContext({isTransitioning:!1});Tm.displayName="ViewTransition";var sp=R.createContext(new Map);sp.displayName="Fetchers";var cp=R.createContext(null);cp.displayName="Await";var Ue=R.createContext(null);Ue.displayName="Navigation";var lu=R.createContext(null);lu.displayName="Location";var Xe=R.createContext({outlet:null,matches:[],isDataRoute:!1});Xe.displayName="Route";var Er=R.createContext(null);Er.displayName="RouteError";var xm="REACT_ROUTER_ERROR",rp="REDIRECT",fp="ROUTE_ERROR_RESPONSE";function op(n){if(n.startsWith(`${xm}:${rp}:{`))try{let s=JSON.parse(n.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function hp(n){if(n.startsWith(`${xm}:${fp}:{`))try{let s=JSON.parse(n.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new lp(s.status,s.statusText,s.data)}catch{}}function dp(n,{relative:s}={}){Mt(au(),"useHref() may be used only in the context of a component.");let{basename:c,navigator:r}=R.useContext(Ue),{hash:d,pathname:h,search:m}=nu(n,{relative:s}),v=h;return c!=="/"&&(v=h==="/"?c:ol([c,h])),r.createHref({pathname:v,search:m,hash:d})}function au(){return R.useContext(lu)!=null}function oa(){return Mt(au(),"useLocation() may be used only in the context of a component."),R.useContext(lu).location}var Rm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Om(n){R.useContext(Ue).static||R.useLayoutEffect(n)}function Am(){let{isDataRoute:n}=R.useContext(Xe);return n?Mp():mp()}function mp(){Mt(au(),"useNavigate() may be used only in the context of a component.");let n=R.useContext(Ia),{basename:s,navigator:c}=R.useContext(Ue),{matches:r}=R.useContext(Xe),{pathname:d}=oa(),h=JSON.stringify(pm(r)),m=R.useRef(!1);return Om(()=>{m.current=!0}),R.useCallback((p,y={})=>{if(He(m.current,Rm),!m.current)return;if(typeof p=="number"){c.go(p);return}let T=gm(p,JSON.parse(h),d,y.relative==="path");n==null&&s!=="/"&&(T.pathname=T.pathname==="/"?s:ol([s,T.pathname])),(y.replace?c.replace:c.push)(T,y.state,y)},[s,c,h,d,n])}var yp=R.createContext(null);function vp(n){let s=R.useContext(Xe).outlet;return R.useMemo(()=>s&&R.createElement(yp.Provider,{value:n},s),[s,n])}function nu(n,{relative:s}={}){let{matches:c}=R.useContext(Xe),{pathname:r}=oa(),d=JSON.stringify(pm(c));return R.useMemo(()=>gm(n,JSON.parse(d),r,s==="path"),[n,d,r,s])}function pp(n,s){return Cm(n,s)}function Cm(n,s,c,r,d){Mt(au(),"useRoutes() may be used only in the context of a component.");let{navigator:h}=R.useContext(Ue),{matches:m}=R.useContext(Xe),v=m[m.length-1],p=v?v.params:{},y=v?v.pathname:"/",T=v?v.pathnameBase:"/",x=v&&v.route;{let X=x&&x.path||"";zm(y,!x||X.endsWith("*")||X.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${y}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. -Please change the parent to .`)}let D=fa(),q;if(s){let X=typeof s=="string"?Wa(s):s;Mt(T==="/"||X.pathname?.startsWith(T),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${T}" but pathname "${X.pathname}" was given in the \`location\` prop.`),q=X}else q=D;let H=q.pathname||"/",B=H;if(T!=="/"){let X=T.replace(/^\//,"").split("/");B="/"+H.replace(/^\//,"").split("/").slice(X.length).join("/")}let Y=hm(n,{pathname:B});He(x||Y!=null,`No routes matched location "${q.pathname}${q.search}${q.hash}" `),He(Y==null||Y[Y.length-1].route.element!==void 0||Y[Y.length-1].route.Component!==void 0||Y[Y.length-1].route.lazy!==void 0,`Matched leaf route at location "${q.pathname}${q.search}${q.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let Q=Tp(Y&&Y.map(X=>Object.assign({},X,{params:Object.assign({},p,X.params),pathname:rl([T,h.encodeLocation?h.encodeLocation(X.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:X.pathname]),pathnameBase:X.pathnameBase==="/"?T:rl([T,h.encodeLocation?h.encodeLocation(X.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:X.pathnameBase])})),m,c,r,d);return s&&Q?R.createElement(eu.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...q},navigationType:"POP"}},Q):Q}function gp(){let n=Cp(),s=ap(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),c=n instanceof Error?n.stack:null,r="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:r},h={padding:"2px 4px",backgroundColor:r},m=null;return console.error("Error handled by React Router default ErrorBoundary:",n),m=R.createElement(R.Fragment,null,R.createElement("p",null,"💿 Hey developer 👋"),R.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",R.createElement("code",{style:h},"ErrorBoundary")," or"," ",R.createElement("code",{style:h},"errorElement")," prop on your route.")),R.createElement(R.Fragment,null,R.createElement("h2",null,"Unexpected Application Error!"),R.createElement("h3",{style:{fontStyle:"italic"}},s),c?R.createElement("pre",{style:d},c):null,m)}var Sp=R.createElement(gp,null),Cm=class extends R.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,s){return s.location!==n.location||s.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:s.error,location:s.location,revalidation:n.revalidation||s.revalidation}}componentDidCatch(n,s){this.props.onError?this.props.onError(n,s):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const c=hp(n.digest);c&&(n=c)}let s=n!==void 0?R.createElement(Xe.Provider,{value:this.props.routeContext},R.createElement(Sr.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?R.createElement(bp,{error:n},s):s}};Cm.contextType=ip;var sr=new WeakMap;function bp({children:n,error:s}){let{basename:c}=R.useContext(Ue);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let r=op(s.digest);if(r){let d=sr.get(s);if(d)throw d;let h=Sm(r.location,c);if(gm&&!sr.get(s))if(h.isExternal||r.reloadDocument)window.location.href=h.absoluteURL||h.to;else{const m=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:r.replace}));throw sr.set(s,m),m}return R.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return n}function Ep({routeContext:n,match:s,children:c}){let r=R.useContext(Pa);return r&&r.static&&r.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=s.route.id),R.createElement(Xe.Provider,{value:n},c)}function Tp(n,s=[],c=null,r=null,d=null){if(n==null){if(!c)return null;if(c.errors)n=c.matches;else if(s.length===0&&!c.initialized&&c.matches.length>0)n=c.matches;else return null}let h=n,m=c?.errors;if(m!=null){let T=h.findIndex(x=>x.route.id&&m?.[x.route.id]!==void 0);Mt(T>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(m).join(",")}`),h=h.slice(0,Math.min(h.length,T+1))}let v=!1,p=-1;if(c)for(let T=0;T=0?h=h.slice(0,p+1):h=[h[0]];break}}}let y=c&&r?(T,x)=>{r(T,{location:c.location,params:c.matches?.[0]?.params??{},unstable_pattern:np(c.matches),errorInfo:x})}:void 0;return h.reduceRight((T,x,D)=>{let q,H=!1,B=null,Y=null;c&&(q=m&&x.route.id?m[x.route.id]:void 0,B=x.route.errorElement||Sp,v&&(p<0&&D===0?(Mm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),H=!0,Y=null):p===D&&(H=!0,Y=x.route.hydrateFallbackElement||null)));let Q=s.concat(h.slice(0,D+1)),X=()=>{let J;return q?J=B:H?J=Y:x.route.Component?J=R.createElement(x.route.Component,null):x.route.element?J=x.route.element:J=T,R.createElement(Ep,{match:x,routeContext:{outlet:T,matches:Q,isDataRoute:c!=null},children:J})};return c&&(x.route.ErrorBoundary||x.route.errorElement||D===0)?R.createElement(Cm,{location:c.location,revalidation:c.revalidation,component:B,error:q,children:X(),routeContext:{outlet:null,matches:Q,isDataRoute:!0},onError:y}):X()},null)}function br(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function xp(n){let s=R.useContext(Pa);return Mt(s,br(n)),s}function Rp(n){let s=R.useContext(_i);return Mt(s,br(n)),s}function Op(n){let s=R.useContext(Xe);return Mt(s,br(n)),s}function Er(n){let s=Op(n),c=s.matches[s.matches.length-1];return Mt(c.route.id,`${n} can only be used on routes that contain a unique "id"`),c.route.id}function Ap(){return Er("useRouteId")}function Cp(){let n=R.useContext(Sr),s=Rp("useRouteError"),c=Er("useRouteError");return n!==void 0?n:s.errors?.[c]}function Mp(){let{router:n}=xp("useNavigate"),s=Er("useNavigate"),c=R.useRef(!1);return Rm(()=>{c.current=!0}),R.useCallback(async(d,h={})=>{He(c.current,xm),c.current&&(typeof d=="number"?await n.navigate(d):await n.navigate(d,{fromRouteId:s,...h}))},[n,s])}var Wd={};function Mm(n,s,c){!s&&!Wd[n]&&(Wd[n]=!0,He(!1,c))}R.memo(zp);function zp({routes:n,future:s,state:c,onError:r}){return Am(n,void 0,c,r,s)}function _p(n){return vp(n.context)}function Wn(n){Mt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Dp({basename:n="/",children:s=null,location:c,navigationType:r="POP",navigator:d,static:h=!1,unstable_useTransitions:m}){Mt(!lu(),"You cannot render a inside another . You should never have more than one in your app.");let v=n.replace(/^\/*/,"/"),p=R.useMemo(()=>({basename:v,navigator:d,static:h,unstable_useTransitions:m,future:{}}),[v,d,h,m]);typeof c=="string"&&(c=Wa(c));let{pathname:y="/",search:T="",hash:x="",state:D=null,key:q="default"}=c,H=R.useMemo(()=>{let B=fl(y,v);return B==null?null:{location:{pathname:B,search:T,hash:x,state:D,key:q},navigationType:r}},[v,y,T,x,D,q,r]);return He(H!=null,` is not able to match the URL "${y}${T}${x}" because it does not start with the basename, so the won't render anything.`),H==null?null:R.createElement(Ue.Provider,{value:p},R.createElement(eu.Provider,{children:s,value:H}))}function Up({children:n,location:s}){return pp(or(n),s)}function or(n,s=[]){let c=[];return R.Children.forEach(n,(r,d)=>{if(!R.isValidElement(r))return;let h=[...s,d];if(r.type===R.Fragment){c.push.apply(c,or(r.props.children,h));return}Mt(r.type===Wn,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),Mt(!r.props.index||!r.props.children,"An index route cannot have child routes.");let m={id:r.props.id||h.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(m.children=or(r.props.children,h)),c.push(m)}),c}var Oi="get",Ai="application/x-www-form-urlencoded";function Di(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function Np(n){return Di(n)&&n.tagName.toLowerCase()==="button"}function jp(n){return Di(n)&&n.tagName.toLowerCase()==="form"}function wp(n){return Di(n)&&n.tagName.toLowerCase()==="input"}function qp(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function Hp(n,s){return n.button===0&&(!s||s==="_self")&&!qp(n)}var xi=null;function Bp(){if(xi===null)try{new FormData(document.createElement("form"),0),xi=!1}catch{xi=!0}return xi}var Qp=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function cr(n){return n!=null&&!Qp.has(n)?(He(!1,`"${n}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Ai}"`),null):n}function Lp(n,s){let c,r,d,h,m;if(jp(n)){let v=n.getAttribute("action");r=v?fl(v,s):null,c=n.getAttribute("method")||Oi,d=cr(n.getAttribute("enctype"))||Ai,h=new FormData(n)}else if(Np(n)||wp(n)&&(n.type==="submit"||n.type==="image")){let v=n.form;if(v==null)throw new Error('Cannot submit a