mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-07 14:05:28 +03:00
Compare commits
5 Commits
d56eae4e23
...
065e4846ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065e4846ea | ||
|
|
a182385618 | ||
|
|
088e1ad7ef | ||
|
|
011ad8eda7 | ||
|
|
ed990ab6a3 |
@ -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
|
||||
|
||||
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 -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 libstdc++-12-dev && \
|
||||
apt-get install -qq -y -t trixie libstdc++-14-dev && \
|
||||
rm -f /etc/apt/sources.list.d/trixie.list && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/frigate
|
||||
|
||||
500
docs/package-lock.json
generated
500
docs/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/plugin-content-docs": "^3.7.0",
|
||||
"@docusaurus/plugin-content-docs": "^3.10.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@inkeep/docusaurus": "^2.0.16",
|
||||
@ -3708,20 +3708,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
|
||||
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz",
|
||||
"integrity": "sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/logger": "3.9.2",
|
||||
"@docusaurus/mdx-loader": "3.9.2",
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/theme-common": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2",
|
||||
"@docusaurus/utils": "3.9.2",
|
||||
"@docusaurus/utils-common": "3.9.2",
|
||||
"@docusaurus/utils-validation": "3.9.2",
|
||||
"@docusaurus/core": "3.10.0",
|
||||
"@docusaurus/logger": "3.10.0",
|
||||
"@docusaurus/mdx-loader": "3.10.0",
|
||||
"@docusaurus/module-type-aliases": "3.10.0",
|
||||
"@docusaurus/theme-common": "3.10.0",
|
||||
"@docusaurus/types": "3.10.0",
|
||||
"@docusaurus/utils": "3.10.0",
|
||||
"@docusaurus/utils-common": "3.10.0",
|
||||
"@docusaurus/utils-validation": "3.10.0",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
@ -3740,6 +3740,355 @@
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-docs/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/plugin-content-pages": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz",
|
||||
@ -3935,6 +4284,39 @@
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
|
||||
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/logger": "3.9.2",
|
||||
"@docusaurus/mdx-loader": "3.9.2",
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/theme-common": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2",
|
||||
"@docusaurus/utils": "3.9.2",
|
||||
"@docusaurus/utils-common": "3.9.2",
|
||||
"@docusaurus/utils-validation": "3.9.2",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"schema-dts": "^1.1.2",
|
||||
"tslib": "^2.6.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"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-classic": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz",
|
||||
@ -3975,6 +4357,39 @@
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
|
||||
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/logger": "3.9.2",
|
||||
"@docusaurus/mdx-loader": "3.9.2",
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/theme-common": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2",
|
||||
"@docusaurus/utils": "3.9.2",
|
||||
"@docusaurus/utils-common": "3.9.2",
|
||||
"@docusaurus/utils-validation": "3.9.2",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"schema-dts": "^1.1.2",
|
||||
"tslib": "^2.6.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"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-common": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz",
|
||||
@ -4062,6 +4477,39 @@
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-search-algolia/node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
|
||||
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/logger": "3.9.2",
|
||||
"@docusaurus/mdx-loader": "3.9.2",
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/theme-common": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2",
|
||||
"@docusaurus/utils": "3.9.2",
|
||||
"@docusaurus/utils-common": "3.9.2",
|
||||
"@docusaurus/utils-validation": "3.9.2",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"schema-dts": "^1.1.2",
|
||||
"tslib": "^2.6.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"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-translations": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz",
|
||||
@ -18811,9 +19259,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-loadable-ssr-addon-v5-slorber": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz",
|
||||
"integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz",
|
||||
"integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.3"
|
||||
@ -20594,24 +21042,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serve-handler": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
|
||||
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
|
||||
"integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.0.0",
|
||||
"content-disposition": "0.5.2",
|
||||
"mime-types": "2.1.18",
|
||||
"minimatch": "3.1.2",
|
||||
"minimatch": "3.1.5",
|
||||
"path-is-inside": "1.0.2",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"range-parser": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-handler/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@ -20640,9 +21088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serve-handler/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/plugin-content-docs": "^3.7.0",
|
||||
"@docusaurus/plugin-content-docs": "^3.10.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@inkeep/docusaurus": "^2.0.16",
|
||||
|
||||
@ -5,13 +5,15 @@ import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import zipfile
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import Iterator, List, Optional
|
||||
|
||||
import psutil
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pathvalidate import sanitize_filepath
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from pathvalidate import sanitize_filename, sanitize_filepath
|
||||
from peewee import DoesNotExist
|
||||
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(
|
||||
"/cases/{case_id}",
|
||||
response_model=GenericResponse,
|
||||
|
||||
@ -20,6 +20,7 @@ class CameraConfigUpdateEnum(str, Enum):
|
||||
ffmpeg = "ffmpeg"
|
||||
live = "live"
|
||||
motion = "motion" # includes motion and motion masks
|
||||
mqtt = "mqtt"
|
||||
notifications = "notifications"
|
||||
objects = "objects"
|
||||
object_genai = "object_genai"
|
||||
@ -33,6 +34,7 @@ class CameraConfigUpdateEnum(str, Enum):
|
||||
lpr = "lpr"
|
||||
snapshots = "snapshots"
|
||||
timestamp_style = "timestamp_style"
|
||||
ui = "ui"
|
||||
zones = "zones"
|
||||
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ class ReviewMetadata(BaseModel):
|
||||
)
|
||||
title: str = Field(
|
||||
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(
|
||||
min_length=150,
|
||||
@ -36,7 +36,7 @@ class ReviewMetadata(BaseModel):
|
||||
)
|
||||
shortSummary: str = Field(
|
||||
min_length=70,
|
||||
max_length=100,
|
||||
max_length=120,
|
||||
description="A brief 2-sentence summary of the scene, suitable for notifications.",
|
||||
)
|
||||
confidence: float = Field(
|
||||
|
||||
@ -517,10 +517,16 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
try:
|
||||
event: Event = Event.get(Event.id == event_id)
|
||||
except DoesNotExist:
|
||||
for processor in self.post_processors:
|
||||
if isinstance(processor, ObjectDescriptionProcessor):
|
||||
processor.cleanup_event(event_id)
|
||||
continue
|
||||
|
||||
# Skip the event if not an object
|
||||
if event.data.get("type") != "object":
|
||||
for processor in self.post_processors:
|
||||
if isinstance(processor, ObjectDescriptionProcessor):
|
||||
processor.cleanup_event(event_id)
|
||||
continue
|
||||
|
||||
# Extract valid thumbnail
|
||||
|
||||
@ -205,6 +205,7 @@ class AudioEventMaintainer(threading.Thread):
|
||||
self.transcription_thread.start()
|
||||
|
||||
self.was_enabled = camera.enabled
|
||||
self.was_audio_enabled = camera.audio.enabled
|
||||
|
||||
def detect_audio(self, audio: np.ndarray) -> None:
|
||||
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)
|
||||
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()
|
||||
|
||||
if self.audio_listener:
|
||||
|
||||
@ -201,9 +201,10 @@ Each line represents a detection state, not necessarily unique individuals. The
|
||||
except json.JSONDecodeError as je:
|
||||
logger.error("Failed to parse review description JSON: %s", je)
|
||||
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.
|
||||
raw.setdefault("observations", [])
|
||||
raw.setdefault("confidence", 0.0)
|
||||
metadata = ReviewMetadata.model_construct(**raw)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
||||
@ -317,16 +317,16 @@ class CameraWatchdog(threading.Thread):
|
||||
if camera != self.config.name:
|
||||
continue
|
||||
|
||||
if 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.invalid.value):
|
||||
if topic.endswith(RecordingsDataTypeEnum.invalid.value):
|
||||
self.logger.warning(
|
||||
f"Invalid recording segment detected for {camera} at {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):
|
||||
if segment_time is not None:
|
||||
self.latest_cache_segment_time = segment_time
|
||||
|
||||
@ -57,6 +57,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||
import {
|
||||
LuDownload,
|
||||
LuFolderPlus,
|
||||
LuFolderX,
|
||||
LuPencil,
|
||||
@ -777,54 +778,76 @@ function Exports() {
|
||||
filters={["cameras"]}
|
||||
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
|
||||
asChild
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
aria-label={t("toolbar.addExport")}
|
||||
onClick={() => setCaseForAddExport(selectedCase)}
|
||||
aria-label={t("button.download", { ns: "common" })}
|
||||
>
|
||||
<LuPlus className="text-secondary-foreground" />
|
||||
{!isMobile && (
|
||||
<div className="text-primary">
|
||||
{t("toolbar.addExport")}
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
download
|
||||
href={`${baseUrl}api/cases/${selectedCase.id}/download`}
|
||||
>
|
||||
<LuDownload className="text-secondary-foreground" />
|
||||
{!isMobile && (
|
||||
<div className="text-primary">
|
||||
{t("button.download", { ns: "common" })}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
aria-label={t("toolbar.editCase")}
|
||||
onClick={() =>
|
||||
setCaseDialog({
|
||||
mode: "edit",
|
||||
exportCase: selectedCase,
|
||||
})
|
||||
}
|
||||
>
|
||||
<LuPencil className="text-secondary-foreground" />
|
||||
{!isMobile && (
|
||||
<div className="text-primary">
|
||||
{t("toolbar.editCase")}
|
||||
</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>
|
||||
)}
|
||||
)}
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Button
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
aria-label={t("toolbar.addExport")}
|
||||
onClick={() => setCaseForAddExport(selectedCase)}
|
||||
>
|
||||
<LuPlus className="text-secondary-foreground" />
|
||||
{!isMobile && (
|
||||
<div className="text-primary">
|
||||
{t("toolbar.addExport")}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
aria-label={t("toolbar.editCase")}
|
||||
onClick={() =>
|
||||
setCaseDialog({
|
||||
mode: "edit",
|
||||
exportCase: selectedCase,
|
||||
})
|
||||
}
|
||||
>
|
||||
<LuPencil className="text-secondary-foreground" />
|
||||
{!isMobile && (
|
||||
<div className="text-primary">
|
||||
{t("toolbar.editCase")}
|
||||
</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>
|
||||
)}
|
||||
</>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user