From 04d753fd04bbcd665eea9ad9a1b4fd533658a4f3 Mon Sep 17 00:00:00 2001 From: SeydX Date: Fri, 15 Apr 2022 23:25:12 +0200 Subject: [PATCH] v1.1.7 --- .gitignore | 3 +- .npmignore | 1 + CHANGELOG.md | 14 +- package-lock.json | 4 +- package.json | 2 +- .../components/config/config.controller.js | 43 ++++++ src/api/components/config/config.routes.js | 45 ++++++ .../notifications/notifications.model.js | 44 +++--- src/api/components/system/system.routes.js | 2 +- src/common/cleartimer.js | 10 +- src/common/ping.js | 3 +- src/common/telegram.js | 3 +- .../camera/services/media.service.js | 7 +- .../camera/services/prebuffer.service.js | 2 +- .../camera/services/videoanalysis.service.js | 90 +++++++----- src/controller/event/event.controller.js | 7 +- src/services/config/config.defaults.js | 60 +++++++- src/services/config/config.service.js | 128 +++++------------- ui/src/api/config.api.js | 6 + ui/src/i18n/locale/de.json | 4 +- ui/src/i18n/locale/en.json | 4 +- ui/src/i18n/locale/es.json | 4 +- ui/src/i18n/locale/fr.json | 4 +- ui/src/i18n/locale/nl.json | 4 +- ui/src/i18n/locale/th.json | 4 +- ui/src/mixins/socket.js | 4 +- ui/src/views/Settings/subpages/backup.vue | 2 +- ui/src/views/Settings/subpages/cameras.vue | 55 +++++--- ui/src/views/Settings/subpages/system.vue | 47 ++++++- 29 files changed, 415 insertions(+), 191 deletions(-) diff --git a/.gitignore b/.gitignore index f173dbec..d98f5155 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,5 @@ parseFFmpeg.js parseUrl.js systeminformation.js parseRegions.js -src2 \ No newline at end of file +src2 +lang.js \ No newline at end of file diff --git a/.npmignore b/.npmignore index 47dc6c84..755c7edf 100644 --- a/.npmignore +++ b/.npmignore @@ -78,3 +78,4 @@ parseUrl.js systeminformation.js parseRegions.js src2 +lang.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fad177b3..253cf62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,26 @@ # Changelog All notable changes to this project will be documented in this file. -# NEXT +# v1.1.7 - 2022-04-15 ## Other Changes - i18n: Thai (th) language added by [@tomzt](https://github.com/tomzt) - i18n: French (fr) language added by [@NebzHB](https://github.com/NebzHB) - i18n: Spanish (es) language added by [@mastefordev](https://github.com/masterfordev) +- Added a new config.json tab to `Interface > Settings > System` +- Improved videoanalysis +- Minor UI improvements - Bump dependencies +## Bugfixes +- Fixed an issue where pinging camera sources with `non-break spaces` failed +- Fixed an issue where Doorbell Topic and Message were not displayed in the interface +- Fixed an issue where changing `recordOnMovement` in the ui settings was resetted after restart +- Fixed an issue where notifications were saved to the database even if the notifications were disabled in the settings +- Fixed an issue where Telegram sometimes could not send videos +- Fixed tests +- Minor bugfixes + # v1.1.6 - 2022-01-25 ## Bugfixes diff --git a/package-lock.json b/package-lock.json index c81f99d4..54381f74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "camera.ui", - "version": "1.1.6", + "version": "1.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "camera.ui", - "version": "1.1.6", + "version": "1.1.7", "funding": [ { "type": "paypal", diff --git a/package.json b/package.json index 37ebef29..1c2528be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "camera.ui", - "version": "1.1.6", + "version": "1.1.7", "description": "NVR like user interface for RTSP capable cameras.", "author": "SeydX (https://github.com/SeydX/camera.ui)", "scripts": { diff --git a/src/api/components/config/config.controller.js b/src/api/components/config/config.controller.js index d6290e1a..68d3cd7b 100644 --- a/src/api/components/config/config.controller.js +++ b/src/api/components/config/config.controller.js @@ -1,8 +1,51 @@ /* eslint-disable unicorn/prevent-abbreviations */ 'use-strict'; +import fs from 'fs-extra'; + import * as ConfigModel from './config.model.js'; +import LoggerService from '../../../services/logger/logger.service.js'; +import ConfigService from '../../../services/config/config.service.js'; + +const { log } = LoggerService; + +export const downloadConfig = async (req, res, next) => { + try { + const configPath = ConfigService.configPath; + + res.header('Content-Type', 'application/json'); + res.sendFile(configPath, (err) => { + if (err) { + if (err?.status === 404 || err?.statusCode === 404) { + log.debug(err.message); + } + + next(); + } + }); + } catch (error) { + res.status(500).send({ + statusCode: 500, + message: error.message, + }); + } +}; + +export const lastModifiedConfig = async (req, res) => { + try { + const configPath = ConfigService.configPath; + const configFileInfo = await fs.stat(configPath); + + res.status(200).send(configFileInfo); + } catch (error) { + res.status(500).send({ + statusCode: 500, + message: error.message, + }); + } +}; + export const show = async (req, res) => { try { const result = await ConfigModel.show(req.jwt, req.query.target); diff --git a/src/api/components/config/config.routes.js b/src/api/components/config/config.routes.js index 80ae415d..8bcb9145 100644 --- a/src/api/components/config/config.routes.js +++ b/src/api/components/config/config.routes.js @@ -36,6 +36,51 @@ export const routesConfig = (app) => { */ app.get('/api/config', [ValidationMiddleware.validJWTOptional, ConfigController.show]); + + /** + * @swagger + * /api/system/config/download: + * get: + * tags: [System] + * security: + * - bearerAuth: [] + * summary: Get config.json + * responses: + * 200: + * description: Successfull + * 401: + * description: Unauthorized + * 500: + * description: Internal server error + */ + app.get('/api/config/download', [ + ValidationMiddleware.validJWTNeeded, + PermissionMiddleware.onlyMasterCanDoThisAction, + ConfigController.downloadConfig, + ]); + + /** + * @swagger + * /api/config/stat: + * get: + * tags: [Config] + * security: + * - bearerAuth: [] + * summary: Get Config file info + * responses: + * 200: + * description: Successfull + * 401: + * description: Unauthorized + * 500: + * description: Internal server error + */ + app.get('/api/config/stat', [ + ValidationMiddleware.validJWTNeeded, + PermissionMiddleware.onlyMasterCanDoThisAction, + ConfigController.lastModifiedConfig, + ]); + /** * @swagger * /api/config/{target}: diff --git a/src/api/components/notifications/notifications.model.js b/src/api/components/notifications/notifications.model.js index 77392ef8..469f8f0c 100644 --- a/src/api/components/notifications/notifications.model.js +++ b/src/api/components/notifications/notifications.model.js @@ -102,20 +102,6 @@ export const createNotification = async (data) => { throw new Error('Can not assign notification to camera!'); } - //Check notification size, if we exceed more than {100} notifications, remove the latest - const notificationList = await Database.interfaceDB.chain.get('notifications').cloneDeep().value(); - - if (notificationList.length > notificationsLimit) { - const diff = notificationList.length - notificationsLimit; - const diffNotifiations = notificationList.slice(-diff); - - for (const notification of diffNotifiations) { - Cleartimer.removeNotificationTimer(notification.id); - } - - await Database.interfaceDB.chain.get('notifications').dropRight(notificationList, diff).value(); - } - const cameraSetting = camerasSettings.find((cameraSetting) => cameraSetting && cameraSetting.name === camera.name); const id = data.id || (await nanoid()); @@ -152,10 +138,6 @@ export const createNotification = async (data) => { label: label, }; - await Database.interfaceDB.chain.get('notifications').push(notification).value(); - - Cleartimer.setNotification(id, timestamp); - const notify = { ...notification, title: cameraName, @@ -171,6 +153,32 @@ export const createNotification = async (data) => { isNotification: true, }; + const notificationSettings = await Database.interfaceDB.chain + .get('settings') + .get('notifications') + .cloneDeep() + .value(); + + if (notificationSettings.active) { + //Check notification size, if we exceed more than {100} notifications, remove the latest + const notificationList = await Database.interfaceDB.chain.get('notifications').cloneDeep().value(); + + if (notificationList.length > notificationsLimit) { + const diff = notificationList.length - notificationsLimit; + const diffNotifiations = notificationList.slice(-diff); + + for (const notification of diffNotifiations) { + Cleartimer.removeNotificationTimer(notification.id); + } + + await Database.interfaceDB.chain.get('notifications').dropRight(notificationList, diff).value(); + } + + await Database.interfaceDB.chain.get('notifications').push(notification).value(); + + Cleartimer.setNotification(id, timestamp); + } + return { notification: notification, notify: notify, diff --git a/src/api/components/system/system.routes.js b/src/api/components/system/system.routes.js index a7670241..b5b9b66a 100644 --- a/src/api/components/system/system.routes.js +++ b/src/api/components/system/system.routes.js @@ -36,7 +36,7 @@ export const routesConfig = (app) => { /** * @swagger - * /api/system/log/download: + * /api/system/db/download: * get: * tags: [System] * security: diff --git a/src/common/cleartimer.js b/src/common/cleartimer.js index 0feee5d5..395e5ead 100644 --- a/src/common/cleartimer.js +++ b/src/common/cleartimer.js @@ -271,16 +271,20 @@ export default class Cleartimer { if (timeValue) { const endTime = moment.unix(timestamp).add(timeValue, timeTyp); - //log.debug(`SET cleartimer for ${isRecording ? 'Recording' : 'Notification'} (${id}) - Endtime: ${endTime}`); + /*log.debug( + `SET cleartimer for ${ + isRecording ? 'Recording' : 'Notification' + } (${id}) - Removing after ${timeValue} ${timeTyp} - Endtime: ${endTime}` + );*/ - const interval = isRecording ? 24 * 60 * 60 * 1000 : 60 * 60 * 1000; + const interval = isRecording ? 6 * 60 * 60 * 1000 : 30 * 60 * 1000; const timer = setInterval(async () => { const now = moment(); if (now > endTime) { - clearInterval(timer); await (isRecording ? Cleartimer.#clearRecording(id) : Cleartimer.#clearNotification(id)); + clearInterval(timer); } }, interval); diff --git a/src/common/ping.js b/src/common/ping.js index e8615a01..0644b49f 100644 --- a/src/common/ping.js +++ b/src/common/ping.js @@ -10,7 +10,8 @@ export default class Ping { static async status(camera, timeout = 1) { const { log } = LoggerService; - let cameraSource = camera.videoConfig.source.split('-i ')[1]; + //fix for non-break-spaces + let cameraSource = camera.videoConfig.source.replace(/\u00A0/g, ' ').split('-i ')[1]; if (!cameraSource) { log.warn(`Can not ping camera source, no source found (${camera.videoConfig.source})`, camera.name); diff --git a/src/common/telegram.js b/src/common/telegram.js index e9ae1d57..981b7b21 100644 --- a/src/common/telegram.js +++ b/src/common/telegram.js @@ -71,7 +71,8 @@ export default class Telegram { if (content.video) { try { log.debug('Telegram: Sending Video'); - await Telegram.bot.sendVideo(chatID, content.video, {}, { filename: content.fileName }); + const stream = fs.createReadStream(content.video); + await Telegram.bot.sendVideo(chatID, stream, {}, { filename: content.fileName }); } catch (error) { log.info('An error occured during sending video!', 'Telegram', 'notifications'); log.error(error?.message || error, 'Telegram', 'notifications'); diff --git a/src/controller/camera/services/media.service.js b/src/controller/camera/services/media.service.js index 0a7647a4..696e69fd 100644 --- a/src/controller/camera/services/media.service.js +++ b/src/controller/camera/services/media.service.js @@ -41,11 +41,6 @@ export default class MediaService { async probe() { // eslint-disable-next-line no-unused-vars return new Promise((resolve, reject) => { - log.debug( - `Probe stream: ${ConfigService.ui.options.videoProcessor} ${this.#camera.videoConfig.subSource}`, - this.cameraName - ); - const arguments_ = [ '-hide_banner', '-loglevel', @@ -57,6 +52,8 @@ export default class MediaService { ...this.#camera.videoConfig.source.split(/\s+/), ]; + log.debug(`Probe stream: ${ConfigService.ui.options.videoProcessor} ${arguments_.join(' ')}`, this.cameraName); + let cp = spawn(ConfigService.ui.options.videoProcessor, arguments_, { env: process.env, }); diff --git a/src/controller/camera/services/prebuffer.service.js b/src/controller/camera/services/prebuffer.service.js index 8c049e1d..7e5485e4 100644 --- a/src/controller/camera/services/prebuffer.service.js +++ b/src/controller/camera/services/prebuffer.service.js @@ -363,7 +363,7 @@ export default class PrebufferService { }; this.events.on(eventName, safeWriteData); - this.prebufferSession.events.once('killed', cleanup); + this.prebufferSession?.events.once('killed', cleanup); const parser = this.parsers[container]; const availablePrebuffers = parser.findSyncFrame( diff --git a/src/controller/camera/services/videoanalysis.service.js b/src/controller/camera/services/videoanalysis.service.js index 4ab9e390..6e5a192a 100644 --- a/src/controller/camera/services/videoanalysis.service.js +++ b/src/controller/camera/services/videoanalysis.service.js @@ -18,6 +18,7 @@ import MotionController from '../../motion/motion.controller.js'; const { log } = LoggerService; +const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const isUINT = (value) => Number.isInteger(value) && value >= 0; const FFMPEG_MODE = 'rgb24'; // gray, rgba, rgb24 @@ -110,31 +111,63 @@ export default class VideoAnalysisService { try { this.resetVideoAnalysis(); - this.cameraState = await this.#pingCamera(); + const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig); + let input = cameraUtils.generateInputSource(videoConfig, videoConfig.subSource).split(/\s+/); + let withPrebuffer = false; - if (!this.cameraState) { - log.warn( - 'Can not start video analysis, camera not reachable. Trying again in 60s..', - this.cameraName, - 'videoanalysis' - ); + if (this.#camera.prebuffering && videoConfig.subSource === videoConfig.source) { + try { + input = withPrebuffer = await this.#prebufferService.getVideo(); + } catch { + // retry + log.debug( + 'Can not start videoanalysis, prebuffer process not yet started, retrying in 10s..', + this.cameraName + ); - this.stop(true); - setTimeout(() => this.start(), 60000); + this.stop(true); - return; + await timeout(10000); + + this.start(); + + return; + } + } else { + this.cameraState = await this.#pingCamera(); + + if (!this.cameraState) { + log.warn( + 'Can not start video analysis, camera not reachable. Trying again in 60s..', + this.cameraName, + 'videoanalysis' + ); + + this.stop(true); + + await timeout(60000); + + this.start(); + + return; + } } - this.videoanalysisSession = await this.#startVideoAnalysis(); + this.videoanalysisSession = await this.#startVideoAnalysis(input, videoConfig); - const timer = this.#millisUntilTime('04:00'); + if (!withPrebuffer) { + const timer = this.#millisUntilTime('04:00'); - log.info(`Videoanalysis scheduled for restart at 4AM: ${Math.round(timer / 1000 / 60)} minutes`, this.cameraName); + log.info( + `Videoanalysis scheduled for restart at 4AM: ${Math.round(timer / 1000 / 60)} minutes`, + this.cameraName + ); - this.restartTimer = setTimeout(() => { - log.info('Sheduled restart of videoanalysis is executed...', this.cameraName); - this.restart(); - }, timer); + this.restartTimer = setTimeout(() => { + log.info('Sheduled restart of videoanalysis is executed...', this.cameraName); + this.restart(); + }, timer); + } } catch (error) { if (error) { log.info('An error occured during starting videoanalysis!', this.cameraName, 'videoanalysis'); @@ -182,13 +215,15 @@ export default class VideoAnalysisService { } } - restart() { + async restart() { log.info('Restart videoanalysis session..', this.cameraName); + this.stop(true); - setTimeout(() => this.start(), 10000); + await timeout(14000); + await this.start(); } - async #startVideoAnalysis() { + async #startVideoAnalysis(input, videoConfig) { if (this.videoanalysisSession) { return this.videoanalysisSession; } @@ -197,22 +232,9 @@ export default class VideoAnalysisService { log.debug('Start videoanalysis...', this.cameraName); - const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig); - let input = cameraUtils.generateInputSource(videoConfig, videoConfig.subSource).split(/\s+/); - let prebufferInput = false; - let invalidSubstream = videoConfig.subSource === videoConfig.source; - - if (this.#camera.prebuffering && invalidSubstream) { - try { - input = prebufferInput = await this.#prebufferService.getVideo(); - } catch { - // ignore - } - } - const videoArguments = ['-an', '-vcodec', 'pam']; - if (!prebufferInput && videoConfig.mapvideo) { + if (!this.#camera.prebuffering && videoConfig.mapvideo) { videoArguments.unshift('-map', videoConfig.mapvideo); } diff --git a/src/controller/event/event.controller.js b/src/controller/event/event.controller.js index 4aa5f819..efafad53 100644 --- a/src/controller/event/event.controller.js +++ b/src/controller/event/event.controller.js @@ -540,7 +540,9 @@ export default class EventController { break; } - // No default + default: + log.warn(`Can not send telegram message, unknown telegram type (${telegramSettings.type})`, cameraName); + break; } } catch (error) { log.info('An error occured during sending telegram notification', cameraName, 'events'); @@ -571,6 +573,9 @@ export default class EventController { method: 'POST', responseType: 'json', json: notification, + https: { + rejectUnauthorized: false, + }, }); log.debug(`Payload was sent successfully to ${webhookSettings.endpoint}`, cameraName); diff --git a/src/services/config/config.defaults.js b/src/services/config/config.defaults.js index fa831da0..c1b982da 100644 --- a/src/services/config/config.defaults.js +++ b/src/services/config/config.defaults.js @@ -141,6 +141,64 @@ export class ConfigSetup { } static setupCameras(cameras = []) { - return cameras || []; + return ( + cameras + // include only cameras with given name, videoConfig and source + .filter((camera) => camera.name && camera.videoConfig?.source) + .map((camera) => { + const sourceArguments = camera.videoConfig.source.split(/\s+/); + + if (!sourceArguments.includes('-i')) { + camera.videoConfig.source = false; + } + + if (camera.videoConfig.subSource) { + const stillArguments = camera.videoConfig.subSource.split(/\s+/); + + if (!stillArguments.includes('-i')) { + camera.videoConfig.subSource = camera.videoConfig.source; + } + } else { + camera.videoConfig.subSource = camera.videoConfig.source; + } + + if (camera.videoConfig.stillImageSource) { + const stillArguments = camera.videoConfig.stillImageSource.split(/\s+/); + + if (!stillArguments.includes('-i')) { + camera.videoConfig.stillImageSource = camera.videoConfig.source; + } + } else { + camera.videoConfig.stillImageSource = camera.videoConfig.source; + } + + // homebridge + camera.recordOnMovement = camera.recordOnMovement !== undefined ? camera.recordOnMovement : !camera.hsv; + + camera.motionTimeout = + camera.motionTimeout === undefined || !(camera.motionTimeout >= 0) ? 15 : camera.motionTimeout; + + // validate prebufferLength + camera.prebufferLength = + camera.prebufferLength >= 4 && camera.prebufferLength <= 8 ? camera.prebufferLength : 4; + + // setup video analysis + camera.videoanalysis = { + active: camera.videoanalysis?.active || false, + }; + + // setup mqtt + camera.smtp = camera.smtp || { + email: camera.name, + }; + + // setup mqtt + camera.mqtt = camera.mqtt || {}; + + return camera; + }) + // exclude cameras with invalid videoConfig, source + .filter((camera) => camera.videoConfig?.source) + ); } } diff --git a/src/services/config/config.service.js b/src/services/config/config.service.js index cb66b9b7..3758e038 100644 --- a/src/services/config/config.service.js +++ b/src/services/config/config.service.js @@ -95,7 +95,7 @@ export default class ConfigService { ConfigService.ui.version = process.env.CUI_MODULE_VERSION; const config = new ConfigSetup(configJson); - ConfigService.configJson = new ConfigSetup(configJson); + ConfigService.configJson = { ...config }; ConfigService.parseConfig(config); @@ -157,7 +157,7 @@ export default class ConfigService { } config = new ConfigSetup(config); - ConfigService.configJson = new ConfigSetup(config); + ConfigService.configJson = { ...config }; fs.writeJSONSync(ConfigService.configPath, config, { spaces: 2 }); @@ -268,101 +268,47 @@ export default class ConfigService { static #configCameras(cameras = []) { ConfigService.ui.topics.clear(); - ConfigService.ui.cameras = cameras - // include only cameras with given name, videoConfig and source - .filter((camera) => camera.name && camera.videoConfig?.source) - .map((camera) => { - const sourceArguments = camera.videoConfig.source.split(/\s+/); - - if (!sourceArguments.includes('-i')) { - camera.videoConfig.source = false; - } - - if (camera.videoConfig.subSource) { - const stillArguments = camera.videoConfig.subSource.split(/\s+/); - - if (!stillArguments.includes('-i')) { - camera.videoConfig.subSource = camera.videoConfig.source; - } - } else { - camera.videoConfig.subSource = camera.videoConfig.source; - } - - if (camera.videoConfig.stillImageSource) { - const stillArguments = camera.videoConfig.stillImageSource.split(/\s+/); - - if (!stillArguments.includes('-i')) { - camera.videoConfig.stillImageSource = camera.videoConfig.source; - } - } else { - camera.videoConfig.stillImageSource = camera.videoConfig.source; - } - - // homebridge - camera.recordOnMovement = !camera.hsv; - - camera.motionTimeout = - camera.motionTimeout === undefined || !(camera.motionTimeout >= 0) ? 15 : camera.motionTimeout; - - // validate prebufferLength - camera.prebufferLength = - camera.prebufferLength >= 4 && camera.prebufferLength <= 8 ? camera.prebufferLength : 4; - - // setup video analysis - camera.videoanalysis = { - active: camera.videoanalysis?.active || false, - }; - - // setup mqtt - camera.smtp = camera.smtp || { - email: camera.name, + ConfigService.ui.cameras = cameras.map((camera) => { + if (camera.mqtt.motionTopic) { + const mqttOptions = { + motionTopic: camera.mqtt.motionTopic, + motionMessage: camera.mqtt.motionMessage !== undefined ? camera.mqtt.motionMessage : 'ON', + motionResetMessage: camera.mqtt.motionResetMessage !== undefined ? camera.mqtt.motionResetMessage : 'OFF', + camera: camera.name, + motion: true, }; - // setup mqtt - camera.mqtt = camera.mqtt || {}; - - if (camera.mqtt.motionTopic) { - const mqttOptions = { - motionTopic: camera.mqtt.motionTopic, - motionMessage: camera.mqtt.motionMessage !== undefined ? camera.mqtt.motionMessage : 'ON', - motionResetMessage: camera.mqtt.motionResetMessage !== undefined ? camera.mqtt.motionResetMessage : 'OFF', - camera: camera.name, - motion: true, - }; + ConfigService.ui.topics.set(mqttOptions.motionTopic, mqttOptions); + } - ConfigService.ui.topics.set(mqttOptions.motionTopic, mqttOptions); - } + if (camera.mqtt.motionResetTopic && camera.mqtt.motionResetTopic !== camera.mqtt.motionTopic) { + const mqttOptions = { + motionResetTopic: camera.mqtt.motionResetTopic, + motionResetMessage: camera.mqtt.motionResetMessage !== undefined ? camera.mqtt.motionResetMessage : 'OFF', + camera: camera.name, + motion: true, + reset: true, + }; - if (camera.mqtt.motionResetTopic && camera.mqtt.motionResetTopic !== camera.mqtt.motionTopic) { - const mqttOptions = { - motionResetTopic: camera.mqtt.motionResetTopic, - motionResetMessage: camera.mqtt.motionResetMessage !== undefined ? camera.mqtt.motionResetMessage : 'OFF', - camera: camera.name, - motion: true, - reset: true, - }; + ConfigService.ui.topics.set(mqttOptions.motionResetTopic, mqttOptions); + } - ConfigService.ui.topics.set(mqttOptions.motionResetTopic, mqttOptions); - } + if ( + camera.mqtt.doorbellTopic && + camera.mqtt.doorbellTopic !== camera.mqtt.motionTopic && + camera.mqtt.doorbellTopic !== camera.mqtt.motionResetTopic + ) { + const mqttOptions = { + doorbellTopic: camera.mqtt.doorbellTopic, + doorbellMessage: camera.mqtt.doorbellMessage !== undefined ? camera.mqtt.doorbellMessage : 'ON', + camera: camera.name, + doorbell: true, + }; - if ( - camera.mqtt.doorbellTopic && - camera.mqtt.doorbellTopic !== camera.mqtt.motionTopic && - camera.mqtt.doorbellTopic !== camera.mqtt.motionResetTopic - ) { - const mqttOptions = { - doorbellTopic: camera.mqtt.doorbellTopic, - doorbellMessage: camera.mqtt.doorbellMessage !== undefined ? camera.mqtt.doorbellMessage : 'ON', - camera: camera.name, - doorbell: true, - }; - - ConfigService.ui.topics.set(mqttOptions.doorbellTopic, mqttOptions); - } + ConfigService.ui.topics.set(mqttOptions.doorbellTopic, mqttOptions); + } - return camera; - }) - // exclude cameras with invalid videoConfig, source - .filter((camera) => camera.videoConfig?.source); + return camera; + }); } } diff --git a/ui/src/api/config.api.js b/ui/src/api/config.api.js index 37c478d5..8f0a6748 100644 --- a/ui/src/api/config.api.js +++ b/ui/src/api/config.api.js @@ -1,8 +1,14 @@ import api from './index'; const resource = '/config'; +const config_download_resource = 'download'; +const config_stat_resource = 'stat'; export const changeConfig = async (configData, parameters) => await api.patch(`${resource}${parameters ? parameters : ''}`, configData); +export const downloadConfig = async () => await api.get(`${resource}/${config_download_resource}`); + export const getConfig = async (parameters) => await api.get(`${resource}${parameters ? parameters : ''}`); + +export const getConfigStat = async () => await api.get(`${resource}/${config_stat_resource}`); diff --git a/ui/src/i18n/locale/de.json b/ui/src/i18n/locale/de.json index ac0504a0..12918db9 100644 --- a/ui/src/i18n/locale/de.json +++ b/ui/src/i18n/locale/de.json @@ -79,7 +79,7 @@ "camera_player_info": "Interface Video Player Parameter", "camera_prebuffering_info": "Kamera Stream Vorpuffern", "camera_rekognition_info": "Bildanalyse beim Erkennen von Bewegung", - "camera_settings_save_info": "Mit (¹) markierte Felder können nur durch Anklicken der Schaltfläche \"Speichern\" gespeichert werden", + "camera_settings_save_info": "Mit (¹) markierte Felder können nur durch Anklicken der Schaltfläche \"✓\" gespeichert werden", "camera_videoanalysis_info": "camera.ui Bewegungserkennungssystem", "cameras": "Kameras", "cameras_nav_info": "Verwalten Sie die Streaming Parameter der Kameras", @@ -97,6 +97,7 @@ "color": "Farbe", "confidence": "Zuversichtlichkeit", "config": "Konfiguration", + "config_information": "config.json", "config_was_saved": "Konfiguration wurde gespeichert", "connected": "Verbunden", "console": "Konsole", @@ -194,6 +195,7 @@ "label": "Label", "labels": "Labels", "language": "Sprache", + "last_changed": "Zuletzt geändert", "last_notification": "Letzte Benachrichtigung", "last_recording": "Letzte Aufnahme", "last_updated": "Zuletzt geändert", diff --git a/ui/src/i18n/locale/en.json b/ui/src/i18n/locale/en.json index de8e8538..a29c8999 100644 --- a/ui/src/i18n/locale/en.json +++ b/ui/src/i18n/locale/en.json @@ -79,7 +79,7 @@ "camera_player_info": "Interface Video Player Parameter", "camera_prebuffering_info": "Camera Stream Prebuffering", "camera_rekognition_info": "Image analysis on movement detection", - "camera_settings_save_info": "Fields marked with (¹) can be saved only by clicking the \"Save\" button", + "camera_settings_save_info": "Fields marked with (¹) can be saved only by clicking the \"✓\" button", "camera_videoanalysis_info": "camera.ui Motion detection system", "cameras": "Cameras", "cameras_nav_info": "Manage the streaming parameters of the cameras", @@ -96,6 +96,7 @@ "color": "Color", "confidence": "Confidence", "config": "Config", + "config_information": "config.json", "config_was_saved": "Config was saved", "connected": "Connected", "console": "Console", @@ -193,6 +194,7 @@ "label": "Label", "labels": "Labels", "language": "Language", + "last_changed": "Last modified", "last_notification": "Last notification", "last_recording": "Last recording", "last_updated": "Last Updated", diff --git a/ui/src/i18n/locale/es.json b/ui/src/i18n/locale/es.json index 0d112191..3b3fcfdf 100644 --- a/ui/src/i18n/locale/es.json +++ b/ui/src/i18n/locale/es.json @@ -79,7 +79,7 @@ "camera_player_info": "Interface Video Player Parameter", "camera_prebuffering_info": "Camera Stream Prebuffering", "camera_rekognition_info": "Image analysis on movement detection", - "camera_settings_save_info": "Fields marked with (¹) can be saved only by clicking the \"Save\" button", + "camera_settings_save_info": "Fields marked with (¹) can be saved only by clicking the \"✓\" button", "camera_videoanalysis_info": "camera.ui Motion detection system", "cameras": "Cámaras", "cameras_nav_info": "Manage the streaming parameters of the cameras", @@ -96,6 +96,7 @@ "color": "Color", "confidence": "Confidence", "config": "Config", + "config_information": "config.json", "config_was_saved": "Configuración guardada", "connected": "Conectado", "console": "Consola", @@ -193,6 +194,7 @@ "label": "Etiqueta", "labels": "Etiquetas", "language": "Idioma", + "last_changed": "Última modificación", "last_notification": "Última notificación", "last_recording": "Última Grabación", "last_updated": "Última Vez Actualizado", diff --git a/ui/src/i18n/locale/fr.json b/ui/src/i18n/locale/fr.json index 24587189..a8e69098 100644 --- a/ui/src/i18n/locale/fr.json +++ b/ui/src/i18n/locale/fr.json @@ -79,7 +79,7 @@ "camera_player_info": "Paramètres Lecteur Vidéo de l'Interface", "camera_prebuffering_info": "Pré-buffering du Flux Caméra", "camera_rekognition_info": "Analyse d'image à la détection de mouvement", - "camera_settings_save_info": "Les champs marqués avec (¹) peuvent être sauvés uniquement en cliquant sur le bouton \"Sauvegarde\"", + "camera_settings_save_info": "Les champs marqués avec (¹) peuvent être sauvés uniquement en cliquant sur le bouton \"✓\"", "camera_videoanalysis_info": "Système de détection de mouvement camera.ui", "cameras": "Caméras", "cameras_nav_info": "Gérer les paramètres de streaming des caméras", @@ -96,6 +96,7 @@ "color": "Couleur", "confidence": "Confiance", "config": "Configuration", + "config_information": "config.json", "config_was_saved": "La configuration a été sauvegardée", "connected": "Connecté", "console": "Console", @@ -193,6 +194,7 @@ "label": "Libellé", "labels": "Libellés", "language": "Langue", + "last_changed": "Dernière modification", "last_notification": "Dernière notification", "last_recording": "Dernier enregistrement", "last_updated": "Dernière mise à jour", diff --git a/ui/src/i18n/locale/nl.json b/ui/src/i18n/locale/nl.json index e74d19d6..4e630555 100644 --- a/ui/src/i18n/locale/nl.json +++ b/ui/src/i18n/locale/nl.json @@ -79,7 +79,7 @@ "camera_player_info": "Interface Videospeler Parameters", "camera_prebuffering_info": "Camera Stream Prebuffering", "camera_rekognition_info": "Beeldanalyse voor bewegingsdetectie", - "camera_settings_save_info": "Velden gemarkeerd met (¹) kunnen alleen worden opgeslagen door op de knop \"Opslaan\" te klikken", + "camera_settings_save_info": "Velden gemarkeerd met (¹) kunnen alleen worden opgeslagen door op de knop \"✓\" te klikken", "camera_videoanalysis_info": "camera.ui Bewegingsdetectiesysteem", "cameras": "Camera's", "cameras_nav_info": "Beheer de streamingparameters van de camera's", @@ -96,6 +96,7 @@ "color": "Kleur", "confidence": "Confidence", "config": "Config", + "config_information": "config.json", "config_was_saved": "De configuratie werd opgeslagen", "connected": "Aangesloten", "console": "Console", @@ -193,6 +194,7 @@ "label": "Label", "labels": "Labels", "language": "Taal", + "last_changed": "Laatst gewijzigd", "last_notification": "Laatste kennisgeving", "last_recording": "Laatste opname", "last_updated": "Laatst gewijzigd", diff --git a/ui/src/i18n/locale/th.json b/ui/src/i18n/locale/th.json index 606c520e..ba1b875a 100644 --- a/ui/src/i18n/locale/th.json +++ b/ui/src/i18n/locale/th.json @@ -79,7 +79,7 @@ "camera_player_info": "พารามิเตอร์อินเทอร์เฟซของเครื่องเล่นวิดีโอ", "camera_prebuffering_info": "พรีบัฟเฟอร์สตรีมของกล้อง", "camera_rekognition_info": "การวิเคราะห์ภาพในการตรวจจับการเคลื่อนไหว", - "camera_settings_save_info": "ฟิลด์ที่มีเครื่องหมาย (¹) สามารถบันทึกได้โดยการคลิกปุ่ม \"บันทึก\" เท่านั้น", + "camera_settings_save_info": "ฟิลด์ที่มีเครื่องหมาย (¹) สามารถบันทึกได้โดยการคลิกปุ่ม \"✓\" เท่านั้น", "camera_videoanalysis_info": "camera.ui ระบบตรวจจับความเคลื่อนไหว", "cameras": "กล้อง", "cameras_nav_info": "จัดการพารามิเตอร์การสตรีมของกล้อง", @@ -96,6 +96,7 @@ "color": "สี", "confidence": "ความมั่นใจ", "config": "การกำหนดค่า", + "config_information": "config.json", "config_was_saved": "บันทึกการกำหนดค่าแล้ว", "connected": "เชื่อมต่อแล้ว", "console": "คอนโซล", @@ -196,6 +197,7 @@ "label": "ลาเบล", "labels": "ลาเบล", "language": "ภาษา", + "last_changed": "Last modified", "last_notification": "การแจ้งเตือนครั้งล่าสุด", "last_recording": "บันทึกล่าสุด", "last_updated": "อัพเดทล่าสุด", diff --git a/ui/src/mixins/socket.js b/ui/src/mixins/socket.js index 58892e37..029f68d8 100644 --- a/ui/src/mixins/socket.js +++ b/ui/src/mixins/socket.js @@ -63,7 +63,7 @@ export default { this.connected = false; await this.$store.dispatch('auth/logout'); - setTimeout(() => this.$router.push('/'), 200); + setTimeout(() => this.$router.push('/'), 500); }, async invalidToken(token) { if (token === this.currentUser.access_token) { @@ -71,7 +71,7 @@ export default { this.connected = false; await this.$store.dispatch('auth/logout'); - setTimeout(() => this.$router.push('/'), 200); + setTimeout(() => this.$router.push('/'), 500); } }, notification(notification) { diff --git a/ui/src/views/Settings/subpages/backup.vue b/ui/src/views/Settings/subpages/backup.vue index bd46639f..06d4b78a 100644 --- a/ui/src/views/Settings/subpages/backup.vue +++ b/ui/src/views/Settings/subpages/backup.vue @@ -27,7 +27,7 @@ p.tw-mt-4.tw-mb-8 {{ $t('backup_info_shedule') }} - span.text-muted.tw-italic coming soon + span.text-muted.tw-italic coming soon...