Compare commits

..

23 Commits

Author SHA1 Message Date
Weblate (bot)
cc7b8dd6e0
Merge dd02e2d88c into dfd837cfb0 2025-12-08 20:08:12 +00:00
Hosted Weblate
dd02e2d88c
Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (119 of 120 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (48 of 48 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/nb_NO/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:08:04 +00:00
Hosted Weblate
c368ceb110
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (46 of 46 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (119 of 119 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hans/
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:08:03 +00:00
Hosted Weblate
c7fd610c6e
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.3% (85 of 92 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 44.5% (53 of 119 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 44.5% (53 of 119 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 15.7% (79 of 501 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 11.8% (76 of 639 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 92.1% (118 of 128 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 98.1% (54 of 55 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 85.3% (111 of 130 strings)

Co-authored-by: Ban <3637117+Ban921@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Yu Chun Huang <yujun@bo2.tw>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/zh_Hant/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-input
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:08:01 +00:00
Hosted Weblate
ef25c39c92
Translated using Weblate (Slovak)
Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Slovak)

Currently translated at 99.1% (118 of 119 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jakub K <klacanjakub0@gmail.com>
Co-authored-by: Michal Klacan <mkbebe@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sk/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:08:00 +00:00
Hosted Weblate
49181dbc18
Translated using Weblate (Swedish)
Currently translated at 100.0% (118 of 118 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Swedish)

Currently translated at 98.3% (117 of 119 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (55 of 55 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kristian Johansson <knmjohansson@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/objects/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sv/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/objects
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
2025-12-08 20:07:59 +00:00
Hosted Weblate
ef569afa87
Translated using Weblate (French)
Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (French)

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (French)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (French)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (French)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (French)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (French)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (French)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (French)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (French)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (French)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (French)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (French)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (French)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (French)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Apocoloquintose <bertrand.moreux@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/fr/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-input
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:07:57 +00:00
Hosted Weblate
946c9e7cc0
Translated using Weblate (Spanish)
Currently translated at 90.2% (83 of 92 strings)

Co-authored-by: Hernán Rossetto <hmronline@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/es/
Translation: Frigate NVR/views-live
2025-12-08 20:07:56 +00:00
Hosted Weblate
53286a49e9
Translated using Weblate (Dutch)
Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-12-08 20:07:55 +00:00
Hosted Weblate
5a1d4257ec
Translated using Weblate (Italian)
Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (55 of 55 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/it/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
2025-12-08 20:07:54 +00:00
Hosted Weblate
eec9eefa7b
Translated using Weblate (Polish)
Currently translated at 95.0% (38 of 40 strings)

Translated using Weblate (Polish)

Currently translated at 83.0% (108 of 130 strings)

Translated using Weblate (Polish)

Currently translated at 38.6% (46 of 119 strings)

Translated using Weblate (Polish)

Currently translated at 98.1% (54 of 55 strings)

Translated using Weblate (Polish)

Currently translated at 89.2% (570 of 639 strings)

Translated using Weblate (Polish)

Currently translated at 92.9% (119 of 128 strings)

Translated using Weblate (Polish)

Currently translated at 98.1% (210 of 214 strings)

Translated using Weblate (Polish)

Currently translated at 85.4% (546 of 639 strings)

Translated using Weblate (Polish)

Currently translated at 95.0% (38 of 40 strings)

Translated using Weblate (Polish)

Currently translated at 83.5% (107 of 128 strings)

Translated using Weblate (Polish)

Currently translated at 98.0% (51 of 52 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Polish)

Currently translated at 37.8% (45 of 119 strings)

Co-authored-by: Bartlomiej Puls <bartlomiej.puls@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mateusz Kulis <kulis.matis@gmail.com>
Co-authored-by: piesu <dogiiee@proton.me>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/pl/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:07:52 +00:00
Hosted Weblate
f1f1188ac5
Translated using Weblate (Czech)
Currently translated at 63.5% (406 of 639 strings)

Translated using Weblate (Czech)

Currently translated at 92.3% (48 of 52 strings)

Translated using Weblate (Czech)

Currently translated at 75.0% (30 of 40 strings)

Translated using Weblate (Czech)

Currently translated at 16.8% (20 of 119 strings)

Translated using Weblate (Czech)

Currently translated at 63.2% (404 of 639 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Libor Vymětalík <vymetalik.libor@gmail.com>
Co-authored-by: Martin Brož <code@martin-broz.cz>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/cs/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/cs/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-12-08 20:07:51 +00:00
Hosted Weblate
a741440b81
Translated using Weblate (Catalan)
Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (55 of 55 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-12-08 20:07:50 +00:00
Hosted Weblate
e61c9e20bd
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-12-08 20:07:48 +00:00
Hosted Weblate
4e1b3faafd
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Bulgarian)

Currently translated at 9.0% (5 of 55 strings)

Translated using Weblate (Bulgarian)

Currently translated at 0.7% (5 of 639 strings)

Translated using Weblate (Bulgarian)

Currently translated at 15.3% (2 of 13 strings)

Translated using Weblate (Bulgarian)

Currently translated at 31.5% (29 of 92 strings)

Translated using Weblate (Bulgarian)

Currently translated at 2.3% (3 of 128 strings)

Translated using Weblate (Bulgarian)

Currently translated at 20.0% (2 of 10 strings)

Translated using Weblate (Bulgarian)

Currently translated at 9.6% (5 of 52 strings)

Translated using Weblate (Bulgarian)

Currently translated at 22.5% (9 of 40 strings)

Translated using Weblate (Bulgarian)

Currently translated at 20.0% (2 of 10 strings)

Translated using Weblate (Bulgarian)

Currently translated at 6.2% (3 of 48 strings)

Translated using Weblate (Bulgarian)

Currently translated at 0.8% (1 of 119 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Bulgarian)

Currently translated at 2.3% (3 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Skye Fox <mardymcfly1985@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-configeditor/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/bg/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/bg/
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-configeditor
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:07:47 +00:00
Hosted Weblate
ee468ac9ad
Translated using Weblate (Romanian)
Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-12-08 20:07:45 +00:00
Hosted Weblate
cb58031c1f
Translated using Weblate (Estonian)
Currently translated at 1.4% (3 of 214 strings)

Translated using Weblate (Estonian)

Currently translated at 4.3% (28 of 639 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Estonian)

Currently translated at 7.6% (1 of 13 strings)

Translated using Weblate (Estonian)

Currently translated at 2.1% (1 of 46 strings)

Translated using Weblate (Estonian)

Currently translated at 2.5% (1 of 40 strings)

Translated using Weblate (Estonian)

Currently translated at 1.3% (1 of 72 strings)

Translated using Weblate (Estonian)

Currently translated at 3.6% (2 of 55 strings)

Translated using Weblate (Estonian)

Currently translated at 20.0% (2 of 10 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (6 of 6 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Estonian)

Currently translated at 4.0% (1 of 25 strings)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-camera/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-icons/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-input/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-player/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-recording/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-camera
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/components-icons
Translation: Frigate NVR/components-input
Translation: Frigate NVR/components-player
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-recording
Translation: Frigate NVR/views-settings
2025-12-08 20:07:43 +00:00
Hosted Weblate
194f875669
Translated using Weblate (Danish)
Currently translated at 85.5% (183 of 214 strings)

Translated using Weblate (Danish)

Currently translated at 36.1% (26 of 72 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: kklar <karred.larsen@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/da/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/da/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-filter
2025-12-08 20:07:42 +00:00
Hosted Weblate
1c969f3250
Translated using Weblate (German)
Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (German)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (German)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (German)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (German)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (German)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (German)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (German)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (German)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (German)

Currently translated at 99.8% (638 of 639 strings)

Translated using Weblate (German)

Currently translated at 99.8% (638 of 639 strings)

Translated using Weblate (German)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (German)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (German)

Currently translated at 100.0% (10 of 10 strings)

Translated using Weblate (German)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (German)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (German)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (German)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (German)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (German)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (German)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 99.5% (213 of 214 strings)

Translated using Weblate (German)

Currently translated at 99.5% (213 of 214 strings)

Translated using Weblate (German)

Currently translated at 83.5% (534 of 639 strings)

Translated using Weblate (German)

Currently translated at 93.8% (470 of 501 strings)

Translated using Weblate (German)

Currently translated at 98.9% (91 of 92 strings)

Translated using Weblate (German)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (German)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (German)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (German)

Currently translated at 34.4% (40 of 116 strings)

Translated using Weblate (German)

Currently translated at 94.8% (37 of 39 strings)

Translated using Weblate (German)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (German)

Currently translated at 78.0% (499 of 639 strings)

Translated using Weblate (German)

Currently translated at 98.4% (126 of 128 strings)

Translated using Weblate (German)

Currently translated at 29.3% (34 of 116 strings)

Translated using Weblate (German)

Currently translated at 96.0% (123 of 128 strings)

Translated using Weblate (German)

Currently translated at 78.0% (499 of 639 strings)

Co-authored-by: Emircanos <emircan368@gmail.com>
Co-authored-by: Fuxle <moritz.hofmann2005@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Sebastian Sie <sebastian.neuplanitz@googlemail.com>
Co-authored-by: jmtatsch <julian@tatsch.it>
Co-authored-by: mvdberge <micha.vordemberge@christmann.info>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-auth/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/de/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-auth
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:07:40 +00:00
Hosted Weblate
7577483009
Translated using Weblate (Portuguese (Brazil))
Currently translated at 29.3% (34 of 116 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jose Machado <machado.jm4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translation: Frigate NVR/views-classificationmodel
2025-12-08 20:07:39 +00:00
Hosted Weblate
3e80b0d5a6
Translated using Weblate (Turkish)
Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (120 of 120 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (40 of 40 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (119 of 119 strings)

Translated using Weblate (Turkish)

Currently translated at 64.6% (413 of 639 strings)

Translated using Weblate (Turkish)

Currently translated at 98.5% (211 of 214 strings)

Translated using Weblate (Turkish)

Currently translated at 66.3% (77 of 116 strings)

Translated using Weblate (Turkish)

Currently translated at 63.7% (74 of 116 strings)

Translated using Weblate (Turkish)

Currently translated at 97.6% (209 of 214 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Turkish)

Currently translated at 94.5% (121 of 128 strings)

Translated using Weblate (Turkish)

Currently translated at 93.7% (120 of 128 strings)

Translated using Weblate (Turkish)

Currently translated at 94.5% (87 of 92 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Turkish)

Currently translated at 58.9% (377 of 639 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (52 of 52 strings)

Co-authored-by: Emircanos <emircan368@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-12-08 20:07:38 +00:00
Josh Hawkins
dfd837cfb0
refactor to use react-hook-form and zod (#21195)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
2025-12-08 09:19:34 -07:00
Josh Hawkins
152e585206
Authentication improvements (#21194)
* jwt permissions

* add old password to body req

* add model and migration

need to track the datetime that passwords were changed for the jwt

* auth api backend changes

- use os.open to create jwt secret with restrictive permissions (0o600: read/write for owner only)
- add backend validation for password strength
- add iat claim to jwt so the server can determine when a token was issued and reject any jwts issued before a user's password_changed_at timestamp, ensuring old tokens are invalidated after a password change
- set logout route to public to avoid 401 when logging out
- issue new jwt for users who change their own password so they stay logged in

* improve set password dialog

- add field to verify old password
- add password strength requirements

* frontend tweaks for password dialog

* i18n

* use verify endpoint for existing password verification

avoid /login side effects (creating a new session)

* public logout

* only check if password has changed on jwt refresh

* fix tests

Fix migration 030 by using raw sql to select usernames (avoid ORM selecting nonexistent columns)

* add multi device warning to password dialog

* remove password verification endpoint

Just send old_password + new password in one request, let the backend handle verification in a single operation
2025-12-08 09:02:28 -07:00
37 changed files with 1193 additions and 285 deletions

View File

@ -123,7 +123,7 @@ auth:
# Optional: Refresh time in seconds (default: shown below)
# When the session is going to expire in less time than this setting,
# it will be refreshed back to the session_length.
refresh_time: 43200 # 12 hours
refresh_time: 1800 # 30 minutes
# Optional: Rate limiting for login failures to help prevent brute force
# login attacks (default: shown below)
# See the docs for more information on valid values

View File

@ -55,8 +55,8 @@ def require_admin_by_default():
"/auth",
"/auth/first_time_login",
"/login",
# Authenticated user endpoints (allow_any_authenticated)
"/logout",
# Authenticated user endpoints (allow_any_authenticated)
"/profile",
# Public info endpoints (allow_public)
"/",
@ -311,7 +311,10 @@ def get_jwt_secret() -> str:
)
jwt_secret = secrets.token_hex(64)
try:
with open(jwt_secret_file, "w") as f:
fd = os.open(
jwt_secret_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600
)
with os.fdopen(fd, "w") as f:
f.write(str(jwt_secret))
except Exception:
logger.warning(
@ -356,9 +359,35 @@ def verify_password(password, password_hash):
return secrets.compare_digest(password_hash, compare_hash)
def validate_password_strength(password: str) -> tuple[bool, Optional[str]]:
"""
Validate password strength.
Returns a tuple of (is_valid, error_message).
"""
if not password:
return False, "Password cannot be empty"
if len(password) < 8:
return False, "Password must be at least 8 characters long"
if not any(c.isupper() for c in password):
return False, "Password must contain at least one uppercase letter"
if not any(c.isdigit() for c in password):
return False, "Password must contain at least one digit"
if not any(c in '!@#$%^&*(),.?":{}|<>' for c in password):
return False, "Password must contain at least one special character"
return True, None
def create_encoded_jwt(user, role, expiration, secret):
return jwt.encode(
{"alg": "HS256"}, {"sub": user, "role": role, "exp": expiration}, secret
{"alg": "HS256"},
{"sub": user, "role": role, "exp": expiration, "iat": int(time.time())},
secret,
)
@ -619,13 +648,27 @@ def auth(request: Request):
return fail_response
# if the jwt cookie is expiring soon
elif jwt_source == "cookie" and expiration - JWT_REFRESH <= current_time:
if jwt_source == "cookie" and expiration - JWT_REFRESH <= current_time:
logger.debug("jwt token expiring soon, refreshing cookie")
# ensure the user hasn't been deleted
# Check if password has been changed since token was issued
# If so, force re-login by rejecting the refresh
try:
User.get_by_id(user)
user_obj = User.get_by_id(user)
if user_obj.password_changed_at is not None:
token_iat = int(token.claims.get("iat", 0))
password_changed_timestamp = int(
user_obj.password_changed_at.timestamp()
)
if token_iat < password_changed_timestamp:
logger.debug(
"jwt token issued before password change, rejecting refresh"
)
return fail_response
except DoesNotExist:
logger.debug("user not found")
return fail_response
new_expiration = current_time + JWT_SESSION_LENGTH
new_encoded_jwt = create_encoded_jwt(
user, role, new_expiration, request.app.jwt_token
@ -660,7 +703,7 @@ def profile(request: Request):
)
@router.get("/logout", dependencies=[Depends(allow_any_authenticated())])
@router.get("/logout", dependencies=[Depends(allow_public())])
def logout(request: Request):
auth_config: AuthConfig = request.app.frigate_config.auth
response = RedirectResponse("/login", status_code=303)
@ -782,10 +825,63 @@ async def update_password(
HASH_ITERATIONS = request.app.frigate_config.auth.hash_iterations
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
User.set_by_id(username, {User.password_hash: password_hash})
try:
user = User.get_by_id(username)
except DoesNotExist:
return JSONResponse(content={"message": "User not found"}, status_code=404)
return JSONResponse(content={"success": True})
# Require old_password when:
# 1. Non-admin user is changing another user's password (admin only action)
# 2. Any user is changing their own password
is_changing_own_password = current_username == username
is_non_admin = current_role != "admin"
if is_changing_own_password or is_non_admin:
if not body.old_password:
return JSONResponse(
content={"message": "Current password is required"},
status_code=400,
)
if not verify_password(body.old_password, user.password_hash):
return JSONResponse(
content={"message": "Current password is incorrect"},
status_code=401,
)
# Validate new password strength
is_valid, error_message = validate_password_strength(body.password)
if not is_valid:
return JSONResponse(
content={"message": error_message},
status_code=400,
)
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
User.update(
{
User.password_hash: password_hash,
User.password_changed_at: datetime.now(),
}
).where(User.username == username).execute()
response = JSONResponse(content={"success": True})
# If user changed their own password, issue a new JWT to keep them logged in
if current_username == username:
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
JWT_COOKIE_SECURE = request.app.frigate_config.auth.cookie_secure
JWT_SESSION_LENGTH = request.app.frigate_config.auth.session_length
expiration = int(time.time()) + JWT_SESSION_LENGTH
encoded_jwt = create_encoded_jwt(
username, current_role, expiration, request.app.jwt_token
)
# Set new JWT cookie on response
set_jwt_cookie(
response, JWT_COOKIE_NAME, encoded_jwt, expiration, JWT_COOKIE_SECURE
)
return response
@router.put(

View File

@ -11,6 +11,7 @@ class AppConfigSetBody(BaseModel):
class AppPutPasswordBody(BaseModel):
password: str
old_password: Optional[str] = None
class AppPostUsersBody(BaseModel):

View File

@ -20,7 +20,7 @@ class AuthConfig(FrigateBaseModel):
default=86400, title="Session length for jwt session tokens", ge=60
)
refresh_time: int = Field(
default=43200,
default=1800,
title="Refresh the session if it is going to expire in this many seconds",
ge=30,
)

View File

@ -133,6 +133,7 @@ class User(Model):
default="admin",
)
password_hash = CharField(null=False, max_length=120)
password_changed_at = DateTimeField(null=True)
notification_tokens = JSONField()
@classmethod

View File

@ -54,7 +54,9 @@ def migrate(migrator, database, fake=False, **kwargs):
# Migrate existing has_been_reviewed data to UserReviewStatus for all users
def migrate_data():
all_users = list(User.select())
# Use raw SQL to avoid ORM issues with columns that don't exist yet
cursor = database.execute_sql('SELECT "username" FROM "user"')
all_users = cursor.fetchall()
if not all_users:
return
@ -63,7 +65,7 @@ def migrate(migrator, database, fake=False, **kwargs):
)
reviewed_segment_ids = [row[0] for row in cursor.fetchall()]
# also migrate for anonymous (unauthenticated users)
usernames = [user.username for user in all_users] + ["anonymous"]
usernames = [user[0] for user in all_users] + ["anonymous"]
for segment_id in reviewed_segment_ids:
for username in usernames:

View File

@ -0,0 +1,42 @@
"""Peewee migrations -- 032_add_password_changed_at.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
import peewee as pw
SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs):
migrator.sql(
"""
ALTER TABLE user ADD COLUMN password_changed_at DATETIME NULL
"""
)
def rollback(migrator, database, fake=False, **kwargs):
migrator.sql(
"""
ALTER TABLE user DROP COLUMN password_changed_at
"""
)

View File

@ -185,5 +185,6 @@
"noNewImages": "Sense noves imatges per entrenar. Classifica més imatges primer.",
"modelNotReady": "El model no está preparat per entrenar",
"noChanges": "No hi ha canvis al conjunt de dades des de l'última formació."
}
},
"none": "Cap"
}

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Klassifikationsmodelle",
"documentTitle": "Klassifizierungsmodelle - Fregatte",
"details": {
"scoreInfo": "Die Punktzahl gibt die durchschnittliche Konfidenz aller Erkennungen dieses Objekts wieder."
},
@ -180,5 +180,6 @@
"description": "Es wird empfohlen für alle Zustände Beispiele auszuwählen. Das Modell wird erst trainiert, wenn für alle Zustände Bilder vorhanden sind. Fahren Sie fort und verwenden Sie die Ansicht „Aktuelle Klassifizierungen“, um Bilder für die fehlenden Zustände zu klassifizieren. Trainieren Sie anschließend das Modell."
}
}
}
},
"none": "Keiner"
}

View File

@ -276,7 +276,7 @@
"millisecondsToOffset": "Millisekunden, um Erkennungs-Anmerkungen zu verschieben. <em>Standard: 0</em>",
"tips": "Verringere den Wert, wenn die Videowiedergabe den Boxen und Wegpunkten voraus ist, und erhöhe den Wert, wenn die Videowiedergabe hinter ihnen zurückbleibt. Dieser Wert kann negativ sein.",
"toast": {
"success": "Der Anmerkungs-Offset für {{camera}} wurde in der Konfigurationsdatei gespeichert. Starte Frigate neu, um Ihre Änderungen zu übernehmen."
"success": "Der Anmerkungs-Offset für {{camera}} wurde in der Konfigurationsdatei gespeichert."
}
}
},

View File

@ -5,7 +5,7 @@
"camera": "Kameraeinstellungen - Frigate",
"masksAndZones": "Masken- und Zoneneditor Frigate",
"object": "Debug - Frigate",
"general": "UI Einstellungen Frigate",
"general": "UI-Einstellungen Frigate",
"frigatePlus": "Frigate+ Einstellungen Frigate",
"classification": "Klassifizierungseinstellungen Frigate",
"motionTuner": "Bewegungserkennungs-Optimierer Frigate",
@ -298,7 +298,7 @@
"zones": {
"edit": "Zone bearbeiten",
"toast": {
"success": "Die Zone ({{zoneName}}) wurde gespeichert. Starten Sie Frigate neu, um die Änderungen zu übernehmen."
"success": "Die Zone ({{zoneName}}) wurde gespeichert."
},
"desc": {
"documentation": "Dokumentation",
@ -361,8 +361,8 @@
"clickDrawPolygon": "Klicke, um ein Polygon auf dem Bild zu zeichnen.",
"toast": {
"success": {
"noName": "Bewegungsmaske wurde gespeichert. Starte Frigate neu, um die Änderungen zu übernehmen.",
"title": "{{polygonName}} wurde gespeichert. Starte Frigate neu, um die Änderungen zu übernehmen."
"noName": "Bewegungsmaske wurde gespeichert.",
"title": "{{polygonName}} wurde gespeichert."
}
},
"add": "Neue Bewegungsmaske",
@ -382,8 +382,8 @@
"documentTitle": "Objektmaske bearbeiten Frigate",
"toast": {
"success": {
"noName": "Objektmaske wurde gespeichert. Starte Frigate neu, um die Änderungen zu übernehmen.",
"title": "{{polygonName}} wurde gespeichert. Starte Frigate neu, um die Änderungen zu übernehmen."
"noName": "Objektmaske wurde gespeichert.",
"title": "{{polygonName}} wurde gespeichert."
}
},
"desc": {

View File

@ -712,6 +712,8 @@
"password": {
"title": "Password",
"placeholder": "Enter password",
"show": "Show password",
"hide": "Hide password",
"confirm": {
"title": "Confirm Password",
"placeholder": "Confirm Password"
@ -723,6 +725,13 @@
"strong": "Strong",
"veryStrong": "Very Strong"
},
"requirements": {
"title": "Password requirements:",
"length": "At least 8 characters",
"uppercase": "At least one uppercase letter",
"digit": "At least one digit",
"special": "At least one special character (!@#$%^&*(),.?\":{}|<>)"
},
"match": "Passwords match",
"notMatch": "Passwords don't match"
},
@ -733,6 +742,10 @@
"placeholder": "Re-enter new password"
}
},
"currentPassword": {
"title": "Current Password",
"placeholder": "Enter your current password"
},
"usernameIsRequired": "Username is required",
"passwordIsRequired": "Password is required"
},
@ -750,9 +763,13 @@
"passwordSetting": {
"cannotBeEmpty": "Password cannot be empty",
"doNotMatch": "Passwords do not match",
"currentPasswordRequired": "Current password is required",
"incorrectCurrentPassword": "Current password is incorrect",
"passwordVerificationFailed": "Failed to verify password",
"updatePassword": "Update Password for {{username}}",
"setPassword": "Set Password",
"desc": "Create a strong password to secure this account."
"desc": "Create a strong password to secure this account.",
"multiDeviceWarning": "Any other devices where you are logged in will be required to re-login within {{refresh_time}}. You can also force all users to re-authenticate immediately by rotating your JWT secret."
},
"changeRole": {
"title": "Change User Role",

View File

@ -185,7 +185,7 @@
},
"label": "Mode sombre"
},
"review": "Événements",
"review": "Activités",
"explore": "Explorer",
"export": "Exporter",
"user": {
@ -247,7 +247,7 @@
"title": "Rôle",
"viewer": "Observateur",
"admin": "Administrateur",
"desc": "Les administrateurs ont un accès complet à toutes les fonctionnalités de l'interface Frigate. Les observateurs sont limités à la consultation des caméras, des événements, et à l'historique des enregistrements dans l'interface."
"desc": "Les administrateurs ont un accès complet à toutes les fonctionnalités de l'interface Frigate. Les observateurs sont limités à la consultation des caméras, des activités, et à l'historique des enregistrements dans l'interface."
},
"pagination": {
"next": {

View File

@ -110,19 +110,19 @@
"recording": {
"confirmDelete": {
"desc": {
"selected": "Êtes-vous sûr(e) de vouloir supprimer toutes les vidéos enregistrées associées à cet événement? <br /><br />Maintenez la touche <em>Maj</em> enfoncée pour éviter cette boîte de dialogue à l'avenir."
"selected": "Êtes-vous sûr(e) de vouloir supprimer toutes les vidéos enregistrées associées à cette activité? <br /><br />Maintenez la touche <em>Maj</em> enfoncée pour éviter cette boîte de dialogue à l'avenir."
},
"title": "Confirmer la suppression",
"toast": {
"success": "Les vidéos associées aux événements sélectionnés ont été supprimées.",
"success": "Les vidéos associées aux activités sélectionnées ont été supprimées.",
"error": "Échec de la suppression : {{error}}"
}
},
"button": {
"export": "Exporter",
"markAsReviewed": "Marquer comme vérifié",
"markAsReviewed": "Marquer comme traité",
"deleteNow": "Supprimer maintenant",
"markAsUnreviewed": "Marquer comme non vérifié"
"markAsUnreviewed": "Marquer comme non traité"
}
},
"imagePicker": {

View File

@ -83,7 +83,7 @@
}
},
"review": {
"showReviewed": "Afficher les éléments vérifiés"
"showReviewed": "Afficher les activités traitées"
},
"cameras": {
"label": "Filtre des caméras",

View File

@ -3,7 +3,7 @@
"downloadVideo": {
"label": "Télécharger la vidéo",
"toast": {
"success": "Le téléchargement de la vidéo de votre événement a commencé."
"success": "Le téléchargement de la vidéo a commencé."
}
}
}

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Modèles de classification",
"documentTitle": "Modèles de classification - Frigate",
"button": {
"deleteClassificationAttempts": "Supprimer les images de classification",
"renameCategory": "Renommer la classe",
@ -156,7 +156,7 @@
"modelCreated": "Modèle créé avec succès. Utilisez la vue Classifications récentes pour ajouter des images pour les états manquants, puis entraînez le modèle.",
"missingStatesWarning": {
"title": "Exemples d'états manquants",
"description": "Vous n'avez pas sélectionné d'exemples pour tous les états. L'entraînement ne pourra débuter que lorsque chaque état disposera d'images. Continuez, puis utilisez la vue Classifications récentes pour classer les images manquantes et lancer l'entraînement."
"description": "Pour des résultats optimaux, il est recommandé de sélectionner des exemples pour tous les états. Vous pouvez continuer sans cette étape, mais le modèle ne sera entraîné que lorsque chaque état disposera d'images. Continuez, puis utilisez la vue Classifications récentes pour classer les images manquantes et lancer l'entraînement."
}
}
},
@ -185,5 +185,6 @@
"noNewImages": "Aucune nouvelle image pour l'entraînement. Veuillez d'abord classifier plus d'images dans le jeu de données.",
"modelNotReady": "Le modèle n'est pas prêt pour l'entraînement.",
"noChanges": "Aucune modification du jeu de données depuis le dernier entraînement"
}
},
"none": "Aucun"
}

View File

@ -7,8 +7,8 @@
"alerts": "Alertes",
"allCameras": "Toutes les caméras",
"empty": {
"alert": "Il n'y a aucune alerte à examiner.",
"detection": "Il n'y a aucune détection à examiner.",
"alert": "Aucune alerte à traiter",
"detection": "Aucune détection à traiter",
"motion": "Aucune donnée de mouvement trouvée"
},
"timeline": "Chronologie",
@ -17,7 +17,7 @@
"aria": "Sélectionner les événements",
"noFoundForTimePeriod": "Aucun événement n'a été trouvé pour cette plage de temps."
},
"documentTitle": "Événements - Frigate",
"documentTitle": "Activités - Frigate",
"recordings": {
"documentTitle": "Enregistrements - Frigate"
},
@ -25,13 +25,13 @@
"last24Hours": "Dernières 24 heures"
},
"timeline.aria": "Sélectionner une chronologie",
"markAsReviewed": "Marquer comme vérifié",
"markAsReviewed": "Marquer comme traitê",
"newReviewItems": {
"button": "Nouveaux événements à examiner",
"label": "Afficher les nouveaux événements"
"button": "Nouvelles activités à traiter",
"label": "Afficher les nouvelles activités"
},
"camera": "Caméra",
"markTheseItemsAsReviewed": "Marquer ces éléments comme vérifiés",
"markTheseItemsAsReviewed": "Marquer ces activités comme traitées",
"selected": "{{count}} sélectionné(s)",
"selected_other": "{{count}} sélectionné(s)",
"selected_one": "{{count}} sélectionné(s)",
@ -39,7 +39,7 @@
"suspiciousActivity": "Activité suspecte",
"threateningActivity": "Activité menaçante",
"detail": {
"noDataFound": "Aucun détail à examiner",
"noDataFound": "Aucun détail à traiter",
"aria": "Activer/désactiver la vue détaillée",
"trackedObject_one": "{{count}} objet",
"trackedObject_other": "{{count}} objets",
@ -48,7 +48,7 @@
"settings": "Paramètres de la vue Détail",
"alwaysExpandActive": {
"title": "Toujours développer l'élément actif",
"desc": "Toujours développer les détails de l'objet de l'événement actif si disponibles"
"desc": "Toujours développer les détails de l'objet pour l'activité en cours"
}
},
"objectTrack": {
@ -58,6 +58,6 @@
"zoomIn": "Zoom avant",
"zoomOut": "Zoom arrière",
"normalActivity": "Normal",
"needsReview": "Nécessite une revue",
"needsReview": "À traiter",
"securityConcern": "Problème de sécurité"
}

View File

@ -32,9 +32,9 @@
"details": {
"timestamp": "Horodatage",
"item": {
"title": "Détails de l'événement",
"title": "Détails de l'activité",
"button": {
"share": "Partager cet événement",
"share": "Partager cette activité",
"viewInExplore": "Afficher dans Explorer"
},
"toast": {
@ -52,12 +52,12 @@
}
},
"tips": {
"mismatch_one": "{{count}} objet indisponible a été détecté et intégré dans cet événement. Cet objet n'a pas été qualifié comme une alerte ou une détection, ou a déjà été nettoyé / supprimé.",
"mismatch_many": "{{count}} objets indisponibles ont été détectés et intégrés dans cet événement. Ces objets n'ont pas été qualifiés comme une alerte ou une détection, ou ont déjà été nettoyés / supprimés.",
"mismatch_other": "{{count}} objets indisponibles ont été détectés et intégrés dans cet événement. Ces objets n'ont pas été qualifiés comme une alerte ou une détection, ou ont déjà été nettoyés / supprimés.",
"mismatch_one": "{{count}} objet indisponible a été détecté et intégré dans cette activité. Cet objet n'a pas été qualifié comme une alerte ou une détection, ou a déjà été nettoyé / supprimé.",
"mismatch_many": "{{count}} objets indisponibles ont été détectés et intégrés dans cette activité. Ces objets n'ont pas été qualifiés comme une alerte ou une détection, ou ont déjà été nettoyés / supprimés.",
"mismatch_other": "{{count}} objets indisponibles ont été détectés et intégrés dans cette activité. Ces objets n'ont pas été qualifiés comme une alerte ou une détection, ou ont déjà été nettoyés / supprimés.",
"hasMissingObjects": "Ajustez votre configuration si vous souhaitez que Frigate enregistre les objets suivis pour les étiquettes suivantes : <em>{{objects}}</em>"
},
"desc": "Détails de l'événement"
"desc": "Détails de l'activité"
},
"label": "Étiquette",
"editSubLabel": {
@ -277,7 +277,7 @@
"millisecondsToOffset": "Millisecondes de décalage pour les annotations de détection. <em>Par défaut : 0</em>",
"tips": "Diminuez la valeur si la lecture vidéo est en avance sur les cadres de détection et les points de tracé, et augmentez-la si la lecture vidéo est en retard sur ceux-ci. Cette valeur peut être négative.",
"toast": {
"success": "Le décalage des annotations pour {{camera}} a été sauvegardé dans le fichier de configuration. Redémarrez Frigate pour appliquer vos modifications."
"success": "Le décalage des annotations pour {{camera}} a été sauvegardé dans le fichier de configuration."
},
"label": "Décalage d'annotation"
},

View File

@ -12,7 +12,7 @@
"notifications": "Paramètres de notification - Frigate",
"enrichments": "Paramètres d'enrichissements - Frigate",
"cameraManagement": "Gestion des caméras - Frigate",
"cameraReview": "Paramètres des événements de caméra - Frigate"
"cameraReview": "Paramètres des activités - Frigate"
},
"menu": {
"ui": "Interface utilisateur",
@ -28,7 +28,7 @@
"triggers": "Déclencheurs",
"roles": "Rôles",
"cameraManagement": "Gestion",
"cameraReview": "Événements"
"cameraReview": "Activités"
},
"dialog": {
"unsavedChanges": {
@ -395,7 +395,7 @@
"name": {
"title": "Nom",
"inputPlaceHolder": "Saisissez un nom.",
"tips": "Le nom doit comporter au moins 2 caractères, dont une lettre, et ne doit pas être le nom d'une caméra ou d'une autre zone."
"tips": "Le nom doit comporter au moins 2 caractères, dont une lettre, et ne doit pas être le nom d'une caméra ou d'une autre zone sur cette caméra."
},
"loiteringTime": {
"desc": "Définit une durée minimale en secondes pendant laquelle l'objet doit rester dans la zone pour qu'elle s'active. <em>Par défaut : 0</em>",
@ -429,7 +429,7 @@
"title": "Inertie"
},
"toast": {
"success": "La zone ({{zoneName}}) a été enregistrée. Redémarrez Frigate pour appliquer les modifications."
"success": "La zone ({{zoneName}}) a été enregistrée."
},
"objects": {
"title": "Objets",
@ -457,8 +457,8 @@
"clickDrawPolygon": "Cliquer pour dessiner un polygone sur l'image.",
"toast": {
"success": {
"title": "{{polygonName}} a été enregistré. Redémarrez Frigate pour appliquer les modifications.",
"noName": "Le masque de mouvement a été enregistré. Redémarrez Frigate pour appliquer les modifications."
"title": "{{polygonName}} a été enregistré.",
"noName": "Le masque de mouvement a été enregistré."
}
},
"desc": {
@ -482,8 +482,8 @@
},
"toast": {
"success": {
"noName": "Le masque d'objet a été enregistré. Redémarrez Frigate pour appliquer les modifications.",
"title": "{{polygonName}} a été enregistré. Redémarrez Frigate pour appliquer les modifications."
"noName": "Le masque d'objet a été enregistré.",
"title": "{{polygonName}} a été enregistré."
}
},
"point_one": "{{count}} point",
@ -720,7 +720,7 @@
},
"label": "Taille du modèle"
},
"desc": "La recherche sémantique de Frigate vous permet de retrouver les objets suivis dans vos événements en utilisant soit l'image elle-même, soit une description textuelle définie par l'utilisateur, soit une description générée automatiquement."
"desc": "La recherche sémantique de Frigate vous permet de retrouver les objets suivis dans vos activités en utilisant soit l'image elle-même, soit une description textuelle définie par l'utilisateur, soit une description générée automatiquement."
},
"unsavedChanges": "Modifications non enregistrées des paramètres d'enrichissements",
"faceRecognition": {
@ -1256,17 +1256,17 @@
}
},
"cameraReview": {
"title": "Paramètres des événements de la caméra",
"title": "Paramètres des activités caméra",
"object_descriptions": {
"title": "Descriptions d'objets par l'IA générative",
"desc": "Active ou désactive temporairement les descriptions d'objets générées par l'IA générative pour cette caméra. Lorsque cette option est désactivée, aucune description par l'IA n'est générée pour les objets suivis sur cette caméra."
},
"review_descriptions": {
"title": "Descriptions des événements par l'IA générative",
"desc": "Active ou désactive temporairement les descriptions par l'IA générative pour cette caméra. Lorsque cette option est désactivée, aucune description par l'IA ne sera générée pour les événements de cette caméra."
"title": "Descriptions des activités par l'IA générative",
"desc": "Active ou désactive temporairement les descriptions par l'IA générative pour cette caméra. Lorsque cette option est désactivée, aucune description par l'IA ne sera générée pour les activités sur cette caméra."
},
"review": {
"title": "Événements",
"title": "Activités",
"desc": "Active ou désactive temporairement les alertes et les détections pour cette caméra jusqu'au redémarrage de Frigate. Lorsque cette option est désactivée, aucun nouvel événement n'est généré. ",
"alerts": "Alertes ",
"detections": "Détections "

View File

@ -186,9 +186,9 @@
"face_recognition": "Reconnaissance faciale",
"text_embedding": "Vitesse d'embedding de visage",
"yolov9_plate_detection_speed": "Vitesse de détection de plaques d'immatriculation YOLOv9",
"review_description": "Description de la revue",
"review_description_speed": "Vitesse de la description de la revue",
"review_description_events_per_second": "Description de la revue",
"review_description": "Description de l'activité",
"review_description_speed": "Vitesse de description des activités",
"review_description_events_per_second": "Description de l'activité",
"object_description": "Description de l'objet",
"object_description_speed": "Vitesse de la description d'objet",
"object_description_events_per_second": "Description de l'objet"

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Klassifiseringsmodeller",
"documentTitle": "Klassifiseringsmodeller - Frigate",
"button": {
"deleteClassificationAttempts": "Slett klassifiseringsbilder",
"renameCategory": "Omdøp klasse",
@ -177,5 +177,6 @@
"descriptionState": "Rediger klassene for denne tilstandsklassifiseringsmodellen. Endringer vil kreve at modellen trenes på nytt.",
"descriptionObject": "Rediger objekttypen og klassifiseringstypen for denne objektklassifiseringsmodellen.",
"stateClassesInfo": "Merk: Endring av tilstandsklasser krever at modellen trenes på nytt med de oppdaterte klassene."
}
},
"none": "Ingen"
}

View File

@ -180,5 +180,6 @@
"noNewImages": "Geen nieuwe afbeeldingen om te trainen. Classificeer eerst meer afbeeldingen in de dataset.",
"modelNotReady": "Model is niet klaar voor training",
"noChanges": "Geen wijzigingen in de dataset sinds de laatste training."
}
},
"none": "Geen herkenning"
}

View File

@ -32,7 +32,7 @@
"sheep": "koyun",
"train": "tren",
"hair_dryer": "saç kurutma makinesi",
"babbling": "aguşlama",
"babbling": "Agulama",
"snicker": "kıkırdama",
"sigh": "iç çekme",
"bellow": "haykırma",
@ -425,5 +425,79 @@
"radio": "radyo",
"field_recording": "alan kaydı",
"scream": ığlık",
"jingle_bell": "küçük çan"
"jingle_bell": "küçük çan",
"sodeling": "Jodel (Yodeling)",
"chird": "Cıvıltı",
"change_ringing": "Sıralı Çan Çalma",
"shofar": "Şofar",
"liquid": "Sıvı",
"splash": "Su Sıçraması",
"slosh": "Çalkalanma",
"squish": "Vıcıklama (Islak Ezilme)",
"drip": "Damlama",
"pour": "Dökülme",
"trickle": ırıldama / İnce Akış",
"gush": "Fışkırma",
"fill": "Doldurma",
"spray": "Püskürtme / Sprey",
"pump": "Pompalama",
"stir": "Karıştırma",
"boiling": "Kaynama",
"sonar": "Sonar Sesi",
"arrow": "Ok Sesi",
"whoosh": "Hışırtı (Hızlı Geçiş Sesi)",
"thump": "Küt Sesi (Boğuk)",
"thunk": "Tok Ses",
"electronic_tuner": "Elektronik Akort Cihazı",
"effects_unit": "Efekt Ünitesi",
"chorus_effect": "Chorus (Koro) Efekti",
"basketball_bounce": "Basketbol Topu Sektirme",
"bang": "Gümleme / Patlama",
"slap": "Tokat / Şaplak",
"whack": "Sert Vuruş / Kütletme",
"smash": "Parçalanma",
"breaking": "Kırılma",
"bouncing": "Sekme / Zıplama",
"whip": "Kırbaç",
"flap": "Kanat Çırpma / Pırpır Etme",
"scratch": "Tırmalama / Cızırtı",
"scrape": "Kazıma / Sürtünme",
"rub": "Ovma / Sürtme",
"roll": "Yuvarlanma",
"crushing": "Ezilme (Kuru/Sert)",
"crumpling": "Buruşturma",
"tearing": "Yırtılma",
"beep": "Bip Sesi",
"ping": "Ping Sesi (Çınlama)",
"ding": "Ding (Zil Sesi)",
"clang": "Çangırtı (Metalik)",
"squeal": "Ciyaklama / Acı Gıcırtı",
"creak": "Gıcırdama (Tahta/Kapı)",
"rustle": "Hışırtı (Kağıt/Yaprak)",
"whir": "Vızıltı (Motor/Pervane)",
"clatter": "Takırtı",
"sizzle": "Cızırdayarak Kızarma",
"clicking": "Tıklama",
"clickety_clack": "Takır Tukur Sesi",
"rumble": "Gürleme / Gümbürtü",
"plop": "Lup Sesi (Suya düşme)",
"hum": "Uğultu / Mırıldanma",
"zing": "Vınlama",
"boing": "Boing (Yay Sesi)",
"crunch": "Kıtırdatma / Çıtırdatma",
"sine_wave": "Sinüs Dalgası",
"harmonic": "Harmonik",
"chirp_tone": "Cıvıltı Tonu (Sinyal)",
"pulse": "Darbe / Pulse",
"inside": "İç Mekan",
"outside": "Dış Mekan",
"reverberation": "Yankılanım (Reverb)",
"echo": "Yankı",
"noise": "Gürültü",
"mains_hum": "Şebeke Uğultusu (Elektrik)",
"distortion": "Bozulma / Distorsiyon",
"sidetone": "Yan Ton",
"cacophony": "Kakofoni (Ses Kargaşası)",
"throbbing": "Zonklama",
"vibration": "Titreşim"
}

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Sınıflandırma Modelleri",
"documentTitle": "Sınıflandırma Modelleri - Frigate",
"details": {
"scoreInfo": "Skor, modelin nesneyi tespit ettiği tüm durumlar için ortalama güven düzeyini gösterir."
},
@ -180,5 +180,6 @@
"description": "En iyi sonuçlar için tavsiye edilir: Tüm durumlar (state) için örnekler seçin. Tüm durumlar için örnek seçmeden devam edebilirsiniz, ancak model, tüm durumlara ait görüntüler eklenene kadar eğitilmeyecektir. Devam ettikten sonra, eksik durumlar için görüntüleri sınıflandırmak ve ardından modeli eğitmek için Son Sınıflandırmalar (Recent Classifications) görünümünü kullanın."
}
}
}
},
"none": "Yok"
}

View File

@ -10,7 +10,7 @@
"viewInExplore": "Keşfet'te Görüntüle"
},
"tips": {
"hasMissingObjects": "Eğer Frigate'in <em>{{objects}}</em> etiketine sahip nesneleri kaydetmesini istiyorsanız yapılandırmanızı buna göre ayarlayın.",
"hasMissingObjects": "Eğer Frigate'in <em>{{objects}}</em> etiketine sahip nesneleri kaydetmesini istiyorsanız yapılandırmanızı buna göre ayarlayın",
"mismatch_one": "Tespit edilmiş olan bir nesne bu İncele öğesine dahil edildi. Bu nesne Alarm veya Tespit olarak derecelendirilemedi veya çoktan silindi/temizlendi.",
"mismatch_other": "Tespit edilmiş olan {{count}} adet nesne bu İncele öğesine dahil edildi. Bu nesneler Alarm veya Tespit olarak derecelendirilemedi veya çoktan silindi/temizlendi."
},
@ -278,7 +278,7 @@
"millisecondsToOffset": "Algılama anotasyonlarının kaydırılacağı milisaniye değeri. <em>Varsayılan: 0</em>",
"tips": "Videonun oynatımı kutulardan ve yol noktalarından öndeyse değeri düşürün; geride kalıyorsa değeri artırın. Bu değer negatif olabilir.",
"toast": {
"success": "{{camera}} için anotasyon kaydırması yapılandırma dosyasına kaydedildi. Değişikliklerin uygulanması için Frigatei yeniden başlatın."
"success": "{{camera}} için anotasyon zaman kaydırması yapılandırma dosyasına kaydedildi."
}
}
},

View File

@ -250,7 +250,8 @@
"hasIllegalCharacter": "Alan adı geçersiz karakterler içeriyor.",
"mustNotBeSameWithCamera": "Alan adı kamera adıyla aynı olmamalıdır.",
"alreadyExists": "Bu kamera için bu ada sahip bir alan zaten mevcut.",
"mustNotContainPeriod": "Alan adı nokta içermemelidir."
"mustNotContainPeriod": "Alan adı nokta içermemelidir.",
"mustHaveAtLeastOneLetter": "Bölge adı en az bir harf içermelidir."
}
},
"distance": {
@ -306,7 +307,7 @@
"name": {
"inputPlaceHolder": "Bir isim girin…",
"title": "İsim",
"tips": "Ad en az 2 karakter olmalı ve bir kamera veya başka bir bölgenin adı olmamalıdır."
"tips": "Ad en az 2 karakter olmalı, en az bir harf içermeli ve bu kameradaki bir kamera adıyla veya başka bir bölge adıyla aynı olmamalıdır."
},
"inertia": {
"title": "Eylemsizlik",
@ -342,7 +343,7 @@
"title": "Hız Alt Sınırı ({{unit}})"
},
"toast": {
"success": "Alan ({{zoneName}}) kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın."
"success": "Bölge ({{zoneName}}) kaydedildi."
},
"allObjects": "Bütün Nesneler"
},
@ -362,8 +363,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın.",
"noName": "Hareket Maskesi kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın."
"title": "{{polygonName}} kaydedildi.",
"noName": "Hareket Maskesi kaydedildi."
}
},
"desc": {
@ -391,8 +392,8 @@
"edit": "Nesne Maskesini Düzenle",
"toast": {
"success": {
"noName": "Nesne Maskesi kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın.",
"title": "{{polygonName}} kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın."
"noName": "Nesne Maskesi kaydedildi.",
"title": "{{polygonName}} kaydedildi."
}
},
"documentTitle": "Nesne Maskesini Düzenle - Frigate",
@ -576,7 +577,8 @@
"intro": "Bu kullanıcı için bir rol seçin:",
"admin": "Yönetici",
"viewer": "Görüntüleyici",
"viewerDesc": "Yalnızca Canlı, İncele, Keşfet ve Dışa Aktar'a girebilir."
"viewerDesc": "Yalnızca Canlı, İncele, Keşfet ve Dışa Aktar'a girebilir.",
"customDesc": "Belirli kamera erişimine sahip özel rol."
},
"select": "Bir rol seçin"
}
@ -750,25 +752,28 @@
"form": {
"name": {
"error": {
"invalidCharacters": "İsim yalnızca harf, rakam, alt çizgi veya tire içerebilir.",
"minLength": "Bu isim en az iki karakterden oluşmalıdır.",
"invalidCharacters": "Alan yalnızca harf, rakam, alt çizgi ve tire içerebilir.",
"minLength": "Alan en az 2 karakter uzunluğunda olmalıdır.",
"alreadyExists": "Bu kamerada aynı isimle bir tetik zaten mevcut."
},
"title": "İsim",
"placeholder": "Tetik için bir isim girin"
"placeholder": "Bu tetikleyiciye ad verin",
"description": "Bu tetikleyiciyi tanımlamak için benzersiz bir ad veya açıklama girin"
},
"enabled": {
"description": "Bu tetiği açın veya kapatın"
},
"type": {
"title": "Tetik Türü",
"placeholder": "Tetik türünü seçin"
"placeholder": "Tetik türünü seçin",
"description": "Benzer izlenen nesne açıklaması algılandığında tetiklenir",
"thumbnail": "Benzer izlenen nesne küçük resmi algılandığında tetiklenir"
},
"content": {
"title": "İçerik",
"imagePlaceholder": "Bir resim seçin",
"imagePlaceholder": "Bir küçük resim seçin",
"textPlaceholder": "Metin içeriği girin",
"imageDesc": "Benzer bir resim tespit edildiğinde tetiklenilmesi için bir resim seçin.",
"imageDesc": "Yalnızca en son 100 küçük resim görüntülenir. İstediğiniz küçük resmi bulamazsanız, lütfen Keşfet bölümündeki önceki nesneleri inceleyin ve oradaki menüden bir tetikleyici ayarlayın.",
"textDesc": "Benzer bir takip edilen nesne açıklaması algılandığında bu eylemi tetiklemek için metin girin.",
"error": {
"required": "İçerik gereklidir."
@ -779,11 +784,12 @@
"error": {
"min": "Tetik eşiği 0 ile 1 arasında olmalıdır",
"max": "Tetik eşiği 0 ile 1 arasında olmalıdır"
}
},
"desc": "Bu tetikleyici için benzerlik eşiğini ayarlayın. Daha yüksek bir eşik, tetiği tetiklemek için daha yakın bir eşleşme gerektiği anlamına gelir."
},
"actions": {
"title": "Eylemler",
"desc": "Varsayılan olarak Frigate bütün tetkler için MQTT'ye bir mesaj atar. İsterseniz yapılacak ek bir eylem de belirleyebilirsiniz.",
"desc": "Varsayılan olarak, Frigate tüm tetikleyiciler için bir MQTT mesajı gönderir. Alt etiketler, tetikleyici adını nesne etiketine ekler. Nitelikler, izlenen nesne meta verilerinde ayrı olarak depolanan aranabilir meta verilerdir.",
"error": {
"min": "En az bir eylem seçilmelidir."
}
@ -804,7 +810,7 @@
},
"documentTitle": "Tetikler",
"management": {
"title": "Tetik Yönetimi",
"title": "Tetikleyiciler",
"desc": "{{camera}} için tetikleri yönetin. Seçtiğiniz takip edilen nesneye benzer küçük resimlerde tetiklemek için küçük resmi kullanın veya belirlediğiniz metne benzer açıklamalar çıkması durumunda tetiklemek için ise açıklama seçeneğini kullanın."
},
"addTrigger": "Tetik Ekle",
@ -825,7 +831,9 @@
},
"actions": {
"alert": "Alarm Olarak İşaretle",
"notification": "Bildirim Gönder"
"notification": "Bildirim Gönder",
"sub_label": "Alt Etiket Ekle",
"attribute": "Özellik Ekle"
},
"toast": {
"success": {
@ -838,6 +846,27 @@
"updateTriggerFailed": "Tetik güncellenemedi: {{errorMessage}}",
"deleteTriggerFailed": "Tetik silinemedi: {{errorMessage}}"
}
},
"semanticSearch": {
"title": "Anlamsal Arama devre dışı bırakıldı",
"desc": "Tetikleyicileri kullanmak için Anlamsal Arama'nın etkinleştirilmesi gerekir."
},
"wizard": {
"title": "Tetikleyici Oluştur",
"step1": {
"description": "Tetikleyiciniz için temel ayarları yapılandırın."
},
"step2": {
"description": "Bu eylemi tetikleyecek içeriği ayarlayın."
},
"step3": {
"description": "Bu tetikleyici için eşik değerini ve eylemleri yapılandırın."
},
"steps": {
"nameAndType": "Ad ve Tür",
"configureData": "Verileri Yapılandır",
"thresholdAndActions": "Eşik ve Eylemler"
}
}
},
"cameraWizard": {
@ -885,7 +914,285 @@
"onvifPort": "ONVIF Portu",
"probeMode": "Kamerayı tara",
"manualMode": "Manuel seçim",
"detectionMethodDescription": "Kamera akış URLlerini bulmak için kamerayı ONVIF ile tarayın (destekleniyorsa) veya ön tanımlı URLleri kullanmak için kamera markasını manuel olarak seçin. Özel bir RTSP URLsi girmek için manuel yöntemi seçin ve “Diğer”i işaretleyin."
"detectionMethodDescription": "Kamera akış URLlerini bulmak için kamerayı ONVIF ile tarayın (destekleniyorsa) veya ön tanımlı URLleri kullanmak için kamera markasını manuel olarak seçin. Özel bir RTSP URLsi girmek için manuel yöntemi seçin ve “Diğer”i işaretleyin.",
"onvifPortDescription": "ONVIF'i destekleyen kameralarda bu genellikle 80 veya 8080'dir.",
"useDigestAuth": "Digest kimlik doğrulamasını kullan",
"errors": {
"nameRequired": "Kamera adı gerekli",
"nameLength": "Kamera adı 64 karakter veya daha az olmalıdır",
"invalidCharacters": "Kamera adı geçersiz karakterler içeriyor",
"nameExists": "Kamera adı zaten mevcut",
"customUrlRtspRequired": "Özel URL'ler \"rtsp://\" ile başlamalıdır. RTSP olmayan kamera akışları için manuel yapılandırma gereklidir.",
"brandOrCustomUrlRequired": "Bir kamera markası seçip host/IP adresi girin ya da özel bir URL kullanmak için Diğer seçeneğini tercih edin"
},
"useDigestAuthDescription": "ONVIF için HTTP digest kimlik doğrulamasını kullanın. Bazı kameralar, standart yönetici kullanıcısı yerine özel bir ONVIF kullanıcı adı/şifresi gerektirebilir."
},
"step2": {
"description": "Mevcut akışları bulmak için kamerayı tarayın veya seçtiğiniz algılama yöntemine göre manuel ayarları yapılandırın.",
"testSuccess": "Bağlantı testi başarılı!",
"testFailed": "Bağlantı testi başarısız oldu. Lütfen tüm alanları kontrol edip tekrar deneyin.",
"testFailedTitle": "Test Başarısız",
"streamDetails": "Akış Ayrıntıları",
"probing": "Kamera taranıyor...",
"retry": "Yeniden dene",
"testing": {
"probingMetadata": "Kamera meta verileri inceleniyor...",
"fetchingSnapshot": "Kamera anlık görüntüsü alınıyor..."
},
"probeFailed": "Kamerayı tarama başarısız oldu: {{error}}",
"probingDevice": "Cihaz taranıyor…",
"probeSuccessful": "Tarama başarılı",
"probeError": "Tarama hatası",
"probeNoSuccess": "Tarama başarısız",
"deviceInfo": "Cihaz Bilgileri",
"manufacturer": "Üretici",
"model": "Modeli",
"firmware": "Donanım yazılımı",
"profiles": "Profiller",
"ptzSupport": "PTZ Desteği",
"autotrackingSupport": "Otomatik Takip Desteği",
"presets": "Ön ayarlar",
"rtspCandidates": "RTSP Yayınları",
"rtspCandidatesDescription": "Kamera taramasından aşağıdaki RTSP URL'leri bulundu. Akış meta verilerini görüntülemek için bağlantıyı test edin.",
"noRtspCandidates": "Kameradan RTSP URL'si bulunamadı. Kimlik bilgileriniz yanlış olabilir veya kamera ONVIF'i veya RTSP URL'lerini almak için kullanılan yöntemi desteklemiyor olabilir. Geri dönün ve RTSP URL'sini manuel olarak girin.",
"candidateStreamTitle": "Yayın {{number}}",
"useCandidate": "Kullan",
"uriCopy": "Kopyala",
"uriCopied": "URI panoya kopyalandı",
"testConnection": "Bağlantıyı Test Et",
"toggleUriView": "Tam URI görünümünü değiştirmek için tıklayın",
"connected": "Bağlandı",
"notConnected": "Bağlı Değil",
"errors": {
"hostRequired": "Host/IP adresi gereklidir"
}
},
"step3": {
"description": "Akış rollerini yapılandırın ve kameranız için ek akışlar ekleyin.",
"streamsTitle": "Kamera Yayınları",
"addStream": "Yayın Ekle",
"addAnotherStream": "Başka Bir Yayın Ekle",
"streamTitle": "Yayın {{number}}",
"streamUrl": "Yayın URL'si",
"streamUrlPlaceholder": "rtsp://kullanıcıadı:şifre@host:port/path",
"selectStream": "Bir yayın seçin",
"searchCandidates": "Yayınları arayın...",
"noStreamFound": "Yayın bulunamadı",
"url": "URL",
"resolution": "Çözünürlük",
"selectResolution": "Çözünürlüğü seçin",
"quality": "Kalite",
"selectQuality": "Kaliteyi seçin",
"roles": "Roller",
"roleLabels": {
"detect": "Nesne Algılama",
"record": "Kayıt",
"audio": "Ses"
},
"testStream": "Bağlantıyı Test Et",
"testSuccess": "Yayın testi başarılı!",
"testFailed": "Yayın testi başarısız oldu",
"testFailedTitle": "Test Başarısız",
"connected": "Bağlı",
"notConnected": "Bağlı Değil",
"featuresTitle": "Özellikler",
"go2rtc": "Kameraya olan bağlantıları azaltın",
"detectRoleWarning": "Devam edebilmek için en az bir akışın \"algılama\" rolüne sahip olması gerekir.",
"rolesPopover": {
"title": "Yayın Rolleri",
"detect": "Nesne tespiti için ana besleme.",
"record": "Yapılandırma ayarlarına göre video akışının bölümlerini kaydeder.",
"audio": "Ses tabanlı algılama için besleme."
},
"featuresPopover": {
"title": "Yayın Özellikleri",
"description": "Kameranıza olan bağlantıları azaltmak için go2rtc yeniden akışını kullanın."
}
},
"step4": {
"disconnectStream": "Bağlantıyı kes",
"estimatedBandwidth": "Tahmini Bant Genişliği",
"roles": "Roller",
"ffmpegModule": "Yayın uyumluluk modunu kullan",
"ffmpegModuleDescription": "Yayın birkaç denemeden sonra yüklenmezse, bunu etkinleştirmeyi deneyin. Etkinleştirildiğinde, Frigate go2rtc ile ffmpeg modülünü kullanacaktır. Bu, bazı kamera yayınları ile daha iyi uyumluluk sağlayabilir.",
"none": "Hiçbiri",
"error": "Hata",
"description": "Yeni kameranızı kaydetmeden önce son doğrulama ve analiz. Kaydetmeden önce her akışı bağlayın.",
"validationTitle": "Yayın Doğrulaması",
"connectAllStreams": "Tüm Yayınları Bağla",
"reconnectionSuccess": "Yeniden bağlantı başarılı.",
"reconnectionPartial": "Bazı yayınlara yeniden bağlanılamadı.",
"streamUnavailable": "Yayın önizlemesi kullanılamıyor",
"reload": "Yeniden yükle",
"connecting": "Bağlanıyor...",
"streamTitle": "Yayın {{number}}",
"valid": "Geçerli",
"failed": "Başarısız",
"notTested": "Test edilmedi",
"connectStream": "Bağlan",
"connectingStream": "Bağlanıyor",
"streamValidated": "{{number}} yayını başarıyla doğrulandı",
"streamValidationFailed": "Yayın {{number}} doğrulaması başarısız oldu",
"saveAndApply": "Yeni Kamerayı Kaydet",
"saveError": "Geçersiz yapılandırma. Lütfen ayarlarınızı kontrol edin.",
"issues": {
"title": "Yayın Doğrulaması",
"videoCodecGood": "Video kodeği {{codec}}.",
"audioCodecGood": "Ses kodeği {{codec}}.",
"resolutionHigh": "{{resolution}} çözünürlüğü kaynak kullanımının artmasına neden olabilir.",
"resolutionLow": "{{resolution}} çözünürlüğü, küçük nesnelerin güvenilir bir şekilde algılanması için çok düşük olabilir.",
"noAudioWarning": "Bu yayın için ses algılanmadı, kayıtlarda ses bulunmayacak.",
"audioCodecRecordError": "Kayıtlarda sesi desteklemek için AAC ses kodeği gereklidir.",
"audioCodecRequired": "Ses algılamayı desteklemek için bir ses akışı gereklidir.",
"restreamingWarning": "Kayıt akışı için kameraya olan bağlantıları azaltmak CPU kullanımını bir miktar artırabilir.",
"brands": {
"reolink-rtsp": "Reolink RTSP önerilmez. Kameranın donanım yazılımı ayarlarında HTTP'yi etkinleştirin ve sihirbazı yeniden başlatın."
},
"dahua": {
"substreamWarning": "Alt akış 1 düşük çözünürlüğe kilitlenmiştir. Birçok Dahua / Amcrest / EmpireTech kamera, kamera ayarlarında etkinleştirilmesi gereken ek alt akışları destekler. Mevcutsa, bu akışları kontrol edip kullanmanız önerilir."
},
"hikvision": {
"substreamWarning": "Alt akış 1 düşük çözünürlüğe kilitlendi. Birçok Hikvision kamera, kamera ayarlarında etkinleştirilmesi gereken ek alt akışları destekler. Mevcutsa, bu akışları kontrol edip kullanmanız önerilir."
}
}
}
},
"cameraManagement": {
"title": "Kameraları Yönet",
"addCamera": "Yeni Kamera Ekle",
"editCamera": "Kamerayı Düzenle:",
"selectCamera": "Bir Kamera Seçin",
"backToSettings": "Kamera Ayarlarına Dön",
"streams": {
"title": "Kameraları Etkinleştir / Devre Dışı Bırak",
"desc": "Frigate yeniden başlatılana kadar bir kamerayı geçici olarak devre dışı bırakın. Bir kamerayı devre dışı bırakmak, Frigate'in bu kameranın akışlarını işlemesini tamamen durdurur. Algılama, kayıt ve hata ayıklama kullanılamaz.<br /> <em>Not: Bu, go2rtc yeniden akışlarını devre dışı bırakmaz.</em>"
},
"cameraConfig": {
"add": "Kamera Ekle",
"edit": "Kamerayı Düzenle",
"description": "Yayınlar ve roller dahil olmak üzere kamera ayarlarını yapılandırın.",
"name": "Kamera Adı",
"nameRequired": "Kamera adı gerekli",
"nameLength": "Kamera adı 64 karakterden az olmalıdır.",
"namePlaceholder": "örneğin, ön_kapı veya Arka Bahçe Genel Bakışı",
"enabled": "Etkinleştirilmiş",
"ffmpeg": {
"inputs": "Giriş Yayınları",
"path": "Yayın Yolu",
"pathRequired": "Yayın yolu gereklidir",
"pathPlaceholder": "rtsp://...",
"roles": "Roller",
"rolesRequired": "En az bir rol gereklidir",
"rolesUnique": "Her rol (ses, algılama, kayıt) yalnızca bir akışa atanabilir",
"addInput": "Giriş Yayını Ekle",
"removeInput": "Giriş Yayınını Kaldır",
"inputsRequired": "En az bir giriş yayını gereklidir"
},
"go2rtcStreams": "go2rtc Yayınları",
"streamUrls": "Yayın URL'leri",
"addUrl": "URL ekle",
"addGo2rtcStream": "go2rtc Yayını Ekle",
"toast": {
"success": "Kamera {{cameraName}} başarıyla kaydedildi"
}
}
},
"cameraReview": {
"title": "Kamera İnceleme Ayarları",
"object_descriptions": {
"title": "Üretken Yapay Zeka Nesne Açıklamaları",
"desc": "Bu kamera için Yapay Zeka Nesne Tanımlamalarını geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki izlenen nesneler için Yapay Zeka tarafından oluşturulan tanımlar istenmeyecektir."
},
"review_descriptions": {
"title": "Üretken Yapay Zeka İnceleme Açıklamaları",
"desc": "Bu kamera için Yapay Zeka Üretici İnceleme açıklamalarını geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, bu kameradaki inceleme öğeleri için Yapay Zeka tarafından oluşturulan açıklamalar istenmeyecektir."
},
"review": {
"title": "İncele",
"desc": "Frigate yeniden başlatılana kadar bu kamera için uyarıları ve algılamaları geçici olarak etkinleştirin/devre dışı bırakın. Devre dışı bırakıldığında, yeni inceleme öğeleri oluşturulmaz. ",
"alerts": "Uyarılar ",
"detections": "Tespitler "
},
"reviewClassification": {
"title": "Sınıflandırmayı İncele",
"desc": "Frigate, inceleme öğelerini Uyarılar ve Algılamalar olarak kategorilere ayırır. Varsayılan olarak, tüm <em>kişi</em> ve <em>araba</em> nesneleri Uyarı olarak kabul edilir. İnceleme öğelerinizin kategorilendirmesini, bunlar için gerekli bölgeleri yapılandırarak iyileştirebilirsiniz.",
"noDefinedZones": "Bu kamera için herhangi bir bölge tanımlanmamıştır.",
"objectAlertsTips": "{{cameraName}} üzerindeki tüm {{alertsLabels}} nesneleri Uyarılar olarak gösterilecektir.",
"zoneObjectAlertsTips": "{{cameraName}} üzerinde, {{zone}} bölgesinde tespit edilen tüm {{alertsLabels}} nesneleri Uyarılar olarak gösterilecektir.",
"objectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, hangi bölgede olursa olsun Tespitler olarak gösterilecektir.",
"zoneObjectDetectionsTips": {
"text": "{{cameraName}} üzerindeki {{zone}} bölgesinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, Tespitler olarak gösterilecektir.",
"notSelectDetections": "{{cameraName}} üzerinde {{zone}} bölgesinde tespit edilen ve Uyarı olarak kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, hangi bölgede olurlarsa olsunlar Tespitler olarak gösterilecektir.",
"regardlessOfZoneObjectDetectionsTips": "{{cameraName}} üzerinde kategorize edilmemiş tüm {{detectionsLabels}} nesneleri, bulundukları bölgeden bağımsız olarak Tespitler (Detections) olarak gösterilecektir."
},
"unsavedChanges": "{{camera}} için Kaydedilmemiş İnceleme Sınıflandırması ayarları",
"selectAlertsZones": "Uyarılar için bölgeleri seçin",
"selectDetectionsZones": "Tespitler için bölgeleri seçin",
"limitDetections": "Tespitleri belirli bölgelerle sınırlayın",
"toast": {
"success": "Sınıflandırma yapılandırması kaydedildi. Değişiklikleri uygulamak için Frigate'i yeniden başlatın."
}
}
},
"roles": {
"management": {
"title": "İzleyici Rol Yönetimi",
"desc": "Bu Frigate örneği için özel görüntüleyici rollerini ve kamera erişim izinlerini yönetin."
},
"addRole": "Rol Ekle",
"table": {
"role": "Rol",
"cameras": "Kameralar",
"actions": "Eylemler",
"noRoles": "Özel rol bulunamadı.",
"editCameras": "Kameraları Düzenle",
"deleteRole": "Rolü Sil"
},
"toast": {
"success": {
"createRole": "{{role}} rolü başarıyla oluşturuldu",
"updateCameras": "{{role}} rolü için kameralar güncellendi",
"deleteRole": "{{role}} rolü başarıyla silindi",
"userRolesUpdated_one": "Bu role atanan {{count}} kullanıcı, tüm kameralara erişimi olan 'görüntüleyici' olarak güncellendi.",
"userRolesUpdated_other": "Bu role atanan {{count}} kullanıcı, tüm kameralara erişimi olan 'görüntüleyici' olarak güncellendi."
},
"error": {
"createRoleFailed": "Rol oluşturulamadı: {{errorMessage}}",
"updateCamerasFailed": "Kameralar güncellenemedi: {{errorMessage}}",
"deleteRoleFailed": "Rol silinemedi: {{errorMessage}}",
"userUpdateFailed": "Kullanıcı rolleri güncellenemedi: {{errorMessage}}"
}
},
"dialog": {
"createRole": {
"title": "Yeni Rol Oluştur",
"desc": "Yeni bir rol ekleyin ve kamera erişim izinlerini belirtin."
},
"editCameras": {
"title": "Rol Kameralarını Düzenle",
"desc": "<strong>{{role}}</strong> rolü için kamera erişimini güncelleyin."
},
"deleteRole": {
"title": "Rolü Sil",
"desc": "Bu işlem geri alınamaz. Bu işlem, rolü kalıcı olarak silecek ve bu role sahip tüm kullanıcıları 'izleyici' rolüne atayacaktır. Bu rol, izleyiciye tüm kameralara erişim sağlayacaktır.",
"warn": "<strong>{{role}}</strong> rolünü silmek istediğinizden emin misiniz?",
"deleting": "Siliniyor..."
},
"form": {
"role": {
"title": "Rol Adı",
"placeholder": "Rol adını girin",
"desc": "Sadece harf, rakam, nokta ve alt çizgi kullanılabilir.",
"roleIsRequired": "Rol adı gereklidir",
"roleOnlyInclude": "Rol adı yalnızca harf, sayı veya _ içerebilir",
"roleExists": "Bu isimde bir rol zaten mevcut."
},
"cameras": {
"title": "Kameralar",
"desc": "Bu rolün erişebileceği kameraları seçin. En az bir kamera gereklidir.",
"required": "En az bir kamera seçilmelidir."
}
}
}
}
}

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Моделі класифікації",
"documentTitle": "Моделі класифікації - Frigate",
"button": {
"deleteClassificationAttempts": "Видалити зображення класифікації",
"renameCategory": "Перейменувати клас",
@ -185,5 +185,6 @@
"noNewImages": "Немає нових зображень для навчання. Спочатку класифікуйте більше зображень у наборі даних.",
"modelNotReady": "Модель не готова до навчання",
"noChanges": "З моменту останнього навчання в наборі даних не було змін."
}
},
"none": "Жоден"
}

View File

@ -283,7 +283,7 @@
"millisecondsToOffset": "Мілісекунди для зміщення виявлених анотацій. <em>За замовчуванням: 0</em>",
"tips": "Зменште значення, якщо відтворення відео відбувається попереду блоків та точок шляху, і збільште значення, якщо відтворення відео відбувається позаду них. Це значення може бути від’ємним.",
"toast": {
"success": "Зміщення анотації для {{camera}} збережено у файлі конфігурації. Перезапустіть Frigate, щоб застосувати зміни."
"success": "Зміщення анотації для {{camera}} було збережено у файлі конфігурації."
}
}
},

View File

@ -142,8 +142,8 @@
"edit": "Редагувати маску руху",
"toast": {
"success": {
"title": "{{polygonName}} збережено. Перезапустіть Frigate, щоб застосувати зміни.",
"noName": "Маску руху збережено. Перезапустіть Frigate, щоб застосувати зміни."
"title": "{{polygonName}} збережено.",
"noName": "Маску руху збережено."
}
},
"label": "Маска руху",
@ -207,7 +207,7 @@
"desc": "Список об'єктів, що належать до цієї зони."
},
"toast": {
"success": "Зону ({{zoneName}}) збережено. Перезапустіть Frigate, щоб застосувати зміни."
"success": "Зону ({{zoneName}}) збережено."
}
},
"objectMasks": {
@ -230,8 +230,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} збережено. Перезапустіть Frigate, щоб застосувати зміни.",
"noName": "Маску об'єкта збережено. Перезапустіть Frigate, щоб застосувати зміни."
"title": "{{polygonName}} збережено.",
"noName": "Маску об'єкта збережено."
}
},
"label": "Маски об'єктів"
@ -558,7 +558,7 @@
"classification": "Налаштування класифікації Фрегат",
"masksAndZones": "Редактор масок та зон Фрегат",
"motionTuner": "Тюнер руху - Фрегат",
"general": "Налаштування інтерфейсу користувача - Frigate",
"general": "Основна Статус Frigate",
"frigatePlus": "Налаштування Frigate+ Frigate",
"enrichments": "Налаштуваннях збагачення Frigate",
"cameraManagement": "Керування камерами - Frigate",

View File

@ -1,5 +1,5 @@
{
"documentTitle": "分类模型",
"documentTitle": "分类模型 - Frigate",
"button": {
"deleteClassificationAttempts": "删除分类图片",
"renameCategory": "重命名类别",
@ -175,5 +175,6 @@
"noNewImages": "没有新的图片可用于训练。请先对数据集中的更多图片进行分类。",
"noChanges": "自上次训练以来,数据集未作任何更改。",
"modelNotReady": "模型尚未准备好进行训练"
}
},
"none": "无标签"
}

View File

@ -279,7 +279,7 @@
"millisecondsToOffset": "用于偏移检测标记的毫秒数。<em> 默认值0</em>",
"tips": "提示:假设有一段人从左向右走的事件录制,如果事件时间轴中的边框始终在人的左侧(即后方),则应该减小偏移值;反之,如果边框始终领先于人物,则应增大偏移值。",
"toast": {
"success": "{{camera}} 的标记偏移量已保存。请重启 Frigate 以应用更改。"
"success": "{{camera}} 的标记偏移量已保存。"
}
}
},

View File

@ -344,7 +344,7 @@
}
},
"toast": {
"success": "区域 ({{zoneName}}) 已保存。请重启 Frigate 以应用更改。"
"success": "区域 ({{zoneName}}) 已保存。"
}
},
"motionMasks": {
@ -369,8 +369,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} 已保存。请重启 Frigate 以应用更改。",
"noName": "画面变动遮罩已保存。请重启 Frigate 以应用更改。"
"title": "{{polygonName}} 已保存。",
"noName": "画面变动遮罩已保存。"
}
}
},
@ -393,8 +393,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} 已保存。请重启 Frigate 以应用更改。",
"noName": "目标遮罩已保存。请重启 Frigate 以应用更改。"
"title": "{{polygonName}} 已保存。",
"noName": "目标遮罩已保存。"
}
}
},

View File

@ -42,19 +42,27 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`;
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
const [passwordError, setPasswordError] = useState<string | null>(null);
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const Container = isDesktop ? DropdownMenu : Drawer;
const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
const Content = isDesktop ? DropdownMenuContent : DrawerContent;
const MenuItem = isDesktop ? DropdownMenuItem : DrawerClose;
const handlePasswordSave = async (password: string) => {
const handlePasswordSave = async (password: string, oldPassword?: string) => {
if (!profile?.username || profile.username === "anonymous") return;
setIsPasswordLoading(true);
axios
.put(`users/${profile.username}/password`, { password })
.put(`users/${profile.username}/password`, {
password,
old_password: oldPassword,
})
.then((response) => {
if (response.status === 200) {
setPasswordDialogOpen(false);
setPasswordError(null);
setIsPasswordLoading(false);
toast.success(t("users.toast.success.updatePassword"), {
position: "top-center",
});
@ -65,14 +73,10 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(
t("users.toast.error.setPasswordFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
// Keep dialog open and show error
setPasswordError(errorMessage);
setIsPasswordLoading(false);
});
};
@ -154,8 +158,13 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
<SetPasswordDialog
show={passwordDialogOpen}
onSave={handlePasswordSave}
onCancel={() => setPasswordDialogOpen(false)}
onCancel={() => {
setPasswordDialogOpen(false);
setPasswordError(null);
}}
initialError={passwordError}
username={profile?.username}
isLoading={isPasswordLoading}
/>
</Container>
);

View File

@ -116,13 +116,22 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
const SubItemContent = isDesktop ? DropdownMenuSubContent : DialogContent;
const Portal = isDesktop ? DropdownMenuPortal : DialogPortal;
const handlePasswordSave = async (password: string) => {
const [passwordError, setPasswordError] = useState<string | null>(null);
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const handlePasswordSave = async (password: string, oldPassword?: string) => {
if (!profile?.username || profile.username === "anonymous") return;
setIsPasswordLoading(true);
axios
.put(`users/${profile.username}/password`, { password })
.put(`users/${profile.username}/password`, {
password,
old_password: oldPassword,
})
.then((response) => {
if (response.status === 200) {
setPasswordDialogOpen(false);
setPasswordError(null);
setIsPasswordLoading(false);
toast.success(
t("users.toast.success.updatePassword", {
ns: "views/settings",
@ -138,15 +147,10 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(
t("users.toast.error.setPasswordFailed", {
ns: "views/settings",
errorMessage,
}),
{
position: "top-center",
},
);
// Keep dialog open and show error
setPasswordError(errorMessage);
setIsPasswordLoading(false);
});
};
@ -554,8 +558,13 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<SetPasswordDialog
show={passwordDialogOpen}
onSave={handlePasswordSave}
onCancel={() => setPasswordDialogOpen(false)}
onCancel={() => {
setPasswordDialogOpen(false);
setPasswordError(null);
}}
initialError={passwordError}
username={profile?.username}
isLoading={isPasswordLoading}
/>
</>
);

View File

@ -1,8 +1,6 @@
"use client";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import {
Dialog,
DialogContent,
@ -11,71 +9,187 @@ import {
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Label } from "../ui/label";
import { LuCheck, LuX } from "react-icons/lu";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { LuCheck, LuX, LuEye, LuEyeOff, LuExternalLink } from "react-icons/lu";
import { useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import useSWR from "swr";
import { formatSecondsToDuration } from "@/utils/dateUtil";
import ActivityIndicator from "../indicators/activity-indicator";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
type SetPasswordProps = {
show: boolean;
onSave: (password: string) => void;
onSave: (password: string, oldPassword?: string) => void;
onCancel: () => void;
initialError?: string | null;
username?: string;
isLoading?: boolean;
};
export default function SetPasswordDialog({
show,
onSave,
onCancel,
initialError,
username,
isLoading = false,
}: SetPasswordProps) {
const { t } = useTranslation(["views/settings"]);
const [password, setPassword] = useState<string>("");
const [confirmPassword, setConfirmPassword] = useState<string>("");
const [passwordStrength, setPasswordStrength] = useState<number>(0);
const [error, setError] = useState<string | null>(null);
const { t } = useTranslation(["views/settings", "common"]);
const { getLocaleDocUrl } = useDocDomain();
// Reset state when dialog opens/closes
useEffect(() => {
if (show) {
setPassword("");
setConfirmPassword("");
setError(null);
}
}, [show]);
const { data: config } = useSWR("config");
const refreshSeconds: number | undefined =
config?.auth?.refresh_time ?? undefined;
const refreshTimeLabel = refreshSeconds
? formatSecondsToDuration(refreshSeconds)
: "30 minutes";
// Simple password strength calculation
useEffect(() => {
if (!password) {
setPasswordStrength(0);
return;
// visibility toggles for password fields
const [showOldPassword, setShowOldPassword] = useState<boolean>(false);
const [showPasswordVisible, setShowPasswordVisible] =
useState<boolean>(false);
const [showConfirmPassword, setShowConfirmPassword] =
useState<boolean>(false);
// Create form schema with conditional old password requirement
const formSchema = useMemo(() => {
const baseSchema = {
password: z
.string()
.min(8, t("users.dialog.form.password.requirements.length"))
.regex(/[A-Z]/, t("users.dialog.form.password.requirements.uppercase"))
.regex(/\d/, t("users.dialog.form.password.requirements.digit"))
.regex(
/[!@#$%^&*(),.?":{}|<>]/,
t("users.dialog.form.password.requirements.special"),
),
confirmPassword: z.string(),
};
if (username) {
return z
.object({
oldPassword: z
.string()
.min(1, t("users.dialog.passwordSetting.currentPasswordRequired")),
...baseSchema,
})
.refine((data) => data.password === data.confirmPassword, {
message: t("users.dialog.passwordSetting.doNotMatch"),
path: ["confirmPassword"],
});
} else {
return z
.object(baseSchema)
.refine((data) => data.password === data.confirmPassword, {
message: t("users.dialog.passwordSetting.doNotMatch"),
path: ["confirmPassword"],
});
}
}, [username, t]);
type FormValues = z.infer<typeof formSchema>;
const defaultValues = username
? {
oldPassword: "",
password: "",
confirmPassword: "",
}
: {
password: "",
confirmPassword: "",
};
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
mode: "onChange",
defaultValues: defaultValues as FormValues,
});
const password = form.watch("password");
const confirmPassword = form.watch("confirmPassword");
// Password strength calculation
const passwordStrength = useMemo(() => {
if (!password) return 0;
let strength = 0;
// Length check
if (password.length >= 8) strength += 1;
// Contains number
if (/\d/.test(password)) strength += 1;
// Contains special char
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength += 1;
// Contains uppercase
if (/[A-Z]/.test(password)) strength += 1;
setPasswordStrength(strength);
return strength;
}, [password]);
const handleSave = () => {
if (!password) {
setError(t("users.dialog.passwordSetting.cannotBeEmpty"));
return;
}
const requirements = useMemo(
() => ({
length: password?.length >= 8,
uppercase: /[A-Z]/.test(password || ""),
digit: /\d/.test(password || ""),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password || ""),
}),
[password],
);
if (password !== confirmPassword) {
setError(t("users.dialog.passwordSetting.doNotMatch"));
return;
// Reset form and visibility toggles when dialog opens/closes
useEffect(() => {
if (show) {
form.reset();
setShowOldPassword(false);
setShowPasswordVisible(false);
setShowConfirmPassword(false);
}
}, [show, form]);
onSave(password);
// Handle backend errors
useEffect(() => {
if (show && initialError) {
const errorMsg = String(initialError);
// Check if the error is about incorrect current password
if (
errorMsg.toLowerCase().includes("current password is incorrect") ||
errorMsg.toLowerCase().includes("current password incorrect")
) {
if (username) {
form.setError("oldPassword" as keyof FormValues, {
type: "manual",
message: t("users.dialog.passwordSetting.incorrectCurrentPassword"),
});
}
} else {
// For other errors, show as form-level error
form.setError("root", {
type: "manual",
message: errorMsg,
});
}
}
}, [show, initialError, form, t, username]);
const onSubmit = async (values: FormValues) => {
const oldPassword =
"oldPassword" in values
? (
values as {
oldPassword: string;
password: string;
confirmPassword: string;
}
).oldPassword
: undefined;
onSave(values.password, oldPassword);
};
const getStrengthLabel = () => {
@ -112,113 +226,333 @@ export default function SetPasswordDialog({
<DialogDescription>
{t("users.dialog.passwordSetting.desc")}
</DialogDescription>
<p className="text-sm text-muted-foreground">
{t("users.dialog.passwordSetting.multiDeviceWarning", {
refresh_time: refreshTimeLabel,
ns: "views/settings",
})}
</p>
<p className="text-sm text-primary-variant">
<a
href={getLocaleDocUrl(
"configuration/authentication#jwt-token-secret",
)}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-primary"
>
{t("readTheDocumentation", { ns: "common" })}
<LuExternalLink className="ml-2 size-3" />
</a>
</p>
</DialogHeader>
<div className="space-y-4 pt-4">
<div className="space-y-2">
<Label htmlFor="password">
{t("users.dialog.form.newPassword.title")}
</Label>
<Input
id="password"
className="h-10"
type="password"
value={password}
onChange={(event) => {
setPassword(event.target.value);
setError(null);
}}
placeholder={t("users.dialog.form.newPassword.placeholder")}
autoFocus
/>
{/* Password strength indicator */}
{password && (
<div className="mt-2 space-y-1">
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-secondary-foreground">
<div
className={`${getStrengthColor()} transition-all duration-300`}
style={{ width: `${(passwordStrength / 3) * 100}%` }}
/>
</div>
<p className="text-xs text-muted-foreground">
{t("users.dialog.form.password.strength.title")}
<span className="font-medium">{getStrengthLabel()}</span>
</p>
</div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 pt-4"
>
{username && (
<FormField
control={form.control}
name={"oldPassword" as keyof FormValues}
render={({ field }) => (
<FormItem>
<FormLabel>
{t("users.dialog.form.currentPassword.title")}
</FormLabel>
<FormControl>
<div className="relative">
<Input
{...field}
type={showOldPassword ? "text" : "password"}
placeholder={t(
"users.dialog.form.currentPassword.placeholder",
)}
className="h-10 pr-10"
/>
<Button
type="button"
variant="ghost"
size="sm"
tabIndex={-1}
aria-label={
showOldPassword
? t("users.dialog.form.password.hide", {
ns: "views/settings",
})
: t("users.dialog.form.password.show", {
ns: "views/settings",
})
}
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowOldPassword(!showOldPassword)}
>
{showOldPassword ? (
<LuEyeOff className="size-4" />
) : (
<LuEye className="size-4" />
)}
</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">
{t("users.dialog.form.password.confirm.title")}
</Label>
<Input
id="confirm-password"
className="h-10"
type="password"
value={confirmPassword}
onChange={(event) => {
setConfirmPassword(event.target.value);
setError(null);
}}
placeholder={t(
"users.dialog.form.newPassword.confirm.placeholder",
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("users.dialog.form.newPassword.title")}
</FormLabel>
<FormControl>
<div className="relative">
<Input
{...field}
type={showPasswordVisible ? "text" : "password"}
placeholder={t(
"users.dialog.form.newPassword.placeholder",
)}
className="h-10 pr-10"
autoFocus
/>
<Button
type="button"
variant="ghost"
size="sm"
tabIndex={-1}
aria-label={
showPasswordVisible
? t("users.dialog.form.password.hide", {
ns: "views/settings",
})
: t("users.dialog.form.password.show", {
ns: "views/settings",
})
}
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() =>
setShowPasswordVisible(!showPasswordVisible)
}
>
{showPasswordVisible ? (
<LuEyeOff className="size-4" />
) : (
<LuEye className="size-4" />
)}
</Button>
</div>
</FormControl>
{password && (
<div className="mt-2 space-y-2">
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-secondary-foreground">
<div
className={`${getStrengthColor()} transition-all duration-300`}
style={{ width: `${(passwordStrength / 4) * 100}%` }}
/>
</div>
<p className="text-xs text-muted-foreground">
{t("users.dialog.form.password.strength.title")}
<span className="font-medium">
{getStrengthLabel()}
</span>
</p>
<div className="space-y-1 rounded-md bg-muted/50 p-2">
<p className="text-xs font-medium text-muted-foreground">
{t("users.dialog.form.password.requirements.title")}
</p>
<ul className="space-y-1">
<li className="flex items-center gap-2 text-xs">
{requirements.length ? (
<LuCheck className="size-3.5 text-green-500" />
) : (
<LuX className="size-3.5 text-red-500" />
)}
<span
className={
requirements.length
? "text-green-600"
: "text-red-600"
}
>
{t(
"users.dialog.form.password.requirements.length",
)}
</span>
</li>
<li className="flex items-center gap-2 text-xs">
{requirements.uppercase ? (
<LuCheck className="size-3.5 text-green-500" />
) : (
<LuX className="size-3.5 text-red-500" />
)}
<span
className={
requirements.uppercase
? "text-green-600"
: "text-red-600"
}
>
{t(
"users.dialog.form.password.requirements.uppercase",
)}
</span>
</li>
<li className="flex items-center gap-2 text-xs">
{requirements.digit ? (
<LuCheck className="size-3.5 text-green-500" />
) : (
<LuX className="size-3.5 text-red-500" />
)}
<span
className={
requirements.digit
? "text-green-600"
: "text-red-600"
}
>
{t(
"users.dialog.form.password.requirements.digit",
)}
</span>
</li>
<li className="flex items-center gap-2 text-xs">
{requirements.special ? (
<LuCheck className="size-3.5 text-green-500" />
) : (
<LuX className="size-3.5 text-red-500" />
)}
<span
className={
requirements.special
? "text-green-600"
: "text-red-600"
}
>
{t(
"users.dialog.form.password.requirements.special",
)}
</span>
</li>
</ul>
</div>
</div>
)}
<FormMessage />
</FormItem>
)}
/>
{/* Password match indicator */}
{password && confirmPassword && (
<div className="mt-1 flex items-center gap-1.5 text-xs">
{password === confirmPassword ? (
<>
<LuCheck className="size-3.5 text-green-500" />
<span className="text-green-600">
{t("users.dialog.form.password.match")}
</span>
</>
) : (
<>
<LuX className="size-3.5 text-red-500" />
<span className="text-red-600">
{t("users.dialog.form.password.notMatch")}
</span>
</>
)}
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("users.dialog.form.password.confirm.title")}
</FormLabel>
<FormControl>
<div className="relative">
<Input
{...field}
type={showConfirmPassword ? "text" : "password"}
placeholder={t(
"users.dialog.form.newPassword.confirm.placeholder",
)}
className="h-10 pr-10"
/>
<Button
type="button"
variant="ghost"
size="sm"
tabIndex={-1}
aria-label={
showConfirmPassword
? t("users.dialog.form.password.hide", {
ns: "views/settings",
})
: t("users.dialog.form.password.show", {
ns: "views/settings",
})
}
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() =>
setShowConfirmPassword(!showConfirmPassword)
}
>
{showConfirmPassword ? (
<LuEyeOff className="size-4" />
) : (
<LuEye className="size-4" />
)}
</Button>
</div>
</FormControl>
{password &&
confirmPassword &&
password === confirmPassword && (
<div className="mt-1 flex items-center gap-1.5 text-xs">
<LuCheck className="size-3.5 text-green-500" />
<span className="text-green-600">
{t("users.dialog.form.password.match")}
</span>
</div>
)}
<FormMessage />
</FormItem>
)}
/>
{form.formState.errors.root && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{form.formState.errors.root.message}
</div>
)}
</div>
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
</div>
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel}
type="button"
>
{t("button.cancel", { ns: "common" })}
</Button>
<Button
variant="select"
aria-label={t("button.save", { ns: "common" })}
className="flex flex-1"
onClick={handleSave}
disabled={!password || password !== confirmPassword}
>
{t("button.save", { ns: "common" })}
</Button>
</div>
</div>
</DialogFooter>
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel}
type="button"
disabled={isLoading}
>
{t("button.cancel", { ns: "common" })}
</Button>
<Button
variant="select"
aria-label={t("button.save", { ns: "common" })}
className="flex flex-1"
type="submit"
disabled={isLoading || !form.formState.isValid}
>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>{t("button.saving", { ns: "common" })}</span>
</div>
) : (
t("button.save", { ns: "common" })
)}
</Button>
</div>
</div>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);

View File

@ -57,6 +57,8 @@ export default function AuthenticationView({
const [showCreateRole, setShowCreateRole] = useState(false);
const [showEditRole, setShowEditRole] = useState(false);
const [showDeleteRole, setShowDeleteRole] = useState(false);
const [passwordError, setPasswordError] = useState<string | null>(null);
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const [selectedUser, setSelectedUser] = useState<string>();
const [selectedUserRole, setSelectedUserRole] = useState<string>();
@ -70,12 +72,15 @@ export default function AuthenticationView({
}, [t]);
const onSavePassword = useCallback(
(user: string, password: string) => {
(user: string, password: string, oldPassword?: string) => {
setIsPasswordLoading(true);
axios
.put(`users/${user}/password`, { password })
.put(`users/${user}/password`, { password, old_password: oldPassword })
.then((response) => {
if (response.status === 200) {
setShowSetPassword(false);
setPasswordError(null);
setIsPasswordLoading(false);
toast.success(t("users.toast.success.updatePassword"), {
position: "top-center",
});
@ -86,14 +91,10 @@ export default function AuthenticationView({
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(
t("users.toast.error.setPasswordFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
// Keep dialog open and show error
setPasswordError(errorMessage);
setIsPasswordLoading(false);
});
},
[t],
@ -563,8 +564,15 @@ export default function AuthenticationView({
</div>
<SetPasswordDialog
show={showSetPassword}
onCancel={() => setShowSetPassword(false)}
onSave={(password) => onSavePassword(selectedUser!, password)}
onCancel={() => {
setShowSetPassword(false);
setPasswordError(null);
}}
initialError={passwordError}
onSave={(password, oldPassword) =>
onSavePassword(selectedUser!, password, oldPassword)
}
isLoading={isPasswordLoading}
/>
<DeleteUserDialog
show={showDelete}