diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e32e030..b953a1c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,29 @@
# Changelog
All notable changes to this project will be documented in this file.
+# v1.1.11 - 2022-04-23
+
+## Notable Changes
+- **Interface:**
+ - **Recordings:** The recordings section has been redesigned and now includes another list mode to view the recordings. The filter function has been redesigned.
+ - **Camera:** Added a new endpoint for direct streaming (`/cameras/:name/feed`)
+ - **Cameras:** The cameras section has been redesigned and now includes another list mode to view the cameras.
+ - **Notifications:** The filter function has been redesigned.
+ - **Console:** Added a new filter function
+ - **System:** Improved loading time (`npm`)
+- **Config:**
+ - Top level `"debug"` in config.json is deprecated now. Replaced with `"logLevel"`. Log Level: Show only defined log level. _(Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)_
+- **MQTT:** When motion is detected, the messages are now also published via MQTT to the topic configured under `Settings > Cameras > Notifications > MQTT Publish Topic`
+
+## Other Changes
+- Minor UI improvements
+- Minor logger improvements
+- Bump dependencies
+- Downgrade `ffmpeg-for-homebridge`
+
+## Bugfixes
+- Minor bugfixes
+
# v1.1.10 - 2022-04-17
## Other Changes
diff --git a/bin/camera.ui.js b/bin/camera.ui.js
index 833b0ec2..d3ec1008 100755
--- a/bin/camera.ui.js
+++ b/bin/camera.ui.js
@@ -17,7 +17,7 @@ import Interface from '../src/main.js';
let moduleName = 'camera.ui';
let globalInstalled = '1';
let sudoEnabled = '1';
-let debugEnabled = '0';
+let logLevel = '1';
let logTimestamps = '1';
let logColourful = '1';
let storagePath = path.resolve(os.homedir(), '.camera.ui');
@@ -27,7 +27,11 @@ const packageJson = fs.readJsonSync(path.resolve(__dirname, '../package.json'));
commander
.allowUnknownOption()
- .option('-D, --debug', 'Turn on debug level logging', () => (debugEnabled = '1'))
+ .option(
+ '-L, --log-level [level]',
+ 'Change Log Level: 1 = Info (Default), 2 = Debug, 3 = Warn, 4 = Error',
+ (l) => (logLevel = l)
+ )
.option('-C, --no-color', 'Disable color in logging', () => (logColourful = '0'))
.option('-T, --no-timestamp', 'Do not issue timestamps in logging', () => (logTimestamps = '0'))
.option('--no-sudo', 'Disable sudo for updating through ui', () => (sudoEnabled = '0'))
@@ -46,7 +50,7 @@ process.env.NTBA_FIX_350 = 1;
process.env.CUI_SERVICE_MODE = '1';
process.env.CUI_LOG_COLOR = logColourful;
-process.env.CUI_LOG_DEBUG = debugEnabled;
+process.env.CUI_LOG_MODE = logLevel;
process.env.CUI_LOG_TIMESTAMPS = logTimestamps;
process.env.CUI_BASE_PATH = path.resolve(__dirname, '../');
diff --git a/nodemon.json b/nodemon.json
index 36547192..aac66321 100644
--- a/nodemon.json
+++ b/nodemon.json
@@ -3,7 +3,7 @@
"verbose": true,
"watch": ["src"],
"ext": "ts,js,json",
- "exec": "node --trace-warnings bin/camera.ui.js -D -S ./test/camera.ui",
+ "exec": "node --trace-warnings bin/camera.ui.js -L 2 -S ./test/camera.ui",
"delay": "5000",
"signal": "SIGTERM"
}
diff --git a/package-lock.json b/package-lock.json
index af116256..c8e44d75 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "camera.ui",
- "version": "1.1.10",
+ "version": "1.1.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "camera.ui",
- "version": "1.1.10",
+ "version": "1.1.11",
"funding": [
{
"type": "paypal",
@@ -23,7 +23,7 @@
],
"license": "MIT",
"dependencies": {
- "@aws-sdk/client-rekognition": "^3.67.0",
+ "@aws-sdk/client-rekognition": "^3.72.0",
"@seydx/lowdb": "^3.0.2",
"alexa-remote2": "^4.1.2",
"axios": "^0.26.1",
@@ -34,20 +34,20 @@
"connect-history-api-fallback": "^1.6.0",
"cors": "^2.8.5",
"express": "^4.17.3",
- "ffmpeg-for-homebridge": "^0.1.0",
- "fs-extra": "^10.0.1",
- "ftp-srv": "^4.5.0",
+ "ffmpeg-for-homebridge": "^0.0.9",
+ "fs-extra": "^10.1.0",
+ "ftp-srv": "^4.6.0",
"got": "^12.0.3",
"helmet": "^5.0.2",
"ip": "^1.1.5",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"mailparser": "^3.4.0",
- "moment": "^2.29.2",
+ "moment": "^2.29.3",
"morgan": "^1.10.0",
"mqtt": "4.2.8",
"multer": "^1.4.4",
- "nanoid": "^3.3.2",
+ "nanoid": "^3.3.3",
"node-telegram-bot-api": "^0.56.0",
"nodejs-tcp-ping": "^1.0.3",
"os": "^0.1.2",
@@ -58,9 +58,9 @@
"smtp-server": "^3.10.0",
"socket.io": "4.4.1",
"socketio-jwt": "^4.6.2",
- "swagger-jsdoc": "^6.2.0",
+ "swagger-jsdoc": "^6.2.1",
"swagger-ui-express": "^4.3.0",
- "systeminformation": "^5.11.9",
+ "systeminformation": "^5.11.12",
"tar": "^6.1.11",
"web-push": "^3.4.5"
},
@@ -3899,11 +3899,14 @@
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"node_modules/detect-libc": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
- "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
"engines": {
- "node": ">=8"
+ "node": ">=0.10"
}
},
"node_modules/detect-newline": {
@@ -4070,11 +4073,11 @@
}
},
"node_modules/dotenv": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz",
- "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==",
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
+ "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"engines": {
- "node": ">=12"
+ "node": ">=10"
}
},
"node_modules/dtrace-provider": {
@@ -5126,16 +5129,16 @@
}
},
"node_modules/ffmpeg-for-homebridge": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/ffmpeg-for-homebridge/-/ffmpeg-for-homebridge-0.1.0.tgz",
- "integrity": "sha512-b1id7Th18dvAO4oXq8ec1jzcvOJKOIst539+lHU4X6Q3cU6KfWmFrO/mMab6O0qgy7BeTmS9Kuc2cTCsSgp4iQ==",
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/ffmpeg-for-homebridge/-/ffmpeg-for-homebridge-0.0.9.tgz",
+ "integrity": "sha512-HmUatYfEf0dI7kbSh2vGLkJCuY7YLJODPn64Ng/Fj8YjpPbrLFGQBzvdQFBUC6RgGpvqEdlgZNQR8N9wEZuziQ==",
"hasInstallScript": true,
"dependencies": {
- "detect-libc": "^2.0.1",
- "dotenv": "^16.0.0",
- "mkdirp": "^1.0.4",
- "simple-get": "^4.0.1",
- "tar": "^6.1.11"
+ "detect-libc": "^1.0.3",
+ "dotenv": "^8.2.0",
+ "mkdirp": "^1.0.3",
+ "simple-get": "^3.1.0",
+ "tar": "^6.0.1"
}
},
"node_modules/file-entry-cache": {
@@ -5346,9 +5349,9 @@
}
},
"node_modules/ftp-srv": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/ftp-srv/-/ftp-srv-4.5.0.tgz",
- "integrity": "sha512-AzUJwQqLGLndMPpVfGq54CzFZhd1ebFLNfiOatLtRSoNfmLpxQFnmpxwjdOsWChyDk7oWs0Pf5cDarlcFdgPIw==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/ftp-srv/-/ftp-srv-4.6.0.tgz",
+ "integrity": "sha512-eRwcKbnXfRYZTNoyb5e+p6osvOxeR9TQMxvnWcdr8u6OQQpbsAJl5/OHg8A2vL7hYJJxQeqjYf006R8qQoJK5w==",
"dependencies": {
"bluebird": "^3.5.1",
"bunyan": "^1.8.12",
@@ -8104,9 +8107,9 @@
"optional": true
},
"node_modules/nanoid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz",
- "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -9973,29 +9976,37 @@
]
},
"node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
+ "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"dependencies": {
- "decompress-response": "^6.0.0",
+ "decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
+ "node_modules/simple-get/node_modules/decompress-response": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+ "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+ "dependencies": {
+ "mimic-response": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/simple-get/node_modules/mimic-response": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+ "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -10475,9 +10486,9 @@
}
},
"node_modules/swagger-jsdoc": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.0.tgz",
- "integrity": "sha512-+37GcTwC1FaQeF/7EREeVzOv94RArhqMw0le8q4BZ+62/aPdJO3bQ5on/ULlucgXSN7vqkvjm4ObFO0W4Bs5aQ==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.1.tgz",
+ "integrity": "sha512-l2BwFf7wzNPb11+NRRy65X+kuHUGLb3ZuGFn6A8xDXXTu73YzJmCiy+LED/6QsOgPBPgO3u3sDEz6KuOHAlCtA==",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
@@ -10557,9 +10568,9 @@
"dev": true
},
"node_modules/systeminformation": {
- "version": "5.11.9",
- "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.11.9.tgz",
- "integrity": "sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA==",
+ "version": "5.11.12",
+ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.11.12.tgz",
+ "integrity": "sha512-4OFXesPSSkNiO6SO4L8DQ0Hj2j/fWPmucybnjQsr9BVTI+K3xH6i3zm8f3SyPZQdULOuskUdzerY0ffmmbWKhA==",
"os": [
"darwin",
"linux",
@@ -14413,9 +14424,9 @@
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"detect-libc": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
- "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"detect-newline": {
"version": "3.1.0",
@@ -14543,9 +14554,9 @@
}
},
"dotenv": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz",
- "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q=="
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
+ "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="
},
"dtrace-provider": {
"version": "0.8.8",
@@ -15342,15 +15353,15 @@
}
},
"ffmpeg-for-homebridge": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/ffmpeg-for-homebridge/-/ffmpeg-for-homebridge-0.1.0.tgz",
- "integrity": "sha512-b1id7Th18dvAO4oXq8ec1jzcvOJKOIst539+lHU4X6Q3cU6KfWmFrO/mMab6O0qgy7BeTmS9Kuc2cTCsSgp4iQ==",
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/ffmpeg-for-homebridge/-/ffmpeg-for-homebridge-0.0.9.tgz",
+ "integrity": "sha512-HmUatYfEf0dI7kbSh2vGLkJCuY7YLJODPn64Ng/Fj8YjpPbrLFGQBzvdQFBUC6RgGpvqEdlgZNQR8N9wEZuziQ==",
"requires": {
- "detect-libc": "^2.0.1",
- "dotenv": "^16.0.0",
- "mkdirp": "^1.0.4",
- "simple-get": "^4.0.1",
- "tar": "^6.1.11"
+ "detect-libc": "^1.0.3",
+ "dotenv": "^8.2.0",
+ "mkdirp": "^1.0.3",
+ "simple-get": "^3.1.0",
+ "tar": "^6.0.1"
}
},
"file-entry-cache": {
@@ -15502,9 +15513,9 @@
"optional": true
},
"ftp-srv": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/ftp-srv/-/ftp-srv-4.5.0.tgz",
- "integrity": "sha512-AzUJwQqLGLndMPpVfGq54CzFZhd1ebFLNfiOatLtRSoNfmLpxQFnmpxwjdOsWChyDk7oWs0Pf5cDarlcFdgPIw==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/ftp-srv/-/ftp-srv-4.6.0.tgz",
+ "integrity": "sha512-eRwcKbnXfRYZTNoyb5e+p6osvOxeR9TQMxvnWcdr8u6OQQpbsAJl5/OHg8A2vL7hYJJxQeqjYf006R8qQoJK5w==",
"requires": {
"bluebird": "^3.5.1",
"bunyan": "^1.8.12",
@@ -17582,9 +17593,9 @@
"optional": true
},
"nanoid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz",
- "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA=="
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w=="
},
"natural-compare": {
"version": "1.4.0",
@@ -18982,13 +18993,28 @@
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
},
"simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
+ "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"requires": {
- "decompress-response": "^6.0.0",
+ "decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
+ },
+ "dependencies": {
+ "decompress-response": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+ "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+ "requires": {
+ "mimic-response": "^2.0.0"
+ }
+ },
+ "mimic-response": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+ "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
+ }
}
},
"sisteransi": {
@@ -19369,9 +19395,9 @@
"dev": true
},
"swagger-jsdoc": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.0.tgz",
- "integrity": "sha512-+37GcTwC1FaQeF/7EREeVzOv94RArhqMw0le8q4BZ+62/aPdJO3bQ5on/ULlucgXSN7vqkvjm4ObFO0W4Bs5aQ==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.1.tgz",
+ "integrity": "sha512-l2BwFf7wzNPb11+NRRy65X+kuHUGLb3ZuGFn6A8xDXXTu73YzJmCiy+LED/6QsOgPBPgO3u3sDEz6KuOHAlCtA==",
"requires": {
"commander": "6.2.0",
"doctrine": "3.0.0",
@@ -19429,9 +19455,9 @@
"dev": true
},
"systeminformation": {
- "version": "5.11.9",
- "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.11.9.tgz",
- "integrity": "sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA=="
+ "version": "5.11.12",
+ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.11.12.tgz",
+ "integrity": "sha512-4OFXesPSSkNiO6SO4L8DQ0Hj2j/fWPmucybnjQsr9BVTI+K3xH6i3zm8f3SyPZQdULOuskUdzerY0ffmmbWKhA=="
},
"tar": {
"version": "6.1.11",
diff --git a/package.json b/package.json
index 5ff4517f..7a272fe5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "camera.ui",
- "version": "1.1.10",
+ "version": "1.1.11",
"description": "NVR like user interface for RTSP capable cameras.",
"author": "SeydX (https://github.com/SeydX/camera.ui)",
"scripts": {
@@ -16,7 +16,7 @@
},
"main": "src/index.js",
"dependencies": {
- "@aws-sdk/client-rekognition": "^3.67.0",
+ "@aws-sdk/client-rekognition": "^3.72.0",
"@seydx/lowdb": "^3.0.2",
"alexa-remote2": "^4.1.2",
"axios": "^0.26.1",
@@ -27,20 +27,20 @@
"connect-history-api-fallback": "^1.6.0",
"cors": "^2.8.5",
"express": "^4.17.3",
- "ffmpeg-for-homebridge": "^0.1.0",
- "fs-extra": "^10.0.1",
- "ftp-srv": "^4.5.0",
+ "ffmpeg-for-homebridge": "^0.0.9",
+ "fs-extra": "^10.1.0",
+ "ftp-srv": "^4.6.0",
"got": "^12.0.3",
"helmet": "^5.0.2",
"ip": "^1.1.5",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"mailparser": "^3.4.0",
- "moment": "^2.29.2",
+ "moment": "^2.29.3",
"morgan": "^1.10.0",
"mqtt": "4.2.8",
"multer": "^1.4.4",
- "nanoid": "^3.3.2",
+ "nanoid": "^3.3.3",
"node-telegram-bot-api": "^0.56.0",
"nodejs-tcp-ping": "^1.0.3",
"os": "^0.1.2",
@@ -51,9 +51,9 @@
"smtp-server": "^3.10.0",
"socket.io": "4.4.1",
"socketio-jwt": "^4.6.2",
- "systeminformation": "^5.11.9",
- "swagger-jsdoc": "^6.2.0",
+ "swagger-jsdoc": "^6.2.1",
"swagger-ui-express": "^4.3.0",
+ "systeminformation": "^5.11.12",
"tar": "^6.1.11",
"web-push": "^3.4.5"
},
diff --git a/src/api/app.js b/src/api/app.js
index 27d26e88..c1ca09ed 100644
--- a/src/api/app.js
+++ b/src/api/app.js
@@ -2,6 +2,7 @@
/* eslint-disable unicorn/prevent-abbreviations */
'use-strict';
+import chalk from 'chalk';
import cors from 'cors';
import fs from 'fs-extra';
import helmet from 'helmet';
@@ -83,14 +84,45 @@ export default class App {
);
app.use(
- morgan('dev', {
- skip: () => !options.debug,
- stream: {
- write: (a) => {
- log.debug(a.replace(/^\s+|\s+$/g, ''));
- },
+ morgan(
+ (tokens, req, res) => {
+ // eslint-disable-next-line unicorn/consistent-function-scoping
+ const headersSent = (res) => {
+ return typeof res.headersSent !== 'boolean' ? Boolean(res._header) : res.headersSent;
+ };
+
+ const status = headersSent(res) ? res.statusCode : undefined;
+
+ const color =
+ status >= 500
+ ? 'redBright'
+ : status >= 400
+ ? 'yellowBright'
+ : status >= 300
+ ? 'cyanBright'
+ : status >= 200
+ ? 'greenBright'
+ : 'gray';
+
+ return [
+ chalk.gray(tokens.method(req, res)),
+ chalk.gray(tokens.url(req, res)),
+ chalk[color](tokens.status(req, res)),
+ chalk.gray(tokens['response-time'](req, res)),
+ chalk.gray('ms'),
+ chalk.gray('-'),
+ chalk.gray(tokens.res(req, res, 'content-length') || ''),
+ ].join(' ');
},
- })
+ {
+ skip: () => !options.debug,
+ stream: {
+ write: (line) => {
+ log.debug(line.replace(/^\s+|\s+$/g, ''));
+ },
+ },
+ }
+ )
);
const backupUpload = multer({
@@ -139,7 +171,7 @@ export default class App {
})
);
- app.use(history({ index: 'index.html', verbose: options.debug }));
+ app.use(history({ index: 'index.html' }));
app.use(express.static(path.join(__dirname, '../../interface')));
return app;
diff --git a/src/api/components/cameras/cameras.routes.js b/src/api/components/cameras/cameras.routes.js
index 18ffd8c1..a741d962 100644
--- a/src/api/components/cameras/cameras.routes.js
+++ b/src/api/components/cameras/cameras.routes.js
@@ -121,6 +121,37 @@ export const routesConfig = (app) => {
CamerasController.removeAll,
]);
+ /**
+ * @swagger
+ * /api/cameras/{name}:
+ * get:
+ * tags: [Cameras]
+ * security:
+ * - bearerAuth: []
+ * summary: Get specific camera by name
+ * parameters:
+ * - in: path
+ * name: name
+ * schema:
+ * type: string
+ * required: true
+ * description: Name of the camera
+ * responses:
+ * 200:
+ * description: Successfull
+ * 401:
+ * description: Unauthorized
+ * 404:
+ * description: Not found
+ * 500:
+ * description: Internal server error
+ */
+ app.get('/api/cameras/:name/feed', [
+ ValidationMiddleware.validJWTNeeded,
+ PermissionMiddleware.minimumPermissionLevelRequired('cameras:access'),
+ CamerasController.getByName,
+ ]);
+
/**
* @swagger
* /api/cameras/{name}:
diff --git a/src/api/database.js b/src/api/database.js
index ca267807..10680fc7 100644
--- a/src/api/database.js
+++ b/src/api/database.js
@@ -119,6 +119,7 @@ const defaultCameraSettingsEntry = {
telegramType: 'Snapshot',
alexa: false,
webhookUrl: '',
+ mqttTopic: 'camera.ui/motion',
privacyMode: false,
camview: {
favourite: true,
@@ -477,6 +478,10 @@ export default class Database {
settings.webhookUrl = defaultCameraSettingsEntry.webhookUrl;
}
+ if (!settings.mqttTopic) {
+ settings.mqttTopic = defaultCameraSettingsEntry.mqttTopic;
+ }
+
if (typeof settings.privacyMode !== 'boolean') {
settings.privacyMode = defaultCameraSettingsEntry.privacyMode;
}
diff --git a/src/api/index.js b/src/api/index.js
index 98f55237..3c090b3c 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -14,7 +14,7 @@ import App from './app.js';
export default class Server {
constructor(controller) {
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: ConfigService.ui.version,
});
diff --git a/src/controller/camera/services/stream.service.js b/src/controller/camera/services/stream.service.js
index b2ed7d72..ffcb482f 100644
--- a/src/controller/camera/services/stream.service.js
+++ b/src/controller/camera/services/stream.service.js
@@ -101,7 +101,7 @@ export default class StreamService {
'-q',
'1',
'-max_muxing_queue_size',
- '9999',
+ '1024',
];
return {
diff --git a/src/controller/camera/utils/camera.utils.js b/src/controller/camera/utils/camera.utils.js
index 9fc65923..01bca9c9 100644
--- a/src/controller/camera/utils/camera.utils.js
+++ b/src/controller/camera/utils/camera.utils.js
@@ -264,9 +264,9 @@ export const startFFMPegFragmetedMP4Session = async (
'-movflags',
'frag_keyframe+empty_moov+default_base_moof',
'-max_muxing_queue_size',
- '9999',
- '-vsync',
- 'cfr',
+ '1024',
+ //'-vsync',
+ //'cfr',
'tcp://127.0.0.1:' + serverPort,
];
@@ -311,8 +311,8 @@ export const generateInputSource = (videoConfig, source) => {
inputSource = `-re ${inputSource}`;
}
- if (videoConfig.stimeout > 0 && !inputSource.includes('-timeout')) {
- inputSource = `-timeout ${videoConfig.stimeout * 10000000} ${inputSource}`;
+ if (videoConfig.stimeout > 0 && !inputSource.includes('-stimeout')) {
+ inputSource = `-stimeout ${videoConfig.stimeout * 10000000} ${inputSource}`;
}
if (videoConfig.maxDelay >= 0 && !inputSource.includes('-max_delay')) {
@@ -349,7 +349,7 @@ export const generateVideoConfig = (videoConfig) => {
config.maxBitrate = config.maxBitrate || 299;
config.vcodec = config.vcodec || 'libx264';
config.acodec = config.acodec || 'libfdk_aac';
- config.encoderOptions = config.encoderOptions || '-preset ultrafast -tune zerolatency';
+ config.encoderOptions = config.encoderOptions || '';
config.packetSize = config.packetSize || 1318;
return config;
diff --git a/src/controller/event/event.controller.js b/src/controller/event/event.controller.js
index 5f9afb49..f52c4379 100644
--- a/src/controller/event/event.controller.js
+++ b/src/controller/event/event.controller.js
@@ -31,12 +31,16 @@ const stringIsAValidUrl = (s) => {
};
export default class EventController {
+ static #controller;
static #movementHandler = {};
constructor(controller) {
- this.triggerEvent = EventController.handle;
+ EventController.#controller = controller;
+ EventController.#controller.on('uiMotion', (event) =>
+ EventController.handle(event.triggerType, event.cameraName, event.state)
+ );
- controller.on('uiMotion', (event) => EventController.handle(event.triggerType, event.cameraName, event.state));
+ this.triggerEvent = EventController.handle;
}
// eslint-disable-next-line no-unused-vars
@@ -102,6 +106,10 @@ export default class EventController {
endpoint: CameraSettings.webhookUrl,
};
+ const mqttPublishSettings = {
+ topic: CameraSettings.mqttTopic,
+ };
+
const webpushSettings = {
publicKey: SettingsDB.webpush.publicKey,
privateKey: SettingsDB.webpush.privateKey,
@@ -114,13 +122,14 @@ export default class EventController {
/*
* Movement Event flow
*
- * 1) If recording not enabled, send ui notification banner and webpush
+ * 1) Publish mqtt message
* 2) If webhook enabled, send webhook notification
- * 3) If alexa enabled, send notification to alexa
- * 4) If telegram enabled and type = "Text" for the camera, send telegram notification
- * 5) Handle recording (Snapshot/Video)
- * 6) If recording enabled, send ui notification banner with media and webpush
- * 7) If telegram enabled and type = "Snapshot" or "Video" for the camera, send additional telegram notification
+ * 3) If recording not enabled, send ui notification banner and webpush
+ * 4) If alexa enabled, send notification to alexa
+ * 5) If telegram enabled and type = "Text" for the camera, send telegram notification
+ * 6) Handle recording (Snapshot/Video)
+ * 7) If recording enabled, send ui notification banner with media and webpush
+ * 8) If telegram enabled and type = "Snapshot" or "Video" for the camera, send additional telegram notification
*/
log.debug(`New ${trigger} alert`, cameraName);
@@ -134,7 +143,7 @@ export default class EventController {
}*/
if (fileBuffer) {
- motionInfo.label = 'no label';
+ motionInfo.label = trigger || 'no label';
motionInfo.type = type || 'Video';
}
@@ -156,6 +165,17 @@ export default class EventController {
const { notification, notify } = await EventController.#handleNotification(motionInfo);
// 1)
+ await EventController.#publishMqtt(cameraName, notification, mqttPublishSettings);
+
+ // 2)
+ await EventController.#sendWebhook(
+ cameraName,
+ notification,
+ webhookSettings,
+ notificationsSettings.active
+ );
+
+ // 3)
if (notificationsSettings.active && !recordingSettings.active) {
log.notify(notify);
@@ -167,18 +187,10 @@ export default class EventController {
);
}
- // 2)
- await EventController.#sendWebhook(
- cameraName,
- notification,
- webhookSettings,
- notificationsSettings.active
- );
-
- // 3)
+ // 4)
await EventController.#sendAlexa(cameraName, alexaSettings, notificationsSettings.active);
- // 4)
+ // 5)
if (telegramSettings.type === 'Text') {
await EventController.#sendTelegram(
cameraName,
@@ -191,10 +203,10 @@ export default class EventController {
);
}
- // 5)
+ // 6)
await EventController.#handleRecording(cameraName, motionInfo, fileBuffer, recordingSettings.active);
- // 6)
+ // 7)
if (notificationsSettings.active && recordingSettings.active) {
log.notify(notify);
@@ -206,7 +218,7 @@ export default class EventController {
);
}
- // 7)
+ // 8)
if (
(telegramSettings.type === 'Text + Snapshot' ||
telegramSettings.type === 'Snapshot' ||
@@ -586,6 +598,25 @@ export default class EventController {
}
}
+ static async #publishMqtt(cameraName, notification, mqttPublishSettings) {
+ try {
+ const mqttClient = EventController.#controller.motionController?.mqttClient;
+
+ if (mqttClient && mqttClient.connected) {
+ if (!mqttPublishSettings.topic) {
+ return log.debug('No MQTT Publish Topic defined, skip MQTT..');
+ }
+
+ mqttClient.publish(mqttPublishSettings.topic, JSON.stringify(notification));
+ } else {
+ return log.debug('MQTT client not connected, skip MQTT..');
+ }
+ } catch (error) {
+ log.info('An error occured during publishing mqtt message', cameraName, 'events');
+ log.error(error, cameraName, 'events');
+ }
+ }
+
static async #sendWebpush(cameraName, notification, webpushSettings, notificationActive) {
if (!notificationActive) {
return log.debug('Notifications not enabled, skip Webpush..', cameraName);
diff --git a/src/index.js b/src/index.js
index 5e6391e5..fcaae4b1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -18,14 +18,28 @@ export default class CameraUI {
throw new Error('No storage path was given for camera.ui');
}
+ const logLevel = configJson.logLevel || 'info';
+ switch (logLevel) {
+ case 'info':
+ process.env.CUI_LOG_MODE = 1;
+ break;
+ case 'debug':
+ process.env.CUI_LOG_MODE = 2;
+ break;
+ case 'error':
+ process.env.CUI_LOG_MODE = 3;
+ break;
+ case 'warn':
+ process.env.CUI_LOG_MODE = 4;
+ break;
+ default:
+ process.env.CUI_LOG_MODE = 1;
+ }
+
process.env.CUI_SERVICE_MODE = 2;
process.env.CUI_LOG_COLOR = 1;
process.env.CUI_LOG_TIMESTAMPS = 1;
- if (configJson.debug) {
- process.env.CUI_LOG_DEBUG = 1;
- }
-
// node-telegram-bot-api
process.env.NTBA_FIX_319 = 1;
process.env.NTBA_FIX_350 = 1;
diff --git a/src/services/config/config.defaults.js b/src/services/config/config.defaults.js
index c1b982da..e131a14a 100644
--- a/src/services/config/config.defaults.js
+++ b/src/services/config/config.defaults.js
@@ -85,7 +85,7 @@ export class ConfigSetup {
static setupUi(config = {}) {
return {
- debug: config?.debug || false,
+ logLevel: config?.logLevel || 'info',
port: config?.port || uiDefaults.port,
atHomeSwitch: config?.atHomeSwitch || false,
};
diff --git a/src/services/config/config.service.js b/src/services/config/config.service.js
index 3758e038..6f7bd7db 100644
--- a/src/services/config/config.service.js
+++ b/src/services/config/config.service.js
@@ -46,7 +46,7 @@ export default class ConfigService {
//defaults
static ui = {
port: uiDefaults.port,
- debug: true,
+ logLevel: '2',
ssl: false,
mqtt: false,
topics: new Map(),
@@ -78,9 +78,27 @@ export default class ConfigService {
ConfigService.recordingsPath = process.env.CUI_STORAGE_RECORDINGS_PATH;
ConfigService.reportsPath = process.env.CUI_STORAGE_REPORTS_PATH;
- ConfigService.debugEnabled = process.env.CUI_LOG_DEBUG === '1';
+ ConfigService.debugEnabled = process.env.CUI_LOG_MODE === '2';
ConfigService.version = process.env.CUI_VERSION;
+ switch (process.env.CUI_LOG_MODE) {
+ case '1':
+ ConfigService.logLevel = 'info';
+ break;
+ case '2':
+ ConfigService.logLevel = 'debug';
+ break;
+ case '3':
+ ConfigService.logLevel = 'warn';
+ break;
+ case '4':
+ ConfigService.logLevel = 'error';
+ break;
+ default:
+ ConfigService.logLevel = 'info';
+ break;
+ }
+
//server env
ConfigService.minimumNodeVersion = minNodeVersion;
ConfigService.serviceMode = process.env.CUI_SERVICE_MODE === '2';
diff --git a/src/services/logger/logger.service.js b/src/services/logger/logger.service.js
index 394314fb..eb19df77 100644
--- a/src/services/logger/logger.service.js
+++ b/src/services/logger/logger.service.js
@@ -35,7 +35,7 @@ export default class LoggerService {
static #logger = console;
static #customLogger = false;
static #withPrefix = true;
- static #debugEnabled = false;
+ static #logLevel = 'info';
static #timestampEnabled = false;
static #filelogger;
@@ -61,8 +61,22 @@ export default class LoggerService {
chalk.level = 1;
}
- if (process.env.CUI_LOG_DEBUG === '1') {
- LoggerService.#debugEnabled = true;
+ switch (process.env.CUI_LOG_MODE) {
+ case '1':
+ LoggerService.#logLevel = 'info';
+ break;
+ case '2':
+ LoggerService.#logLevel = 'debug';
+ break;
+ case '3':
+ LoggerService.#logLevel = 'warn';
+ break;
+ case '4':
+ LoggerService.#logLevel = 'error';
+ break;
+ default:
+ LoggerService.#logLevel = 'info';
+ break;
}
if (process.env.CUI_LOG_TIMESTAMPS === '1') {
@@ -124,9 +138,39 @@ export default class LoggerService {
return LoggerService.log;
}
+ static allowLogging(level) {
+ switch (level) {
+ case 'info':
+ return Boolean(LoggerService.#logLevel === LogLevel.DEBUG || LoggerService.#logLevel === LogLevel.INFO);
+ case 'debug':
+ return Boolean(LoggerService.#logLevel === LogLevel.DEBUG);
+ case 'warn':
+ return Boolean(
+ LoggerService.#logLevel === LogLevel.WARN ||
+ LoggerService.#logLevel === LogLevel.ERROR ||
+ LoggerService.#logLevel === LogLevel.INFO ||
+ LoggerService.#logLevel === LogLevel.DEBUG
+ );
+ case 'error':
+ return Boolean(
+ LoggerService.#logLevel === LogLevel.ERROR ||
+ LoggerService.#logLevel === LogLevel.INFO ||
+ LoggerService.#logLevel === LogLevel.DEBUG
+ );
+ default:
+ return false;
+ }
+ }
+
static formatMessage(message, name, level) {
let formatted = '';
+ if (level === LogLevel.WARN) {
+ formatted += `${chalk.bgYellowBright.black.bold(' WARNING ')} `;
+ } else if (level === LogLevel.ERROR) {
+ formatted += `${chalk.bgRedBright.white.bold(' ERROR ')} `;
+ }
+
if (name) {
formatted += `${name}: `;
}
@@ -185,7 +229,7 @@ export default class LoggerService {
}
static #logging(level, message, name, fromExtern) {
- if (level === LogLevel.DEBUG && !LoggerService.#debugEnabled) {
+ if (!LoggerService.allowLogging(level)) {
return;
}
diff --git a/test/__tests__/auth.test.js b/test/__tests__/auth.test.js
index 1ae36950..c7e9d35e 100644
--- a/test/__tests__/auth.test.js
+++ b/test/__tests__/auth.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/backup.test.js b/test/__tests__/backup.test.js
index a6f97e15..6638a5e0 100644
--- a/test/__tests__/backup.test.js
+++ b/test/__tests__/backup.test.js
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/cameras.test.js b/test/__tests__/cameras.test.js
index cf989240..b280057b 100644
--- a/test/__tests__/cameras.test.js
+++ b/test/__tests__/cameras.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/config.test.js b/test/__tests__/config.test.js
index 39a169ba..8c32fc15 100644
--- a/test/__tests__/config.test.js
+++ b/test/__tests__/config.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/notifications.test.js b/test/__tests__/notifications.test.js
index 9757a33d..179b55f5 100644
--- a/test/__tests__/notifications.test.js
+++ b/test/__tests__/notifications.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/recordings.test.js b/test/__tests__/recordings.test.js
index c083d375..644f29e8 100644
--- a/test/__tests__/recordings.test.js
+++ b/test/__tests__/recordings.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/settings.test.js b/test/__tests__/settings.test.js
index 048d5cfa..18b8e727 100644
--- a/test/__tests__/settings.test.js
+++ b/test/__tests__/settings.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/__tests__/users.test.js b/test/__tests__/users.test.js
index 066724c4..7a94121a 100644
--- a/test/__tests__/users.test.js
+++ b/test/__tests__/users.test.js
@@ -4,7 +4,7 @@ import App from '../../src/api/app.js';
import Database from '../../src/api/database.js';
const app = new App({
- debug: process.env.CUI_LOG_DEBUG === '1',
+ debug: process.env.CUI_LOG_MODE === '2',
version: process.env.CUI_MODULE_VERSION,
});
diff --git a/test/setup.js b/test/setup.js
index 4dd78e29..9247d91c 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -13,7 +13,7 @@ const packageJson = fs.readJsonSync(path.resolve(__dirname, '../package.json'));
let moduleName = 'camera.ui';
let globalInstalled = '1';
let sudoEnabled = '1';
-let debugEnabled = '0';
+let logLevel = '1';
let logTimestamps = '1';
let logColourful = '1';
let storagePath = path.resolve('test', 'camera.ui');
@@ -25,7 +25,7 @@ process.env.NTBA_FIX_350 = 1;
process.env.CUI_SERVICE_MODE = '1';
process.env.CUI_LOG_COLOR = logColourful;
-process.env.CUI_LOG_DEBUG = debugEnabled;
+process.env.CUI_LOG_MODE = logLevel;
process.env.CUI_LOG_TIMESTAMPS = logTimestamps;
process.env.CUI_STORAGE_PATH = storagePath;
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 0ae0754c..9dbdf3a7 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -17,16 +17,18 @@
"gridstack": "^5.0.0",
"register-service-worker": "^1.7.2",
"socket.io-client": "4.4.1",
+ "strip-ansi": "^7.0.1",
"swiper": "4.5.1",
"v-jsoneditor": "^1.4.5",
"vue": "2.6.14",
+ "vue-aspect-ratio": "^0.1.1",
"vue-chartjs": "3.5.1",
"vue-i18n": "8.27.1",
"vue-infinite-loading": "^2.4.5",
"vue-inline-svg": "^2.1.0",
"vue-it-bigger": "^0.2.2",
"vue-markdown": "^2.2.4",
- "vue-router": "^3.5.3",
+ "vue-router": "3.5.3",
"vue-socket.io-extended": "^4.2.0",
"vue-toastification": "^1.7.14",
"vuedraggable": "^2.24.3",
@@ -1888,6 +1890,15 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
+ "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@soda/friendly-errors-webpack-plugin/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -1943,6 +1954,18 @@
"node": ">=8"
}
},
+ "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@soda/friendly-errors-webpack-plugin/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -2808,6 +2831,27 @@
"strip-ansi": "^6.0.0"
}
},
+ "node_modules/@vue/cli-shared-utils/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@vue/cli-shared-utils/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@vue/component-compiler-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
@@ -3215,12 +3259,14 @@
}
},
"node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
@@ -4768,6 +4814,27 @@
"wrap-ansi": "^6.2.0"
}
},
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@@ -5280,7 +5347,6 @@
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz",
"integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==",
- "dev": true,
"hasInstallScript": true,
"funding": {
"type": "opencollective",
@@ -8651,6 +8717,15 @@
"node": ">=8.0.0"
}
},
+ "node_modules/inquirer/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inquirer/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -8709,6 +8784,18 @@
"node": ">=8"
}
},
+ "node_modules/inquirer/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inquirer/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -14347,6 +14434,27 @@
"node": ">=8"
}
},
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
@@ -14388,15 +14496,17 @@
}
},
"node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-comments": {
@@ -16045,6 +16155,14 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
+ "node_modules/vue-aspect-ratio": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/vue-aspect-ratio/-/vue-aspect-ratio-0.1.1.tgz",
+ "integrity": "sha512-DU6J99HeO7xlbHX3c1ZpmG8s0Ti6N/f0Sf/BaiJtujyezcO5kcTKSp3szB2X+zbbJAraBU8c3wgUHFj5f992aQ==",
+ "dependencies": {
+ "core-js": "^3.3.2"
+ }
+ },
"node_modules/vue-chartjs": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-3.5.1.tgz",
@@ -17944,6 +18062,15 @@
"node": ">=8"
}
},
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -17977,6 +18104,18 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -18097,6 +18236,15 @@
"node": ">=10"
}
},
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yargs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -18141,6 +18289,18 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yargs/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -19536,6 +19696,12 @@
"strip-ansi": "^6.0.1"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -19576,6 +19742,15 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -20305,6 +20480,23 @@
"request": "^2.88.2",
"semver": "^6.1.0",
"strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
}
},
"@vue/component-compiler-utils": {
@@ -20656,10 +20848,9 @@
"dev": true
},
"ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
},
"ansi-styles": {
"version": "3.2.1",
@@ -21899,6 +22090,23 @@
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
}
},
"clone": {
@@ -22309,8 +22517,7 @@
"core-js": {
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz",
- "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==",
- "dev": true
+ "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig=="
},
"core-js-compat": {
"version": "3.21.1",
@@ -24946,6 +25153,12 @@
"through": "^2.3.6"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -24986,6 +25199,15 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -29575,6 +29797,23 @@
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
}
},
"string.prototype.trimend": {
@@ -29609,12 +29848,11 @@
}
},
"strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
"requires": {
- "ansi-regex": "^5.0.1"
+ "ansi-regex": "^6.0.1"
}
},
"strip-comments": {
@@ -30906,6 +31144,14 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
+ "vue-aspect-ratio": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/vue-aspect-ratio/-/vue-aspect-ratio-0.1.1.tgz",
+ "integrity": "sha512-DU6J99HeO7xlbHX3c1ZpmG8s0Ti6N/f0Sf/BaiJtujyezcO5kcTKSp3szB2X+zbbJAraBU8c3wgUHFj5f992aQ==",
+ "requires": {
+ "core-js": "^3.3.2"
+ }
+ },
"vue-chartjs": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-3.5.1.tgz",
@@ -32414,6 +32660,12 @@
"strip-ansi": "^6.0.0"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -32437,6 +32689,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
}
}
},
@@ -32518,6 +32779,12 @@
"yargs-parser": "^20.2.2"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -32553,6 +32820,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/ui/package.json b/ui/package.json
index ec252157..d55233e8 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -17,9 +17,11 @@
"gridstack": "^5.0.0",
"register-service-worker": "^1.7.2",
"socket.io-client": "4.4.1",
+ "strip-ansi": "^7.0.1",
"swiper": "4.5.1",
"v-jsoneditor": "^1.4.5",
"vue": "2.6.14",
+ "vue-aspect-ratio": "^0.1.1",
"vue-chartjs": "3.5.1",
"vue-i18n": "8.27.1",
"vue-infinite-loading": "^2.4.5",
diff --git a/ui/src/App.vue b/ui/src/App.vue
index 3d8faa3c..5c86a783 100644
--- a/ui/src/App.vue
+++ b/ui/src/App.vue
@@ -17,7 +17,7 @@ v-app.app(:style="$route.name === 'Camview' ? 'background: #121212 !important' :
.overlay(v-if="showOverlay")
- v-main.tw-relative(:class="$route.name !== 'Login' && $route.name !== 'Start' && $route.name !== '404' && ($route.meta.config && !$route.meta.config.showMinifiedNavbar ? 'content ' : '') + (extendSidebar ? 'extended-sidebar' : '')")
+ v-main.tw-relative(:class="$route.name !== 'Login' && $route.name !== 'Start' && $route.name !== '404' && ($route.meta.config && !$route.meta.config.showMinifiedNavbar && $route.meta.config.showSidebar ? 'content ' : '') + (extendSidebar ? 'extended-sidebar' : '')")
Navbar(v-if="$route.meta.config && $route.meta.config.showNavbar")
.router-container.tw-relative(:class="$route.meta.config && $route.meta.config.fixedNavbar ? 'fixed-navbar' : ''")
transition(name='fade' mode='out-in')
diff --git a/ui/src/assets/css/main.css b/ui/src/assets/css/main.css
index e2abaaef..293de6ab 100644
--- a/ui/src/assets/css/main.css
+++ b/ui/src/assets/css/main.css
@@ -129,6 +129,10 @@ body .v-application .v-stepper__step__step.primary {
font-family: 'Inter', sans-serif !important;
}*/
+.v-data-table tbody tr.v-data-table__selected {
+ background: rgba(var(--cui-text-default-rgb), 0.05) !important;
+}
+
.v-messages {
color: rgba(var(--cui-text-default-rgb), 0.6) !important;
}
@@ -144,6 +148,10 @@ body .v-application .v-stepper__step__step.primary {
background: var(--cui-primary) !important;
}
+.v-select__selection--disabled {
+ color: #747474 !important;
+}
+
.v-btn.v-btn--disabled,
.v-btn.v-btn--disabled .v-icon,
.v-input__icon .v-icon.v-icon.v-icon--disabled,
diff --git a/ui/src/components/add-camera.vue b/ui/src/components/add-camera.vue
index e85d0b1b..5b0be25b 100644
--- a/ui/src/components/add-camera.vue
+++ b/ui/src/components/add-camera.vue
@@ -26,7 +26,7 @@ v-dialog(v-model="dialog" width="600" scrollable @click:outside="closeDialog")
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiAlphabetical'] }}
- label.form-input-label Sub Source
+ label.form-input-label Video Substream Source
v-text-field(v-model="cam.videoConfig.subSource" :hint="$t('sub_source_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiAlphabetical'] }}
diff --git a/ui/src/components/camera-card.vue b/ui/src/components/camera-card.vue
index 9e118cce..35f06638 100644
--- a/ui/src/components/camera-card.vue
+++ b/ui/src/components/camera-card.vue
@@ -338,6 +338,11 @@ export default {
try {
const status = await getCameraStatus(this.camera.name, this.camera.settings.pingTimeout);
+ this.$emit('cameraStatus', {
+ name: this.camera.name,
+ status: status.data.status,
+ });
+
if (status.data.status === 'ONLINE') {
const snapshot = await getCameraSnapshot(this.camera.name, '?buffer=true');
this.loading = false;
diff --git a/ui/src/components/console.vue b/ui/src/components/console.vue
index fce8f38c..eb6f2add 100644
--- a/ui/src/components/console.vue
+++ b/ui/src/components/console.vue
@@ -3,6 +3,7 @@
diff --git a/ui/src/components/navbar.vue b/ui/src/components/navbar.vue
index fba643d9..91eec2e4 100644
--- a/ui/src/components/navbar.vue
+++ b/ui/src/components/navbar.vue
@@ -3,7 +3,7 @@
.top-navi-bar-minified(v-if="$route.meta.config.showMinifiedNavbar")
v-btn.text-muted.included(@click="toggleNavi" fab elevation="1" height="38px" width="38px" color="rgba(0, 0, 0, 0.5)" retain-focus-on-click)
v-icon {{ showSidebar ? icons['mdiArrowLeftThick'] : icons['mdiArrowRightThick'] }}
- v-app-bar.top-navi-bar.pt-safe(v-else height="64px" :class="($route.meta.config.fixedNavbar ? 'top-navi-bar-fixed ' : '') + (extendSidebar ? 'extended-sidebar' : '')")
+ v-app-bar.top-navi-bar.pt-safe(v-else height="64px" :class="($route.meta.config.fixedNavbar ? 'top-navi-bar-fixed ' : '') + (extendSidebar ? 'extended-sidebar' : '')" style="border-top: 1px solid rgba(121,121,121,0.1);")
.navi-wrap.pl-safe.pr-safe
v-btn.text-default.included(@click="toggleNavi" icon height="38px" width="38px")
v-icon {{ icons['mdiMenu'] }}
diff --git a/ui/src/components/recording-card.vue b/ui/src/components/recording-card.vue
index b649107f..3750e813 100644
--- a/ui/src/components/recording-card.vue
+++ b/ui/src/components/recording-card.vue
@@ -1,5 +1,7 @@
-v-card.card.fill-height.video-card.tw-overflow-hidden.tw-flex.tw-flex-col
+v-card.card.fill-height.video-card.tw-overflow-hidden.tw-flex.tw-flex-col(v-if="!list")
+
+ v-checkbox.select-checkbox(v-model="selected" @change="$emit('select')")
v-img.video-card-img.tw-items-end.tw-text-white.tw-relative.tw-cursor-pointer(v-on:error="handleErrorImg" :src="src" height="calc(100% - 37px)" @click="$emit('show')" :class="errorImg ? 'errorImg' : ''")
template(v-slot:placeholder)
@@ -13,10 +15,18 @@ v-card.card.fill-height.video-card.tw-overflow-hidden.tw-flex.tw-flex-col
.video-card-content.tw-relative
v-icon.text-default(small style="margin-top: -3px; margin-right: 5px") {{ icons['mdiClockTimeNineOutline'] }}
span.text-font-disabled {{ recording.time }}
- v-btn.tw-text-white(style="top: -20px; right: 55px" width="36px" height="36px" @click="download(item)" :loading="downloading" absolute color="#333333" fab right top)
+ v-btn.tw-text-white(style="top: -20px; right: 10px" width="36px" height="36px" @click="download(item)" :loading="downloading" absolute color="#333333" fab right top)
v-icon(small) {{ icons['mdiDownload'] }}
- v-btn.tw-text-white(style="top: -20px; right: 10px" width="36px" height="36px" @click="remove" :loading="removing" small absolute color="red" fab right top)
+ //v-btn.tw-text-white(style="top: -20px; right: 10px" width="36px" height="36px" @click="remove" :loading="removing" small absolute color="red" fab right top)
v-icon(small) {{ icons['mdiTrashCan'] }}
+
+v-card.card.fill-height.video-card.tw-overflow-hidden.tw-flex.tw-flex-col(v-else)
+ v-img.video-card-img.tw-items-end.tw-text-white.tw-relative.tw-cursor-pointer(v-on:error="handleErrorImg" :src="src" height="calc(100% - 37px)" @click="$emit('show')" :class="errorImg ? 'errorImg' : ''")
+ template(v-slot:placeholder)
+ .tw-flex.tw-justify-center.tw-items-center.tw-h-full
+ v-progress-circular(indeterminate color="var(--cui-primary)" size="22")
+ .shadow.tw-absolute.tw-inset-0
+
+
+
diff --git a/ui/src/components/sidebar.vue b/ui/src/components/sidebar.vue
index dad7a8cb..330e97ba 100644
--- a/ui/src/components/sidebar.vue
+++ b/ui/src/components/sidebar.vue
@@ -136,6 +136,7 @@ import {
mdiImageMultiple,
mdiScript,
mdiTextBoxOutline,
+ mdiTimelineOutline,
mdiTune,
mdiViewDashboard,
} from '@mdi/js';
@@ -172,6 +173,7 @@ export default {
'mdi-grid-large': mdiGridLarge,
'mdi-image-multiple': mdiImageMultiple,
'mdi-script': mdiScript,
+ 'mdi-timeline-outline': mdiTimelineOutline,
'mdi-text-box-outline': mdiTextBoxOutline,
'mdi-view-dashboard': mdiViewDashboard,
},
diff --git a/ui/src/i18n/locale/de.json b/ui/src/i18n/locale/de.json
index 20e1709c..eeb79760 100644
--- a/ui/src/i18n/locale/de.json
+++ b/ui/src/i18n/locale/de.json
@@ -32,14 +32,14 @@
"audio": "Audio",
"audio_codec": "Audio Codec",
"audio_codec_info": "Set the codec used for encoding audio sent to HomeKit for HSV, must be AAC-based (-acodec).",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "Enables audio streaming from camera.",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "August",
"auto": "Auto",
"auto_darkmode": "Auto Darkmode",
"automated_backup": "Automatisiertes Backup",
"automation": "Automation",
- "automation_from": "Von",
- "automation_to": "Bis",
"aws": "Amazon Web Services",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Kontingent Übrig",
@@ -64,6 +64,7 @@
"base": "Basis",
"bitrate": "Bitrate",
"bitrate_info": "The bitrate used for video stream.",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "Blau Grau",
"blue": "Blau",
"blue_gray": "Blau Grau",
@@ -116,6 +117,7 @@
"debug": "Debug",
"debug_info": "Includes debugging output from the main FFmpeg process in the log.",
"december": "Dezember",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "Deaktiviert",
"domain": "Domain",
"doorbell": "Türklingel",
@@ -138,6 +140,7 @@
"enabled": "Aktiviert",
"encoder_options": "Encoder Optionen",
"encoder_options_info": "Options to be passed to the video encoder.",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "Endpunkte",
"enter_new_password": "Bitte geben Sie ein neues Passwort ein",
"error": "Fehler",
@@ -150,6 +153,7 @@
"ffmpeg_and_stream": "FFmpeg und Stream",
"field_must_not_be_empty": "Feld darf nicht leer sein",
"fill_all_required_fields": "Bitte füllen Sie alle erforderlichen Felder",
+ "filter": "Filter",
"filters": "Filter",
"finish": "Beenden",
"finish_zone": "Zone beenden",
@@ -159,7 +163,9 @@
"forgotpw_title": "Passwort vergessen?",
"fps": "FPS",
"fps_info": "The fps used for video stream.",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "Freitag",
+ "from": "Von",
"ftp": "FTP",
"ftp_absolute_path": "FTP Absoluter Pfad",
"ftp_server_config": "FTP Server Konfiguration",
@@ -172,6 +178,7 @@
"green": "Grün",
"height": "Video Höhe",
"height_info": "The height used for video stream.",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "Um Ihnen den Einstieg zu erleichtern und gute Erfahrungen mit camera.ui zu machen, nehmen Sie bitte die folgende Konfiguration vor.",
"homebridge_restart_info": "Mit (²) markierte Felder benötigen ein Neustart von Homebridge",
"host": "Host",
@@ -201,6 +208,7 @@
"last_updated": "Zuletzt geändert",
"light": "Hell",
"list_of_existing_user": "Liste vorhandener Benutzer",
+ "live": "Live",
"livestream": "Livestream",
"livestream_snapshot": "Schaltet zwischen Livestream oder Snapshot Interval",
"load": "Auslastung",
@@ -209,6 +217,8 @@
"location": "Ort",
"log": "Protokoll",
"login": "Login",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "Set the manufacturer name for display in the Home app",
"map_audio": "Audio Stream",
"map_audio_info": "Selects the stream used for audio (-map).",
@@ -240,6 +250,7 @@
"movement_on": "Bewegung am",
"mqtt": "MQTT",
"mqtt_config": "MQTT Konfiguration",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "Name",
"never": "Niemals",
"new": "Neu",
@@ -252,6 +263,7 @@
"no_access": "Keine Zugriffsberechtigung",
"no_camera_selected": "Keine Kamera ausgewählt",
"no_cameras": "Keine Kameras",
+ "no_data": "Keine Daten",
"no_data_available": "Keine Daten verfügbar",
"no_feed": "Kein Feed",
"no_file_selected": "Keine Datei ausgewählt",
@@ -274,6 +286,7 @@
"notification": "Benachrichtigung",
"notification_text": "@ hat eine neue Bewegung erfasst im Raum %",
"notifications": "Benachrichtigungen",
+ "notifications_alert": "Sie haben insgesamt % Benachrichtigung(en)",
"notifications_nav_info": "Verwalten Sie die Alexa, Telegram und Webhookeinstellungen",
"november": "November",
"now": "Jetzt",
@@ -324,6 +337,7 @@
"recording_type": "Aufnahmetyp",
"recording_type_not_editable": "Aufnahmetyp kann nicht bearbeitet werden. Einstellungen können im config.json geändert werden.",
"recordings": "Aufnahmen",
+ "recordings_alert": "Sie haben insgesamt % Aufnahme(n) und # Aufnahmen wurden ausgewählt",
"recordings_nav_info": "Verwalten Sie die Aufnahmeeinstellungen",
"refresh": "Aktualisieren",
"registered_user": "Registrierte Benutzer",
@@ -389,6 +403,7 @@
"snapshot_timer": "Snapshot Timer",
"source": "Video Quelle",
"source_info": "FFmpeg options on where to find and how to decode your camera's video stream. The most basic form is '-i' followed by your camera's URL.",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "Leerzeichen ersetzen mit",
"speaker_end_time": "Lautsprecheraussage bis",
"speaker_start_time": "Lautsprecheraussage von",
@@ -426,10 +441,14 @@
"themes": "Themen",
"thursday": "Donnerstag",
"time": "Zeit",
+ "timeline": "Zeitleiste",
"timeout": "Zeitüberschreitung",
- "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-timeout).",
+ "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-stimeout).",
+ "timerange": "Zeitspanne",
"timestamp": "Zeitstempel",
+ "to": "Bis",
"token": "Token",
+ "total": "Gesamt",
"tuesday": "Dienstag",
"type": "Typ",
"unbridge_info": "Bridged cameras can cause slowdowns of the entire Homebridge instance. If unbridged, the camera will need to be added to HomeKit manually.",
@@ -455,6 +474,7 @@
"video": "Video",
"video_codec": "Video Codec",
"video_codec_info": "Set the codec used for encoding video sent to HomeKit, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "Zusätzliche Videofilter",
"video_filter_info": "Comma-delimited list of additional video filters for FFmpeg to run on the video. If 'none' is included, the default video filters are disabled (-filter:v).",
"video_processor_config": "Video Prozessor Konfiguration",
@@ -475,6 +495,7 @@
"widgets": "Widgets",
"width": "Video Breite",
"width_info": "The width used for video stream.",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "Gelb"
}
}
diff --git a/ui/src/i18n/locale/en.json b/ui/src/i18n/locale/en.json
index 0844be77..e3a88499 100644
--- a/ui/src/i18n/locale/en.json
+++ b/ui/src/i18n/locale/en.json
@@ -32,14 +32,14 @@
"audio": "Audio",
"audio_codec": "Audio Codec",
"audio_codec_info": "Set the codec used for encoding audio sent to HomeKit for HSV, must be AAC-based (-acodec).",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "Enables audio streaming from camera.",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "August",
"auto": "Auto",
"auto_darkmode": "Auto Darkmode",
"automated_backup": "Automated Backup",
"automation": "Automation",
- "automation_from": "From",
- "automation_to": "To",
"aws": "Amazon Web Services",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Contingent Left",
@@ -64,6 +64,7 @@
"base": "Base",
"bitrate": "Bitrate",
"bitrate_info": "The bitrate used for video stream.",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "Blue Gray",
"blue": "Blue",
"blue_gray": "Blue Gray",
@@ -115,6 +116,7 @@
"debug": "Debug",
"debug_info": "Includes debugging output from the main FFmpeg process in the log.",
"december": "December",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "Disabled",
"domain": "Domain",
"doorbell": "Doorbell",
@@ -137,6 +139,7 @@
"enabled": "Enabled",
"encoder_options": "Encoder Options",
"encoder_options_info": "Options to be passed to the video encoder.",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "Endpoints",
"enter_new_password": "Please enter a new password",
"error": "Error",
@@ -149,6 +152,7 @@
"ffmpeg_and_stream": "FFmpeg and Stream",
"field_must_not_be_empty": "Field must not be empty",
"fill_all_required_fields": "Please fill all required fields",
+ "filter": "Filter",
"filters": "Filters",
"finish": "Finish",
"finish_zone": "Finish Zone",
@@ -158,7 +162,9 @@
"forgotpw_title": "Forgot password?",
"fps": "FPS",
"fps_info": "The fps used for video stream.",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "Friday",
+ "from": "From",
"ftp": "FTP",
"ftp_absolute_path": "FTP Absolute Path",
"ftp_server_config": "FTP Server Configuration",
@@ -171,6 +177,7 @@
"green": "Green",
"height": "Video Height",
"height_info": "The height used for video stream.",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "To help you get started and provide good experience with camera.ui, please complete the following configuration.",
"homebridge_restart_info": "Changing the fields marked with (²) requires restart of Homebridge",
"host": "Host",
@@ -200,6 +207,7 @@
"last_updated": "Last Updated",
"light": "Light",
"list_of_existing_user": "List of registered user",
+ "live": "Live",
"livestream": "Livestream",
"livestream_snapshot": "Switches between live stream or snapshot interval",
"load": "Load",
@@ -208,6 +216,8 @@
"location": "Location",
"log": "Log",
"login": "Login",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "Set the manufacturer name for display in the Home app",
"map_audio": "Audio Stream",
"map_audio_info": "Selects the stream used for audio (-map).",
@@ -239,6 +249,7 @@
"movement_on": "Movement on",
"mqtt": "MQTT",
"mqtt_config": "MQTT Configuration",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "Name",
"never": "Never",
"new": "New",
@@ -251,6 +262,7 @@
"no_access": "No access permission",
"no_camera_selected": "No Camera selected",
"no_cameras": "No cameras",
+ "no_data": "No Data",
"no_data_available": "No data available",
"no_feed": "No Feed",
"no_file_selected": "No file selected",
@@ -273,6 +285,7 @@
"notification": "Notification",
"notification_text": "@ detected a new movement in room %",
"notifications": "Notifications",
+ "notifications_alert": "You have a total of % notifications",
"notifications_nav_info": "Manage the Alexa, Telegram and Webhook settings",
"november": "November",
"now": "Now",
@@ -323,6 +336,7 @@
"recording_type": "Recording type",
"recording_type_not_editable": "Recording type cannot be edited. Settings can be changed in config.json.",
"recordings": "Recordings",
+ "recordings_alert": "You have % recordings in total and # recordings were selected",
"recordings_nav_info": "Manage the recording settings and specify the recording type",
"refresh": "Reresh",
"registered_user": "Registered User",
@@ -388,6 +402,7 @@
"snapshot_timer": "Snapshot Timer",
"source": "Video Source",
"source_info": "FFmpeg options on where to find and how to decode your camera's video stream. The most basic form is '-i' followed by your camera's URL.",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "Space replace",
"speaker_end_time": "Speaker end time",
"speaker_start_time": "Speaker start time",
@@ -425,9 +440,13 @@
"themes": "Themes",
"thursday": "Thursday",
"time": "Time",
+ "timeline": "Timeline",
"timeout": "Timeout",
- "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-timeout).",
+ "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-stimeout).",
+ "timerange": "Time Range",
"timestamp": "Timestamp",
+ "to": "To",
+ "total": "Total",
"tuesday": "Tuesday",
"typ": "Typ",
"unbridge_info": "Bridged cameras can cause slowdowns of the entire Homebridge instance. If unbridged, the camera will need to be added to HomeKit manually.",
@@ -453,6 +472,7 @@
"video": "Video",
"video_codec": "Video Codec",
"video_codec_info": "Set the codec used for encoding video sent to HomeKit, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "Video filter",
"video_filter_info": "Comma-delimited list of additional video filters for FFmpeg to run on the video. If 'none' is included, the default video filters are disabled (-filter:v).",
"video_processor_config": "Video Prozessor Configuration",
@@ -473,6 +493,7 @@
"widgets": "Widgets",
"width": "Video Width",
"width_info": "The width used for video stream.",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "Yellow"
}
}
diff --git a/ui/src/i18n/locale/es.json b/ui/src/i18n/locale/es.json
index db2b983d..bdd4210f 100644
--- a/ui/src/i18n/locale/es.json
+++ b/ui/src/i18n/locale/es.json
@@ -32,14 +32,14 @@
"audio": "Audio",
"audio_codec": "Codec de Audio",
"audio_codec_info": "Establezca el códec utilizado para codificar el audio enviado a HomeKit para HSV, debe estar basado en AAC (-acodec).",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "Activar la transmisión de audio de la cámara.",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "Agosto",
"auto": "Auto",
"auto_darkmode": "Modo Oscuro Automático",
"automated_backup": "Respaldo Automático",
"automation": "Automatización",
- "automation_from": "Desde",
- "automation_to": "Hasta",
"aws": "Amazon Web Services",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Contingent Left",
@@ -64,6 +64,7 @@
"base": "Base",
"bitrate": "Bitrate",
"bitrate_info": "El bitrate utilizado para la transmisión de video.",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "Azul Gris",
"blue": "Azul",
"blue_gray": "Azul Gris",
@@ -115,6 +116,7 @@
"debug": "Depuración",
"debug_info": "Includes debugging output from the main FFmpeg process in the log.",
"december": "Diciembre",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "Desactivado",
"domain": "Dominio",
"doorbell": "Timbre",
@@ -137,6 +139,7 @@
"enabled": "Activado",
"encoder_options": "Encoder Options",
"encoder_options_info": "Options to be passed to the video encoder.",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "Endpoints",
"enter_new_password": "Por favor, ingrese una nueva contraseña",
"error": "Error",
@@ -149,6 +152,7 @@
"ffmpeg_and_stream": "FFmpeg y Stream",
"field_must_not_be_empty": "El campo no puede estar vacío",
"fill_all_required_fields": "Por favor rellene todos los campos requeridos",
+ "filter": "Filter",
"filters": "Filtros",
"finish": "Finish",
"finish_zone": "Finish Zone",
@@ -158,7 +162,9 @@
"forgotpw_title": "¿Contraseña Olvidada?",
"fps": "FPS",
"fps_info": "The fps used for video stream.",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "Viernes",
+ "from": "Desde",
"ftp": "FTP",
"ftp_absolute_path": "FTP Absolute Path",
"ftp_server_config": "FTP Server Configuration",
@@ -171,6 +177,7 @@
"green": "Verde",
"height": "Video Height",
"height_info": "The height used for video stream.",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "To help you get started and provide good experience with camera.ui, please complete the following configuration.",
"homebridge_restart_info": "Changing the fields marked with (²) requires restart of Homebridge",
"host": "Host",
@@ -200,6 +207,7 @@
"last_updated": "Última Vez Actualizado",
"light": "Light",
"list_of_existing_user": "Lista de usuarios registrados",
+ "live": "Live",
"livestream": "Livestream",
"livestream_snapshot": "Switches between live stream or snapshot interval",
"load": "Load",
@@ -208,6 +216,8 @@
"location": "Location",
"log": "Log",
"login": "Acceder",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "Ingresa el nombre del fabricante para mostrar en la aplicación Casa",
"map_audio": "Audio Stream",
"map_audio_info": "Selects the stream used for audio (-map).",
@@ -239,6 +249,7 @@
"movement_on": "Movement on",
"mqtt": "MQTT",
"mqtt_config": "Configuración de MQTT",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "Nombre",
"never": "Nunca",
"new": "Nuevo",
@@ -251,6 +262,7 @@
"no_access": "Sin permiso de acceso",
"no_camera_selected": "No Camera selected",
"no_cameras": "No cameras",
+ "no_data": "No Data",
"no_data_available": "No data available",
"no_feed": "No Feed",
"no_file_selected": "No file selected",
@@ -273,6 +285,7 @@
"notification": "Notificación",
"notification_text": "@ detectó un nuevo moviento en la habitación %",
"notifications": "Notificaciones",
+ "notifications_alert": "You have a total of % notifications",
"notifications_nav_info": "Manage the Alexa, Telegram and Webhook settings",
"november": "Noviembre",
"now": "Ahora",
@@ -323,6 +336,7 @@
"recording_type": "Recording type",
"recording_type_not_editable": "Recording type cannot be edited. Settings can be changed in config.json.",
"recordings": "Grabaciones",
+ "recordings_alert": "You have % recordings in total and # recordings were selected",
"recordings_nav_info": "Manage the recording settings and specify the recording type",
"refresh": "Reresh",
"registered_user": "Registered User",
@@ -388,6 +402,7 @@
"snapshot_timer": "Snapshot Timer",
"source": "Fuente del Video",
"source_info": "FFmpeg options on where to find and how to decode your camera's video stream. The most basic form is '-i' followed by your camera's URL.",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "Space replace",
"speaker_end_time": "Speaker end time",
"speaker_start_time": "Speaker start time",
@@ -425,9 +440,13 @@
"themes": "Temas",
"thursday": "Jueves",
"time": "Tiempo",
+ "timeline": "Timeline",
"timeout": "Timeout",
- "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-timeout).",
+ "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-stimeout).",
+ "timerange": "Time Range",
"timestamp": "Timestamp",
+ "to": "Hasta",
+ "total": "Total",
"tuesday": "Martes",
"typ": "Typ",
"unbridge_info": "Bridged cameras can cause slowdowns of the entire Homebridge instance. If unbridged, the camera will need to be added to HomeKit manually.",
@@ -453,6 +472,7 @@
"video": "Video",
"video_codec": "Codec de Video",
"video_codec_info": "Set the codec used for encoding video sent to HomeKit, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "Filtro de Video",
"video_filter_info": "Comma-delimited list of additional video filters for FFmpeg to run on the video. If 'none' is included, the default video filters are disabled (-filter:v).",
"video_processor_config": "Video Prozessor Configuration",
@@ -473,6 +493,7 @@
"widgets": "Widgets",
"width": "Ancho del Video",
"width_info": "El tamaño de ancho utilizado para la transmisión del video.",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "Amarillo"
}
}
diff --git a/ui/src/i18n/locale/fr.json b/ui/src/i18n/locale/fr.json
index 8f82f6ad..85581e6c 100644
--- a/ui/src/i18n/locale/fr.json
+++ b/ui/src/i18n/locale/fr.json
@@ -32,14 +32,14 @@
"audio": "Audio",
"audio_codec": "Codec Audio",
"audio_codec_info": "Définir le codec utilisé pour encoder l'audio envoyé à HomeKit pour HSV, doit être basé sur ACC (-acodec).",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "Active le streaming audio depuis la caméra.",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "Août",
"auto": "Auto",
"auto_darkmode": "Mode Sombre Auto",
"automated_backup": "Sauvegarde Automatisée",
"automation": "Automatisation",
- "automation_from": "De",
- "automation_to": "À",
"aws": "Services Amazon Web",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Contingent Left",
@@ -64,6 +64,7 @@
"base": "Base",
"bitrate": "Débit",
"bitrate_info": "Débit utilisé pour le flux vidée.",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "Bleu Gris",
"blue": "Bleu",
"blue_gray": "Bleu Gris",
@@ -115,6 +116,7 @@
"debug": "Debug",
"debug_info": "Inclure les sortie de debuggage du processus principal de FFmpeg dans le journal.",
"december": "Décembre",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "Désactivé",
"domain": "Domaine",
"doorbell": "Sonette",
@@ -137,6 +139,7 @@
"enabled": "Activé",
"encoder_options": "Options de l'Encodeur",
"encoder_options_info": "Options à être passées à l'Encodeur Vidéo.",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "Terminaux",
"enter_new_password": "Entrer un mot de passe svp",
"error": "Erreur",
@@ -149,6 +152,7 @@
"ffmpeg_and_stream": "FFmpeg et Flux",
"field_must_not_be_empty": "Le champ ne peut être vide",
"fill_all_required_fields": "Veuillez remplir tous les champs requis",
+ "filter": "Filter",
"filters": "Filtres",
"finish": "Finir",
"finish_zone": "Finir la Zone",
@@ -158,7 +162,9 @@
"forgotpw_title": "Mot de passe oublié ?",
"fps": "FPS",
"fps_info": "Les fps (images par secondes) utilisé pour le flux vidéo.",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "Vendredi",
+ "from": "De",
"ftp": "FTP",
"ftp_absolute_path": "Chemin Absolu FTP",
"ftp_server_config": "Configuration Serveur FTP",
@@ -171,6 +177,7 @@
"green": "Vert",
"height": "Hauteur Vidéo",
"height_info": "Hauteur utilisée pour le flux vidéo.",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "Pour vous aider à démarrer et vous fournir une bonne expérience avec camera.ui, Veuillez remplir la configuration suivante.",
"homebridge_restart_info": "Changer les champs marqués avec (²) nécessitera un redémarrage de Homebridge",
"host": "Hôte",
@@ -200,6 +207,7 @@
"last_updated": "Dernière mise à jour",
"light": "Lumière",
"list_of_existing_user": "Liste des utilisateurs enregistrés",
+ "live": "Live",
"livestream": "Flux en temps réel",
"livestream_snapshot": "Passer entre le flux en temps réel et l'interval de clichés",
"load": "Charge",
@@ -208,6 +216,8 @@
"location": "Localisation",
"log": "Journal",
"login": "Login",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "Définir le nom du constructeur à afficher dans l'app Maison/Domicile/Home",
"map_audio": "Flux Audio",
"map_audio_info": "Sélectionner le flux utilisé pour l'audio (-map).",
@@ -239,6 +249,7 @@
"movement_on": "Mouvement sur",
"mqtt": "MQTT",
"mqtt_config": "Configuration MQTT",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "Nom",
"never": "Jamais",
"new": "Nouveau",
@@ -251,6 +262,7 @@
"no_access": "Pas de permission d'accès",
"no_camera_selected": "Aucune caméra sélectionnée",
"no_cameras": "Aucune caméra",
+ "no_data": "No Data",
"no_data_available": "Aucune donnée disponible",
"no_feed": "Aucun Flux RSS",
"no_file_selected": "Aucun fichier sélectionné",
@@ -273,6 +285,7 @@
"notification": "Notification",
"notification_text": "@ a détecté un nouveau mouvement dans la pièce %",
"notifications": "Notifications",
+ "notifications_alert": "You have a total of % notifications",
"notifications_nav_info": "Gérer les paramètres Alexa, Telegram et Webhook",
"november": "Novembre",
"now": "Maintenant",
@@ -323,6 +336,7 @@
"recording_type": "Type d'enregistrement",
"recording_type_not_editable": "Type d'enregistrement ne peut être édité. Les paramètres peuvent être changés dans config.json.",
"recordings": "Enregistrements",
+ "recordings_alert": "You have % recordings in total and # recordings were selected",
"recordings_nav_info": "Gérer les paramètres d'enregistrement et spécifier le type d'enregistrement",
"refresh": "Rafraîchir",
"registered_user": "Utilisateur Enregistré",
@@ -388,6 +402,7 @@
"snapshot_timer": "Décompte Cliché",
"source": "Source Vidéo",
"source_info": "Options FFmpeg sur comment trouver et décoder votre flux vidéo de votre caméra. La forme la plus basique est '-i' suivi de l'url de la caméra.",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "Replacer les espaces",
"speaker_end_time": "Temps de fin Haut-Parleur",
"speaker_start_time": "Temps de début Haut-Parleur",
@@ -425,9 +440,13 @@
"themes": "Thèmes",
"thursday": "Jeudi",
"time": "Temps",
+ "timeline": "Timeline",
"timeout": "Délai d'expiration",
- "timeout_info": "Délai d'expiration des E/S du socket TCP en secondes. Si vous avez des problèmes de processus FFmpeg bloqués en arrière-plan, vous pouvez entrer une valeur ici pour stopper le processus automatiquement après le temps désiré si aucune réponse n'arrive (-timeout).",
+ "timeout_info": "Délai d'expiration des E/S du socket TCP en secondes. Si vous avez des problèmes de processus FFmpeg bloqués en arrière-plan, vous pouvez entrer une valeur ici pour stopper le processus automatiquement après le temps désiré si aucune réponse n'arrive (-stimeout).",
+ "timerange": "Time Range",
"timestamp": "Horodatage",
+ "to": "À",
+ "total": "Total",
"tuesday": "Mardi",
"typ": "Type",
"unbridge_info": "Les caméras reliées au pont principal peuvent causer des ralentissements de toute l'instance Homebridge. Une fois déliées du pont, la camera devra être ajoutée à HomeKit manuellement.",
@@ -453,6 +472,7 @@
"video": "Vidéo",
"video_codec": "Codec Vidéo",
"video_codec_info": "Définir le codec utilisé pour encoder les vidéos envoyées à HomeKit, doit être basé sur H.264. Vous pouvez le modifier pour un codec qui utilise l'accélération matérielle avec cette option, s'il y en a un de disponible (-vcodec).",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "Filtre Vidéo",
"video_filter_info": "Liste de filtres vidéos additionnels séparés par des virgules à appliquer sur la vidéo par FFmpeg. Si 'none' est inclu, les filtres vidéo par défaut sont désactivés (-filter:v).",
"video_processor_config": "Configuration du Processeur de Vidéo",
@@ -473,6 +493,7 @@
"widgets": "Widgets",
"width": "Largeur Vidéo",
"width_info": "La largeur utilisée pour le flux vidéo.",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "Jaune"
}
}
diff --git a/ui/src/i18n/locale/nl.json b/ui/src/i18n/locale/nl.json
index b961c66a..5dc2f7f9 100644
--- a/ui/src/i18n/locale/nl.json
+++ b/ui/src/i18n/locale/nl.json
@@ -32,14 +32,14 @@
"audio": "Audio",
"audio_codec": "Audio Codec",
"audio_codec_info": "Set the codec used for encoding audio sent to HomeKit for HSV, must be AAC-based (-acodec).",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "Enables audio streaming from camera.",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "Augustus",
"auto": "Auto",
"auto_darkmode": "Auto Darkmode",
"automated_backup": "Geautomatiseerde back-up",
"automation": "Automatisering",
- "automation_from": "Van",
- "automation_to": "Tot",
"aws": "Amazon Web Services",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Contingent Übrig",
@@ -64,6 +64,7 @@
"base": "Basis",
"bitrate": "Bitrate",
"bitrate_info": "The bitrate used for video stream.",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "Blauw Grijs",
"blue": "Blauw",
"blue_gray": "Blauw Grijs",
@@ -115,6 +116,7 @@
"debug": "Debug",
"debug_info": "Includes debugging output from the main FFmpeg process in the log.",
"december": "December",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "Uitgeschakeld",
"domain": "Domain",
"doorbell": "Deurbel",
@@ -137,6 +139,7 @@
"enabled": "Activeert",
"encoder_options": "Encoder Opties",
"encoder_options_info": "Options to be passed to the video encoder.",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "Endpunkte",
"enter_new_password": "Voer een nieuw wachtwoord in",
"error": "Fout",
@@ -149,6 +152,7 @@
"ffmpeg_and_stream": "FFmpeg en Stream",
"field_must_not_be_empty": "Het veld mag niet leeg zijn",
"fill_all_required_fields": "Gelieve alle verplichte velden in te vullen",
+ "filter": "Filter",
"filters": "Filter",
"finish": "Einde",
"finish_zone": "Finish Zone",
@@ -158,7 +162,9 @@
"forgotpw_title": "Wachtwoord vergeten?",
"fps": "FPS",
"fps_info": "The fps used for video stream.",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "Vrijdag",
+ "from": "Van",
"ftp": "FTP",
"ftp_absolute_path": "FTP absoluut pad",
"ftp_server_config": "FTP Server Configuratie",
@@ -171,6 +177,7 @@
"green": "Groen",
"height": "Video Hoogte",
"height_info": "The height used for video stream.",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "Om u op weg te helpen en een goede ervaring met camera.ui te bieden, verzoeken wij u de volgende configuratie uit te voeren.",
"homebridge_restart_info": "Om de velden gemarkeerd met (²) te wijzigen, moet Homebridge opnieuw worden opgestart",
"host": "Host",
@@ -200,6 +207,7 @@
"last_updated": "Laatst gewijzigd",
"light": "Helder",
"list_of_existing_user": "Lijst van geregistreerde gebruikers",
+ "live": "Live",
"livestream": "Livestream",
"livestream_snapshot": "Schakelt tussen live stream of snapshot interval",
"load": "Belasting",
@@ -208,6 +216,8 @@
"location": "Locatie",
"log": "Log",
"login": "Login",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "Set the manufacturer name for display in the Home app",
"map_audio": "Audio Stream",
"map_audio_info": "Selects the stream used for audio (-map).",
@@ -239,6 +249,7 @@
"movement_on": "Beweging op",
"mqtt": "MQTT",
"mqtt_config": "MQTT Configuratie",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "Naam",
"never": "Never",
"new": "Nieuw",
@@ -251,6 +262,7 @@
"no_access": "Geen toegangsautorisatie",
"no_camera_selected": "Geen camera geselecteerd",
"no_cameras": "Geen camera's",
+ "no_data": "No Data",
"no_data_available": "Geen gegevens beschikbaar",
"no_feed": "No Feed",
"no_file_selected": "No file selected",
@@ -273,6 +285,7 @@
"notification": "Kennisgeving",
"notification_text": "@ heeft een nieuwe beweging in de ruimte gevangen %",
"notifications": "Kennisgevingen",
+ "notifications_alert": "You have a total of % notifications",
"notifications_nav_info": "Beheer de Alexa, Telegram en webhook instellingen",
"november": "November",
"now": "Now",
@@ -323,6 +336,7 @@
"recording_type": "Opnametype",
"recording_type_not_editable": "Opnametype kan niet worden bewerkt. Instellingen kunnen worden gewijzigd in config.json.",
"recordings": "Opnames",
+ "recordings_alert": "You have % recordings in total and # recordings were selected",
"recordings_nav_info": "De opname-instellingen beheren en het opnametype specificeren",
"refresh": "Refresh",
"registered_user": "Geregistreerde gebruikers",
@@ -388,6 +402,7 @@
"snapshot_timer": "Snapshot Timer",
"source": "Videobron",
"source_info": "FFmpeg options on where to find and how to decode your camera's video stream. The most basic form is '-i' followed by your camera's URL.",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "Ruimte vervangen",
"speaker_end_time": "Sprekers verklaring tot",
"speaker_start_time": "Sprekers verklaring van",
@@ -425,9 +440,13 @@
"themes": "Themes",
"thursday": "Donderdag",
"time": "Tijd",
+ "timeline": "Timeline",
"timeout": "Timeout",
- "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-timeout).",
+ "timeout_info": "Socket TCP I/O timeout in seconds. If you have problems with hanging FFmpeg processes in the background, you can enter any value here to stop the process automatically after the entered time, if no response comes (-stimeout).",
+ "timerange": "Time Range",
"timestamp": "Tijdstempel",
+ "to": "Tot",
+ "total": "Total",
"tuesday": "Dinsdag",
"type": "Type",
"unbridge_info": "Bridged cameras can cause slowdowns of the entire Homebridge instance. If unbridged, the camera will need to be added to HomeKit manually.",
@@ -453,6 +472,7 @@
"video": "Video",
"video_codec": "Video Codec",
"video_codec_info": "Set the codec used for encoding video sent to HomeKit, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "Video filter",
"video_filter_info": "Comma-delimited list of additional video filters for FFmpeg to run on the video. If 'none' is included, the default video filters are disabled (-filter:v).",
"video_processor_config": "Video Processor Configuratie",
@@ -473,6 +493,7 @@
"widgets": "Widgets",
"width": "Video Breedte",
"width_info": "The width used for video stream.",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "Geel"
}
}
diff --git a/ui/src/i18n/locale/th.json b/ui/src/i18n/locale/th.json
index 0362f307..b52b39a4 100644
--- a/ui/src/i18n/locale/th.json
+++ b/ui/src/i18n/locale/th.json
@@ -32,14 +32,14 @@
"audio": "เสียง",
"audio_codec": "ตัวแปลงสัญญาณเสียง",
"audio_codec_info": "ตั้งค่าตัวแปลงสัญญาณที่ใช้สำหรับการเข้ารหัสเสียงที่ส่งไปยัง HomeKit สำหรับ HSV ต้องเป็นแบบ AAC (-acodec)",
+ "audio_codec_info_hksv": "Set the codec used for encoding audio for HKSV, must be AAC-based (-acodec).",
"audio_info": "เปิดใช้งานการสตรีมเสียงจากกล้อง",
+ "audio_info_hksv": "Enables audio for HKSV recordings.",
"august": "สิงหาคม",
"auto": "อัตโนมัติ",
"auto_darkmode": "โหมดมืดอัตโนมัติ",
"automated_backup": "การสำรองข้อมูลอัตโนมัติ",
"automation": "ระบบอัตโนมัติ",
- "automation_from": "จาก",
- "automation_to": "ถึง",
"aws": "Amazon Web Services",
"aws_access_key_id": "Access Key ID",
"aws_contingent_left": "Contingent Left",
@@ -64,6 +64,7 @@
"base": "ฐาน",
"bitrate": "บิตเรต",
"bitrate_info": "บิเรตตที่ใช้สำหรับการสตรีมวิดีโอ",
+ "bitrate_info_hksv": "The maximum bitrate used HKSV, in kbit/s. If not set, will use any bitrate HomeKit requests (-b:v).",
"blgray": "น้ำเงินเทา",
"blue": "สีน้ำเงินเทา",
"blue_gray": "น้ำเงินเทา",
@@ -115,6 +116,7 @@
"debug": "ดีบัก",
"debug_info": "รวมเอาท์พุตการดีบักจากกระบวนการ FFmpeg หลักในบันทึก",
"december": "ธันวาคม",
+ "disable_info": "Disables the camera and removes it from HomeKit.",
"disabled": "ปิดการใช้งาน",
"domain": "โดเมน",
"doorbell": "กริ่งประตู",
@@ -138,6 +140,7 @@
"enabled": "เปิดใช้งาน",
"encoder_options": "ตัวเลือกการตัวเข้ารหัส",
"encoder_options_info": "ตัวเลือกที่จะส่งผ่านไปยังตัวเข้ารหัสวิดีโอ",
+ "encoder_options_info_hksv": "Options to be passed to the video encoder for the HKSV recording process.",
"endpoints": "ปลายทาง",
"english": "ภาษาอังกฤษ",
"enter_new_password": "กรุณาใส่รหัสผ่านใหม่",
@@ -151,6 +154,7 @@
"ffmpeg_and_stream": "FFmpeg และการสตรีม",
"field_must_not_be_empty": "ช่องต้องไม่เว้นว่าง",
"fill_all_required_fields": "กรุณากรอกข้อมูลให้ครบถ้วน",
+ "filter": "Filter",
"filters": "ตัวกรอง",
"finish": "เสร็จสิ้น",
"finish_zone": "โซนสุดท้าย",
@@ -160,7 +164,9 @@
"forgotpw_title": "ลืมรหัสผ่าน?",
"fps": "FPS",
"fps_info": "fps ที่ใช้สำหรับการสตรีมวิดีโอ",
+ "fps_info_hksv": "The maximum frame rate used for HKSV. If not set, will use any size HomeKit requests (-r).",
"friday": "วันศุกร์",
+ "from": "จาก",
"ftp": "FTP",
"ftp_absolute_path": "เส้นทางแอบโซลูท FTP",
"ftp_server_config": "การกำหนดค่าเซิร์ฟเวอร์ FTP",
@@ -174,6 +180,7 @@
"green": "สีเขียว",
"height": "ความสูงของวิดีโอ",
"height_info": "ความสูงที่ใช้สำหรับการสตรีมวิดีโอ",
+ "height_info_hksv": "The maximum height used for HKSV. If not set, will use any size HomeKit requests (-s).",
"help_started": "เพื่อช่วยให้คุณเริ่มต้นและมอบประสบการณ์ที่ดีกับ camera.ui โปรดทำการกำหนดค่าต่อไปนี้ให้สมบูรณ์",
"homebridge_restart_info": "การเปลี่ยนช่องที่มีเครื่องหมาย (²) ต้องรีสตาร์ท Homebridge",
"host": "โฮสต์",
@@ -203,6 +210,7 @@
"last_updated": "อัพเดทล่าสุด",
"light": "แสงสว่าง",
"list_of_existing_user": "รายชื่อผู้ใช้ที่ลงทะเบียน",
+ "live": "Live",
"livestream": "สตรีมสด",
"livestream_snapshot": "สลับระหว่างสตรีมแบบสดหรือช่วงเวลาสแนปชอต",
"load": "โหลด",
@@ -211,6 +219,8 @@
"location": "ที่ตั้ง",
"log": "บันทึก",
"login": "เข้าสู่ระบบ",
+ "loglevel": "Log Level",
+ "loglevel_info": "Show only defined log level (Info = Show informative messages during processing. This is in addition to warnings and errors - Debug: Show everything, including debugging information - Warning: Show only warnings and errors - Error: Show only errors)",
"manufacturer_info": "ตั้งชื่อผู้ผลิตเพื่อแสดงในแอปโฮม",
"map_audio": "สตรีมเสียง",
"map_audio_info": "เลือกสตรีมที่ใช้สำหรับเสียง (-map)",
@@ -242,6 +252,7 @@
"movement_on": "มีการเคลื่อนไหวบน",
"mqtt": "MQTT",
"mqtt_config": "การกำหนดค่า MQTT",
+ "mqtt_publish_topic": "MQTT Publish Topic",
"name": "ชื่อ",
"never": "ไม่เคย",
"new": "ใหม่",
@@ -254,6 +265,7 @@
"no_access": "ไม่มีสิทธิ์เข้าถึง",
"no_camera_selected": "ยังไม่ได้เลือกกล้อง",
"no_cameras": "ไม่มีกล้อง",
+ "no_data": "No Data",
"no_data_available": "ไม่มีข้อมูลที่สามารถใช้ได้",
"no_feed": "ไม่มีฟีด",
"no_file_selected": "ไม่ได้เลือกไฟล์",
@@ -276,6 +288,7 @@
"notification": "การแจ้งเตือน",
"notification_text": "@ ตรวจพบการเคลื่อนไหวใหม่ในห้อง %",
"notifications": "การแจ้งเตือน",
+ "notifications_alert": "You have a total of % notifications",
"notifications_nav_info": "จัดการการตั้งค่า Alexa, Telegram และ Webhook",
"november": "พฤศจิกายน",
"now": "ตอนนี้",
@@ -326,6 +339,7 @@
"recording_type": "ประเภทการบันทึก",
"recording_type_not_editable": "แก้ไขประเภทการบันทึกไม่ได้ สามารถเปลี่ยนการตั้งค่าได้ใน config.json",
"recordings": "การบันทึก",
+ "recordings_alert": "You have % recordings in total and # recordings were selected",
"recordings_nav_info": "จัดการการตั้งค่าการบันทึกและระบุประเภทการบันทึก",
"refresh": "รีเฟรช",
"registered_user": "ผู้ใช้ที่ลงทะเบียนแล้ว",
@@ -391,6 +405,7 @@
"snapshot_timer": "ตัวจับเวลาสแนปชอต",
"source": "แหล่งที่มาของวิดีโอ",
"source_info": "ตัวเลือก FFmpeg เกี่ยวกับตำแหน่งที่จะค้นหาและวิธีถอดรหัสสตรีมวิดีโอของกล้องของคุณ รูปแบบพื้นฐานที่สุดคือ '-i' ตามด้วย URL ของกล้อง",
+ "source_info_hksv": "Here you can set a custom video source for HKSV recordings only.",
"space_replace": "พื้นที่แทนที่",
"speaker_end_time": "เวลาสิ้นสุดของลำโพง",
"speaker_start_time": "เวลาเริ่มต้นของลำโพง",
@@ -428,9 +443,13 @@
"themes": "ธีม",
"thursday": "วันพฤหัสบดี",
"time": "เวลา",
+ "timeline": "Timeline",
"timeout": "หมดเวลา",
- "timeout_info": "ซ็อกเก็ต TCP I/O หมดเวลาในไม่กี่วินาที หากคุณมีปัญหากับการหยุดทำงาน FFmpeg ในพื้นหลัง คุณสามารถป้อนค่าใดๆ ที่นี่เพื่อหยุดกระบวนการโดยอัตโนมัติหลังจากเวลาที่ป้อน หากไม่มีการตอบสนอง (-timeout)",
+ "timeout_info": "ซ็อกเก็ต TCP I/O หมดเวลาในไม่กี่วินาที หากคุณมีปัญหากับการหยุดทำงาน FFmpeg ในพื้นหลัง คุณสามารถป้อนค่าใดๆ ที่นี่เพื่อหยุดกระบวนการโดยอัตโนมัติหลังจากเวลาที่ป้อน หากไม่มีการตอบสนอง (-stimeout)",
+ "timerange": "Time Range",
"timestamp": "การประทับเวลา",
+ "to": "ถึง",
+ "total": "Total",
"tuesday": "วันอังคาร",
"typ": "Typ",
"unbridge_info": "กล้องบริดจ์สามารถทำให้อินสแตนซ์ Homebridge ทั้งหมดทำงานช้าลง หากไม่บริดจ์ จะต้องเพิ่มกล้องใน HomeKit ด้วยตนเอง",
@@ -456,6 +475,7 @@
"video": "วีดีโอ",
"video_codec": "ตัวแปลงสัญญาณวิดีโอ",
"video_codec_info": "ตั้งค่าตัวแปลงสัญญาณที่ใช้สำหรับการเข้ารหัสวิดีโอที่ส่งไปยัง HomeKit ต้องเป็นแบบ H.264 คุณสามารถเปลี่ยนเป็นตัวแปลงสัญญาณวิดีโอที่เร่งด้วยฮาร์ดแวร์ด้วยตัวเลือกนี้ หากมี (-vcodec)",
+ "video_codec_info_hksv": "Set the codec used for encoding video for HKSV, must be H.264-based. You can change to a hardware accelerated video codec with this option, if one is available (-vcodec).",
"video_filter": "ตัวกรองวิดีโอ",
"video_filter_info": "รายการตัวกรองวิดีโอเพิ่มเติมที่คั่นด้วยเครื่องหมายจุลภาคเพื่อให้ FFmpeg ทำงานบนวิดีโอ หากรวม 'ไม่มี' ไว้ ตัวกรองวิดีโอเริ่มต้นจะถูกปิดใช้งาน (-filter:v)",
"video_processor_config": "การกำหนดค่าโปรเซสเซอร์วิดีโอ",
@@ -476,6 +496,7 @@
"widgets": "วิดเจ็ต",
"width": "ความกว้างของวิดีโอ",
"width_info": "ความกว้างที่ใช้สำหรับสตรีมวิดีโอ",
+ "width_info_hksv": "The maximum width used for HKSV. If not set, will use any size HomeKit requests (-s).",
"yellow": "สีเหลือง"
}
}
diff --git a/ui/src/mixins/socket.js b/ui/src/mixins/socket.js
index 029f68d8..686b1d47 100644
--- a/ui/src/mixins/socket.js
+++ b/ui/src/mixins/socket.js
@@ -187,6 +187,10 @@ export default {
if (this.isPage('Recordings')) {
this.recordings?.unshift(recording);
+ if (this.totalRecordings !== undefined) {
+ this.totalRecordings++;
+ }
+
let mediaContainer = {
type: 'image',
caption: `${recording.camera} - ${recording.time}`,
diff --git a/ui/src/router/index.js b/ui/src/router/index.js
index 986fe2ad..557a7ace 100644
--- a/ui/src/router/index.js
+++ b/ui/src/router/index.js
@@ -121,6 +121,23 @@ export const routes = [
},
component: () => import(/* webpackChunkName: "camera" */ '@/views/Camera/Camera.vue'),
},
+ {
+ path: '/cameras/:name/feed',
+ name: 'CameraFeed',
+ meta: {
+ auth: {
+ requiresAuth: true,
+ requiredLevel: ['cameras:access'],
+ },
+ config: {
+ fixedNavbar: false,
+ showFooter: false,
+ showNavbar: false,
+ showSidebar: false,
+ },
+ },
+ component: () => import(/* webpackChunkName: "cameraFeed" */ '@/views/Camera/CameraFeed.vue'),
+ },
{
path: '/recordings',
name: 'Recordings',
@@ -248,8 +265,8 @@ export const routes = [
component: () => import(/* webpackChunkName: "utilization" */ '@/views/Utilization/Utilization.vue'),
},
{
- path: '/reports',
- name: 'Reports',
+ path: '/timeline',
+ name: 'Timeline',
meta: {
auth: {
requiresAuth: true,
@@ -263,10 +280,10 @@ export const routes = [
},
navigation: {
extras: true,
- icon: 'mdi-script',
+ icon: 'mdi-timeline-outline',
},
},
- component: () => import(/* webpackChunkName: "reports" */ '@/views/Reports/Reports.vue'),
+ component: () => import(/* webpackChunkName: "timeline" */ '@/views/Timeline/Timeline.vue'),
},
{
path: '/settings',
diff --git a/ui/src/views/Camera/Camera.vue b/ui/src/views/Camera/Camera.vue
index 6db1adff..c1174128 100644
--- a/ui/src/views/Camera/Camera.vue
+++ b/ui/src/views/Camera/Camera.vue
@@ -6,13 +6,18 @@
.tw-flex.tw-flex-wrap
v-row.tw-w-full.tw-h-full
- v-col.tw-mb-3.py-2(:cols="cols")
- VideoCard(:ref="camera.name" :camera="camera" stream noLink hideNotifications)
+ v-col.tw-mb-3(:cols="cols")
+ vue-aspect-ratio(ar="16:9" width="100%")
+ VideoCard(:ref="camera.name" :camera="camera" stream noLink hideNotifications)
v-col.tw-flex.tw-justify-between.tw-items-center.tw-mt-2(cols="12")
- .tw-block
- h2.tw-leading-6 {{ $route.params.name }}
- span.subtitle {{ camera.settings.room }}
+ .tw-w-full.tw-flex.tw-justify-between.tw-items-center
+ .tw-block
+ h2.tw-leading-6 {{ $route.params.name }}
+ span.subtitle {{ camera.settings.room }}
+ .tw-block
+ v-btn.tw-text-white(fab small color="var(--cui-primary)" @click="$router.push(`/cameras/${camera.name}/feed`)")
+ v-icon(size="20") {{ icons['mdiOpenInNew'] }}
v-col.tw-px-0.tw-flex.tw-justify-between.tw-items-center.tw-mt-2(:cols="cols")
v-expansion-panels(v-model="notificationsPanel" multiple)
@@ -64,7 +69,8 @@
+
+
diff --git a/ui/src/views/Cameras/Cameras.vue b/ui/src/views/Cameras/Cameras.vue
index 0cff7ab4..be0a3fb9 100644
--- a/ui/src/views/Cameras/Cameras.vue
+++ b/ui/src/views/Cameras/Cameras.vue
@@ -4,17 +4,44 @@
.tw-py-6.tw-px-4(v-else)
.pl-safe.pr-safe
- .header.tw-justify-between.tw-items-center.tw-relative.tw-z-10.tw-items-stretch
- .tw-block
- h2 {{ $t($route.name.toLowerCase()) }}
+ .tw-flex.tw-justify-between
+ .header-title.tw-flex.tw-items-center
+ .page-title {{ $t($route.name.toLowerCase()) }}
+ .header-utils.tw-flex.tw-justify-center.tw-items-center
+ v-btn.tw-mr-1(v-if="showListOptions" icon height="35px" width="35px" :color="listMode ? 'var(--cui-primary)' : 'grey'" @click="changeListView(1)")
+ v-icon(size="25") {{ icons['mdiFormatListBulleted'] }}
+ v-btn(v-if="showListOptions" icon height="35px" width="35px" :color="!listMode ? 'var(--cui-primary)' : 'grey'" @click="changeListView(2)")
+ v-icon(size="25") {{ icons['mdiViewModule'] }}
- .tw-mt-10(v-for="room in rooms" :key="room" v-if="(room === 'Standard' && cameras.find((cam) => cam.settings.room === room)) || room !== 'Standard'")
- h3 {{ room === 'Standard' ? $t('standard') : room }}
- v-divider.tw-mt-3
+ .tw-mt-5
+ v-data-table.tw-w-full(v-if="listMode && cameras.length" @click:row="clickRow" :items-per-page="-1" calculate-widths disable-pagination hide-default-footer :loading="loading" :headers="headers" :items="cameras" :no-data-text="$t('no_data_available')" item-key="name" class="elevation-1" mobile-breakpoint="0")
+ template(v-slot:item.status="{ item }")
+ .tw-w-full.tw-text-center
+ v-icon(size="10" :color="camStates.some((cam) => cam.name === item.name && cam.status === 'ONLINE') ? 'success' : 'error'") {{ icons['mdiCircle'] }}
+ template(v-slot:item.preview="{ item }")
+ vue-aspect-ratio.tw-m-3(ar="16:9" width="100px")
+ VideoCard(:camera="item" snapshot @cameraStatus="cameraStatus")
+ template(v-slot:item.name="{ item }")
+ b {{ item.name }}
+ template(v-slot:item.model="{ item }")
+ .text-font-disabled {{ item.model || 'IP Camera' }}
+ template(v-slot:item.address="{ item }")
+ .text-font-disabled {{ item.url }}
+ template(v-slot:item.lastNotification="{ item }")
+ .text-font-disabled {{ item.lastNotification ? item.lastNotification.time : $t('no_data') }}
+ template(v-slot:item.liveFeed="{ item }")
+ v-chip(color="var(--cui-primary)" dark small style="cursor: pointer" @click="$router.push(`/cameras/${item.name}`)") {{ camStates.some((cam) => cam.name === item.name && cam.status === 'ONLINE') ? $t('live') : $t('offline') }}
- v-layout.tw-mt-5(row wrap)
- v-flex.tw-mb-3.tw-px-2(xs12 sm6 md4 lg3 v-for="camera in cameras" :key="camera.name" :style="`height: ${height}px`" v-if="camera.settings.room === room")
- VideoCard(:camera="camera" title titlePosition="bottom" snapshot)
+ div(v-for="room in rooms" :key="room" v-if="!listMode && ((room === 'Standard' && cameras.find((cam) => cam.settings.room === room)) || room !== 'Standard')")
+ .tw-mt-7(v-if="room !== 'Standard'")
+
+ h4(style="font-weight: 700;") {{ room === 'Standard' ? $t('standard') : room }}
+ v-divider.tw-mt-3
+
+ v-layout.tw-mt-5(row wrap)
+ v-flex.tw-mb-3.tw-px-2(v-if="!listMode && camera.settings.room === room" xs12 sm6 md4 lg3 v-for="camera in cameras" :key="camera.name")
+ vue-aspect-ratio(ar="4:3")
+ VideoCard(:camera="camera" title titlePosition="bottom" snapshot)
infinite-loading(:identifier="infiniteId", @infinite="infiniteHandler")
div(slot="spinner")
@@ -37,7 +64,8 @@
import LightBox from 'vue-it-bigger';
import 'vue-it-bigger/dist/vue-it-bigger.min.css';
import InfiniteLoading from 'vue-infinite-loading';
-import { mdiPlus } from '@mdi/js';
+import { mdiCircle, mdiPlus, mdiFormatListBulleted, mdiViewModule } from '@mdi/js';
+import VueAspectRatio from 'vue-aspect-ratio';
import { getSetting } from '@/api/settings.api';
import { getCameras, getCameraSettings } from '@/api/cameras.api';
@@ -56,6 +84,7 @@ export default {
FilterCard,
InfiniteLoading,
VideoCard,
+ 'vue-aspect-ratio': VueAspectRatio,
},
mixins: [socket],
@@ -67,7 +96,10 @@ export default {
data: () => ({
icons: {
+ mdiCircle,
mdiPlus,
+ mdiFormatListBulleted,
+ mdiViewModule,
},
cameras: [],
@@ -75,36 +107,111 @@ export default {
infiniteId: Date.now(),
page: 1,
query: '',
+
rooms: [],
+ camStates: [],
+
+ backupHeaders: [],
+ headers: [
+ {
+ text: 'Status',
+ value: 'status',
+ align: 'start',
+ sortable: false,
+ class: 'tw-py-3',
+ cellClass: 'tw-py-3',
+ width: '30px',
+ },
+ {
+ text: '',
+ value: 'preview',
+ align: 'start',
+ sortable: false,
+ width: '100px',
+ class: 'tw-px-1',
+ cellClass: 'tw-px-0',
+ },
+ {
+ text: 'Name',
+ value: 'name',
+ align: 'start',
+ sortable: true,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ {
+ text: 'Model',
+ value: 'model',
+ align: 'start',
+ sortable: true,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ {
+ text: 'Address',
+ value: 'address',
+ align: 'start',
+ sortable: false,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ {
+ text: 'Last Motion',
+ value: 'lastNotification',
+ align: 'start',
+ sortable: true,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ {
+ text: '',
+ value: 'liveFeed',
+ align: 'start',
+ sortable: false,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ ],
+
+ oldSelected: false,
+ listMode: false,
+ showListOptions: true,
}),
- computed: {
- height() {
- switch (this.$vuetify.breakpoint.name) {
- case 'xs':
- return 300;
- case 'sm':
- return 250;
- case 'md':
- return 225;
- case 'lg':
- return 225;
- case 'xl':
- return 250;
- default:
- return 250;
- }
- },
+ beforeDestroy() {
+ ['resize', 'orientationchange'].forEach((event) => {
+ window.removeEventListener(event, this.onResize);
+ });
},
async mounted() {
const response = await getSetting('general');
this.rooms = response.data.rooms;
+ this.listMode = this.oldSelected = localStorage.getItem('listModeCameras') === '1';
+ this.backupHeaders = [...this.headers];
this.loading = false;
+
+ ['resize', 'orientationchange'].forEach((event) => {
+ window.addEventListener(event, this.onResize);
+ });
+
+ this.onResize();
},
methods: {
+ cameraStatus(data) {
+ if (!this.camStates.some((cam) => cam.name === data.name)) {
+ this.camStates.push(data);
+ }
+ },
+ clickRow(item) {
+ this.$router.push(`/cameras/${item.name}`);
+ },
+ changeListView(view) {
+ localStorage.setItem('listModeCameras', view);
+ this.listMode = this.oldSelected = view === 1;
+ },
filter(filterQuery) {
this.loading = true;
this.cameras = [];
@@ -123,6 +230,14 @@ export default {
const lastNotification = await getNotifications(`?cameras=${camera.name}&pageSize=5`);
camera.lastNotification = lastNotification.data.result.length > 0 ? lastNotification.data.result[0] : false;
+
+ camera.url = camera.videoConfig.source.replace(/\u00A0/g, ' ').split('-i ')[1];
+
+ if (!camera.url.startsWith('/')) {
+ const protocol = camera.url.split('://')[0];
+ const url = new URL(camera.url.replace(protocol, 'http'));
+ camera.url = `${protocol}://${url.hostname}:${url.port || 80}${url.pathname}`;
+ }
}
if (response.data.result.length > 0) {
@@ -138,12 +253,66 @@ export default {
this.$toast.error(err.message);
}
},
+ onResize() {
+ const removeHeaders = [];
+
+ if (this.windowWidth() < 415) {
+ removeHeaders.push('model', 'address', 'lastNotification', 'liveFeed');
+ } else if (this.windowWidth() < 650) {
+ removeHeaders.push('model', 'address', 'lastNotification');
+ } else if (this.windowWidth() <= 800) {
+ removeHeaders.push('model', 'lastNotification');
+ } else if (this.windowWidth() < 900) {
+ removeHeaders.push('model');
+
+ /*if (!this.toggleView) {
+ this.toggleView = true;
+ this.oldSelected = this.listMode;
+ }
+
+ this.showListOptions = false;
+ this.listMode = false;*/
+ } else {
+ /*this.showListOptions = true;
+
+ if (this.toggleView) {
+ this.listMode = this.oldSelected;
+ this.toggleView = false;
+ }*/
+ }
+
+ let headers = [...this.backupHeaders];
+
+ if (removeHeaders.length) {
+ headers = headers.filter((header) => !removeHeaders.some((val) => header.value === val));
+ }
+
+ this.headers = headers;
+ },
+ windowWidth() {
+ return window.innerWidth && document.documentElement.clientWidth
+ ? Math.min(window.innerWidth, document.documentElement.clientWidth)
+ : window.innerWidth ||
+ document.documentElement.clientWidth ||
+ document.getElementsByTagName('body')[0].clientWidth;
+ },
},
};
diff --git a/ui/src/views/Console/Console.vue b/ui/src/views/Console/Console.vue
index 2d893d95..b6d2da4c 100644
--- a/ui/src/views/Console/Console.vue
+++ b/ui/src/views/Console/Console.vue
@@ -1,20 +1,37 @@
.tw-flex.tw-justify-center.tw-items-center.page-loading(v-if="loading")
v-progress-circular(indeterminate color="var(--cui-primary)")
-.console-container.tw-relative(v-else)
- #log.tw-relative.tw-h-full
- .utils.tw-flex.tw-justify-between.tw-items-center
- .remove-btn.tw-block.tw-ml-auto
- v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onRemove" :loading="loadingRemove")
- v-icon.tw-text-white {{ icons['mdiDeleteEmpty'] }}
- .dl-btn.tw-block.tw-ml-1
- v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onDownload" :loading="loadingDownload")
- v-icon.tw-text-white {{ icons['mdiDownload'] }}
- .share-btn.tw-block.tw-ml-1(v-if="showShare")
- v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onShare" :loading="loadingShare")
- v-icon.tw-text-white {{ icons['mdiShareVariant'] }}
-
- my-terminal(:terminal="terminal" ref="xterm")
+.tw-relative(v-else)
+ .filter-button(ref="filterButton" @click="toggleFilter" v-click-outside="{ handler: hideFilter, include: include }")
+ v-icon(size="20" color="white") {{ filterOpened ? icons['mdiChevronUp'] : icons['mdiChevronDown'] }}
+ .filter.tw-flex.tw-items-center.tw-p-2(ref="filter" v-click-outside="{ handler: hideFilter, include: include }")
+ span {{ $t('filter') }}
+ v-combobox.tw-ml-3.selector(v-model="selectedFilter" background-color="var(--cui-bg-card)" :no-data-text="$t('no_data_available')" :items="filterData" item-value="key" item-text="key" :search-input.sync="search" prepend-inner-icon="mdi-filter" hide-selected label="..." multiple small-chips deletable-chips dense hide-details solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiFilter'] }}
+ template(v-slot:no-data v-if="search")
+ v-list-item
+ v-list-item-content
+ v-list-item-title
+ span {{ $t('no_label_matching') }}
+ strong "{{ search }}"
+ span . {{ $t('press_enter_to_create').split(' %')[0] }}
+ kbd {{ $t('press_enter_to_create').split(' %')[1].split('% ')[0] }}
+ span {{ $t('press_enter_to_create').split('% ')[1] }}
+ .console-container.tw-relative
+ #log.tw-relative.tw-h-full
+ .utils.tw-flex.tw-justify-between.tw-items-center
+ .remove-btn.tw-block.tw-ml-auto
+ v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onRemove" :loading="loadingRemove")
+ v-icon.tw-text-white {{ icons['mdiDeleteEmpty'] }}
+ .dl-btn.tw-block.tw-ml-1
+ v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onDownload" :loading="loadingDownload")
+ v-icon.tw-text-white {{ icons['mdiDownload'] }}
+ .share-btn.tw-block.tw-ml-1(v-if="showShare")
+ v-btn.tw-text-white.tw-mr-1(fab height="40px" width="40px" color="rgba(var(--cui-primary-rgb))" @click="onShare" :loading="loadingShare")
+ v-icon.tw-text-white {{ icons['mdiShareVariant'] }}
+
+ my-terminal(:terminal="terminal" :filter="selectedFilter" ref="xterm")
LightBox(
ref="lightboxBanner"
@@ -30,8 +47,9 @@
diff --git a/ui/src/views/Notifications/Notifications.vue b/ui/src/views/Notifications/Notifications.vue
index c59415dc..b27ab235 100644
--- a/ui/src/views/Notifications/Notifications.vue
+++ b/ui/src/views/Notifications/Notifications.vue
@@ -2,25 +2,33 @@
.tw-flex.tw-justify-center.tw-items-center.page-loading(v-if="loading")
v-progress-circular(indeterminate color="var(--cui-primary)")
.tw-py-6.tw-px-4(v-else)
- .pl-safe.pr-safe
-
- .header.tw-justify-between.tw-items-center.header.tw-relative.tw-z-10.tw-items-stretch
- .tw-block
- h2 {{ $t($route.name.toLowerCase()) }}
- .header-utils.tw-flex.tw-justify-center.tw-items-center
- FilterCard(camerasSelect datePicker labelSelect roomSelect typeSelect @filter="filter")
- v-btn(icon height="30px" width="30px" color="red" :disabled="!notifications.length" @click="removeAll")
- v-icon(size="20") {{ icons['mdiDelete'] }}
-
- v-row.tw-mt-5.overflow-hidden
- v-col(v-for="(notification,i) in notifications" :key="notification.id" cols="12")
- NotificationCard(ref="notifications" :notification="notification" @remove="remove(notification, i)" @show="openGallery(notification)" @slideStart="closeNotifications(notification)")
-
- infinite-loading(:identifier="infiniteId", @infinite="infiniteHandler")
- .tw-mt-10(slot="spinner")
- v-progress-circular(indeterminate color="var(--cui-primary)")
- .tw-mt-10.tw-text-sm.text-muted(slot="no-more") {{ $t("no_more_notifications") }}
- .tw-mt-10.tw-text-sm.text-muted(slot="no-results") {{ $t("no_notifications") }}
+ .tw-flex.tw-relative.pl-safe.pr-safe
+
+ Sidebar(camerasSelect datePicker labelSelect roomSelect typeSelect @filter="filter")
+
+ .overlay(v-if="showOverlay")
+
+ .filter-content.filter-included.tw-w-full.tw-relative
+ .tw-flex.tw-justify-between
+ .header-title.tw-flex.tw-items-center
+ v-btn.included.filter-nav-toggle(@click="toggleFilterNavi" icon height="38px" width="38px" :color="selectedFilter.length ? 'var(--cui-primary)' : 'var(--cui-text-default)'")
+ v-icon {{ icons['mdiFilter'] }}
+ v-badge(overlap :content="totalNotifications.toString()" offset-x="0")
+ .page-title {{ $t($route.name.toLowerCase()) }}
+ .header-utils.tw-flex.tw-justify-center.tw-items-center
+ v-btn(fab elevation="1" height="35px" width="35px" color="red" :disabled="!notifications.length" @click="removeAll")
+ v-icon(size="20" color="white") {{ icons['mdiDelete'] }}
+
+ .tw-mt-5
+ v-row.overflow-hidden
+ v-col(v-for="(notification,i) in notifications" :key="notification.id" cols="12")
+ NotificationCard(ref="notifications" :notification="notification" @remove="remove(notification, i)" @show="openGallery(notification)" @slideStart="closeNotifications(notification)")
+
+ infinite-loading(:identifier="infiniteId", @infinite="infiniteHandler")
+ .tw-mt-10(slot="spinner")
+ v-progress-circular(indeterminate color="var(--cui-primary)")
+ .tw-mt-10.tw-text-sm.text-muted(slot="no-more") {{ $t("no_more_notifications") }}
+ .tw-mt-10.tw-text-sm.text-muted(slot="no-results") {{ $t("no_notifications") }}
LightBox(
ref="lightbox"
@@ -46,12 +54,15 @@
import LightBox from 'vue-it-bigger';
import 'vue-it-bigger/dist/vue-it-bigger.min.css';
import InfiniteLoading from 'vue-infinite-loading';
-import { mdiDelete } from '@mdi/js';
+import { mdiDelete, mdiFilter } from '@mdi/js';
import { getNotifications, removeNotification, removeNotifications } from '@/api/notifications.api';
+import { bus } from '@/main';
+
import FilterCard from '@/components/filter.vue';
import NotificationCard from '@/components/notification-card.vue';
+import Sidebar from '@/components/sidebar-filter.vue';
import socket from '@/mixins/socket';
@@ -63,6 +74,7 @@ export default {
FilterCard,
InfiniteLoading,
NotificationCard,
+ Sidebar,
},
mixins: [socket],
@@ -75,6 +87,7 @@ export default {
data: () => ({
icons: {
mdiDelete,
+ mdiFilter,
},
images: [],
index: null,
@@ -83,8 +96,27 @@ export default {
notifications: [],
page: 1,
query: '',
+ selectedFilter: [],
+ totalNotifications: 0,
+ showOverlay: false,
}),
+ watch: {
+ query: {
+ handler() {
+ this.selectedFilter = this.query.split('&').filter((query) => query);
+ },
+ },
+ },
+
+ created() {
+ bus.$on('showFilterOverlay', this.triggerFilterOverlay);
+ },
+
+ beforeDestroy() {
+ bus.$off('showFilterOverlay', this.triggerFilterOverlay);
+ },
+
mounted() {
this.loading = false;
},
@@ -98,17 +130,21 @@ export default {
});
},
filter(filterQuery) {
- this.loading = true;
- this.notifications = [];
- this.page = 1;
- this.query = filterQuery;
- this.infiniteId = Date.now();
- this.loading = false;
+ if (this.query !== filterQuery) {
+ this.loading = true;
+ this.notifications = [];
+ this.page = 1;
+ this.query = filterQuery;
+ this.infiniteId = Date.now();
+ this.loading = false;
+ }
},
async infiniteHandler($state) {
try {
const response = await getNotifications(`?pageSize=5&page=${this.page || 1}` + this.query);
+ this.totalNotifications = response.data.pagination.totalItems;
+
if (response.data.result.length > 0) {
this.page += 1;
this.notifications.push(...response.data.result);
@@ -188,6 +224,8 @@ export default {
this.images.splice(imgIndex, 1);
}
+ this.totalNotifications--;
+
this.$toast.success(`${this.$t('notification')} ${this.$t('removed')}!`);
} catch (err) {
console.log(err);
@@ -201,17 +239,33 @@ export default {
this.$store.dispatch('notifications/removeAll');
this.notifications = [];
this.images = [];
+ this.totalNotifications = 0;
this.$toast.success(this.$t('all_notifications_removed'));
} catch (err) {
console.log(err);
this.$toast.error(err.message);
}
},
+ triggerFilterOverlay(state) {
+ this.showOverlay = state;
+ },
+ toggleFilterNavi() {
+ this.showOverlay = true;
+ bus.$emit('showFilterNavi', true);
+ },
},
};
diff --git a/ui/src/views/Recordings/Recordings.vue b/ui/src/views/Recordings/Recordings.vue
index 65a4f8e6..f0182302 100644
--- a/ui/src/views/Recordings/Recordings.vue
+++ b/ui/src/views/Recordings/Recordings.vue
@@ -2,25 +2,56 @@
.tw-flex.tw-justify-center.tw-items-center.page-loading(v-if="loading")
v-progress-circular(indeterminate color="var(--cui-primary)")
.tw-py-6.tw-px-4(v-else)
- .pl-safe.pr-safe
-
- .header.tw-justify-between.tw-items-center.tw-relative.tw-z-10.tw-items-stretch
+ .tw-flex.tw-relative.pl-safe.pr-safe
+
+ Sidebar(camerasSelect datePicker labelSelect roomSelect typeSelect @filter="filter")
+
+ .overlay(v-if="showOverlay")
+
+ .filter-content.filter-included.tw-w-full.tw-relative
+ .tw-flex.tw-justify-between
+ .header-title.tw-flex.tw-items-center
+ v-btn.included.filter-nav-toggle(@click="toggleFilterNavi" icon height="38px" width="38px" :color="selectedFilter.length ? 'var(--cui-primary)' : 'var(--cui-text-default)'")
+ v-icon {{ icons['mdiFilter'] }}
+ v-badge(overlap :content="totalRecordings.toString()" offset-x="0")
+ .page-title {{ $t($route.name.toLowerCase()) }}
+ .header-utils.tw-flex.tw-justify-center.tw-items-center
+ v-btn.tw-mr-1(v-if="showListOptions" icon height="35px" width="35px" :color="listMode ? 'var(--cui-primary)' : 'grey'" @click="changeListView(1)")
+ v-icon(size="25") {{ icons['mdiFormatListBulleted'] }}
+ v-btn.tw-mr-3(v-if="showListOptions" icon height="35px" width="35px" :color="!listMode ? 'var(--cui-primary)' : 'grey'" @click="changeListView(2)")
+ v-icon(size="25") {{ icons['mdiViewModule'] }}
+ v-btn(fab elevation="1" height="35px" width="35px" color="red" :disabled="!selected.length" @click="remove")
+ v-badge(v-if="selected.length" :content="selected.length > 999 ? '999+' : selected.length" overlap offset-x="9" offset-y="10" color="grey")
+ v-icon(size="20" color="white") {{ icons['mdiDelete'] }}
+ v-icon(v-else size="20" color="white") {{ icons['mdiDelete'] }}
+
.tw-block
- h2 {{ $t($route.name.toLowerCase()) }}
- .header-utils.tw-flex.tw-justify-center.tw-items-center
- FilterCard(camerasSelect datePicker labelSelect roomSelect typeSelect @filter="filter")
- v-btn(icon height="30px" width="30px" color="red" :disabled="!recordings.length" @click="removeAll")
- v-icon(size="20") {{ icons['mdiDelete'] }}
-
- v-layout.tw-mt-5(row wrap)
- v-flex.tw-mb-4.tw-px-2(xs12 sm6 md4 lg3 xl2 v-for="(recording, i) in recordings" :key="recording.id" :style="`height: ${height}px`")
- RecordingCard(ref="recordings" :recording="recording" @show="openGallery(i)" @remove="remove(recording, i)")
-
- infinite-loading(:identifier="infiniteId" @infinite="infiniteHandler")
- .tw-mt-10(slot="spinner")
- v-progress-circular(indeterminate color="var(--cui-primary)")
- .tw-mt-10.tw-text-sm.text-muted(slot="no-more") {{ $t("no_more_recordings") }}
- .tw-mt-10.tw-text-sm.text-muted(slot="no-results") {{ $t("no_recordings") }}
+ v-layout(row wrap :class="listMode && recordings.length ? 'tw-m-0 tw-mt-5' : 'tw-mt-5'")
+ v-flex.tw-mb-4.tw-px-2(v-if="!listMode" xs12 sm6 md4 lg3 xl2 v-for="(recording, i) in recordings" :key="recording.id")
+ vue-aspect-ratio(ar="4:3")
+ RecordingCard(ref="recordings" :selectedItems="selected" :recording="recording" @select="clickRow(recording)" @show="openGallery(recording)" @remove="remove(recording, i)")
+
+ v-data-table.tw-w-full(v-if="listMode && recordings.length" :items-per-page="-1" calculate-widths disable-pagination hide-default-footer v-model="selected" :loading="loading" :headers="headers" :items="recordings" :no-data-text="$t('no_data_available')" item-key="id" show-select class="elevation-1" checkbox-color="var(--cui-primary)" mobile-breakpoint="0" @click:row="clickRow")
+ template(v-slot:item.preview="{ item }")
+ vue-aspect-ratio.tw-m-3(ar="16:9" width="100px")
+ RecordingCard(ref="recordings" list :selectedItems="selected" :recording="item" @show="openGallery(item)" @remove="remove(item, item.index)")
+ template(v-slot:item.camera="{ item }")
+ b {{ item.camera }}
+ template(v-slot:item.recordType="{ item }")
+ .text-font-disabled {{ item.recordType }}
+ template(v-slot:item.time="{ item }")
+ .text-font-disabled {{ item.time }}
+ template(v-slot:item.label="{ item }")
+ v-chip(color="var(--cui-primary)" dark small) {{ item.label.includes("no label") ? $t("no_label") : item.label.includes("Custom") ? $t("custom") : item.label }}
+ template(v-slot:item.download="{ item }")
+ v-btn.tw-text-white(fab x-small color="#434343" elevation="1" @click="download(item)")
+ v-icon {{ icons['mdiDownload'] }}
+
+ infinite-loading(:identifier="infiniteId" @infinite="infiniteHandler")
+ .tw-mt-10(slot="spinner")
+ v-progress-circular(indeterminate color="var(--cui-primary)")
+ .tw-mt-10.tw-text-sm.text-muted(slot="no-more") {{ $t("no_more_recordings") }}
+ .tw-mt-10.tw-text-sm.text-muted(slot="no-results") {{ $t("no_recordings") }}
LightBox(
ref="lightbox"
@@ -46,12 +77,17 @@
import LightBox from 'vue-it-bigger';
import 'vue-it-bigger/dist/vue-it-bigger.min.css';
import InfiniteLoading from 'vue-infinite-loading';
-import { mdiDelete } from '@mdi/js';
+import { mdiDelete, mdiDownload, mdiFilter, mdiFormatListBulleted, mdiViewModule } from '@mdi/js';
+import { saveAs } from 'file-saver';
+import VueAspectRatio from 'vue-aspect-ratio';
+
+import { getRecordings, removeRecording, removeRecordings } from '@/api/recordings.api';
-import { getRecordings, removeRecordings } from '@/api/recordings.api';
+import { bus } from '@/main';
import FilterCard from '@/components/filter.vue';
import RecordingCard from '@/components/recording-card.vue';
+import Sidebar from '@/components/sidebar-filter.vue';
import socket from '@/mixins/socket';
@@ -63,6 +99,8 @@ export default {
InfiniteLoading,
LightBox,
RecordingCard,
+ Sidebar,
+ 'vue-aspect-ratio': VueAspectRatio,
},
mixins: [socket],
@@ -72,54 +110,202 @@ export default {
next();
},
- data: () => ({
- icons: {
- mdiDelete,
- },
- images: [],
- infiniteId: Date.now(),
- loading: false,
- page: 1,
- query: '',
- recordings: [],
- }),
-
- computed: {
- height() {
- switch (this.$vuetify.breakpoint.name) {
- case 'xs':
- return 300;
- case 'sm':
- return 250;
- case 'md':
- return 225;
- case 'lg':
- return 225;
- case 'xl':
- return 250;
- default:
- return 250;
- }
+ data() {
+ return {
+ icons: {
+ mdiDelete,
+ mdiDownload,
+ mdiFilter,
+ mdiFormatListBulleted,
+ mdiViewModule,
+ },
+ images: [],
+ infiniteId: Date.now(),
+ loading: false,
+ page: 1,
+ query: '',
+ selectedFilter: [],
+ recordings: [],
+ totalRecordings: 0,
+ showOverlay: false,
+
+ listMode: false,
+ showListOptions: true,
+ toggleView: false,
+ oldSelected: false,
+
+ selected: [],
+
+ backupHeaders: [],
+ headers: [
+ {
+ text: '',
+ value: 'preview',
+ align: 'start',
+ sortable: false,
+ width: '100px',
+ class: 'tw-px-1',
+ cellClass: 'tw-px-0',
+ },
+ {
+ text: this.$t('camera'),
+ value: 'camera',
+ align: 'start',
+ sortable: true,
+ class: 'tw-pl-3 tw-pr-1',
+ cellClass: 'tw-pl-3 tw-pr-1',
+ },
+ {
+ text: this.$t('type'),
+ value: 'recordType',
+ align: 'start',
+ sortable: true,
+ class: 'tw-py-3',
+ cellClass: 'tw-py-3',
+ },
+ {
+ text: this.$t('time'),
+ value: 'time',
+ align: 'start',
+ sortable: true,
+ class: 'tw-px-1',
+ cellClass: 'tw-px-1',
+ },
+ {
+ text: this.$t('label'),
+ value: 'label',
+ align: 'start',
+ sortable: true,
+ class: 'tw-px-1',
+ cellClass: 'tw-px-1',
+ },
+ {
+ text: '',
+ value: 'download',
+ align: 'start',
+ sortable: false,
+ width: '80px',
+ class: 'tw-py-1',
+ cellClass: 'tw-py-1',
+ },
+ ],
+ };
+ },
+
+ watch: {
+ query: {
+ handler() {
+ this.selectedFilter = this.query.split('&').filter((query) => query);
+ },
},
},
+ created() {
+ bus.$on('showFilterOverlay', this.triggerFilterOverlay);
+ },
+
+ beforeDestroy() {
+ bus.$off('showFilterOverlay', this.triggerFilterOverlay);
+
+ ['resize', 'orientationchange'].forEach((event) => {
+ window.removeEventListener(event, this.onResize);
+ });
+ },
+
mounted() {
+ this.backupHeaders = [...this.headers];
+ this.listMode = this.oldSelected = localStorage.getItem('listModeRecordings') === '1';
+
this.loading = false;
+
+ ['resize', 'orientationchange'].forEach((event) => {
+ window.addEventListener(event, this.onResize);
+ });
+
+ this.onResize();
},
methods: {
+ clickRow(data) {
+ if (this.downloading) {
+ return;
+ }
+
+ if (this.selected.find((item) => item.id === data.id)) {
+ this.selected = this.selected.filter((item) => item.id !== data.id);
+ } else {
+ this.selected.push(data);
+ }
+ },
+ changeListView(view) {
+ localStorage.setItem('listModeRecordings', view);
+ this.listMode = this.oldSelected = view === 1;
+ },
+ download({ url, fileName }) {
+ this.downloading = true;
+
+ const isSafari = navigator.appVersion.indexOf('Safari/') !== -1 && navigator.appVersion.indexOf('Chrome') === -1;
+
+ const downloadFinished = () => {
+ setTimeout(() => (this.downloading = false), 1000);
+ };
+
+ if (isSafari) {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.responseType = 'blob';
+
+ xhr.onload = function () {
+ saveAs(xhr.response, fileName);
+ downloadFinished();
+ };
+
+ xhr.onerror = function () {
+ console.error('download failed', url);
+ this.$toast.error(`${this.$t('download_failed')}`);
+ downloadFinished();
+ };
+
+ xhr.send();
+ return;
+ }
+
+ // Create download link.
+ const link = document.createElement('a');
+
+ if (fileName) {
+ link.download = fileName;
+ }
+
+ link.href = url;
+ link.style.display = 'none';
+
+ document.body.appendChild(link);
+
+ // Start download.
+ link.click();
+
+ // Remove download link.
+ document.body.removeChild(link);
+
+ downloadFinished();
+ },
filter(filterQuery) {
- this.loading = true;
- this.recordings = [];
- this.page = 1;
- this.query = filterQuery;
- this.infiniteId = Date.now();
- this.loading = false;
+ if (this.query !== filterQuery) {
+ this.loading = true;
+ this.recordings = [];
+ this.page = 1;
+ this.query = filterQuery;
+ this.infiniteId = Date.now();
+ this.loading = false;
+ }
},
async infiniteHandler($state) {
try {
const response = await getRecordings(`?refresh=true&pageSize=6&page=${this.page || 1}` + this.query);
+ this.totalRecordings = response.data.pagination.totalItems;
+
if (response.data.result.length > 0) {
this.page += 1;
this.recordings.push(...response.data.result);
@@ -163,31 +349,125 @@ export default {
this.$toast.error(err.message);
}
},
- openGallery(index) {
+ openGallery(item) {
+ const index = this.recordings.findIndex((recording) => recording.id === item.id);
this.$refs.lightbox.showImage(index);
},
- async remove(recording, index) {
- this.recordings = this.recordings.filter((recording, i) => i !== index);
- this.images = this.images.filter((image, i) => i !== index);
- this.$toast.success(`${this.$t('recording')} ${this.$t('removed')}!`);
+ onResize() {
+ const removeHeaders = [];
+
+ if (this.windowWidth() < 350) {
+ removeHeaders.push('time', 'label', 'recordType', 'download');
+ } else if (this.windowWidth() < 415) {
+ removeHeaders.push('time', 'label', 'recordType');
+ } else if (this.windowWidth() <= 550) {
+ removeHeaders.push('time', 'label');
+ } else if (this.windowWidth() < 605) {
+ removeHeaders.push('time');
+
+ /*if (!this.toggleView) {
+ this.toggleView = true;
+ this.oldSelected = this.listMode;
+ }
+
+ this.showListOptions = false;
+ this.listMode = false;*/
+ } else {
+ /*this.showListOptions = true;
+
+ if (this.toggleView) {
+ this.listMode = this.oldSelected;
+ this.toggleView = false;
+ }*/
+ }
+
+ let headers = [...this.backupHeaders];
+
+ if (removeHeaders.length) {
+ headers = headers.filter((header) => !removeHeaders.some((val) => header.value === val));
+ }
+
+ this.headers = headers;
+ },
+ selectAllRecordings() {
+ if (this.recordings.length && this.selected.length === this.recordings.length) {
+ this.selected = [];
+ } else {
+ this.selected = this.recordings.map((recording) => recording);
+ }
+ },
+ async remove() {
+ if (!this.selected.length) {
+ return;
+ }
+
+ if (this.selected.length === this.recordings.length) {
+ this.removeAll();
+ }
+
+ for (const recording of this.selected) {
+ try {
+ await removeRecording(recording.id, '?refresh=true');
+
+ const index = this.recordings.findIndex((rec) => rec.id === recording.id);
+
+ this.selected = this.selected.filter((rec) => rec.id !== recording.id);
+ this.recordings = this.recordings.filter((rec) => rec.id !== recording.id);
+
+ this.images = this.images.filter((image, i) => i !== index);
+ this.$toast.success(`${this.$t('recording')} ${this.$t('removed')}!`);
+
+ this.totalRecordings--;
+ } catch (err) {
+ console.log(err);
+ this.$toast.error(err.message);
+
+ return;
+ }
+ }
},
async removeAll() {
try {
await removeRecordings();
+ this.selected = [];
this.recordings = [];
this.images = [];
this.$toast.success(this.$t('all_recordings_removed'));
+
+ this.totalRecordings = 0;
} catch (err) {
console.log(err);
this.$toast.error(err.message);
}
},
+ triggerFilterOverlay(state) {
+ this.showOverlay = state;
+ },
+ toggleFilterNavi() {
+ this.showOverlay = true;
+ bus.$emit('showFilterNavi', true);
+ },
+ windowWidth() {
+ return window.innerWidth && document.documentElement.clientWidth
+ ? Math.min(window.innerWidth, document.documentElement.clientWidth)
+ : window.innerWidth ||
+ document.documentElement.clientWidth ||
+ document.getElementsByTagName('body')[0].clientWidth;
+ },
},
};
diff --git a/ui/src/views/Reports/Reports.vue b/ui/src/views/Reports/Reports.vue
deleted file mode 100644
index 2214e712..00000000
--- a/ui/src/views/Reports/Reports.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-
-.tw-flex.tw-justify-center.tw-items-center.page-loading(v-if="loading")
- v-progress-circular(indeterminate color="var(--cui-primary)")
-.tw-py-6.tw-px-4(v-else)
- .pl-safe.pr-safe
-
- .header.tw-justify-between.tw-items-center.header.tw-relative.tw-z-10.tw-items-stretch
- .tw-block
- h2 {{ $t($route.name.toLowerCase()) }}
-
- span.text-muted coming soon...
-
- LightBox(
- ref="lightboxBanner"
- :media="notImages"
- :showLightBox="false"
- :showThumbs="false"
- showCaption
- disableScroll
- )
-
-
-
-
-
-
diff --git a/ui/src/views/Settings/Settings.vue b/ui/src/views/Settings/Settings.vue
index b4eb2d32..34f48c06 100644
--- a/ui/src/views/Settings/Settings.vue
+++ b/ui/src/views/Settings/Settings.vue
@@ -9,7 +9,7 @@
.overlay(v-if="showOverlay")
.settings-content.settings-included.tw-w-full.tw-relative
- .tw-flex.tw-flex
+ .tw-flex.tw-items-center
v-btn.text-default.included.settings-nav-toggle(@click="toggleSettingsNavi" icon height="38px" width="38px")
v-icon {{ icons['mdiMenu'] }}
.page-title {{ $t($route.meta.name.toLowerCase()) }}
@@ -99,7 +99,7 @@ export default {
diff --git a/ui/src/views/Settings/subpages/cameras.vue b/ui/src/views/Settings/subpages/cameras.vue
index f84efc36..b27b5cb4 100644
--- a/ui/src/views/Settings/subpages/cameras.vue
+++ b/ui/src/views/Settings/subpages/cameras.vue
@@ -128,6 +128,11 @@
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiLink'] }}
+ label.form-input-label {{ $t('mqtt_publish_topic') }}
+ v-text-field(v-model="camera.mqttTopic" prepend-inner-icon="mdi-link" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiLink'] }}
+
v-expansion-panel
v-expansion-panel-header
div
@@ -293,6 +298,14 @@
v-sheet.tw-p-3.tw-mb-5.mx-auto.tw-text-sm(rounded width="100%" color="rgba(var(--cui-text-default-rgb), 0.1)")
span.text-default {{ $t('homebridge_restart_info') }}
+ .tw-flex.tw-justify-between.tw-items-center
+ .tw-block.tw-w-full.tw-pr-2
+ label.form-input-label Disable ¹ ²
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ $t('disable_info') }}
+ v-switch(color="var(--cui-primary)" v-model="cam.disable")
+
.tw-flex.tw-justify-between.tw-items-center
.tw-block.tw-w-full.tw-pr-2
label.form-input-label Privacy Mode
@@ -682,7 +695,7 @@
.input-info.tw-italic {{ message }}
label.form-input-label Video Stream Map ¹
- v-text-field(v-model="cam.videoConfig.vmap" :hint="$t('map_video_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ v-text-field(v-model="cam.videoConfig.mapvideo" :hint="$t('map_video_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiAlphabetical'] }}
template(v-slot:message="{ key, message}")
@@ -691,7 +704,7 @@
.input-info.tw-italic {{ message }}
label.form-input-label Audio Stream Map ¹
- v-text-field(v-model="cam.videoConfig.amap" :hint="$t('map_audio_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ v-text-field(v-model="cam.videoConfig.mapaudio" :hint="$t('map_audio_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiAlphabetical'] }}
template(v-slot:message="{ key, message}")
@@ -699,7 +712,7 @@
v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
.input-info.tw-italic {{ message }}
- label.form-input-label Encodec Options ¹
+ label.form-input-label Encoder Options ¹
v-text-field(v-model="cam.videoConfig.encoderOptions" :hint="$t('encoder_options_info')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
template(v-slot:prepend-inner)
v-icon.text-muted {{ icons['mdiAlphabetical'] }}
@@ -707,6 +720,89 @@
.tw-flex.tw-flex-row.tw-items-center.tw-break-normal
v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
.input-info.tw-italic {{ message }}
+
+ .tw-mt-3(v-if="moduleName === 'homebridge-camera-ui' || env === 'development'")
+ .page-subtitle HKSV Configuration
+
+ .tw-flex.tw-justify-between.tw-items-center
+ .tw-block.tw-w-full.tw-pr-2
+ label.form-input-label Audio ¹
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ $t('audio_info_hksv') }}
+ v-switch(color="var(--cui-primary)" :input-value="cam.hksvConfig ? cam.hksvConfig.audio : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'audio', $event)")
+
+ label.form-input-label Video Source ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.source : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'source', $event)" :hint="$t('source_info_hksv')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiAlphabetical'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Video Width ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.maxWidth : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'maxWidth', $event)" :hint="$t('width_info_hksv')" persistent-hint type="number" prepend-inner-icon="mdi-numeric" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiNumeric'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Video Height ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.maxHeight : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'maxHeight', $event)" :hint="$t('height_info_hksv')" persistent-hint type="number" prepend-inner-icon="mdi-numeric" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiNumeric'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label FPS ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.maxFPS : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'maxFPS', $event)" :hint="$t('fps_info_hksv')" persistent-hint type="number" prepend-inner-icon="mdi-numeric" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiNumeric'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Bitrate ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.maxBitrate : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'maxBitrate', $event)" :hint="$t('bitrate_info_hksv')" persistent-hint type="number" prepend-inner-icon="mdi-numeric" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiNumeric'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Video Codec ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.vcodec : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'vcodec', $event)" :hint="$t('video_codec_info_hksv')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiAlphabetical'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Audio Codec ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.acodec : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'acodec', $event)" :hint="$t('audio_codec_info_hksv')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiAlphabetical'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+ label.form-input-label Encoder Options ¹
+ v-text-field(:value="cam.hksvConfig ? cam.hksvConfig.encoderOptions : undefined" v-on:change="addToObject(cam, 'hksvConfig', 'encoderOptions', $event)" :hint="$t('encoder_options_info_hksv')" persistent-hint prepend-inner-icon="mdi-alphabetical" background-color="var(--cui-bg-card)" color="var(--cui-text-default)" solo)
+ template(v-slot:prepend-inner)
+ v-icon.text-muted {{ icons['mdiAlphabetical'] }}
+ template(v-slot:message="{ key, message}")
+ .tw-flex.tw-flex-row.tw-items-center.tw-break-normal
+ v-icon.text-muted.tw-mr-1(small) {{ icons['mdiInformationOutline'] }}
+ .input-info.tw-italic {{ message }}
+
+
diff --git a/ui/src/views/Utilization/Utilization.vue b/ui/src/views/Utilization/Utilization.vue
index aa044810..76cfbaa6 100644
--- a/ui/src/views/Utilization/Utilization.vue
+++ b/ui/src/views/Utilization/Utilization.vue
@@ -6,7 +6,7 @@
.header.tw-justify-between.tw-items-center.header.tw-relative.tw-z-10.tw-items-stretch
.tw-block
- h2 {{ $t($route.name.toLowerCase()) }}
+ .page-title {{ $t($route.name.toLowerCase()) }}
.tw-mt-10.tw-relative
h3 {{ $t('cpu_load') }}
@@ -329,6 +329,13 @@ export default {