mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-25 21:48:30 +03:00
Merge branch 'dev' into misc-fixes
This commit is contained in:
commit
b6ea89b820
@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
# Frigate NVR™ - 一个具有实时目标检测的本地 NVR
|
# Frigate NVR™ - 一个具有实时目标检测的本地 NVR
|
||||||
|
|
||||||
[English](https://github.com/blakeblackshear/frigate) | \[简体中文\]
|
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
<a href="https://hosted.weblate.org/engage/frigate-nvr/-/zh_Hans/">
|
<a href="https://hosted.weblate.org/engage/frigate-nvr/-/zh_Hans/">
|
||||||
<img src="https://hosted.weblate.org/widget/frigate-nvr/-/zh_Hans/svg-badge.svg" alt="翻译状态" />
|
<img src="https://hosted.weblate.org/widget/frigate-nvr/-/zh_Hans/svg-badge.svg" alt="翻译状态" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
[English](https://github.com/blakeblackshear/frigate) | \[简体中文\]
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
一个完整的本地网络视频录像机(NVR),专为[Home Assistant](https://www.home-assistant.io)设计,具备 AI 目标/物体检测功能。使用 OpenCV 和 TensorFlow 在本地为 IP 摄像头执行实时物体检测。
|
一个完整的本地网络视频录像机(NVR),专为[Home Assistant](https://www.home-assistant.io)设计,具备 AI 目标/物体检测功能。使用 OpenCV 和 TensorFlow 在本地为 IP 摄像头执行实时物体检测。
|
||||||
|
|
||||||
强烈推荐使用 GPU 或者 AI 加速器(例如[Google Coral 加速器](https://coral.ai/products/) 或者 [Hailo](https://hailo.ai/)等)。它们的运行效率远远高于现在的顶级 CPU,并且功耗也极低。
|
强烈推荐使用 GPU 或者 AI 加速器(例如[Google Coral 加速器](https://coral.ai/products/) 或者 [Hailo](https://hailo.ai/)等)。它们的运行效率远远高于现在的顶级 CPU,并且功耗也极低。
|
||||||
@ -38,6 +38,7 @@
|
|||||||
## 协议
|
## 协议
|
||||||
|
|
||||||
本项目采用 **MIT 许可证**授权。
|
本项目采用 **MIT 许可证**授权。
|
||||||
|
|
||||||
**代码部分**:本代码库中的源代码、配置文件和文档均遵循 [MIT 许可证](LICENSE)。您可以自由使用、修改和分发这些代码,但必须保留原始版权声明。
|
**代码部分**:本代码库中的源代码、配置文件和文档均遵循 [MIT 许可证](LICENSE)。您可以自由使用、修改和分发这些代码,但必须保留原始版权声明。
|
||||||
|
|
||||||
**商标部分**:“Frigate”名称、“Frigate NVR”品牌以及 Frigate 的 Logo 为 **Frigate LLC 的商标**,**不在** MIT 许可证覆盖范围内。
|
**商标部分**:“Frigate”名称、“Frigate NVR”品牌以及 Frigate 的 Logo 为 **Frigate LLC 的商标**,**不在** MIT 许可证覆盖范围内。
|
||||||
|
|||||||
@ -143,17 +143,6 @@ def require_admin_by_default():
|
|||||||
return admin_checker
|
return admin_checker
|
||||||
|
|
||||||
|
|
||||||
def _is_authenticated(request: Request) -> bool:
|
|
||||||
"""
|
|
||||||
Helper to determine if a request is from an authenticated user.
|
|
||||||
|
|
||||||
Returns True if the request has a valid authenticated user (not anonymous).
|
|
||||||
Port 5000 internal requests are considered anonymous despite having admin role.
|
|
||||||
"""
|
|
||||||
username = request.headers.get("remote-user")
|
|
||||||
return username is not None and username != "anonymous"
|
|
||||||
|
|
||||||
|
|
||||||
def allow_public():
|
def allow_public():
|
||||||
"""
|
"""
|
||||||
Override dependency to allow unauthenticated access to an endpoint.
|
Override dependency to allow unauthenticated access to an endpoint.
|
||||||
@ -173,27 +162,24 @@ def allow_public():
|
|||||||
|
|
||||||
def allow_any_authenticated():
|
def allow_any_authenticated():
|
||||||
"""
|
"""
|
||||||
Override dependency to allow any authenticated user (bypass admin requirement).
|
Override dependency to allow any request that passed through the /auth endpoint.
|
||||||
|
|
||||||
Allows:
|
Allows:
|
||||||
- Port 5000 internal requests (have admin role despite anonymous user)
|
- Port 5000 internal requests (remote-user: "anonymous", remote-role: "admin")
|
||||||
- Any authenticated user with a real username (not "anonymous")
|
- Authenticated users with JWT tokens (remote-user: username)
|
||||||
|
- Unauthenticated requests when auth is disabled (remote-user: "anonymous")
|
||||||
|
|
||||||
Rejects:
|
Rejects:
|
||||||
- Port 8971 requests with anonymous user (auth disabled, no proxy auth)
|
- Requests with no remote-user header (did not pass through /auth endpoint)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@router.get("/authenticated-endpoint", dependencies=[Depends(allow_any_authenticated())])
|
@router.get("/authenticated-endpoint", dependencies=[Depends(allow_any_authenticated())])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def auth_checker(request: Request):
|
async def auth_checker(request: Request):
|
||||||
# Port 5000 requests have admin role and should be allowed
|
# Ensure a remote-user has been set by the /auth endpoint
|
||||||
role = request.headers.get("remote-role")
|
username = request.headers.get("remote-user")
|
||||||
if role == "admin":
|
if username is None:
|
||||||
return
|
|
||||||
|
|
||||||
# Otherwise require a real authenticated user (not anonymous)
|
|
||||||
if not _is_authenticated(request):
|
|
||||||
raise HTTPException(status_code=401, detail="Authentication required")
|
raise HTTPException(status_code=401, detail="Authentication required")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": "Classification Models - Frigate",
|
"documentTitle": "Classification Models - Frigate",
|
||||||
"details": {
|
"details": {
|
||||||
"scoreInfo": "Score represents the average classification confidence across all detections of this object."
|
"scoreInfo": "Score represents the average classification confidence across all detections of this object.",
|
||||||
|
"none": "None",
|
||||||
|
"unknown": "Unknown"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"deleteClassificationAttempts": "Delete Classification Images",
|
"deleteClassificationAttempts": "Delete Classification Images",
|
||||||
@ -83,7 +85,6 @@
|
|||||||
"aria": "Select Recent Classifications"
|
"aria": "Select Recent Classifications"
|
||||||
},
|
},
|
||||||
"categories": "Classes",
|
"categories": "Classes",
|
||||||
"none": "None",
|
|
||||||
"createCategory": {
|
"createCategory": {
|
||||||
"new": "Create New Class"
|
"new": "Create New Class"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -161,7 +161,11 @@ export const ClassificationCard = forwardRef<
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="break-all smart-capitalize">
|
<div className="break-all smart-capitalize">
|
||||||
{data.name == "unknown" ? t("details.unknown") : data.name}
|
{data.name == "unknown"
|
||||||
|
? t("details.unknown")
|
||||||
|
: data.name == "none"
|
||||||
|
? t("details.none")
|
||||||
|
: data.name}
|
||||||
</div>
|
</div>
|
||||||
{data.score != undefined && (
|
{data.score != undefined && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export default function ClassificationSelectionDialog({
|
|||||||
onClick={() => onCategorizeImage(category)}
|
onClick={() => onCategorizeImage(category)}
|
||||||
>
|
>
|
||||||
{category === "none"
|
{category === "none"
|
||||||
? t("none")
|
? t("details.none")
|
||||||
: category.replaceAll("_", " ")}
|
: category.replaceAll("_", " ")}
|
||||||
</SelectorItem>
|
</SelectorItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -170,7 +170,9 @@ export function ClassFilterContent({
|
|||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
key={item}
|
key={item}
|
||||||
label={
|
label={
|
||||||
item === "none" ? t("none") : item.replaceAll("_", " ")
|
item === "none"
|
||||||
|
? t("details.none", { ns: "views/classificationModel" })
|
||||||
|
: item.replaceAll("_", " ")
|
||||||
}
|
}
|
||||||
isChecked={classes?.includes(item) ?? false}
|
isChecked={classes?.includes(item) ?? false}
|
||||||
onCheckedChange={(isChecked) => {
|
onCheckedChange={(isChecked) => {
|
||||||
|
|||||||
@ -707,7 +707,7 @@ function LibrarySelector({
|
|||||||
className="flex-grow cursor-pointer capitalize"
|
className="flex-grow cursor-pointer capitalize"
|
||||||
onClick={() => setPageToggle(id)}
|
onClick={() => setPageToggle(id)}
|
||||||
>
|
>
|
||||||
{id === "none" ? t("none") : id.replaceAll("_", " ")}
|
{id === "none" ? t("details.none") : id.replaceAll("_", " ")}
|
||||||
<span className="ml-2 text-muted-foreground">
|
<span className="ml-2 text-muted-foreground">
|
||||||
({dataset?.[id].length})
|
({dataset?.[id].length})
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user