From 06d84c881d2a1433ce66dd87af8edb90feaa09ab Mon Sep 17 00:00:00 2001 From: callemand Date: Tue, 14 Nov 2023 11:18:44 +0100 Subject: [PATCH 1/5] Add warning system for battery level --- front/src/config/i18n/en.json | 4 +- front/src/config/i18n/fr.json | 4 +- .../settings-system/SettingsSystemPage.jsx | 26 ++++++++++ .../routes/settings/settings-system/index.js | 41 +++++++++++++++- server/config/scheduler-jobs.js | 5 ++ server/lib/device/device.checkBatteries.js | 49 +++++++++++++++++++ server/lib/device/index.js | 5 ++ server/lib/index.js | 2 +- server/lib/user/index.js | 2 + server/lib/user/user.getByRole.js | 34 +++++++++++++ server/utils/constants.js | 2 + 11 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 server/lib/device/device.checkBatteries.js create mode 100644 server/lib/user/user.getByRole.js diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index a384f33efd..05f30675a2 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -2052,7 +2052,9 @@ "paused": "Paused", "exited": "Exited", "dead": "Dead" - } + }, + "batteryLevel": "Battery level alert", + "batteryLevelDescription": "A message will be sent to all admins as soon as the battery level drops below the chosen percentage" }, "newArea": { "createNewZoneButton": "Create new zone", diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json index b8bf98d7bf..a4e101ec34 100644 --- a/front/src/config/i18n/fr.json +++ b/front/src/config/i18n/fr.json @@ -2053,7 +2053,9 @@ "paused": "En Pause", "exited": "Arrêté", "dead": "Mort" - } + }, + "batteryLevel": "Alerte sur le niveau de batterie", + "batteryLevelDescription": "Un message sera envoyer à tous les admins dès que le niveau du batterie parsera en dessous du pourcentage choisi" }, "newArea": { "createNewZoneButton": "Créer une zone", diff --git a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx index 73ca6502c4..25a90ec2c7 100644 --- a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx +++ b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx @@ -255,6 +255,32 @@ const SystemPage = ({ children, ...props }) => (

+
+ +

+ + + +

+
+ +
+ + + +
+
+
diff --git a/front/src/routes/settings/settings-system/index.js b/front/src/routes/settings/settings-system/index.js index fb91108fb9..fa5b52a840 100644 --- a/front/src/routes/settings/settings-system/index.js +++ b/front/src/routes/settings/settings-system/index.js @@ -6,6 +6,7 @@ import SettingsSystemPage from './SettingsSystemPage'; import actions from '../../../actions/system'; import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; import { RequestStatus } from '../../../utils/consts'; +import debounce from 'debounce'; class SettingsSystem extends Component { updateTimezone = async option => { @@ -48,6 +49,38 @@ class SettingsSystem extends Component { } }; + getBatteryLevelUnderWarning = async () => { + try { + const { value } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING}` + ); + this.setState({ + batteryLevelUnderWarning: value + }); + } catch (e) { + console.error(e); + } + }; + + updateBatteryLevelUnderWarning = async e => { + await this.setState({ + batteryLevelUnderWarning: e.target.value, + savingBatteryLevelUnderWarning: true + }); + try { + await this.props.httpClient.post(`/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING}`, { + value: e.target.value + }); + } catch (e) { + console.error(e); + } + await this.setState({ + savingBatteryLevelUnderWarning: false + }); + }; + + debouncedUpdateBatteryLevelUnderWarning = debounce(this.updateBatteryLevelUnderWarning, 200); + updateDeviceStateHistory = async e => { await this.setState({ deviceStateHistoryInDays: e.target.value, @@ -163,6 +196,7 @@ class SettingsSystem extends Component { this.getDeviceStateHistoryPreference(); this.getDeviceAggregateStateHistoryPreference(); this.getNumberOfHoursBeforeStateIsOutdated(); + this.getBatteryLevelUnderWarning(); // we start the ping a little bit after to give it some time to breathe this.refreshPingIntervalId = setInterval(() => { this.props.ping(); @@ -188,7 +222,9 @@ class SettingsSystem extends Component { deviceAggregateStateHistoryInDays, vacuumStarted, numberOfHoursBeforeStateIsOutdated, - savingNumberOfHourseBeforeStateIsOutdated + savingNumberOfHourseBeforeStateIsOutdated, + batteryLevelUnderWarning, + savingBatteryLevelUnderWarning } ) { const isDocker = get(props, 'systemInfos.is_docker'); @@ -215,6 +251,9 @@ class SettingsSystem extends Component { updateNumberOfHoursBeforeStateIsOutdated={this.updateNumberOfHoursBeforeStateIsOutdated} vacuumDatabase={this.vacuumDatabase} vacuumStarted={vacuumStarted} + batteryLevelUnderWarning={batteryLevelUnderWarning} + savingBatteryLevelUnderWarning={savingBatteryLevelUnderWarning} + updateBatteryLevelUnderWarning={this.debouncedUpdateBatteryLevelUnderWarning} /> ); } diff --git a/server/config/scheduler-jobs.js b/server/config/scheduler-jobs.js index 15dbe69f17..3f9110b426 100644 --- a/server/config/scheduler-jobs.js +++ b/server/config/scheduler-jobs.js @@ -21,6 +21,11 @@ const jobs = [ rule: '0 0 22 * * *', // every day at 22:00 event: EVENTS.JOB.PURGE_OLD_JOBS, }, + { + name: 'hourly-device-check-batteries', + rule: '0 0 * * * *', // every hour + event: EVENTS.DEVICE.CHECK_BATTERIES, + }, ]; module.exports = jobs; diff --git a/server/lib/device/device.checkBatteries.js b/server/lib/device/device.checkBatteries.js new file mode 100644 index 0000000000..a0764b558e --- /dev/null +++ b/server/lib/device/device.checkBatteries.js @@ -0,0 +1,49 @@ +const { Op } = require('sequelize'); +const db = require('../../models'); +const logger = require('../../utils/logger'); +const { + SYSTEM_VARIABLE_NAMES, + AVAILABLE_LANGUAGES, + DEVICE_FEATURE_CATEGORIES, + USER_ROLE, +} = require('../../utils/constants'); + +/** + * @description Check battery level and warn if needed. + * @returns {Promise} Resolve when finished. + * @example + * device.purgeStates(); + */ +async function checkBatteries() { + logger.debug('Checking batteries ...'); + + const minPercentBattery = await this.variable.getValue(SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING); + + const admins = await this.user.getByRole(USER_ROLE.ADMIN); + + if (!minPercentBattery || !admins || admins.length === 0) { + return; + } + + const devices = await this.get({ device_feature_category: DEVICE_FEATURE_CATEGORIES.BATTERY }); + + devices.forEach((device) => { + device.features + .filter((feature) => { + return feature.last_value < minPercentBattery; + }) + .forEach((feature) => { + const messages = { + [AVAILABLE_LANGUAGES.EN]: `Warning !!! Battery level on ${device.name} is under ${minPercentBattery}% (current: ${feature.last_value}%)`, + [AVAILABLE_LANGUAGES.FR]: `Avertissement !!! Le niveau de la batterie de ${device.name} est inférieur à ${minPercentBattery} % (actuel : ${feature.last_value} %)`, + }; + admins.forEach((admin) => { + this.messageManager.sendToUser(admin.selector, messages[admin.language]); + }); + }); + }); +} + +module.exports = { + checkBatteries, +}; diff --git a/server/lib/device/index.js b/server/lib/device/index.js index ab8faf2bc3..069948d22a 100644 --- a/server/lib/device/index.js +++ b/server/lib/device/index.js @@ -34,6 +34,7 @@ const { setValue } = require('./device.setValue'); const { setupPoll } = require('./device.setupPoll'); const { newStateEvent } = require('./device.newStateEvent'); const { notify } = require('./device.notify'); +const { checkBatteries } = require('./device.checkBatteries'); const DeviceManager = function DeviceManager( eventManager, @@ -44,6 +45,7 @@ const DeviceManager = function DeviceManager( variable, job, brain, + user, ) { this.eventManager = eventManager; this.messageManager = messageManager; @@ -53,6 +55,7 @@ const DeviceManager = function DeviceManager( this.variable = variable; this.job = job; this.brain = brain; + this.user = user; this.STATES_TO_PURGE_PER_DEVICE_FEATURE_CLEAN_BATCH = 1000; this.WAIT_TIME_BETWEEN_DEVICE_FEATURE_CLEAN_BATCH = 100; @@ -84,6 +87,7 @@ const DeviceManager = function DeviceManager( EVENTS.DEVICE.PURGE_STATES_SINGLE_FEATURE, eventFunctionWrapper(this.purgeStatesByFeatureId.bind(this)), ); + this.eventManager.on(EVENTS.DEVICE.CHECK_BATTERIES, eventFunctionWrapper(this.checkBatteries.bind(this))); }; DeviceManager.prototype.add = add; @@ -112,5 +116,6 @@ DeviceManager.prototype.setParam = setParam; DeviceManager.prototype.setupPoll = setupPoll; DeviceManager.prototype.setValue = setValue; DeviceManager.prototype.notify = notify; +DeviceManager.prototype.checkBatteries = checkBatteries; module.exports = DeviceManager; diff --git a/server/lib/index.js b/server/lib/index.js index 0dfbd312a8..1222ba3527 100644 --- a/server/lib/index.js +++ b/server/lib/index.js @@ -66,7 +66,7 @@ function Gladys(params = {}) { const message = new MessageHandler(event, brain, service, stateManager, variable); const user = new User(session, stateManager, variable); const location = new Location(user, event); - const device = new Device(event, message, stateManager, service, room, variable, job, brain); + const device = new Device(event, message, stateManager, service, room, variable, job, brain, user); const calendar = new Calendar(service); const scheduler = new Scheduler(event); const weather = new Weather(service, event, message, house); diff --git a/server/lib/user/index.js b/server/lib/user/index.js index 465c26ae54..d579aa82ea 100644 --- a/server/lib/user/index.js +++ b/server/lib/user/index.js @@ -12,6 +12,7 @@ const { forgotPassword } = require('./user.forgotPassword'); const { update } = require('./user.update'); const { updateBySelector } = require('./user.updateBySelector'); const { updatePassword } = require('./user.updatePassword'); +const { getByRole } = require('./user.getByRole'); const User = function User(session, stateManager, variable) { this.session = session; @@ -33,5 +34,6 @@ User.prototype.getByTelegramUserId = getByTelegramUserId; User.prototype.update = update; User.prototype.updateBySelector = updateBySelector; User.prototype.updatePassword = updatePassword; +User.prototype.getByRole = getByRole; module.exports = User; diff --git a/server/lib/user/user.getByRole.js b/server/lib/user/user.getByRole.js new file mode 100644 index 0000000000..c0ea1344da --- /dev/null +++ b/server/lib/user/user.getByRole.js @@ -0,0 +1,34 @@ +const db = require('../../models'); + +/** + * @private + * @description Get list of users by role. + * @name gladys.user.getByRole + * @param {string} role - The role of the users. + * @returns {Promise} Promise. + * @example + * await gladys.user.getByRole('admin'); + */ +async function getByRole(role) { + const queryParams = { + attributes: ['id', 'firstname', 'lastname', 'selector', 'email', 'language'], + offset: 0, + order: [['firstname', 'ASC']], + where: { + role, + }, + }; + + const users = await db.User.findAll(queryParams); + + const usersPlain = users.map((user) => { + // we converted the user to plain object + return user.get({ plain: true }); + }); + + return usersPlain; +} + +module.exports = { + getByRole, +}; diff --git a/server/utils/constants.js b/server/utils/constants.js index 09a07b77c0..130c162ded 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -99,6 +99,7 @@ const SYSTEM_VARIABLE_NAMES = { GLADYS_GATEWAY_ALEXA_USER_IS_CONNECTED_WITH_GATEWAY: 'GLADYS_GATEWAY_ALEXA_USER_IS_CONNECTED_WITH_GATEWAY', GLADYS_GATEWAY_OPEN_AI_ENABLED: 'GLADYS_GATEWAY_OPEN_AI_ENABLED', TIMEZONE: 'TIMEZONE', + DEVICE_BATTERY_LEVEL_WARNING: 'DEVICE_BATTERY_LEVEL_WARNING', }; const EVENTS = { @@ -125,6 +126,7 @@ const EVENTS = { PURGE_STATES: 'device.purge-states', CALCULATE_HOURLY_AGGREGATE: 'device.calculate-hourly-aggregate', PURGE_STATES_SINGLE_FEATURE: 'device.purge-states-single-feature', + CHECK_BATTERIES: 'device.check-batteries', }, GATEWAY: { CREATE_BACKUP: 'gateway.create-backup', From 5c75c93c58a21a5df4205b688c5b4f1795454215 Mon Sep 17 00:00:00 2001 From: callemand Date: Thu, 16 Nov 2023 08:25:00 +0100 Subject: [PATCH 2/5] Add enabled --- .../settings-system/SettingsSystemPage.jsx | 45 ++++++++++---- .../routes/settings/settings-system/index.js | 61 +++++++++++++++---- server/lib/device/device.checkBatteries.js | 8 ++- ...-default-system-variable-device-battery.js | 17 ++++++ server/utils/constants.js | 3 +- 5 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 server/migrations/20231115163530-default-system-variable-device-battery.js diff --git a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx index 25a90ec2c7..6dd01452a0 100644 --- a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx +++ b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx @@ -255,14 +255,37 @@ const SystemPage = ({ children, ...props }) => (

-
- -

- - - + + +

+

+ + +

+
+ +

+

( type="number" min="1" max="100" - disabled={props.savingBatteryLevelUnderWarning} - value={props.batteryLevelUnderWarning} - onChange={props.updateBatteryLevelUnderWarning} + disabled={props.savingBatteryLevelUnderWarning || !props.batteryLevelUnderWarningEnabled} + value={props.batteryLevelUnderWarningThreshold} + onChange={props.updateBatteryLevelUnderWarningThreshold} />
diff --git a/front/src/routes/settings/settings-system/index.js b/front/src/routes/settings/settings-system/index.js index fa5b52a840..0a427f8d13 100644 --- a/front/src/routes/settings/settings-system/index.js +++ b/front/src/routes/settings/settings-system/index.js @@ -51,26 +51,37 @@ class SettingsSystem extends Component { getBatteryLevelUnderWarning = async () => { try { - const { value } = await this.props.httpClient.get( - `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING}` + const { value: batteryLevelUnderWarningThreshold } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD}` + ); + + const { value: batteryLevelUnderWarningEnabled } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED}` ); + + console.log(batteryLevelUnderWarningThreshold, batteryLevelUnderWarningEnabled); + this.setState({ - batteryLevelUnderWarning: value + batteryLevelUnderWarningThreshold, + batteryLevelUnderWarningEnabled: batteryLevelUnderWarningEnabled === '1' }); } catch (e) { console.error(e); } }; - updateBatteryLevelUnderWarning = async e => { + updateBatteryLevelUnderWarningThreshold = async e => { await this.setState({ - batteryLevelUnderWarning: e.target.value, + batteryLevelUnderWarningThreshold: e.target.value, savingBatteryLevelUnderWarning: true }); try { - await this.props.httpClient.post(`/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING}`, { - value: e.target.value - }); + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD}`, + { + value: e.target.value + } + ); } catch (e) { console.error(e); } @@ -79,7 +90,30 @@ class SettingsSystem extends Component { }); }; - debouncedUpdateBatteryLevelUnderWarning = debounce(this.updateBatteryLevelUnderWarning, 200); + debouncedUpdateBatteryLevelUnderWarningThreshold = debounce(this.updateBatteryLevelUnderWarningThreshold, 200); + + updateBatteryLevelUnderWarningEnabled = async () => { + console.log(this.state.batteryLevelUnderWarningEnabled); + const value = !this.state.batteryLevelUnderWarningEnabled; + console.log(value); + await this.setState({ + batteryLevelUnderWarningEnabled: value, + savingBatteryLevelUnderWarning: true + }); + try { + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED}`, + { + value + } + ); + } catch (e) { + console.error(e); + } + await this.setState({ + savingBatteryLevelUnderWarning: false + }); + }; updateDeviceStateHistory = async e => { await this.setState({ @@ -223,7 +257,8 @@ class SettingsSystem extends Component { vacuumStarted, numberOfHoursBeforeStateIsOutdated, savingNumberOfHourseBeforeStateIsOutdated, - batteryLevelUnderWarning, + batteryLevelUnderWarningThreshold, + batteryLevelUnderWarningEnabled, savingBatteryLevelUnderWarning } ) { @@ -251,9 +286,11 @@ class SettingsSystem extends Component { updateNumberOfHoursBeforeStateIsOutdated={this.updateNumberOfHoursBeforeStateIsOutdated} vacuumDatabase={this.vacuumDatabase} vacuumStarted={vacuumStarted} - batteryLevelUnderWarning={batteryLevelUnderWarning} + batteryLevelUnderWarningThreshold={batteryLevelUnderWarningThreshold} + batteryLevelUnderWarningEnabled={batteryLevelUnderWarningEnabled} savingBatteryLevelUnderWarning={savingBatteryLevelUnderWarning} - updateBatteryLevelUnderWarning={this.debouncedUpdateBatteryLevelUnderWarning} + updateBatteryLevelUnderWarningThreshold={this.debouncedUpdateBatteryLevelUnderWarningThreshold} + updateBatteryLevelUnderWarningEnabled={this.updateBatteryLevelUnderWarningEnabled} /> ); } diff --git a/server/lib/device/device.checkBatteries.js b/server/lib/device/device.checkBatteries.js index a0764b558e..317ecebc5c 100644 --- a/server/lib/device/device.checkBatteries.js +++ b/server/lib/device/device.checkBatteries.js @@ -1,5 +1,3 @@ -const { Op } = require('sequelize'); -const db = require('../../models'); const logger = require('../../utils/logger'); const { SYSTEM_VARIABLE_NAMES, @@ -15,9 +13,13 @@ const { * device.purgeStates(); */ async function checkBatteries() { + const enabled = await this.variable.getValue(SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED); + if (!enabled) { + return; + } logger.debug('Checking batteries ...'); - const minPercentBattery = await this.variable.getValue(SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING); + const minPercentBattery = await this.variable.getValue(SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD); const admins = await this.user.getByRole(USER_ROLE.ADMIN); diff --git a/server/migrations/20231115163530-default-system-variable-device-battery.js b/server/migrations/20231115163530-default-system-variable-device-battery.js new file mode 100644 index 0000000000..2df2f84e8d --- /dev/null +++ b/server/migrations/20231115163530-default-system-variable-device-battery.js @@ -0,0 +1,17 @@ +const db = require('../models'); +const { SYSTEM_VARIABLE_NAMES } = require('../utils/constants'); + +module.exports = { + up: async () => { + await db.Variable.create({ + name: SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED, + value: true, + }); + await db.Variable.create({ + name: SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD, + value: '10', + }); + }, + + down: async () => {}, +}; diff --git a/server/utils/constants.js b/server/utils/constants.js index 130c162ded..1a575748a8 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -99,7 +99,8 @@ const SYSTEM_VARIABLE_NAMES = { GLADYS_GATEWAY_ALEXA_USER_IS_CONNECTED_WITH_GATEWAY: 'GLADYS_GATEWAY_ALEXA_USER_IS_CONNECTED_WITH_GATEWAY', GLADYS_GATEWAY_OPEN_AI_ENABLED: 'GLADYS_GATEWAY_OPEN_AI_ENABLED', TIMEZONE: 'TIMEZONE', - DEVICE_BATTERY_LEVEL_WARNING: 'DEVICE_BATTERY_LEVEL_WARNING', + DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD: 'DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD', + DEVICE_BATTERY_LEVEL_WARNING_ENABLED: 'DEVICE_BATTERY_LEVEL_WARNING_ENABLED', }; const EVENTS = { From a3d14372d12768e4df01da3d18681d6f9c2340c1 Mon Sep 17 00:00:00 2001 From: callemand Date: Fri, 17 Nov 2023 15:34:08 +0100 Subject: [PATCH 3/5] Change config system to box mode and change cron check device --- front/src/config/i18n/en.json | 2 +- front/src/config/i18n/fr.json | 2 +- .../SettingsSystemBatteryLevelWarning.jsx | 130 ++++++++ .../SettingsSystemContainers.jsx | 54 ++++ .../SettingsSystemDatabaseCleaning.jsx | 54 ++++ .../SettingsSystemKeepAggregatedStates.jsx | 98 ++++++ .../SettingsSystemKeepDeviceHistory.jsx | 83 +++++ .../SettingsSystemOperations.jsx | 49 +++ .../settings-system/SettingsSystemPage.jsx | 286 +----------------- .../SettingsSystemTimeExpiryState.jsx | 82 +++++ .../SettingsSystemTimezone.jsx | 61 ++++ .../routes/settings/settings-system/index.js | 283 +---------------- server/config/scheduler-jobs.js | 2 +- 13 files changed, 634 insertions(+), 552 deletions(-) create mode 100644 front/src/routes/settings/settings-system/SettingsSystemBatteryLevelWarning.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemContainers.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemDatabaseCleaning.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemKeepAggregatedStates.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemKeepDeviceHistory.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemOperations.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemTimeExpiryState.jsx create mode 100644 front/src/routes/settings/settings-system/SettingsSystemTimezone.jsx diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 05f30675a2..f979d58c49 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -2054,7 +2054,7 @@ "dead": "Dead" }, "batteryLevel": "Battery level alert", - "batteryLevelDescription": "A message will be sent to all admins as soon as the battery level drops below the chosen percentage" + "batteryLevelDescription": "At 09:00 AM, only on Saturday, a message will be sent to all admins as soon as the battery level drops below the chosen percentage" }, "newArea": { "createNewZoneButton": "Create new zone", diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json index a4e101ec34..f80bf628c2 100644 --- a/front/src/config/i18n/fr.json +++ b/front/src/config/i18n/fr.json @@ -2055,7 +2055,7 @@ "dead": "Mort" }, "batteryLevel": "Alerte sur le niveau de batterie", - "batteryLevelDescription": "Un message sera envoyer à tous les admins dès que le niveau du batterie parsera en dessous du pourcentage choisi" + "batteryLevelDescription": "A 09h00, uniquement le samedi, un message sera envoyer à tous les admins dès que le niveau du batterie parsera en dessous du pourcentage choisi" }, "newArea": { "createNewZoneButton": "Créer une zone", diff --git a/front/src/routes/settings/settings-system/SettingsSystemBatteryLevelWarning.jsx b/front/src/routes/settings/settings-system/SettingsSystemBatteryLevelWarning.jsx new file mode 100644 index 0000000000..20f65516e4 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemBatteryLevelWarning.jsx @@ -0,0 +1,130 @@ +import cx from 'classnames'; +import { Text } from 'preact-i18n'; +import { Component } from 'preact'; +import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; +import debounce from 'debounce'; +import { connect } from 'unistore/preact'; + +class SettingsSystemBatteryLevelWarning extends Component { + getBatteryLevelUnderWarning = async () => { + try { + const { value: batteryLevelUnderWarningThreshold } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD}` + ); + + const { value: batteryLevelUnderWarningEnabled } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED}` + ); + + this.setState({ + batteryLevelUnderWarningThreshold, + batteryLevelUnderWarningEnabled: batteryLevelUnderWarningEnabled === '1' + }); + } catch (e) { + console.error(e); + } + }; + + updateBatteryLevelUnderWarningThreshold = async e => { + await this.setState({ + batteryLevelUnderWarningThreshold: e.target.value, + savingBatteryLevelUnderWarning: true + }); + try { + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_THRESHOLD}`, + { + value: e.target.value + } + ); + } catch (e) { + console.error(e); + } + await this.setState({ + savingBatteryLevelUnderWarning: false + }); + }; + + debouncedUpdateBatteryLevelUnderWarningThreshold = debounce(this.updateBatteryLevelUnderWarningThreshold, 200); + + updateBatteryLevelUnderWarningEnabled = async () => { + const value = !this.state.batteryLevelUnderWarningEnabled; + await this.setState({ + batteryLevelUnderWarningEnabled: value, + savingBatteryLevelUnderWarning: true + }); + try { + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_BATTERY_LEVEL_WARNING_ENABLED}`, + { + value + } + ); + } catch (e) { + console.error(e); + } + await this.setState({ + savingBatteryLevelUnderWarning: false + }); + }; + + componentDidMount() { + this.getBatteryLevelUnderWarning(); + } + + render({}, { batteryLevelUnderWarningThreshold, batteryLevelUnderWarningEnabled, savingBatteryLevelUnderWarning }) { + return ( +
+

+ + +

+
+ +

+ +

+
+ +
+ + + +
+
+ +
+
+ ); + } +} + +export default connect('httpClient', null)(SettingsSystemBatteryLevelWarning); diff --git a/front/src/routes/settings/settings-system/SettingsSystemContainers.jsx b/front/src/routes/settings/settings-system/SettingsSystemContainers.jsx new file mode 100644 index 0000000000..006326f053 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemContainers.jsx @@ -0,0 +1,54 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import cx from 'classnames'; + +class SettingsSystemContainers extends Component { + render({ systemContainers }, {}) { + return ( +
+

+ +

+
+ + + + + + + + + + {systemContainers && + systemContainers.map(container => ( + + + + + + ))} + +
+ + + + + +
{container.name}{container.created_at_formatted} + + + +
+
+
+ ); + } +} + +export default connect('systemContainers', null)(SettingsSystemContainers); diff --git a/front/src/routes/settings/settings-system/SettingsSystemDatabaseCleaning.jsx b/front/src/routes/settings/settings-system/SettingsSystemDatabaseCleaning.jsx new file mode 100644 index 0000000000..1f790f34b0 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemDatabaseCleaning.jsx @@ -0,0 +1,54 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; + +class SettingsSystemDatabaseCleaning extends Component { + constructor(props) { + super(props); + this.state = { + vacuumStarted: false + }; + } + + vacuumDatabase = async e => { + e.preventDefault(); + this.setState({ + vacuumStarted: true + }); + try { + await this.props.httpClient.post('/api/v1/system/vacuum'); + } catch (e) { + console.error(e); + } + }; + + render({}, { vacuumStarted }) { + return ( +
+

+ +

+ +
+
+

+ +

+

+ {vacuumStarted && ( +

+ +
+ )} + +

+
+
+
+ ); + } +} + +export default connect('httpClient', null)(SettingsSystemDatabaseCleaning); diff --git a/front/src/routes/settings/settings-system/SettingsSystemKeepAggregatedStates.jsx b/front/src/routes/settings/settings-system/SettingsSystemKeepAggregatedStates.jsx new file mode 100644 index 0000000000..ded8ce8a76 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemKeepAggregatedStates.jsx @@ -0,0 +1,98 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; +import get from 'get-value'; + +class SettingsSystemKeepAggregatedStates extends Component { + getDeviceAggregateStateHistoryPreference = async () => { + try { + const { value } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_AGGREGATE_STATE_HISTORY_IN_DAYS}` + ); + this.setState({ + deviceAggregateStateHistoryInDays: value + }); + } catch (e) { + console.error(e); + const status = get(e, 'response.status'); + if (status === 404) { + // Default value is -1 + this.setState({ + deviceAggregateStateHistoryInDays: '-1' + }); + } + } + }; + + updateDeviceAggregateStateHistory = async e => { + await this.setState({ + deviceAggregateStateHistoryInDays: e.target.value, + savingDeviceStateHistory: true + }); + try { + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_AGGREGATE_STATE_HISTORY_IN_DAYS}`, + { + value: e.target.value + } + ); + } catch (e) { + console.error(e); + } + await this.setState({ + savingDeviceStateHistory: false + }); + }; + + componentDidMount() { + this.getDeviceAggregateStateHistoryPreference(); + } + + render({}, { deviceAggregateStateHistoryInDays }) { + return ( +
+

+ +

+ +
+
+

+ +

+ +
+
+
+ ); + } +} + +export default connect('httpClient', null)(SettingsSystemKeepAggregatedStates); diff --git a/front/src/routes/settings/settings-system/SettingsSystemKeepDeviceHistory.jsx b/front/src/routes/settings/settings-system/SettingsSystemKeepDeviceHistory.jsx new file mode 100644 index 0000000000..eed8498fb9 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemKeepDeviceHistory.jsx @@ -0,0 +1,83 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; + +class SettingsSystemKeepDeviceHistory extends Component { + getDeviceStateHistoryPreference = async () => { + try { + const { value } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_STATE_HISTORY_IN_DAYS}` + ); + this.setState({ + deviceStateHistoryInDays: value + }); + } catch (e) { + console.error(e); + } + }; + + updateDeviceStateHistory = async e => { + await this.setState({ + deviceStateHistoryInDays: e.target.value, + savingDeviceStateHistory: true + }); + try { + await this.props.httpClient.post(`/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_STATE_HISTORY_IN_DAYS}`, { + value: e.target.value + }); + } catch (e) { + console.error(e); + } + await this.setState({ + savingDeviceStateHistory: false + }); + }; + + componentDidMount() { + this.getDeviceStateHistoryPreference(); + } + + render({}, { deviceStateHistoryInDays }) { + return ( +
+

+ +

+ +
+
+

+ +

+ +
+
+
+ ); + } +} + +export default connect('httpClient', null)(SettingsSystemKeepDeviceHistory); diff --git a/front/src/routes/settings/settings-system/SettingsSystemOperations.jsx b/front/src/routes/settings/settings-system/SettingsSystemOperations.jsx new file mode 100644 index 0000000000..4aeef229cc --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemOperations.jsx @@ -0,0 +1,49 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; + +class SettingsSystemOperations extends Component { + render({ systemInfos }, {}) { + return ( +
+

+ +

+ + {systemInfos && systemInfos.new_release_available === true && ( +
+
+

+ +

+

+ +

+
+
+ )} + + {systemInfos && systemInfos.new_release_available === false && ( +
+ + + + + + + +
+ + + + + +
+
+ )} +
+ ); + } +} + +export default connect('systemInfos', null)(SettingsSystemOperations); diff --git a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx index 6dd01452a0..35901fada5 100644 --- a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx +++ b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx @@ -1,7 +1,13 @@ import { Text } from 'preact-i18n'; import SettingsLayout from '../SettingsLayout'; -import cx from 'classnames'; -import Select from 'react-select'; +import SettingsSystemBatteryLevelWarning from './SettingsSystemBatteryLevelWarning'; +import SettingsSystemContainers from './SettingsSystemContainers'; +import SettingsSystemOperations from './SettingsSystemOperations'; +import SettingsSystemTimezone from './SettingsSystemTimezone'; +import SettingsSystemKeepDeviceHistory from './SettingsSystemKeepDeviceHistory'; +import SettingsSystemKeepAggregatedStates from './SettingsSystemKeepAggregatedStates'; +import SettingsSystemTimeExpiryState from './SettingsSystemTimeExpiryState'; +import SettingsSystemDatabaseCleaning from './SettingsSystemDatabaseCleaning'; const SystemPage = ({ children, ...props }) => ( @@ -79,276 +85,16 @@ const SystemPage = ({ children, ...props }) => (
-
-

- -

- - {props.systemInfos && props.systemInfos.new_release_available === true && ( -
-
-

- -

-

- -

-
-
- )} - - {props.systemInfos && props.systemInfos.new_release_available === false && ( -
- - - - - - - -
- - - - - -
-
- )} -
- -
-

- -

-
-
- -

- - - -

- - - - - - - - - -
-
- -

- - - -

- -
-
- -

- - - -

-
- -
- - - -
-
-
-
- -

- - - -

-

- {props.vacuumStarted && ( -

- -
- )} - -

-
-
-
-
-

- - -

-
-
-

- -

-
- -
- - - -
-
-
-
-
+ + + +
-
-

- -

-
- - - - - - - - - - {props.systemContainers && - props.systemContainers.map(container => ( - - - - - - ))} - -
- - - - - -
{container.name}{container.created_at_formatted} - - - -
-
-
+ + + +
diff --git a/front/src/routes/settings/settings-system/SettingsSystemTimeExpiryState.jsx b/front/src/routes/settings/settings-system/SettingsSystemTimeExpiryState.jsx new file mode 100644 index 0000000000..ff755910cf --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemTimeExpiryState.jsx @@ -0,0 +1,82 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; + +class SettingsSystemTimeExpiryState extends Component { + getNumberOfHoursBeforeStateIsOutdated = async () => { + try { + const { value } = await this.props.httpClient.get( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_STATE_NUMBER_OF_HOURS_BEFORE_STATE_IS_OUTDATED}` + ); + this.setState({ + numberOfHoursBeforeStateIsOutdated: value + }); + } catch (e) { + console.error(e); + // if variable doesn't exist, value is 48 + this.setState({ + numberOfHoursBeforeStateIsOutdated: 48 + }); + } + }; + + updateNumberOfHoursBeforeStateIsOutdated = async e => { + await this.setState({ + numberOfHoursBeforeStateIsOutdated: e.target.value, + savingNumberOfHourseBeforeStateIsOutdated: true + }); + try { + await this.props.httpClient.post( + `/api/v1/variable/${SYSTEM_VARIABLE_NAMES.DEVICE_STATE_NUMBER_OF_HOURS_BEFORE_STATE_IS_OUTDATED}`, + { + value: e.target.value + } + ); + } catch (e) { + console.error(e); + } + await this.setState({ + savingNumberOfHourseBeforeStateIsOutdated: false + }); + }; + + componentDidMount() { + this.getNumberOfHoursBeforeStateIsOutdated(); + } + + render({}, { numberOfHoursBeforeStateIsOutdated, savingNumberOfHourseBeforeStateIsOutdated }) { + return ( +
+

+ +

+ +
+
+

+ +

+
+ +
+ + + +
+
+
+
+
+ ); + } +} + +export default connect('httpClient', null)(SettingsSystemTimeExpiryState); diff --git a/front/src/routes/settings/settings-system/SettingsSystemTimezone.jsx b/front/src/routes/settings/settings-system/SettingsSystemTimezone.jsx new file mode 100644 index 0000000000..692c551657 --- /dev/null +++ b/front/src/routes/settings/settings-system/SettingsSystemTimezone.jsx @@ -0,0 +1,61 @@ +import { connect } from 'unistore/preact'; +import { Component } from 'preact'; +import { Text } from 'preact-i18n'; +import Select from 'react-select'; +import timezones from '../../../config/timezones'; +import { SYSTEM_VARIABLE_NAMES } from '../../../../../server/utils/constants'; + +class SettingsSystemTimezone extends Component { + getTimezone = async () => { + try { + const { value } = await this.props.httpClient.get(`/api/v1/variable/${SYSTEM_VARIABLE_NAMES.TIMEZONE}`); + const selectedTimezone = timezones.find(tz => tz.value === value); + if (selectedTimezone) { + this.setState({ + selectedTimezone + }); + } + } catch (e) { + console.error(e); + } + }; + + updateTimezone = async option => { + this.setState({ + savingTimezone: true, + selectedTimezone: option + }); + try { + await this.props.httpClient.post(`/api/v1/variable/${SYSTEM_VARIABLE_NAMES.TIMEZONE}`, { + value: option.value + }); + } catch (e) { + console.error(e); + } + }; + + componentDidMount() { + this.getTimezone(); + } + + render({}, { selectedTimezone }) { + return ( +
+

+ +

+ +
+
+

+ +

+