mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-07 14:05:28 +03:00
Compare commits
5 Commits
d317f975e5
...
42820f2094
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42820f2094 | ||
|
|
a182385618 | ||
|
|
088e1ad7ef | ||
|
|
011ad8eda7 | ||
|
|
d9458105d6 |
@ -32,11 +32,14 @@ RUN echo /opt/rocm/lib|tee /opt/rocm-dist/etc/ld.so.conf.d/rocm.conf
|
|||||||
FROM deps AS deps-prelim
|
FROM deps AS deps-prelim
|
||||||
|
|
||||||
COPY docker/rocm/debian-backports.sources /etc/apt/sources.list.d/debian-backports.sources
|
COPY docker/rocm/debian-backports.sources /etc/apt/sources.list.d/debian-backports.sources
|
||||||
RUN apt-get update && \
|
# install_deps.sh upgraded libstdc++6 from trixie for Battlemage; the matching
|
||||||
|
# -dev package must also come from trixie or apt refuses to satisfy it.
|
||||||
|
RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list && \
|
||||||
|
apt-get update && \
|
||||||
apt-get install -y libnuma1 && \
|
apt-get install -y libnuma1 && \
|
||||||
apt-get install -qq -y -t bookworm-backports mesa-va-drivers mesa-vulkan-drivers && \
|
apt-get install -qq -y -t bookworm-backports mesa-va-drivers mesa-vulkan-drivers && \
|
||||||
# Install C++ standard library headers for HIPRTC kernel compilation fallback
|
apt-get install -qq -y -t trixie libstdc++-14-dev && \
|
||||||
apt-get install -qq -y libstdc++-12-dev && \
|
rm -f /etc/apt/sources.list.d/trixie.list && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /opt/frigate
|
WORKDIR /opt/frigate
|
||||||
|
|||||||
393
docs/package-lock.json
generated
393
docs/package-lock.json
generated
@ -11,7 +11,7 @@
|
|||||||
"@docusaurus/core": "^3.7.0",
|
"@docusaurus/core": "^3.7.0",
|
||||||
"@docusaurus/plugin-content-docs": "^3.7.0",
|
"@docusaurus/plugin-content-docs": "^3.7.0",
|
||||||
"@docusaurus/preset-classic": "^3.7.0",
|
"@docusaurus/preset-classic": "^3.7.0",
|
||||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
"@docusaurus/theme-mermaid": "^3.10.0",
|
||||||
"@inkeep/docusaurus": "^2.0.16",
|
"@inkeep/docusaurus": "^2.0.16",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -4004,16 +4004,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docusaurus/theme-mermaid": {
|
"node_modules/@docusaurus/theme-mermaid": {
|
||||||
"version": "3.9.2",
|
"version": "3.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.0.tgz",
|
||||||
"integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==",
|
"integrity": "sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.9.2",
|
"@docusaurus/core": "3.10.0",
|
||||||
"@docusaurus/module-type-aliases": "3.9.2",
|
"@docusaurus/module-type-aliases": "3.10.0",
|
||||||
"@docusaurus/theme-common": "3.9.2",
|
"@docusaurus/theme-common": "3.10.0",
|
||||||
"@docusaurus/types": "3.9.2",
|
"@docusaurus/types": "3.10.0",
|
||||||
"@docusaurus/utils-validation": "3.9.2",
|
"@docusaurus/utils-validation": "3.10.0",
|
||||||
"mermaid": ">=11.6.0",
|
"mermaid": ">=11.6.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
@ -4031,6 +4031,355 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/babel": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.25.9",
|
||||||
|
"@babel/generator": "^7.25.9",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.25.9",
|
||||||
|
"@babel/preset-env": "^7.25.9",
|
||||||
|
"@babel/preset-react": "^7.25.9",
|
||||||
|
"@babel/preset-typescript": "^7.25.9",
|
||||||
|
"@babel/runtime": "^7.25.9",
|
||||||
|
"@babel/traverse": "^7.25.9",
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"tslib": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/bundler": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.25.9",
|
||||||
|
"@docusaurus/babel": "3.10.0",
|
||||||
|
"@docusaurus/cssnano-preset": "3.10.0",
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/types": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"babel-loader": "^9.2.1",
|
||||||
|
"clean-css": "^5.3.3",
|
||||||
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
|
"css-loader": "^6.11.0",
|
||||||
|
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||||
|
"cssnano": "^6.1.2",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"html-minifier-terser": "^7.2.0",
|
||||||
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
|
"null-loader": "^4.0.1",
|
||||||
|
"postcss": "^8.5.4",
|
||||||
|
"postcss-loader": "^7.3.4",
|
||||||
|
"postcss-preset-env": "^10.2.1",
|
||||||
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"url-loader": "^4.1.1",
|
||||||
|
"webpack": "^5.95.0",
|
||||||
|
"webpackbar": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@docusaurus/faster": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@docusaurus/faster": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/core": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/babel": "3.10.0",
|
||||||
|
"@docusaurus/bundler": "3.10.0",
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/mdx-loader": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"@docusaurus/utils-common": "3.10.0",
|
||||||
|
"@docusaurus/utils-validation": "3.10.0",
|
||||||
|
"boxen": "^6.2.1",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"cli-table3": "^0.6.3",
|
||||||
|
"combine-promises": "^1.1.0",
|
||||||
|
"commander": "^5.1.0",
|
||||||
|
"core-js": "^3.31.1",
|
||||||
|
"detect-port": "^1.5.1",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"eta": "^2.2.0",
|
||||||
|
"eval": "^0.1.8",
|
||||||
|
"execa": "^5.1.1",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"html-tags": "^3.3.1",
|
||||||
|
"html-webpack-plugin": "^5.6.0",
|
||||||
|
"leven": "^3.1.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"open": "^8.4.0",
|
||||||
|
"p-map": "^4.0.0",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
|
||||||
|
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0",
|
||||||
|
"react-loadable-ssr-addon-v5-slorber": "^1.0.3",
|
||||||
|
"react-router": "^5.3.4",
|
||||||
|
"react-router-config": "^5.1.1",
|
||||||
|
"react-router-dom": "^5.3.4",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"serve-handler": "^6.1.7",
|
||||||
|
"tinypool": "^1.0.2",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"update-notifier": "^6.0.2",
|
||||||
|
"webpack": "^5.95.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
|
"webpack-dev-server": "^5.2.2",
|
||||||
|
"webpack-merge": "^6.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"docusaurus": "bin/docusaurus.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@docusaurus/faster": "*",
|
||||||
|
"@mdx-js/react": "^3.0.0",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@docusaurus/faster": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/cssnano-preset": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cssnano-preset-advanced": "^6.1.2",
|
||||||
|
"postcss": "^8.5.4",
|
||||||
|
"postcss-sort-media-queries": "^5.2.0",
|
||||||
|
"tslib": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/logger": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"tslib": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/mdx-loader": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"@docusaurus/utils-validation": "3.10.0",
|
||||||
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
|
"@slorber/remark-comment": "^1.0.0",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"estree-util-value-to-estree": "^3.0.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"image-size": "^2.0.2",
|
||||||
|
"mdast-util-mdx": "^3.0.0",
|
||||||
|
"mdast-util-to-string": "^4.0.0",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
|
"remark-directive": "^3.0.0",
|
||||||
|
"remark-emoji": "^4.0.0",
|
||||||
|
"remark-frontmatter": "^5.0.0",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
|
"stringify-object": "^3.3.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"unified": "^11.0.3",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"url-loader": "^4.1.1",
|
||||||
|
"vfile": "^6.0.1",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/module-type-aliases": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/types": "3.10.0",
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-router-config": "*",
|
||||||
|
"@types/react-router-dom": "*",
|
||||||
|
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
|
||||||
|
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-dom": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/theme-common": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/mdx-loader": "3.10.0",
|
||||||
|
"@docusaurus/module-type-aliases": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"@docusaurus/utils-common": "3.10.0",
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-router-config": "*",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"parse-numeric-range": "^1.3.0",
|
||||||
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"utility-types": "^3.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@docusaurus/plugin-content-docs": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/types": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/mdast": "^4.0.2",
|
||||||
|
"@types/react": "*",
|
||||||
|
"commander": "^5.1.0",
|
||||||
|
"joi": "^17.9.2",
|
||||||
|
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
|
||||||
|
"utility-types": "^3.10.0",
|
||||||
|
"webpack": "^5.95.0",
|
||||||
|
"webpack-merge": "^5.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/types/node_modules/webpack-merge": {
|
||||||
|
"version": "5.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
|
||||||
|
"integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clone-deep": "^4.0.1",
|
||||||
|
"flat": "^5.0.2",
|
||||||
|
"wildcard": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/utils": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/types": "3.10.0",
|
||||||
|
"@docusaurus/utils-common": "3.10.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"execa": "^5.1.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"github-slugger": "^1.5.0",
|
||||||
|
"globby": "^11.1.0",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"jiti": "^1.20.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"micromatch": "^4.0.5",
|
||||||
|
"p-queue": "^6.6.2",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"resolve-pathname": "^3.0.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"url-loader": "^4.1.1",
|
||||||
|
"utility-types": "^3.10.0",
|
||||||
|
"webpack": "^5.88.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/utils-common": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/types": "3.10.0",
|
||||||
|
"tslib": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/utils-validation": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/logger": "3.10.0",
|
||||||
|
"@docusaurus/utils": "3.10.0",
|
||||||
|
"@docusaurus/utils-common": "3.10.0",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"joi": "^17.9.2",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"tslib": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@docusaurus/theme-search-algolia": {
|
"node_modules/@docusaurus/theme-search-algolia": {
|
||||||
"version": "3.9.2",
|
"version": "3.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz",
|
||||||
@ -18811,9 +19160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-loadable-ssr-addon-v5-slorber": {
|
"node_modules/react-loadable-ssr-addon-v5-slorber": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz",
|
||||||
"integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==",
|
"integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.10.3"
|
"@babel/runtime": "^7.10.3"
|
||||||
@ -20594,24 +20943,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-handler": {
|
"node_modules/serve-handler": {
|
||||||
"version": "6.1.6",
|
"version": "6.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
|
||||||
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
|
"integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.0.0",
|
"bytes": "3.0.0",
|
||||||
"content-disposition": "0.5.2",
|
"content-disposition": "0.5.2",
|
||||||
"mime-types": "2.1.18",
|
"mime-types": "2.1.18",
|
||||||
"minimatch": "3.1.2",
|
"minimatch": "3.1.5",
|
||||||
"path-is-inside": "1.0.2",
|
"path-is-inside": "1.0.2",
|
||||||
"path-to-regexp": "3.3.0",
|
"path-to-regexp": "3.3.0",
|
||||||
"range-parser": "1.2.0"
|
"range-parser": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-handler/node_modules/brace-expansion": {
|
"node_modules/serve-handler/node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@ -20640,9 +20989,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-handler/node_modules/minimatch": {
|
"node_modules/serve-handler/node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
"@docusaurus/core": "^3.7.0",
|
"@docusaurus/core": "^3.7.0",
|
||||||
"@docusaurus/plugin-content-docs": "^3.7.0",
|
"@docusaurus/plugin-content-docs": "^3.7.0",
|
||||||
"@docusaurus/preset-classic": "^3.7.0",
|
"@docusaurus/preset-classic": "^3.7.0",
|
||||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
"@docusaurus/theme-mermaid": "^3.10.0",
|
||||||
"@inkeep/docusaurus": "^2.0.16",
|
"@inkeep/docusaurus": "^2.0.16",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@ -5,13 +5,15 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
|
import zipfile
|
||||||
|
from collections import deque
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import Iterator, List, Optional
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends, Query, Request
|
from fastapi import APIRouter, Depends, Query, Request
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
from pathvalidate import sanitize_filepath
|
from pathvalidate import sanitize_filename, sanitize_filepath
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
@ -361,6 +363,136 @@ def get_export_case(case_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_ZIP_STREAM_CHUNK_SIZE = 1024 * 1024 # 1 MiB
|
||||||
|
|
||||||
|
|
||||||
|
class _StreamingZipBuffer:
|
||||||
|
"""File-like sink for ZipFile that exposes written bytes via drain().
|
||||||
|
|
||||||
|
ZipFile writes synchronously into this buffer; the generator drains the
|
||||||
|
queue between writes so StreamingResponse can yield bytes without
|
||||||
|
materializing the whole archive in memory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._queue: deque[bytes] = deque()
|
||||||
|
self._offset = 0
|
||||||
|
|
||||||
|
def write(self, data: bytes) -> int:
|
||||||
|
if data:
|
||||||
|
self._queue.append(bytes(data))
|
||||||
|
self._offset += len(data)
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def tell(self) -> int:
|
||||||
|
return self._offset
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def drain(self) -> Iterator[bytes]:
|
||||||
|
while self._queue:
|
||||||
|
yield self._queue.popleft()
|
||||||
|
|
||||||
|
|
||||||
|
def _unique_archive_name(export: Export, used: set[str]) -> str:
|
||||||
|
base = sanitize_filename(export.name) if export.name else None
|
||||||
|
if not base:
|
||||||
|
base = f"{export.camera}_{int(datetime.datetime.timestamp(export.date))}"
|
||||||
|
|
||||||
|
candidate = f"{base}.mp4"
|
||||||
|
counter = 1
|
||||||
|
while candidate in used:
|
||||||
|
candidate = f"{base}_{counter}.mp4"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
used.add(candidate)
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_case_archive(exports: List[Export]) -> Iterator[bytes]:
|
||||||
|
"""Yield bytes of a zip archive built from the given exports' mp4 files."""
|
||||||
|
buffer = _StreamingZipBuffer()
|
||||||
|
used_names: set[str] = set()
|
||||||
|
|
||||||
|
# ZIP_STORED: mp4 is already compressed, recompressing wastes CPU for ~0% size win.
|
||||||
|
with zipfile.ZipFile(
|
||||||
|
buffer,
|
||||||
|
mode="w",
|
||||||
|
compression=zipfile.ZIP_STORED,
|
||||||
|
allowZip64=True,
|
||||||
|
) as archive:
|
||||||
|
for export in exports:
|
||||||
|
source = Path(export.video_path)
|
||||||
|
if not source.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
arcname = _unique_archive_name(export, used_names)
|
||||||
|
|
||||||
|
with (
|
||||||
|
archive.open(arcname, mode="w", force_zip64=True) as entry,
|
||||||
|
source.open("rb") as src,
|
||||||
|
):
|
||||||
|
while True:
|
||||||
|
chunk = src.read(_ZIP_STREAM_CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
|
||||||
|
entry.write(chunk)
|
||||||
|
yield from buffer.drain()
|
||||||
|
|
||||||
|
yield from buffer.drain()
|
||||||
|
|
||||||
|
yield from buffer.drain()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/cases/{case_id}/download",
|
||||||
|
dependencies=[Depends(allow_any_authenticated())],
|
||||||
|
summary="Download export case as zip",
|
||||||
|
description="Streams a zip archive containing every completed export's mp4 for the given case.",
|
||||||
|
)
|
||||||
|
def download_export_case(
|
||||||
|
case_id: str,
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
case = ExportCase.get(ExportCase.id == case_id)
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(
|
||||||
|
content={"success": False, "message": "Export case not found"},
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
|
||||||
|
exports = list(
|
||||||
|
Export.select()
|
||||||
|
.where(
|
||||||
|
Export.export_case == case_id,
|
||||||
|
~Export.in_progress,
|
||||||
|
Export.camera << allowed_cameras,
|
||||||
|
)
|
||||||
|
.order_by(Export.date.asc())
|
||||||
|
)
|
||||||
|
|
||||||
|
if not exports:
|
||||||
|
return JSONResponse(
|
||||||
|
content={"success": False, "message": "No exports available to download."},
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
|
||||||
|
archive_base = sanitize_filename(case.name) if case.name else ""
|
||||||
|
if not archive_base:
|
||||||
|
archive_base = case_id
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
_stream_case_archive(exports),
|
||||||
|
media_type="application/zip",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f'attachment; filename="{archive_base}.zip"',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.patch(
|
@router.patch(
|
||||||
"/cases/{case_id}",
|
"/cases/{case_id}",
|
||||||
response_model=GenericResponse,
|
response_model=GenericResponse,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ class CameraConfigUpdateEnum(str, Enum):
|
|||||||
ffmpeg = "ffmpeg"
|
ffmpeg = "ffmpeg"
|
||||||
live = "live"
|
live = "live"
|
||||||
motion = "motion" # includes motion and motion masks
|
motion = "motion" # includes motion and motion masks
|
||||||
|
mqtt = "mqtt"
|
||||||
notifications = "notifications"
|
notifications = "notifications"
|
||||||
objects = "objects"
|
objects = "objects"
|
||||||
object_genai = "object_genai"
|
object_genai = "object_genai"
|
||||||
@ -33,6 +34,7 @@ class CameraConfigUpdateEnum(str, Enum):
|
|||||||
lpr = "lpr"
|
lpr = "lpr"
|
||||||
snapshots = "snapshots"
|
snapshots = "snapshots"
|
||||||
timestamp_style = "timestamp_style"
|
timestamp_style = "timestamp_style"
|
||||||
|
ui = "ui"
|
||||||
zones = "zones"
|
zones = "zones"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class ReviewMetadata(BaseModel):
|
|||||||
)
|
)
|
||||||
title: str = Field(
|
title: str = Field(
|
||||||
max_length=80,
|
max_length=80,
|
||||||
description="A short title characterizing what took place and where, under 10 words.",
|
description="Under 10 words. Name the apparent purpose or outcome of the activity together with the location involved. Do not narrate or list the sequence of actions step by step.",
|
||||||
)
|
)
|
||||||
scene: str = Field(
|
scene: str = Field(
|
||||||
min_length=150,
|
min_length=150,
|
||||||
@ -36,7 +36,7 @@ class ReviewMetadata(BaseModel):
|
|||||||
)
|
)
|
||||||
shortSummary: str = Field(
|
shortSummary: str = Field(
|
||||||
min_length=70,
|
min_length=70,
|
||||||
max_length=100,
|
max_length=120,
|
||||||
description="A brief 2-sentence summary of the scene, suitable for notifications.",
|
description="A brief 2-sentence summary of the scene, suitable for notifications.",
|
||||||
)
|
)
|
||||||
confidence: float = Field(
|
confidence: float = Field(
|
||||||
|
|||||||
@ -517,10 +517,16 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
|
for processor in self.post_processors:
|
||||||
|
if isinstance(processor, ObjectDescriptionProcessor):
|
||||||
|
processor.cleanup_event(event_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip the event if not an object
|
# Skip the event if not an object
|
||||||
if event.data.get("type") != "object":
|
if event.data.get("type") != "object":
|
||||||
|
for processor in self.post_processors:
|
||||||
|
if isinstance(processor, ObjectDescriptionProcessor):
|
||||||
|
processor.cleanup_event(event_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Extract valid thumbnail
|
# Extract valid thumbnail
|
||||||
|
|||||||
@ -205,6 +205,7 @@ class AudioEventMaintainer(threading.Thread):
|
|||||||
self.transcription_thread.start()
|
self.transcription_thread.start()
|
||||||
|
|
||||||
self.was_enabled = camera.enabled
|
self.was_enabled = camera.enabled
|
||||||
|
self.was_audio_enabled = camera.audio.enabled
|
||||||
|
|
||||||
def detect_audio(self, audio: np.ndarray) -> None:
|
def detect_audio(self, audio: np.ndarray) -> None:
|
||||||
if not self.camera_config.audio.enabled or self.stop_event.is_set():
|
if not self.camera_config.audio.enabled or self.stop_event.is_set():
|
||||||
@ -363,6 +364,17 @@ class AudioEventMaintainer(threading.Thread):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
audio_enabled = self.camera_config.audio.enabled
|
||||||
|
if audio_enabled != self.was_audio_enabled:
|
||||||
|
if not audio_enabled:
|
||||||
|
self.logger.debug(
|
||||||
|
f"Disabling audio detections for {self.camera_config.name}, ending events"
|
||||||
|
)
|
||||||
|
self.requestor.send_data(
|
||||||
|
EXPIRE_AUDIO_ACTIVITY, self.camera_config.name
|
||||||
|
)
|
||||||
|
self.was_audio_enabled = audio_enabled
|
||||||
|
|
||||||
self.read_audio()
|
self.read_audio()
|
||||||
|
|
||||||
if self.audio_listener:
|
if self.audio_listener:
|
||||||
|
|||||||
@ -201,9 +201,10 @@ Each line represents a detection state, not necessarily unique individuals. The
|
|||||||
except json.JSONDecodeError as je:
|
except json.JSONDecodeError as je:
|
||||||
logger.error("Failed to parse review description JSON: %s", je)
|
logger.error("Failed to parse review description JSON: %s", je)
|
||||||
return None
|
return None
|
||||||
# observations is required on the model; fill an empty default
|
# observations and confidence are required on the model; fill an empty default
|
||||||
# if the response omitted it so attribute access stays safe.
|
# if the response omitted it so attribute access stays safe.
|
||||||
raw.setdefault("observations", [])
|
raw.setdefault("observations", [])
|
||||||
|
raw.setdefault("confidence", 0.0)
|
||||||
metadata = ReviewMetadata.model_construct(**raw)
|
metadata = ReviewMetadata.model_construct(**raw)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@ -317,16 +317,16 @@ class CameraWatchdog(threading.Thread):
|
|||||||
if camera != self.config.name:
|
if camera != self.config.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if topic.endswith(RecordingsDataTypeEnum.valid.value):
|
if topic.endswith(RecordingsDataTypeEnum.invalid.value):
|
||||||
self.logger.debug(
|
|
||||||
f"Latest valid recording segment time on {camera}: {segment_time}"
|
|
||||||
)
|
|
||||||
self.latest_valid_segment_time = segment_time
|
|
||||||
elif topic.endswith(RecordingsDataTypeEnum.invalid.value):
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Invalid recording segment detected for {camera} at {segment_time}"
|
f"Invalid recording segment detected for {camera} at {segment_time}"
|
||||||
)
|
)
|
||||||
self.latest_invalid_segment_time = segment_time
|
self.latest_invalid_segment_time = segment_time
|
||||||
|
elif topic.endswith(RecordingsDataTypeEnum.valid.value):
|
||||||
|
self.logger.debug(
|
||||||
|
f"Latest valid recording segment time on {camera}: {segment_time}"
|
||||||
|
)
|
||||||
|
self.latest_valid_segment_time = segment_time
|
||||||
elif topic.endswith(RecordingsDataTypeEnum.latest.value):
|
elif topic.endswith(RecordingsDataTypeEnum.latest.value):
|
||||||
if segment_time is not None:
|
if segment_time is not None:
|
||||||
self.latest_cache_segment_time = segment_time
|
self.latest_cache_segment_time = segment_time
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||||
import {
|
import {
|
||||||
|
LuDownload,
|
||||||
LuFolderPlus,
|
LuFolderPlus,
|
||||||
LuFolderX,
|
LuFolderX,
|
||||||
LuPencil,
|
LuPencil,
|
||||||
@ -777,54 +778,76 @@ function Exports() {
|
|||||||
filters={["cameras"]}
|
filters={["cameras"]}
|
||||||
onUpdateFilter={setExportFilter}
|
onUpdateFilter={setExportFilter}
|
||||||
/>
|
/>
|
||||||
{isAdmin && (
|
<div className="flex items-center gap-1 md:gap-2">
|
||||||
<div className="flex items-center gap-1 md:gap-2">
|
{(exportsByCase[selectedCase.id]?.length ?? 0) > 0 && (
|
||||||
<Button
|
<Button
|
||||||
|
asChild
|
||||||
className="flex items-center gap-2 p-2"
|
className="flex items-center gap-2 p-2"
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label={t("toolbar.addExport")}
|
aria-label={t("button.download", { ns: "common" })}
|
||||||
onClick={() => setCaseForAddExport(selectedCase)}
|
|
||||||
>
|
>
|
||||||
<LuPlus className="text-secondary-foreground" />
|
<a
|
||||||
{!isMobile && (
|
download
|
||||||
<div className="text-primary">
|
href={`${baseUrl}api/cases/${selectedCase.id}/download`}
|
||||||
{t("toolbar.addExport")}
|
>
|
||||||
</div>
|
<LuDownload className="text-secondary-foreground" />
|
||||||
)}
|
{!isMobile && (
|
||||||
|
<div className="text-primary">
|
||||||
|
{t("button.download", { ns: "common" })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
)}
|
||||||
className="flex items-center gap-2 p-2"
|
{isAdmin && (
|
||||||
size="sm"
|
<>
|
||||||
aria-label={t("toolbar.editCase")}
|
<Button
|
||||||
onClick={() =>
|
className="flex items-center gap-2 p-2"
|
||||||
setCaseDialog({
|
size="sm"
|
||||||
mode: "edit",
|
aria-label={t("toolbar.addExport")}
|
||||||
exportCase: selectedCase,
|
onClick={() => setCaseForAddExport(selectedCase)}
|
||||||
})
|
>
|
||||||
}
|
<LuPlus className="text-secondary-foreground" />
|
||||||
>
|
{!isMobile && (
|
||||||
<LuPencil className="text-secondary-foreground" />
|
<div className="text-primary">
|
||||||
{!isMobile && (
|
{t("toolbar.addExport")}
|
||||||
<div className="text-primary">
|
</div>
|
||||||
{t("toolbar.editCase")}
|
)}
|
||||||
</div>
|
</Button>
|
||||||
)}
|
<Button
|
||||||
</Button>
|
className="flex items-center gap-2 p-2"
|
||||||
<Button
|
size="sm"
|
||||||
className="flex items-center gap-2 p-2"
|
aria-label={t("toolbar.editCase")}
|
||||||
size="sm"
|
onClick={() =>
|
||||||
aria-label={t("toolbar.deleteCase")}
|
setCaseDialog({
|
||||||
onClick={() => setCaseToDelete(selectedCase)}
|
mode: "edit",
|
||||||
>
|
exportCase: selectedCase,
|
||||||
<LuTrash2 className="text-secondary-foreground" />
|
})
|
||||||
{!isMobile && (
|
}
|
||||||
<div className="text-primary">
|
>
|
||||||
{t("toolbar.deleteCase")}
|
<LuPencil className="text-secondary-foreground" />
|
||||||
</div>
|
{!isMobile && (
|
||||||
)}
|
<div className="text-primary">
|
||||||
</Button>
|
{t("toolbar.editCase")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex items-center gap-2 p-2"
|
||||||
|
size="sm"
|
||||||
|
aria-label={t("toolbar.deleteCase")}
|
||||||
|
onClick={() => setCaseToDelete(selectedCase)}
|
||||||
|
>
|
||||||
|
<LuTrash2 className="text-secondary-foreground" />
|
||||||
|
{!isMobile && (
|
||||||
|
<div className="text-primary">
|
||||||
|
{t("toolbar.deleteCase")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user