mirror of
				https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
				synced 2025-11-03 19:57:46 +00:00 
			
		
		
		
	UI fixes, standardization
This commit is contained in:
		@@ -4,6 +4,7 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
 | 
				
			|||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
 | 
					const CopyWebpackPlugin = require('copy-webpack-plugin');
 | 
				
			||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
 | 
					const HtmlWebpackPlugin = require('html-webpack-plugin');
 | 
				
			||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 | 
					const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 | 
				
			||||||
 | 
					const webpack = require('webpack');
 | 
				
			||||||
const path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
const paths = require('./paths');
 | 
					const paths = require('./paths');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,6 +20,9 @@ module.exports = {
 | 
				
			|||||||
    preferRelative: true,
 | 
					    preferRelative: true,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [
 | 
					  plugins: [
 | 
				
			||||||
 | 
					    new webpack.DefinePlugin({
 | 
				
			||||||
 | 
					      'process.env.VERSION': JSON.stringify(process.env.npm_package_version),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
    new MiniCssExtractPlugin({
 | 
					    new MiniCssExtractPlugin({
 | 
				
			||||||
      filename: 'styles/[name].[contenthash].css',
 | 
					      filename: 'styles/[name].[contenthash].css',
 | 
				
			||||||
      chunkFilename: '[id].[contenthash].css',
 | 
					      chunkFilename: '[id].[contenthash].css',
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,12 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ucentral-client",
 | 
					  "name": "ucentral-client",
 | 
				
			||||||
  "version": "2.1.10",
 | 
					  "version": "2.1.12",
 | 
				
			||||||
  "lockfileVersion": 2,
 | 
					  "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "ucentral-client",
 | 
					      "name": "ucentral-client",
 | 
				
			||||||
      "version": "2.1.10",
 | 
					      "version": "2.1.12",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -32,7 +32,7 @@
 | 
				
			|||||||
        "react-tooltip": "^4.2.21",
 | 
					        "react-tooltip": "^4.2.21",
 | 
				
			||||||
        "react-widgets": "^5.1.1",
 | 
					        "react-widgets": "^5.1.1",
 | 
				
			||||||
        "sass": "^1.35.1",
 | 
					        "sass": "^1.35.1",
 | 
				
			||||||
        "ucentral-libs": "^0.9.17",
 | 
					        "ucentral-libs": "^0.9.18",
 | 
				
			||||||
        "uuid": "^8.3.2"
 | 
					        "uuid": "^8.3.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
@@ -14812,9 +14812,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/ucentral-libs": {
 | 
					    "node_modules/ucentral-libs": {
 | 
				
			||||||
      "version": "0.9.17",
 | 
					      "version": "0.9.18",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.17.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.18.tgz",
 | 
				
			||||||
      "integrity": "sha512-GQtEaOhdFcBmNeu4NhYiq4yVsycZ7i8DeotubcKN8lt9v/TjAPecle0FsDdiXgXphOblToym6mk2tCAbH/958g==",
 | 
					      "integrity": "sha512-znLLUdrRdBCYyufpFk/LWMVx0iajbuMTqayxWJek8JJeiaFlgH58+52s1OKjsLEb6Q4kPOIeUHguN3pJ/EfB1w==",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -27653,9 +27653,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ucentral-libs": {
 | 
					    "ucentral-libs": {
 | 
				
			||||||
      "version": "0.9.17",
 | 
					      "version": "0.9.18",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.17.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ucentral-libs/-/ucentral-libs-0.9.18.tgz",
 | 
				
			||||||
      "integrity": "sha512-GQtEaOhdFcBmNeu4NhYiq4yVsycZ7i8DeotubcKN8lt9v/TjAPecle0FsDdiXgXphOblToym6mk2tCAbH/958g==",
 | 
					      "integrity": "sha512-znLLUdrRdBCYyufpFk/LWMVx0iajbuMTqayxWJek8JJeiaFlgH58+52s1OKjsLEb6Q4kPOIeUHguN3pJ/EfB1w==",
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "@coreui/coreui": "^3.4.0",
 | 
					        "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
        "@coreui/icons": "^2.0.1",
 | 
					        "@coreui/icons": "^2.0.1",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ucentral-client",
 | 
					  "name": "ucentral-client",
 | 
				
			||||||
  "version": "2.1.10",
 | 
					  "version": "2.1.12",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@coreui/coreui": "^3.4.0",
 | 
					    "@coreui/coreui": "^3.4.0",
 | 
				
			||||||
    "@coreui/icons": "^2.0.1",
 | 
					    "@coreui/icons": "^2.0.1",
 | 
				
			||||||
@@ -26,7 +26,7 @@
 | 
				
			|||||||
    "react-tooltip": "^4.2.21",
 | 
					    "react-tooltip": "^4.2.21",
 | 
				
			||||||
    "react-widgets": "^5.1.1",
 | 
					    "react-widgets": "^5.1.1",
 | 
				
			||||||
    "sass": "^1.35.1",
 | 
					    "sass": "^1.35.1",
 | 
				
			||||||
    "ucentral-libs": "^0.9.17",
 | 
					    "ucentral-libs": "^0.9.18",
 | 
				
			||||||
    "uuid": "^8.3.2"
 | 
					    "uuid": "^8.3.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,11 @@
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
	"commands": {
 | 
						"commands": {
 | 
				
			||||||
		"error": "Fehler beim Senden des Befehls!",
 | 
							"error": "Fehler beim Senden des Befehls!",
 | 
				
			||||||
 | 
							"error_delete_log": "Fehler beim Versuch zu löschen: {{error}}",
 | 
				
			||||||
		"event_queue": "Ereigniswarteschlange",
 | 
							"event_queue": "Ereigniswarteschlange",
 | 
				
			||||||
		"success": "Befehl wurde erfolgreich übermittelt",
 | 
							"success": "Befehl wurde erfolgreich übermittelt",
 | 
				
			||||||
		"title": "Gerätebefehle",
 | 
							"title": "Gerätebefehle",
 | 
				
			||||||
		"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden"
 | 
							"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden: {{error}}"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"common": {
 | 
						"common": {
 | 
				
			||||||
		"access_policy": "Zugangsrichtlinien",
 | 
							"access_policy": "Zugangsrichtlinien",
 | 
				
			||||||
@@ -205,6 +206,9 @@
 | 
				
			|||||||
		"title": "Gerät konfigurieren",
 | 
							"title": "Gerät konfigurieren",
 | 
				
			||||||
		"valid_json": "Sie müssen ein gültiges JSON eingeben"
 | 
							"valid_json": "Sie müssen ein gültiges JSON eingeben"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"connect": {
 | 
				
			||||||
 | 
							"error_trying_to_connect": "Fehler beim Versuch, eine Verbindung zum Gerät herzustellen: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"delete_command": {
 | 
						"delete_command": {
 | 
				
			||||||
		"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.",
 | 
							"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.",
 | 
				
			||||||
		"title": "Befehl löschen"
 | 
							"title": "Befehl löschen"
 | 
				
			||||||
@@ -215,6 +219,10 @@
 | 
				
			|||||||
		"explanation": "Dadurch werden alle {{object}} vor dem von Ihnen gewählten Datum gelöscht. Seien Sie vorsichtig, diese Aktion ist nicht umkehrbar.",
 | 
							"explanation": "Dadurch werden alle {{object}} vor dem von Ihnen gewählten Datum gelöscht. Seien Sie vorsichtig, diese Aktion ist nicht umkehrbar.",
 | 
				
			||||||
		"healthchecks_title": "Healthchecks löschen"
 | 
							"healthchecks_title": "Healthchecks löschen"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"device": {
 | 
				
			||||||
 | 
							"error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}",
 | 
				
			||||||
 | 
							"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"device_logs": {
 | 
						"device_logs": {
 | 
				
			||||||
		"log": "Protokoll",
 | 
							"log": "Protokoll",
 | 
				
			||||||
		"severity": "Wichtigkeit",
 | 
							"severity": "Wichtigkeit",
 | 
				
			||||||
@@ -486,7 +494,7 @@
 | 
				
			|||||||
		"create_success": "Benutzer erfolgreich erstellt",
 | 
							"create_success": "Benutzer erfolgreich erstellt",
 | 
				
			||||||
		"creating": "Benutzer erstellen ...",
 | 
							"creating": "Benutzer erstellen ...",
 | 
				
			||||||
		"delete_avatar": "Avatar löschen",
 | 
							"delete_avatar": "Avatar löschen",
 | 
				
			||||||
		"delete_failure": "Fehler beim Versuch, den Benutzer zu löschen",
 | 
							"delete_failure": "Fehler beim Versuch, den Benutzer zu löschen: {{error}}",
 | 
				
			||||||
		"delete_success": "Benutzer erfolgreich gelöscht!",
 | 
							"delete_success": "Benutzer erfolgreich gelöscht!",
 | 
				
			||||||
		"delete_title": "Benutzer löschen",
 | 
							"delete_title": "Benutzer löschen",
 | 
				
			||||||
		"delete_warning": "Warnung: Sobald Sie einen Benutzer gelöscht haben, können Sie ihn nicht wiederherstellen",
 | 
							"delete_warning": "Warnung: Sobald Sie einen Benutzer gelöscht haben, können Sie ihn nicht wiederherstellen",
 | 
				
			||||||
@@ -494,6 +502,7 @@
 | 
				
			|||||||
		"description": "Beschreibung",
 | 
							"description": "Beschreibung",
 | 
				
			||||||
		"edit": "Benutzer bearbeiten",
 | 
							"edit": "Benutzer bearbeiten",
 | 
				
			||||||
		"email_address": "E-Mail-Addresse",
 | 
							"email_address": "E-Mail-Addresse",
 | 
				
			||||||
 | 
							"error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}",
 | 
				
			||||||
		"force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
 | 
							"force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
 | 
				
			||||||
		"id": "Benutzeridentifikation.",
 | 
							"id": "Benutzeridentifikation.",
 | 
				
			||||||
		"last_login": "Letzte Anmeldung",
 | 
							"last_login": "Letzte Anmeldung",
 | 
				
			||||||
@@ -509,7 +518,7 @@
 | 
				
			|||||||
		"provide_password": "Bitte geben Sie ein gültiges Passwort ein",
 | 
							"provide_password": "Bitte geben Sie ein gültiges Passwort ein",
 | 
				
			||||||
		"save_avatar": "Avatar speichern",
 | 
							"save_avatar": "Avatar speichern",
 | 
				
			||||||
		"show_hide_password": "Passwort anzeigen/verbergen",
 | 
							"show_hide_password": "Passwort anzeigen/verbergen",
 | 
				
			||||||
		"update_failure": "Stellen Sie sicher, dass alle Ihre Daten gültig sind. Wenn Sie das Kennwort ändern, stellen Sie sicher, dass es sich nicht um ein altes handelt.",
 | 
							"update_failure": "Fehler beim Aktualisieren: {{error}}",
 | 
				
			||||||
		"update_failure_title": "Update fehlgeschlagen",
 | 
							"update_failure_title": "Update fehlgeschlagen",
 | 
				
			||||||
		"update_success": "Benutzer erfolgreich aktualisiert",
 | 
							"update_success": "Benutzer erfolgreich aktualisiert",
 | 
				
			||||||
		"update_success_title": "Erfolg",
 | 
							"update_success_title": "Erfolg",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,11 @@
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
	"commands": {
 | 
						"commands": {
 | 
				
			||||||
		"error": "Error while submitting command!",
 | 
							"error": "Error while submitting command!",
 | 
				
			||||||
 | 
							"error_delete_log": "Error while trying to delete: {{error}}",
 | 
				
			||||||
		"event_queue": "Event Queue",
 | 
							"event_queue": "Event Queue",
 | 
				
			||||||
		"success": "Command submitted successfully, you can look at the Commands log for the result",
 | 
							"success": "Command submitted successfully, you can look at the Commands log for the result",
 | 
				
			||||||
		"title": "Command History",
 | 
							"title": "Command History",
 | 
				
			||||||
		"unable_queue": "Unable to complete event queue request"
 | 
							"unable_queue": "Unable to complete event queue request: {{error}}"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"common": {
 | 
						"common": {
 | 
				
			||||||
		"access_policy": "Access Policy",
 | 
							"access_policy": "Access Policy",
 | 
				
			||||||
@@ -205,6 +206,9 @@
 | 
				
			|||||||
		"title": "Configure",
 | 
							"title": "Configure",
 | 
				
			||||||
		"valid_json": "You need to enter valid JSON"
 | 
							"valid_json": "You need to enter valid JSON"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"connect": {
 | 
				
			||||||
 | 
							"error_trying_to_connect": "Error while trying to connect to device: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"delete_command": {
 | 
						"delete_command": {
 | 
				
			||||||
		"explanation": "Are you sure you want to delete this command? This action is not reversible.",
 | 
							"explanation": "Are you sure you want to delete this command? This action is not reversible.",
 | 
				
			||||||
		"title": "Delete Command"
 | 
							"title": "Delete Command"
 | 
				
			||||||
@@ -215,6 +219,10 @@
 | 
				
			|||||||
		"explanation": "This will delete all of the {{object}} before the date you choose. Be careful, this action is not reversible.",
 | 
							"explanation": "This will delete all of the {{object}} before the date you choose. Be careful, this action is not reversible.",
 | 
				
			||||||
		"healthchecks_title": "Delete Healthchecks"
 | 
							"healthchecks_title": "Delete Healthchecks"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"device": {
 | 
				
			||||||
 | 
							"error_fetching_device": "Error fetching device information: {{error}}",
 | 
				
			||||||
 | 
							"error_fetching_devices": "Error while fetching devices: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"device_logs": {
 | 
						"device_logs": {
 | 
				
			||||||
		"log": "Log",
 | 
							"log": "Log",
 | 
				
			||||||
		"severity": "Severity",
 | 
							"severity": "Severity",
 | 
				
			||||||
@@ -486,7 +494,7 @@
 | 
				
			|||||||
		"create_success": "User Created Successfully",
 | 
							"create_success": "User Created Successfully",
 | 
				
			||||||
		"creating": "Creating User...",
 | 
							"creating": "Creating User...",
 | 
				
			||||||
		"delete_avatar": "Delete Avatar",
 | 
							"delete_avatar": "Delete Avatar",
 | 
				
			||||||
		"delete_failure": "Error while trying to delete user",
 | 
							"delete_failure": "Error while trying to delete user: {{error}}",
 | 
				
			||||||
		"delete_success": "User successfully deleted!",
 | 
							"delete_success": "User successfully deleted!",
 | 
				
			||||||
		"delete_title": "Delete User",
 | 
							"delete_title": "Delete User",
 | 
				
			||||||
		"delete_warning": "Warning: Once you delete a user you cannot revert",
 | 
							"delete_warning": "Warning: Once you delete a user you cannot revert",
 | 
				
			||||||
@@ -494,6 +502,7 @@
 | 
				
			|||||||
		"description": "Description",
 | 
							"description": "Description",
 | 
				
			||||||
		"edit": "Edit User",
 | 
							"edit": "Edit User",
 | 
				
			||||||
		"email_address": "Email Address",
 | 
							"email_address": "Email Address",
 | 
				
			||||||
 | 
							"error_fetching_users": "Error fetching users: {{error}}",
 | 
				
			||||||
		"force_password_change": "Force Password Change on Login",
 | 
							"force_password_change": "Force Password Change on Login",
 | 
				
			||||||
		"id": "User Id.",
 | 
							"id": "User Id.",
 | 
				
			||||||
		"last_login": "Last Login",
 | 
							"last_login": "Last Login",
 | 
				
			||||||
@@ -509,7 +518,7 @@
 | 
				
			|||||||
		"provide_password": "Please provide a valid password",
 | 
							"provide_password": "Please provide a valid password",
 | 
				
			||||||
		"save_avatar": "Save Avatar",
 | 
							"save_avatar": "Save Avatar",
 | 
				
			||||||
		"show_hide_password": "Show/Hide Password",
 | 
							"show_hide_password": "Show/Hide Password",
 | 
				
			||||||
		"update_failure": "Make sure all of your data is valid. If you are modifying the password, make sure it is not an old one.",
 | 
							"update_failure": "Error while trying to update: {{error}}",
 | 
				
			||||||
		"update_failure_title": "Update Failed",
 | 
							"update_failure_title": "Update Failed",
 | 
				
			||||||
		"update_success": "User Updated Successfully",
 | 
							"update_success": "User Updated Successfully",
 | 
				
			||||||
		"update_success_title": "Success",
 | 
							"update_success_title": "Success",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,11 @@
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
	"commands": {
 | 
						"commands": {
 | 
				
			||||||
		"error": "¡Error al enviar el comando!",
 | 
							"error": "¡Error al enviar el comando!",
 | 
				
			||||||
 | 
							"error_delete_log": "Error al intentar eliminar: {{error}}",
 | 
				
			||||||
		"event_queue": "Cola de eventos",
 | 
							"event_queue": "Cola de eventos",
 | 
				
			||||||
		"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
 | 
							"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
 | 
				
			||||||
		"title": "Historial de Comandos",
 | 
							"title": "Historial de Comandos",
 | 
				
			||||||
		"unable_queue": "No se pudo completar la solicitud de cola de eventos"
 | 
							"unable_queue": "No se pudo completar la solicitud de cola de eventos: {{error}}"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"common": {
 | 
						"common": {
 | 
				
			||||||
		"access_policy": "Política de acceso",
 | 
							"access_policy": "Política de acceso",
 | 
				
			||||||
@@ -205,6 +206,9 @@
 | 
				
			|||||||
		"title": "Configurar",
 | 
							"title": "Configurar",
 | 
				
			||||||
		"valid_json": "Debes ingresar un JSON válido"
 | 
							"valid_json": "Debes ingresar un JSON válido"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"connect": {
 | 
				
			||||||
 | 
							"error_trying_to_connect": "Error al intentar conectarse al dispositivo: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"delete_command": {
 | 
						"delete_command": {
 | 
				
			||||||
		"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
 | 
							"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
 | 
				
			||||||
		"title": "Eliminar comando"
 | 
							"title": "Eliminar comando"
 | 
				
			||||||
@@ -215,6 +219,10 @@
 | 
				
			|||||||
		"explanation": "Esto eliminará todos los {{object}} antes de la fecha que elija. Tenga cuidado, esta acción no es reversible.",
 | 
							"explanation": "Esto eliminará todos los {{object}} antes de la fecha que elija. Tenga cuidado, esta acción no es reversible.",
 | 
				
			||||||
		"healthchecks_title": "Eliminar comprobaciones de estado"
 | 
							"healthchecks_title": "Eliminar comprobaciones de estado"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"device": {
 | 
				
			||||||
 | 
							"error_fetching_device": "Error al obtener la información del dispositivo: {{error}}",
 | 
				
			||||||
 | 
							"error_fetching_devices": "Error al recuperar dispositivos: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"device_logs": {
 | 
						"device_logs": {
 | 
				
			||||||
		"log": "Iniciar sesión",
 | 
							"log": "Iniciar sesión",
 | 
				
			||||||
		"severity": "Gravedad",
 | 
							"severity": "Gravedad",
 | 
				
			||||||
@@ -486,7 +494,7 @@
 | 
				
			|||||||
		"create_success": "Usuario creado con éxito",
 | 
							"create_success": "Usuario creado con éxito",
 | 
				
			||||||
		"creating": "Creando usuario ...",
 | 
							"creating": "Creando usuario ...",
 | 
				
			||||||
		"delete_avatar": "Eliminar avatar",
 | 
							"delete_avatar": "Eliminar avatar",
 | 
				
			||||||
		"delete_failure": "Error al intentar eliminar al usuario",
 | 
							"delete_failure": "Error al intentar eliminar al usuario: {{error}}",
 | 
				
			||||||
		"delete_success": "¡Usuario eliminado correctamente!",
 | 
							"delete_success": "¡Usuario eliminado correctamente!",
 | 
				
			||||||
		"delete_title": "Borrar usuario",
 | 
							"delete_title": "Borrar usuario",
 | 
				
			||||||
		"delete_warning": "Advertencia: una vez que elimina un usuario, no puede revertir",
 | 
							"delete_warning": "Advertencia: una vez que elimina un usuario, no puede revertir",
 | 
				
			||||||
@@ -494,6 +502,7 @@
 | 
				
			|||||||
		"description": "Descripción",
 | 
							"description": "Descripción",
 | 
				
			||||||
		"edit": "editar usuario",
 | 
							"edit": "editar usuario",
 | 
				
			||||||
		"email_address": "Dirección de correo electrónico",
 | 
							"email_address": "Dirección de correo electrónico",
 | 
				
			||||||
 | 
							"error_fetching_users": "Error al obtener usuarios: {{error}}",
 | 
				
			||||||
		"force_password_change": "Forzar cambio de contraseña al iniciar sesión",
 | 
							"force_password_change": "Forzar cambio de contraseña al iniciar sesión",
 | 
				
			||||||
		"id": "Id. De usuario",
 | 
							"id": "Id. De usuario",
 | 
				
			||||||
		"last_login": "Último acceso",
 | 
							"last_login": "Último acceso",
 | 
				
			||||||
@@ -509,7 +518,7 @@
 | 
				
			|||||||
		"provide_password": "Proporcione una contraseña válida",
 | 
							"provide_password": "Proporcione una contraseña válida",
 | 
				
			||||||
		"save_avatar": "Guardar avatar",
 | 
							"save_avatar": "Guardar avatar",
 | 
				
			||||||
		"show_hide_password": "Mostrar / Ocultar contraseña",
 | 
							"show_hide_password": "Mostrar / Ocultar contraseña",
 | 
				
			||||||
		"update_failure": "Asegúrese de que todos sus datos sean válidos. Si está modificando la contraseña, asegúrese de que no sea antigua.",
 | 
							"update_failure": "Error al intentar actualizar: {{error}}",
 | 
				
			||||||
		"update_failure_title": "Actualización fallida",
 | 
							"update_failure_title": "Actualización fallida",
 | 
				
			||||||
		"update_success": "Usuario actualizado con éxito",
 | 
							"update_success": "Usuario actualizado con éxito",
 | 
				
			||||||
		"update_success_title": "Éxito",
 | 
							"update_success_title": "Éxito",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,11 @@
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
	"commands": {
 | 
						"commands": {
 | 
				
			||||||
		"error": "Erreur lors de la soumission de la commande !",
 | 
							"error": "Erreur lors de la soumission de la commande !",
 | 
				
			||||||
 | 
							"error_delete_log": "Erreur lors de la tentative de suppression : {{error}}",
 | 
				
			||||||
		"event_queue": "File d'attente d'événements",
 | 
							"event_queue": "File d'attente d'événements",
 | 
				
			||||||
		"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
 | 
							"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
 | 
				
			||||||
		"title": "Historique des commandes",
 | 
							"title": "Historique des commandes",
 | 
				
			||||||
		"unable_queue": "Impossible de terminer la demande de file d'attente d'événements"
 | 
							"unable_queue": "Impossible de terminer la demande de file d'attente d'événements: {{error}}"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"common": {
 | 
						"common": {
 | 
				
			||||||
		"access_policy": "Politique d'accès",
 | 
							"access_policy": "Politique d'accès",
 | 
				
			||||||
@@ -205,6 +206,9 @@
 | 
				
			|||||||
		"title": "Configurer",
 | 
							"title": "Configurer",
 | 
				
			||||||
		"valid_json": "Vous devez entrer un JSON valide"
 | 
							"valid_json": "Vous devez entrer un JSON valide"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"connect": {
 | 
				
			||||||
 | 
							"error_trying_to_connect": "Erreur lors de la tentative de connexion à l'appareil : {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"delete_command": {
 | 
						"delete_command": {
 | 
				
			||||||
		"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
 | 
							"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
 | 
				
			||||||
		"title": "Supprimer la commande"
 | 
							"title": "Supprimer la commande"
 | 
				
			||||||
@@ -215,6 +219,10 @@
 | 
				
			|||||||
		"explanation": "Cela supprimera tous les {{object}} avant la date que vous choisissez. Attention, cette action n'est pas réversible.",
 | 
							"explanation": "Cela supprimera tous les {{object}} avant la date que vous choisissez. Attention, cette action n'est pas réversible.",
 | 
				
			||||||
		"healthchecks_title": "Supprimer les vérifications d'état"
 | 
							"healthchecks_title": "Supprimer les vérifications d'état"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"device": {
 | 
				
			||||||
 | 
							"error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}",
 | 
				
			||||||
 | 
							"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"device_logs": {
 | 
						"device_logs": {
 | 
				
			||||||
		"log": "Bûche",
 | 
							"log": "Bûche",
 | 
				
			||||||
		"severity": "Gravité",
 | 
							"severity": "Gravité",
 | 
				
			||||||
@@ -486,7 +494,7 @@
 | 
				
			|||||||
		"create_success": "L'utilisateur a été créé avec succès",
 | 
							"create_success": "L'utilisateur a été créé avec succès",
 | 
				
			||||||
		"creating": "Création de l'utilisateur...",
 | 
							"creating": "Création de l'utilisateur...",
 | 
				
			||||||
		"delete_avatar": "Supprimer l'avatar",
 | 
							"delete_avatar": "Supprimer l'avatar",
 | 
				
			||||||
		"delete_failure": "Erreur lors de la tentative de suppression de l'utilisateur",
 | 
							"delete_failure": "Erreur lors de la tentative de suppression de l'utilisateur: {{error}}",
 | 
				
			||||||
		"delete_success": "Utilisateur supprimé avec succès !",
 | 
							"delete_success": "Utilisateur supprimé avec succès !",
 | 
				
			||||||
		"delete_title": "Supprimer l'utilisateur",
 | 
							"delete_title": "Supprimer l'utilisateur",
 | 
				
			||||||
		"delete_warning": "Avertissement : Une fois que vous avez supprimé un utilisateur, vous ne pouvez plus revenir en arrière",
 | 
							"delete_warning": "Avertissement : Une fois que vous avez supprimé un utilisateur, vous ne pouvez plus revenir en arrière",
 | 
				
			||||||
@@ -494,6 +502,7 @@
 | 
				
			|||||||
		"description": "La description",
 | 
							"description": "La description",
 | 
				
			||||||
		"edit": "Modifier l'utilisateur",
 | 
							"edit": "Modifier l'utilisateur",
 | 
				
			||||||
		"email_address": "Adresse électronique",
 | 
							"email_address": "Adresse électronique",
 | 
				
			||||||
 | 
							"error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}",
 | 
				
			||||||
		"force_password_change": "Forcer le changement de mot de passe lors de la connexion",
 | 
							"force_password_change": "Forcer le changement de mot de passe lors de la connexion",
 | 
				
			||||||
		"id": "Identifiant d'utilisateur.",
 | 
							"id": "Identifiant d'utilisateur.",
 | 
				
			||||||
		"last_login": "Dernière connexion",
 | 
							"last_login": "Dernière connexion",
 | 
				
			||||||
@@ -509,7 +518,7 @@
 | 
				
			|||||||
		"provide_password": "Veuillez fournir un mot de passe valide",
 | 
							"provide_password": "Veuillez fournir un mot de passe valide",
 | 
				
			||||||
		"save_avatar": "Enregistrer l'avatar",
 | 
							"save_avatar": "Enregistrer l'avatar",
 | 
				
			||||||
		"show_hide_password": "Afficher/Masquer le mot de passe",
 | 
							"show_hide_password": "Afficher/Masquer le mot de passe",
 | 
				
			||||||
		"update_failure": "Assurez-vous que toutes vos données sont valides. Si vous modifiez le mot de passe, assurez-vous qu'il ne s'agit pas d'un ancien.",
 | 
							"update_failure": "Erreur lors de la tentative de mise à jour : {{error}}",
 | 
				
			||||||
		"update_failure_title": "mise à jour a échoué",
 | 
							"update_failure_title": "mise à jour a échoué",
 | 
				
			||||||
		"update_success": "L'utilisateur a bien été mis à jour",
 | 
							"update_success": "L'utilisateur a bien été mis à jour",
 | 
				
			||||||
		"update_success_title": "Succès",
 | 
							"update_success_title": "Succès",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,11 @@
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
	"commands": {
 | 
						"commands": {
 | 
				
			||||||
		"error": "Erro ao enviar comando!",
 | 
							"error": "Erro ao enviar comando!",
 | 
				
			||||||
 | 
							"error_delete_log": "Erro ao tentar excluir: {{error}}",
 | 
				
			||||||
		"event_queue": "Fila de Eventos",
 | 
							"event_queue": "Fila de Eventos",
 | 
				
			||||||
		"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
 | 
							"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
 | 
				
			||||||
		"title": "Histórico de Comandos",
 | 
							"title": "Histórico de Comandos",
 | 
				
			||||||
		"unable_queue": "Incapaz de completar o pedido de fila de eventos"
 | 
							"unable_queue": "Incapaz de completar o pedido de fila de eventos: {{error}}"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"common": {
 | 
						"common": {
 | 
				
			||||||
		"access_policy": "Política de Acesso",
 | 
							"access_policy": "Política de Acesso",
 | 
				
			||||||
@@ -205,6 +206,9 @@
 | 
				
			|||||||
		"title": "Configurar",
 | 
							"title": "Configurar",
 | 
				
			||||||
		"valid_json": "Você precisa inserir um JSON válido"
 | 
							"valid_json": "Você precisa inserir um JSON válido"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"connect": {
 | 
				
			||||||
 | 
							"error_trying_to_connect": "Erro ao tentar conectar ao dispositivo: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"delete_command": {
 | 
						"delete_command": {
 | 
				
			||||||
		"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
 | 
							"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
 | 
				
			||||||
		"title": "Apagar Comando"
 | 
							"title": "Apagar Comando"
 | 
				
			||||||
@@ -215,6 +219,10 @@
 | 
				
			|||||||
		"explanation": "Isso excluirá todos os {{object}} antes da data que você escolheu. Cuidado, esta ação não é reversível.",
 | 
							"explanation": "Isso excluirá todos os {{object}} antes da data que você escolheu. Cuidado, esta ação não é reversível.",
 | 
				
			||||||
		"healthchecks_title": "Excluir verificações de saúde"
 | 
							"healthchecks_title": "Excluir verificações de saúde"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"device": {
 | 
				
			||||||
 | 
							"error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}",
 | 
				
			||||||
 | 
							"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"device_logs": {
 | 
						"device_logs": {
 | 
				
			||||||
		"log": "Registro",
 | 
							"log": "Registro",
 | 
				
			||||||
		"severity": "Gravidade",
 | 
							"severity": "Gravidade",
 | 
				
			||||||
@@ -486,7 +494,7 @@
 | 
				
			|||||||
		"create_success": "Usuário criado com sucesso",
 | 
							"create_success": "Usuário criado com sucesso",
 | 
				
			||||||
		"creating": "Criando usuário ...",
 | 
							"creating": "Criando usuário ...",
 | 
				
			||||||
		"delete_avatar": "Apagar Avatar",
 | 
							"delete_avatar": "Apagar Avatar",
 | 
				
			||||||
		"delete_failure": "Erro ao tentar excluir usuário",
 | 
							"delete_failure": "Erro ao tentar excluir usuário: {{error}}",
 | 
				
			||||||
		"delete_success": "Usuário excluído com sucesso!",
 | 
							"delete_success": "Usuário excluído com sucesso!",
 | 
				
			||||||
		"delete_title": "Deletar usuário",
 | 
							"delete_title": "Deletar usuário",
 | 
				
			||||||
		"delete_warning": "Aviso: depois de excluir um usuário, você não pode reverter",
 | 
							"delete_warning": "Aviso: depois de excluir um usuário, você não pode reverter",
 | 
				
			||||||
@@ -494,6 +502,7 @@
 | 
				
			|||||||
		"description": "Descrição",
 | 
							"description": "Descrição",
 | 
				
			||||||
		"edit": "Editar usuário",
 | 
							"edit": "Editar usuário",
 | 
				
			||||||
		"email_address": "Endereço de e-mail",
 | 
							"email_address": "Endereço de e-mail",
 | 
				
			||||||
 | 
							"error_fetching_users": "Erro ao buscar usuários: {{error}}",
 | 
				
			||||||
		"force_password_change": "Forçar mudança de senha no login",
 | 
							"force_password_change": "Forçar mudança de senha no login",
 | 
				
			||||||
		"id": "ID do usuário.",
 | 
							"id": "ID do usuário.",
 | 
				
			||||||
		"last_login": "Último login",
 | 
							"last_login": "Último login",
 | 
				
			||||||
@@ -509,7 +518,7 @@
 | 
				
			|||||||
		"provide_password": "Forneça uma senha válida",
 | 
							"provide_password": "Forneça uma senha válida",
 | 
				
			||||||
		"save_avatar": "Salvar Avatar",
 | 
							"save_avatar": "Salvar Avatar",
 | 
				
			||||||
		"show_hide_password": "Mostrar / ocultar senha",
 | 
							"show_hide_password": "Mostrar / ocultar senha",
 | 
				
			||||||
		"update_failure": "Certifique-se de que todos os seus dados são válidos. Se você estiver modificando a senha, certifique-se de que não seja uma senha antiga.",
 | 
							"update_failure": "Erro ao tentar atualizar: {{error}}",
 | 
				
			||||||
		"update_failure_title": "Atualização falhou",
 | 
							"update_failure_title": "Atualização falhou",
 | 
				
			||||||
		"update_success": "Usuário atualizado com sucesso",
 | 
							"update_success": "Usuário atualizado com sucesso",
 | 
				
			||||||
		"update_success_title": "Sucesso",
 | 
							"update_success_title": "Sucesso",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,10 @@ import {
 | 
				
			|||||||
  CFormGroup,
 | 
					  CFormGroup,
 | 
				
			||||||
  CInputRadio,
 | 
					  CInputRadio,
 | 
				
			||||||
  CLabel,
 | 
					  CLabel,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
@@ -88,8 +91,15 @@ const BlinkModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggleModal}>
 | 
					    <CModal show={show} onClose={toggleModal}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('blink.device_leds')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('blink.device_leds')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      {result === 'success' ? (
 | 
					      {result === 'success' ? (
 | 
				
			||||||
        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
					        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,27 +1,24 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import {
 | 
					import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
				
			||||||
  CButton,
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
  CModal,
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
  CModalHeader,
 | 
					 | 
				
			||||||
  CModalBody,
 | 
					 | 
				
			||||||
  CModalTitle,
 | 
					 | 
				
			||||||
  CModalFooter,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
 | 
					const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
 | 
				
			||||||
  <CModal size="lg" show={show} onClose={toggle}>
 | 
					  <CModal size="lg" show={show} onClose={toggle}>
 | 
				
			||||||
    <CModalHeader closeButton>
 | 
					    <CModalHeader className="p-1">
 | 
				
			||||||
      <CModalTitle className="text-dark">{commandUuid}</CModalTitle>
 | 
					      <CModalTitle className="text-dark">{commandUuid}</CModalTitle>
 | 
				
			||||||
 | 
					      <div className="text-right">
 | 
				
			||||||
 | 
					        <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					          <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					            <CIcon content={cilX} />
 | 
				
			||||||
 | 
					          </CButton>
 | 
				
			||||||
 | 
					        </CPopover>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
    </CModalHeader>
 | 
					    </CModalHeader>
 | 
				
			||||||
    <CModalBody>
 | 
					    <CModalBody>
 | 
				
			||||||
      <pre className="ignore">{JSON.stringify(details, null, 4)}</pre>
 | 
					      <pre className="ignore">{JSON.stringify(details, null, 4)}</pre>
 | 
				
			||||||
    </CModalBody>
 | 
					    </CModalBody>
 | 
				
			||||||
    <CModalFooter>
 | 
					 | 
				
			||||||
      <CButton color="secondary" onClick={toggle}>
 | 
					 | 
				
			||||||
        {t('common.close')}
 | 
					 | 
				
			||||||
      </CButton>
 | 
					 | 
				
			||||||
    </CModalFooter>
 | 
					 | 
				
			||||||
  </CModal>
 | 
					  </CModal>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,11 +5,11 @@ import {
 | 
				
			|||||||
  CWidgetDropdown,
 | 
					  CWidgetDropdown,
 | 
				
			||||||
  CRow,
 | 
					  CRow,
 | 
				
			||||||
  CCol,
 | 
					  CCol,
 | 
				
			||||||
  CCollapse,
 | 
					 | 
				
			||||||
  CButton,
 | 
					  CButton,
 | 
				
			||||||
  CDataTable,
 | 
					  CDataTable,
 | 
				
			||||||
  CCard,
 | 
					  CCard,
 | 
				
			||||||
  CPopover,
 | 
					  CPopover,
 | 
				
			||||||
 | 
					  CButtonToolbar,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
@@ -21,7 +21,6 @@ import ConfirmModal from 'components/ConfirmModal';
 | 
				
			|||||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
 | 
					import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
 | 
				
			||||||
import WifiScanResultModalWidget from 'components/WifiScanResultModal';
 | 
					import WifiScanResultModalWidget from 'components/WifiScanResultModal';
 | 
				
			||||||
import DetailsModal from './DetailsModal';
 | 
					import DetailsModal from './DetailsModal';
 | 
				
			||||||
import styles from './index.module.scss';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceCommands = () => {
 | 
					const DeviceCommands = () => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
@@ -38,8 +37,6 @@ const DeviceCommands = () => {
 | 
				
			|||||||
  const [showDetailsModal, setShowDetailsModal] = useState(false);
 | 
					  const [showDetailsModal, setShowDetailsModal] = useState(false);
 | 
				
			||||||
  const [detailsUuid, setDetailsUuid] = useState('');
 | 
					  const [detailsUuid, setDetailsUuid] = useState('');
 | 
				
			||||||
  const [modalDetails, setModalDetails] = useState({});
 | 
					  const [modalDetails, setModalDetails] = useState({});
 | 
				
			||||||
  // Main collapsible
 | 
					 | 
				
			||||||
  const [collapse, setCollapse] = useState(false);
 | 
					 | 
				
			||||||
  // General states
 | 
					  // General states
 | 
				
			||||||
  const [commands, setCommands] = useState([]);
 | 
					  const [commands, setCommands] = useState([]);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
@@ -50,11 +47,6 @@ const DeviceCommands = () => {
 | 
				
			|||||||
  const [loadingMore, setLoadingMore] = useState(false);
 | 
					  const [loadingMore, setLoadingMore] = useState(false);
 | 
				
			||||||
  const [showLoadingMore, setShowLoadingMore] = useState(true);
 | 
					  const [showLoadingMore, setShowLoadingMore] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggle = (e) => {
 | 
					 | 
				
			||||||
    setCollapse(!collapse);
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const toggleScanModal = () => {
 | 
					  const toggleScanModal = () => {
 | 
				
			||||||
    setShowScanModal(!showScanModal);
 | 
					    setShowScanModal(!showScanModal);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -196,17 +188,15 @@ const DeviceCommands = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const columns = [
 | 
					  const columns = [
 | 
				
			||||||
    { key: 'UUID', label: t('common.id'), _style: { width: '28%' } },
 | 
					    { key: 'command', label: t('common.command'), _style: { width: '15%' } },
 | 
				
			||||||
    { key: 'command', label: t('common.command'), _style: { width: '10%' } },
 | 
					    { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '20%' } },
 | 
				
			||||||
    { key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
 | 
					    { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
 | 
				
			||||||
    { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '16%' } },
 | 
					 | 
				
			||||||
    { key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: 'show_buttons',
 | 
					      key: 'show_buttons',
 | 
				
			||||||
      label: '',
 | 
					      label: '',
 | 
				
			||||||
      sorter: false,
 | 
					      sorter: false,
 | 
				
			||||||
      filter: false,
 | 
					      filter: false,
 | 
				
			||||||
      _style: { width: '14%' },
 | 
					      _style: { width: '1%' },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -252,23 +242,13 @@ const DeviceCommands = () => {
 | 
				
			|||||||
  }, [commands]);
 | 
					  }, [commands]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CWidgetDropdown
 | 
					    <div>
 | 
				
			||||||
      inverse="true"
 | 
					      <CWidgetDropdown
 | 
				
			||||||
      color="gradient-primary"
 | 
					        inverse="true"
 | 
				
			||||||
      header={t('commands.title')}
 | 
					        color="gradient-primary"
 | 
				
			||||||
      footerSlot={
 | 
					        header={t('commands.title')}
 | 
				
			||||||
        <div className={styles.footer}>
 | 
					        footerSlot={
 | 
				
			||||||
          <CCollapse show={collapse}>
 | 
					          <div className="pb-1 px-3">
 | 
				
			||||||
            <CRow>
 | 
					 | 
				
			||||||
              <CCol />
 | 
					 | 
				
			||||||
              <CCol className="text-right">
 | 
					 | 
				
			||||||
                <div>
 | 
					 | 
				
			||||||
                  <CButton onClick={refreshCommands} size="sm">
 | 
					 | 
				
			||||||
                    <CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
 | 
					 | 
				
			||||||
                  </CButton>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
            </CRow>
 | 
					 | 
				
			||||||
            <CRow className="mb-2">
 | 
					            <CRow className="mb-2">
 | 
				
			||||||
              <CCol>
 | 
					              <CCol>
 | 
				
			||||||
                From:
 | 
					                From:
 | 
				
			||||||
@@ -280,8 +260,9 @@ const DeviceCommands = () => {
 | 
				
			|||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
            <CCard>
 | 
					            <CCard>
 | 
				
			||||||
              <div className={['overflow-auto', styles.scrollableBox].join(' ')}>
 | 
					              <div className="overflow-auto" style={{ height: '200px' }}>
 | 
				
			||||||
                <CDataTable
 | 
					                <CDataTable
 | 
				
			||||||
 | 
					                  border
 | 
				
			||||||
                  loading={loading}
 | 
					                  loading={loading}
 | 
				
			||||||
                  items={commands ?? []}
 | 
					                  items={commands ?? []}
 | 
				
			||||||
                  fields={columns}
 | 
					                  fields={columns}
 | 
				
			||||||
@@ -302,98 +283,100 @@ const DeviceCommands = () => {
 | 
				
			|||||||
                          : 'Pending'}
 | 
					                          : 'Pending'}
 | 
				
			||||||
                      </td>
 | 
					                      </td>
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    executed: (item) => (
 | 
					 | 
				
			||||||
                      <td>
 | 
					 | 
				
			||||||
                        {item.executed && item.executed !== ''
 | 
					 | 
				
			||||||
                          ? prettyDate(item.executed)
 | 
					 | 
				
			||||||
                          : 'Pending'}
 | 
					 | 
				
			||||||
                      </td>
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    show_buttons: (item, index) => (
 | 
					                    show_buttons: (item, index) => (
 | 
				
			||||||
                      <td>
 | 
					                      <td>
 | 
				
			||||||
                        <CRow>
 | 
					                        <CButtonToolbar
 | 
				
			||||||
                          <CCol>
 | 
					                          role="group"
 | 
				
			||||||
                            <CPopover
 | 
					                          className="justify-content-flex-end"
 | 
				
			||||||
                              content={
 | 
					                          style={{ width: '170px' }}
 | 
				
			||||||
                                item.command === 'trace' ? t('common.download') : t('common.result')
 | 
					                        >
 | 
				
			||||||
                              }
 | 
					                          <CPopover
 | 
				
			||||||
 | 
					                            content={
 | 
				
			||||||
 | 
					                              item.command === 'trace' ? t('common.download') : t('common.result')
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            <CButton
 | 
				
			||||||
 | 
					                              color="primary"
 | 
				
			||||||
 | 
					                              variant="outline"
 | 
				
			||||||
 | 
					                              shape="square"
 | 
				
			||||||
 | 
					                              size="sm"
 | 
				
			||||||
 | 
					                              className="mx-2"
 | 
				
			||||||
 | 
					                              onClick={() => {
 | 
				
			||||||
 | 
					                                toggleDetails(item);
 | 
				
			||||||
 | 
					                              }}
 | 
				
			||||||
                            >
 | 
					                            >
 | 
				
			||||||
                              <CButton
 | 
					                              {item.command === 'trace' ? (
 | 
				
			||||||
                                color="primary"
 | 
					                                <CIcon
 | 
				
			||||||
                                variant="outline"
 | 
					                                  name="cil-cloud-download"
 | 
				
			||||||
                                shape="square"
 | 
					                                  content={cilCloudDownload}
 | 
				
			||||||
                                size="sm"
 | 
					                                  size="lg"
 | 
				
			||||||
                                onClick={() => {
 | 
					                                />
 | 
				
			||||||
                                  toggleDetails(item);
 | 
					                              ) : (
 | 
				
			||||||
                                }}
 | 
					                                <CIcon
 | 
				
			||||||
                              >
 | 
					                                  name="cil-calendar-check"
 | 
				
			||||||
                                {item.command === 'trace' ? (
 | 
					                                  content={cilCalendarCheck}
 | 
				
			||||||
                                  <CIcon content={cilCloudDownload} size="lg" />
 | 
					                                  size="lg"
 | 
				
			||||||
                                ) : (
 | 
					                                />
 | 
				
			||||||
                                  <CIcon content={cilCalendarCheck} size="lg" />
 | 
					                              )}
 | 
				
			||||||
                                )}
 | 
					                            </CButton>
 | 
				
			||||||
                              </CButton>
 | 
					                          </CPopover>
 | 
				
			||||||
                            </CPopover>
 | 
					                          <CPopover content={t('common.details')}>
 | 
				
			||||||
                          </CCol>
 | 
					                            <CButton
 | 
				
			||||||
                          <CCol>
 | 
					                              color="primary"
 | 
				
			||||||
                            <CPopover content={t('common.details')}>
 | 
					                              variant="outline"
 | 
				
			||||||
                              <CButton
 | 
					                              shape="square"
 | 
				
			||||||
                                color="primary"
 | 
					                              size="sm"
 | 
				
			||||||
                                variant="outline"
 | 
					                              className="mx-2"
 | 
				
			||||||
                                shape="square"
 | 
					                              onClick={() => {
 | 
				
			||||||
                                size="sm"
 | 
					                                toggleResponse(item);
 | 
				
			||||||
                                onClick={() => {
 | 
					                              }}
 | 
				
			||||||
                                  toggleResponse(item);
 | 
					                            >
 | 
				
			||||||
                                }}
 | 
					                              <CIcon name="cilList" size="lg" />
 | 
				
			||||||
                              >
 | 
					                            </CButton>
 | 
				
			||||||
                                <CIcon name="cilList" size="lg" />
 | 
					                          </CPopover>
 | 
				
			||||||
                              </CButton>
 | 
					                          <CPopover content={t('common.delete')}>
 | 
				
			||||||
                            </CPopover>
 | 
					                            <CButton
 | 
				
			||||||
                          </CCol>
 | 
					                              color="primary"
 | 
				
			||||||
                          <CCol>
 | 
					                              variant="outline"
 | 
				
			||||||
                            <CPopover content={t('common.delete')}>
 | 
					                              shape="square"
 | 
				
			||||||
                              <CButton
 | 
					                              size="sm"
 | 
				
			||||||
                                color="primary"
 | 
					                              className="mx-2"
 | 
				
			||||||
                                variant="outline"
 | 
					                              onClick={() => {
 | 
				
			||||||
                                shape="square"
 | 
					                                toggleConfirmModal(item.UUID, index);
 | 
				
			||||||
                                size="sm"
 | 
					                              }}
 | 
				
			||||||
                                onClick={() => {
 | 
					                            >
 | 
				
			||||||
                                  toggleConfirmModal(item.UUID, index);
 | 
					                              <CIcon name="cilTrash" size="lg" />
 | 
				
			||||||
                                }}
 | 
					                            </CButton>
 | 
				
			||||||
                              >
 | 
					                          </CPopover>
 | 
				
			||||||
                                <CIcon name="cilTrash" size="lg" />
 | 
					                        </CButtonToolbar>
 | 
				
			||||||
                              </CButton>
 | 
					 | 
				
			||||||
                            </CPopover>
 | 
					 | 
				
			||||||
                          </CCol>
 | 
					 | 
				
			||||||
                        </CRow>
 | 
					 | 
				
			||||||
                      </td>
 | 
					                      </td>
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <CRow className={styles.loadMoreSpacing}>
 | 
					
 | 
				
			||||||
                  {showLoadingMore && (
 | 
					                {showLoadingMore && (
 | 
				
			||||||
 | 
					                  <div className="mb-3">
 | 
				
			||||||
                    <LoadingButton
 | 
					                    <LoadingButton
 | 
				
			||||||
                      label="View More"
 | 
					                      label={t('common.view_more')}
 | 
				
			||||||
                      isLoadingLabel="Loading More..."
 | 
					                      isLoadingLabel={t('common.loading_more_ellipsis')}
 | 
				
			||||||
                      isLoading={loadingMore}
 | 
					                      isLoading={loadingMore}
 | 
				
			||||||
                      action={showMoreCommands}
 | 
					                      action={showMoreCommands}
 | 
				
			||||||
                      variant="outline"
 | 
					                      variant="outline"
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  )}
 | 
					                  </div>
 | 
				
			||||||
                </CRow>
 | 
					                )}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </CCard>
 | 
					            </CCard>
 | 
				
			||||||
          </CCollapse>
 | 
					          </div>
 | 
				
			||||||
          <CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
 | 
					        }
 | 
				
			||||||
            <CIcon
 | 
					      >
 | 
				
			||||||
              name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
 | 
					        <div className="text-right float-right">
 | 
				
			||||||
              className="text-white"
 | 
					          <CButton onClick={refreshCommands} size="sm">
 | 
				
			||||||
              size="lg"
 | 
					            <CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </CButton>
 | 
					          </CButton>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      }
 | 
					      </CWidgetDropdown>
 | 
				
			||||||
    >
 | 
					
 | 
				
			||||||
      <WifiScanResultModalWidget
 | 
					      <WifiScanResultModalWidget
 | 
				
			||||||
        show={showScanModal}
 | 
					        show={showScanModal}
 | 
				
			||||||
        toggle={toggleScanModal}
 | 
					        toggle={toggleScanModal}
 | 
				
			||||||
@@ -408,7 +391,7 @@ const DeviceCommands = () => {
 | 
				
			|||||||
        details={modalDetails}
 | 
					        details={modalDetails}
 | 
				
			||||||
        commandUuid={detailsUuid}
 | 
					        commandUuid={detailsUuid}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </CWidgetDropdown>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
.footer {
 | 
					 | 
				
			||||||
  padding: 20px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.scrollableBox {
 | 
					 | 
				
			||||||
  height: 200px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.customIconHeight {
 | 
					 | 
				
			||||||
  height: 19px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.loadMoreSpacing {
 | 
					 | 
				
			||||||
  margin-bottom: 1%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -12,7 +12,10 @@ import {
 | 
				
			|||||||
  CTextarea,
 | 
					  CTextarea,
 | 
				
			||||||
  CInvalidFeedback,
 | 
					  CInvalidFeedback,
 | 
				
			||||||
  CInputFile,
 | 
					  CInputFile,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
@@ -125,8 +128,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggleModal} size="lg">
 | 
					    <CModal show={show} onClose={toggleModal} size="lg">
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('configure.title')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('configure.title')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      {hadSuccess ? (
 | 
					      {hadSuccess ? (
 | 
				
			||||||
        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
					        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,10 @@ import {
 | 
				
			|||||||
  CModalFooter,
 | 
					  CModalFooter,
 | 
				
			||||||
  CSpinner,
 | 
					  CSpinner,
 | 
				
			||||||
  CBadge,
 | 
					  CBadge,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ConfirmModal = ({ show, toggle, action }) => {
 | 
					const ConfirmModal = ({ show, toggle, action }) => {
 | 
				
			||||||
@@ -63,8 +66,15 @@ const ConfirmModal = ({ show, toggle, action }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal className="text-dark" show={show} onClose={toggle}>
 | 
					    <CModal className="text-dark" show={show} onClose={toggle}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('delete_command.title')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('delete_command.title')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <h6>{t('delete_command.explanation')}</h6>
 | 
					        <h6>{t('delete_command.explanation')}</h6>
 | 
				
			||||||
@@ -73,9 +83,6 @@ const ConfirmModal = ({ show, toggle, action }) => {
 | 
				
			|||||||
        <CButton disabled={loading} color="primary" onClick={() => doAction()}>
 | 
					        <CButton disabled={loading} color="primary" onClick={() => doAction()}>
 | 
				
			||||||
          {getButtonContent()}
 | 
					          {getButtonContent()}
 | 
				
			||||||
        </CButton>
 | 
					        </CButton>
 | 
				
			||||||
        <CButton color="secondary" onClick={toggle}>
 | 
					 | 
				
			||||||
          {t('common.cancel')}
 | 
					 | 
				
			||||||
        </CButton>
 | 
					 | 
				
			||||||
      </CModalFooter>
 | 
					      </CModalFooter>
 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { CModal, CModalHeader, CModalBody } from '@coreui/react';
 | 
					import { CModal, CModalHeader, CModalBody, CModalTitle, CPopover, CButton } from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilSave, cilX } from '@coreui/icons';
 | 
				
			||||||
import { CreateUserForm, useFormFields, useAuth, useToast } from 'ucentral-libs';
 | 
					import { CreateUserForm, useFormFields, useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { testRegex, validateEmail } from 'utils/helper';
 | 
					import { testRegex, validateEmail } from 'utils/helper';
 | 
				
			||||||
@@ -143,14 +145,26 @@ const CreateUserModal = ({ show, toggle, getUsers }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggle} size="xl">
 | 
					    <CModal show={show} onClose={toggle} size="xl">
 | 
				
			||||||
      <CModalHeader>{t('user.create')}</CModalHeader>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
 | 
					        <CModalTitle className="pl-1 pt-1">{t('user.create')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('user.create')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" onClick={createUser} disabled={loading}>
 | 
				
			||||||
 | 
					              <CIcon content={cilSave} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <CreateUserForm
 | 
					        <CreateUserForm
 | 
				
			||||||
          t={t}
 | 
					          t={t}
 | 
				
			||||||
          fields={formFields}
 | 
					          fields={formFields}
 | 
				
			||||||
          updateField={updateFieldWithId}
 | 
					          updateField={updateFieldWithId}
 | 
				
			||||||
          createUser={createUser}
 | 
					 | 
				
			||||||
          loading={loading}
 | 
					 | 
				
			||||||
          policies={policies}
 | 
					          policies={policies}
 | 
				
			||||||
          toggleChange={toggleChange}
 | 
					          toggleChange={toggleChange}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,20 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { CModal, CModalHeader, CModalTitle, CModalBody, CCol, CRow } from '@coreui/react';
 | 
					import {
 | 
				
			||||||
 | 
					  CModal,
 | 
				
			||||||
 | 
					  CModalHeader,
 | 
				
			||||||
 | 
					  CModalTitle,
 | 
				
			||||||
 | 
					  CModalBody,
 | 
				
			||||||
 | 
					  CCol,
 | 
				
			||||||
 | 
					  CRow,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
 | 
					  CButton,
 | 
				
			||||||
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { ConfirmFooter, useAuth, useDevice } from 'ucentral-libs';
 | 
					import { ConfirmFooter, useAuth, useDevice, useToast } from 'ucentral-libs';
 | 
				
			||||||
import { dateToUnix } from 'utils/helper';
 | 
					import { dateToUnix } from 'utils/helper';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import eventBus from 'utils/eventBus';
 | 
					import eventBus from 'utils/eventBus';
 | 
				
			||||||
@@ -11,6 +22,7 @@ import eventBus from 'utils/eventBus';
 | 
				
			|||||||
const DeleteLogModal = ({ show, toggle, object }) => {
 | 
					const DeleteLogModal = ({ show, toggle, object }) => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [maxDate, setMaxDate] = useState(new Date().toString());
 | 
					  const [maxDate, setMaxDate] = useState(new Date().toString());
 | 
				
			||||||
@@ -36,7 +48,14 @@ const DeleteLogModal = ({ show, toggle, object }) => {
 | 
				
			|||||||
    return axiosInstance
 | 
					    return axiosInstance
 | 
				
			||||||
      .delete(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
 | 
					      .delete(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
 | 
				
			||||||
      .then(() => {})
 | 
					      .then(() => {})
 | 
				
			||||||
      .catch(() => {})
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('commands.error_delete_log', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
        if (object === 'healthchecks')
 | 
					        if (object === 'healthchecks')
 | 
				
			||||||
          eventBus.dispatch('deletedHealth', { message: 'Healthcheck was deleted' });
 | 
					          eventBus.dispatch('deletedHealth', { message: 'Healthcheck was deleted' });
 | 
				
			||||||
@@ -54,12 +73,19 @@ const DeleteLogModal = ({ show, toggle, object }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal className="text-dark" show={show} onClose={toggle}>
 | 
					    <CModal className="text-dark" show={show} onClose={toggle}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">
 | 
				
			||||||
          {object === 'healthchecks'
 | 
					          {object === 'healthchecks'
 | 
				
			||||||
            ? t('delete_logs.healthchecks_title')
 | 
					            ? t('delete_logs.healthchecks_title')
 | 
				
			||||||
            : t('delete_logs.device_logs_title')}
 | 
					            : t('delete_logs.device_logs_title')}
 | 
				
			||||||
        </CModalTitle>
 | 
					        </CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <h6>{t('delete_logs.explanation', { object })}</h6>
 | 
					        <h6>{t('delete_logs.explanation', { object })}</h6>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,7 +70,14 @@ const DeviceActions = () => {
 | 
				
			|||||||
        const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
 | 
					        const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
 | 
				
			||||||
        if (newWindow) newWindow.opener = null;
 | 
					        if (newWindow) newWindow.opener = null;
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {})
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
      .finally(() => {
 | 
					      .finally(() => {
 | 
				
			||||||
        setConnectLoading(false);
 | 
					        setConnectLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -131,7 +138,7 @@ const DeviceActions = () => {
 | 
				
			|||||||
            </CButton>
 | 
					            </CButton>
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
        <CRow className="mt-3">
 | 
					        <CRow className="mt-4">
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <CButton block color="primary" onClick={toggleUpgradeModal}>
 | 
					            <CButton block color="primary" onClick={toggleUpgradeModal}>
 | 
				
			||||||
              {t('actions.firmware_upgrade')}
 | 
					              {t('actions.firmware_upgrade')}
 | 
				
			||||||
@@ -143,7 +150,7 @@ const DeviceActions = () => {
 | 
				
			|||||||
            </CButton>
 | 
					            </CButton>
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
        <CRow className="mt-3">
 | 
					        <CRow className="mt-4">
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <CButton block color="primary" onClick={toggleScanModal}>
 | 
					            <CButton block color="primary" onClick={toggleScanModal}>
 | 
				
			||||||
              {t('actions.wifi_scan')}
 | 
					              {t('actions.wifi_scan')}
 | 
				
			||||||
@@ -155,7 +162,7 @@ const DeviceActions = () => {
 | 
				
			|||||||
            </CButton>
 | 
					            </CButton>
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
        <CRow className="mt-3">
 | 
					        <CRow className="mt-4">
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <LoadingButton
 | 
					            <LoadingButton
 | 
				
			||||||
              isLoading={connectLoading}
 | 
					              isLoading={connectLoading}
 | 
				
			||||||
@@ -170,7 +177,7 @@ const DeviceActions = () => {
 | 
				
			|||||||
            </CButton>
 | 
					            </CButton>
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
        <CRow className="mt-3">
 | 
					        <CRow className="mt-4">
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <CButton block color="primary" onClick={toggleQueueModal}>
 | 
					            <CButton block color="primary" onClick={toggleQueueModal}>
 | 
				
			||||||
              {t('commands.event_queue')}
 | 
					              {t('commands.event_queue')}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ import {
 | 
				
			|||||||
  NotesTable,
 | 
					  NotesTable,
 | 
				
			||||||
  useAuth,
 | 
					  useAuth,
 | 
				
			||||||
  useDevice,
 | 
					  useDevice,
 | 
				
			||||||
 | 
					  useToast,
 | 
				
			||||||
} from 'ucentral-libs';
 | 
					} from 'ucentral-libs';
 | 
				
			||||||
import DeviceConfigurationModal from './DeviceConfigurationModal';
 | 
					import DeviceConfigurationModal from './DeviceConfigurationModal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,6 +30,7 @@ const DeviceConfiguration = () => {
 | 
				
			|||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [showPassword, setShowPassword] = useState(false);
 | 
					  const [showPassword, setShowPassword] = useState(false);
 | 
				
			||||||
  const [collapse, setCollapse] = useState(false);
 | 
					  const [collapse, setCollapse] = useState(false);
 | 
				
			||||||
@@ -61,7 +63,14 @@ const DeviceConfiguration = () => {
 | 
				
			|||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
        setDevice(response.data);
 | 
					        setDevice(response.data);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {});
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const saveNote = (currentNote) => {
 | 
					  const saveNote = (currentNote) => {
 | 
				
			||||||
@@ -153,46 +162,46 @@ const DeviceConfiguration = () => {
 | 
				
			|||||||
                {device.firmware}
 | 
					                {device.firmware}
 | 
				
			||||||
              </CCol>
 | 
					              </CCol>
 | 
				
			||||||
            </CRow>
 | 
					            </CRow>
 | 
				
			||||||
            <CRow className="mt-2">
 | 
					 | 
				
			||||||
              <CCol md="3">
 | 
					 | 
				
			||||||
                <CLabel>{t('configuration.last_configuration_change')} : </CLabel>
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
              <CCol xs="12" md="9">
 | 
					 | 
				
			||||||
                {prettyDate(device.lastConfigurationChange)}
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
            </CRow>
 | 
					 | 
				
			||||||
            <CRow className="mt-2">
 | 
					 | 
				
			||||||
              <CCol md="3">
 | 
					 | 
				
			||||||
                <CLabel>{t('common.mac')} :</CLabel>
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
              <CCol xs="12" md="9">
 | 
					 | 
				
			||||||
                {device.macAddress}
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
            </CRow>
 | 
					 | 
				
			||||||
            <CRow className="mt-2 mb-4">
 | 
					 | 
				
			||||||
              <CCol md="3">
 | 
					 | 
				
			||||||
                <CLabel className="align-middle">{t('configuration.device_password')} : </CLabel>
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
              <CCol xs="12" md="2">
 | 
					 | 
				
			||||||
                {getPassword()}
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
              <CCol md="7">
 | 
					 | 
				
			||||||
                <HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
 | 
					 | 
				
			||||||
                <CopyToClipboardButton
 | 
					 | 
				
			||||||
                  t={t}
 | 
					 | 
				
			||||||
                  size="sm"
 | 
					 | 
				
			||||||
                  content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
            </CRow>
 | 
					 | 
				
			||||||
            <NotesTable
 | 
					 | 
				
			||||||
              t={t}
 | 
					 | 
				
			||||||
              notes={device.notes}
 | 
					 | 
				
			||||||
              loading={loading}
 | 
					 | 
				
			||||||
              addNote={saveNote}
 | 
					 | 
				
			||||||
              descriptionColumn={false}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <CCollapse show={collapse}>
 | 
					            <CCollapse show={collapse}>
 | 
				
			||||||
 | 
					              <CRow className="mt-2">
 | 
				
			||||||
 | 
					                <CCol md="3">
 | 
				
			||||||
 | 
					                  <CLabel>{t('configuration.last_configuration_change')} : </CLabel>
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					                <CCol xs="12" md="9">
 | 
				
			||||||
 | 
					                  {prettyDate(device.lastConfigurationChange)}
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					              </CRow>
 | 
				
			||||||
 | 
					              <CRow className="mt-2">
 | 
				
			||||||
 | 
					                <CCol md="3">
 | 
				
			||||||
 | 
					                  <CLabel>{t('common.mac')} :</CLabel>
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					                <CCol xs="12" md="9">
 | 
				
			||||||
 | 
					                  {device.macAddress}
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					              </CRow>
 | 
				
			||||||
 | 
					              <CRow className="mt-2 mb-4">
 | 
				
			||||||
 | 
					                <CCol md="3">
 | 
				
			||||||
 | 
					                  <CLabel className="align-middle">{t('configuration.device_password')} : </CLabel>
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					                <CCol xs="12" md="2">
 | 
				
			||||||
 | 
					                  {getPassword()}
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					                <CCol md="7">
 | 
				
			||||||
 | 
					                  <HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
 | 
				
			||||||
 | 
					                  <CopyToClipboardButton
 | 
				
			||||||
 | 
					                    t={t}
 | 
				
			||||||
 | 
					                    size="sm"
 | 
				
			||||||
 | 
					                    content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </CCol>
 | 
				
			||||||
 | 
					              </CRow>
 | 
				
			||||||
 | 
					              <NotesTable
 | 
				
			||||||
 | 
					                t={t}
 | 
				
			||||||
 | 
					                notes={device.notes}
 | 
				
			||||||
 | 
					                loading={loading}
 | 
				
			||||||
 | 
					                addNote={saveNote}
 | 
				
			||||||
 | 
					                descriptionColumn={false}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
              <CRow className="mt-2">
 | 
					              <CRow className="mt-2">
 | 
				
			||||||
                <CCol md="3">
 | 
					                <CCol md="3">
 | 
				
			||||||
                  <CLabel>{t('configuration.last_configuration_download')} : </CLabel>
 | 
					                  <CLabel>{t('configuration.last_configuration_download')} : </CLabel>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ import {
 | 
				
			|||||||
  CPopover,
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilTrash } from '@coreui/icons';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
import { prettyDate, dateToUnix } from 'utils/helper';
 | 
					import { prettyDate, dateToUnix } from 'utils/helper';
 | 
				
			||||||
@@ -25,7 +26,6 @@ const DeviceHealth = () => {
 | 
				
			|||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
  const [collapse, setCollapse] = useState(false);
 | 
					 | 
				
			||||||
  const [details, setDetails] = useState([]);
 | 
					  const [details, setDetails] = useState([]);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [healthChecks, setHealthChecks] = useState([]);
 | 
					  const [healthChecks, setHealthChecks] = useState([]);
 | 
				
			||||||
@@ -42,11 +42,6 @@ const DeviceHealth = () => {
 | 
				
			|||||||
    setShowDeleteModal(!showDeleteModal);
 | 
					    setShowDeleteModal(!showDeleteModal);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggle = (e) => {
 | 
					 | 
				
			||||||
    setCollapse(!collapse);
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const modifyStart = (value) => {
 | 
					  const modifyStart = (value) => {
 | 
				
			||||||
    setStart(value);
 | 
					    setStart(value);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -195,96 +190,71 @@ const DeviceHealth = () => {
 | 
				
			|||||||
      color={barColor}
 | 
					      color={barColor}
 | 
				
			||||||
      inverse="true"
 | 
					      inverse="true"
 | 
				
			||||||
      footerSlot={
 | 
					      footerSlot={
 | 
				
			||||||
        <div className="p-4">
 | 
					        <div className="pb-1 px-3">
 | 
				
			||||||
          <CProgress className="mb-3" color="white" value={sanityLevel ?? 0} />
 | 
					          <CProgress className="mb-3" color="white" value={sanityLevel ?? 0} />
 | 
				
			||||||
          <CCollapse show={collapse}>
 | 
					          <CRow className="mb-3">
 | 
				
			||||||
            <div className="text-right">
 | 
					            <CCol>
 | 
				
			||||||
              <CPopover content={t('common.delete')}>
 | 
					              {t('common.from')}
 | 
				
			||||||
                <CButton
 | 
					              :
 | 
				
			||||||
                  color="light"
 | 
					              <DatePicker includeTime onChange={(date) => modifyStart(date)} />
 | 
				
			||||||
                  shape="square"
 | 
					            </CCol>
 | 
				
			||||||
                  size="sm"
 | 
					            <CCol>
 | 
				
			||||||
                  onClick={() => {
 | 
					              {t('common.to')}
 | 
				
			||||||
                    toggleDeleteModal();
 | 
					              :
 | 
				
			||||||
                  }}
 | 
					              <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
 | 
				
			||||||
                >
 | 
					            </CCol>
 | 
				
			||||||
                  <CIcon name="cilTrash" size="lg" />
 | 
					          </CRow>
 | 
				
			||||||
                </CButton>
 | 
					          <CCard className="p-0">
 | 
				
			||||||
              </CPopover>
 | 
					            <div className="overflow-auto" style={{ height: '200px' }}>
 | 
				
			||||||
 | 
					              <CDataTable
 | 
				
			||||||
 | 
					                border
 | 
				
			||||||
 | 
					                items={healthChecks ?? []}
 | 
				
			||||||
 | 
					                fields={columns}
 | 
				
			||||||
 | 
					                className="text-white"
 | 
				
			||||||
 | 
					                loading={loading}
 | 
				
			||||||
 | 
					                sorterValue={{ column: 'recorded', desc: 'true' }}
 | 
				
			||||||
 | 
					                scopedSlots={{
 | 
				
			||||||
 | 
					                  UUID: (item) => <td className="align-middle">{item.UUID}</td>,
 | 
				
			||||||
 | 
					                  recorded: (item) => <td className="align-middle">{prettyDate(item.recorded)}</td>,
 | 
				
			||||||
 | 
					                  sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
 | 
				
			||||||
 | 
					                  show_details: (item, index) => (
 | 
				
			||||||
 | 
					                    <td className="align-middle">
 | 
				
			||||||
 | 
					                      <CButton
 | 
				
			||||||
 | 
					                        color="primary"
 | 
				
			||||||
 | 
					                        variant={details.includes(index) ? '' : 'outline'}
 | 
				
			||||||
 | 
					                        shape="square"
 | 
				
			||||||
 | 
					                        size="sm"
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          toggleDetails(index);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <CIcon name="cilList" size="lg" />
 | 
				
			||||||
 | 
					                      </CButton>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  details: (item, index) => (
 | 
				
			||||||
 | 
					                    <CCollapse show={details.includes(index)}>
 | 
				
			||||||
 | 
					                      <CCardBody>
 | 
				
			||||||
 | 
					                        <h5>{t('common.details')}</h5>
 | 
				
			||||||
 | 
					                        <div>{getDetails(index, item.values)}</div>
 | 
				
			||||||
 | 
					                      </CCardBody>
 | 
				
			||||||
 | 
					                    </CCollapse>
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              {showLoadingMore && (
 | 
				
			||||||
 | 
					                <div className="mb-3">
 | 
				
			||||||
 | 
					                  <LoadingButton
 | 
				
			||||||
 | 
					                    label={t('common.view_more')}
 | 
				
			||||||
 | 
					                    isLoadingLabel={t('common.loading_more_ellipsis')}
 | 
				
			||||||
 | 
					                    isLoading={loadingMore}
 | 
				
			||||||
 | 
					                    action={showMoreLogs}
 | 
				
			||||||
 | 
					                    variant="outline"
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <CRow className="mb-3">
 | 
					          </CCard>
 | 
				
			||||||
              <CCol>
 | 
					 | 
				
			||||||
                {t('common.from')}
 | 
					 | 
				
			||||||
                :
 | 
					 | 
				
			||||||
                <DatePicker includeTime onChange={(date) => modifyStart(date)} />
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
              <CCol>
 | 
					 | 
				
			||||||
                {t('common.to')}
 | 
					 | 
				
			||||||
                :
 | 
					 | 
				
			||||||
                <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
 | 
					 | 
				
			||||||
              </CCol>
 | 
					 | 
				
			||||||
            </CRow>
 | 
					 | 
				
			||||||
            <CCard className="p-0">
 | 
					 | 
				
			||||||
              <div className="overflow-auto" style={{ height: '250px' }}>
 | 
					 | 
				
			||||||
                <CDataTable
 | 
					 | 
				
			||||||
                  border
 | 
					 | 
				
			||||||
                  items={healthChecks ?? []}
 | 
					 | 
				
			||||||
                  fields={columns}
 | 
					 | 
				
			||||||
                  className="text-white"
 | 
					 | 
				
			||||||
                  loading={loading}
 | 
					 | 
				
			||||||
                  sorterValue={{ column: 'recorded', desc: 'true' }}
 | 
					 | 
				
			||||||
                  scopedSlots={{
 | 
					 | 
				
			||||||
                    UUID: (item) => <td className="align-middle">{item.UUID}</td>,
 | 
					 | 
				
			||||||
                    recorded: (item) => (
 | 
					 | 
				
			||||||
                      <td className="align-middle">{prettyDate(item.recorded)}</td>
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
 | 
					 | 
				
			||||||
                    show_details: (item, index) => (
 | 
					 | 
				
			||||||
                      <td className="align-middle">
 | 
					 | 
				
			||||||
                        <CButton
 | 
					 | 
				
			||||||
                          color="primary"
 | 
					 | 
				
			||||||
                          variant={details.includes(index) ? '' : 'outline'}
 | 
					 | 
				
			||||||
                          shape="square"
 | 
					 | 
				
			||||||
                          size="sm"
 | 
					 | 
				
			||||||
                          onClick={() => {
 | 
					 | 
				
			||||||
                            toggleDetails(index);
 | 
					 | 
				
			||||||
                          }}
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <CIcon name="cilList" size="lg" />
 | 
					 | 
				
			||||||
                        </CButton>
 | 
					 | 
				
			||||||
                      </td>
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    details: (item, index) => (
 | 
					 | 
				
			||||||
                      <CCollapse show={details.includes(index)}>
 | 
					 | 
				
			||||||
                        <CCardBody>
 | 
					 | 
				
			||||||
                          <h5>{t('common.details')}</h5>
 | 
					 | 
				
			||||||
                          <div>{getDetails(index, item.values)}</div>
 | 
					 | 
				
			||||||
                        </CCardBody>
 | 
					 | 
				
			||||||
                      </CCollapse>
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  }}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                {showLoadingMore && (
 | 
					 | 
				
			||||||
                  <div className="mb-3">
 | 
					 | 
				
			||||||
                    <LoadingButton
 | 
					 | 
				
			||||||
                      label={t('common.view_more')}
 | 
					 | 
				
			||||||
                      isLoadingLabel={t('common.loading_more_ellipsis')}
 | 
					 | 
				
			||||||
                      isLoading={loadingMore}
 | 
					 | 
				
			||||||
                      action={showMoreLogs}
 | 
					 | 
				
			||||||
                      variant="outline"
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </CCard>
 | 
					 | 
				
			||||||
          </CCollapse>
 | 
					 | 
				
			||||||
          <CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
 | 
					 | 
				
			||||||
            <CIcon
 | 
					 | 
				
			||||||
              name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
 | 
					 | 
				
			||||||
              className="text-white"
 | 
					 | 
				
			||||||
              size="lg"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </CButton>
 | 
					 | 
				
			||||||
          <DeleteLogModal
 | 
					          <DeleteLogModal
 | 
				
			||||||
            serialNumber={deviceSerialNumber}
 | 
					            serialNumber={deviceSerialNumber}
 | 
				
			||||||
            object="healthchecks"
 | 
					            object="healthchecks"
 | 
				
			||||||
@@ -293,7 +263,15 @@ const DeviceHealth = () => {
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    />
 | 
					    >
 | 
				
			||||||
 | 
					      <div className="text-right float-right">
 | 
				
			||||||
 | 
					        <CPopover content={t('common.delete')}>
 | 
				
			||||||
 | 
					          <CButton onClick={toggleDeleteModal} size="sm">
 | 
				
			||||||
 | 
					            <CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
 | 
				
			||||||
 | 
					          </CButton>
 | 
				
			||||||
 | 
					        </CPopover>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </CWidgetDropdown>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,7 +99,13 @@ const DeviceList = () => {
 | 
				
			|||||||
        setDevices(fullDevices);
 | 
					        setDevices(fullDevices);
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -130,7 +136,13 @@ const DeviceList = () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        getDeviceInformation(selectedPage);
 | 
					        getDeviceInformation(selectedPage);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -179,7 +191,13 @@ const DeviceList = () => {
 | 
				
			|||||||
        setDevices(newList);
 | 
					        setDevices(newList);
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -282,10 +300,10 @@ const DeviceList = () => {
 | 
				
			|||||||
        const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
 | 
					        const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
 | 
				
			||||||
        if (newWindow) newWindow.opener = null;
 | 
					        if (newWindow) newWindow.opener = null;
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        addToast({
 | 
				
			||||||
          title: t('common.error'),
 | 
					          title: t('common.error'),
 | 
				
			||||||
          body: t('common.unable_to_connect'),
 | 
					          body: t('connect.error_trying_to_connect', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
          color: 'danger',
 | 
					          color: 'danger',
 | 
				
			||||||
          autohide: true,
 | 
					          autohide: true,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import {
 | 
				
			|||||||
  CPopover,
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilTrash } from '@coreui/icons';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
import { prettyDate, dateToUnix } from 'utils/helper';
 | 
					import { prettyDate, dateToUnix } from 'utils/helper';
 | 
				
			||||||
@@ -24,7 +25,6 @@ const DeviceLogs = () => {
 | 
				
			|||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
  const [collapse, setCollapse] = useState(false);
 | 
					 | 
				
			||||||
  const [details, setDetails] = useState([]);
 | 
					  const [details, setDetails] = useState([]);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [logs, setLogs] = useState([]);
 | 
					  const [logs, setLogs] = useState([]);
 | 
				
			||||||
@@ -39,11 +39,6 @@ const DeviceLogs = () => {
 | 
				
			|||||||
    setShowDeleteModal(!showDeleteModal);
 | 
					    setShowDeleteModal(!showDeleteModal);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggle = (e) => {
 | 
					 | 
				
			||||||
    setCollapse(!collapse);
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const modifyStart = (value) => {
 | 
					  const modifyStart = (value) => {
 | 
				
			||||||
    setStart(value);
 | 
					    setStart(value);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -176,91 +171,76 @@ const DeviceLogs = () => {
 | 
				
			|||||||
        color="gradient-info"
 | 
					        color="gradient-info"
 | 
				
			||||||
        header={t('device_logs.title')}
 | 
					        header={t('device_logs.title')}
 | 
				
			||||||
        footerSlot={
 | 
					        footerSlot={
 | 
				
			||||||
          <div className="p-4">
 | 
					          <div className="pb-1 px-3">
 | 
				
			||||||
            <CCollapse show={collapse}>
 | 
					            <CRow className="mb-3">
 | 
				
			||||||
              <div className="text-right">
 | 
					              <CCol>
 | 
				
			||||||
                <CPopover content={t('common.delete')}>
 | 
					                {t('common.from')}
 | 
				
			||||||
                  <CButton
 | 
					                <DatePicker includeTime onChange={(date) => modifyStart(date)} />
 | 
				
			||||||
                    color="light"
 | 
					              </CCol>
 | 
				
			||||||
                    shape="square"
 | 
					              <CCol>
 | 
				
			||||||
                    size="sm"
 | 
					                {t('common.to')}
 | 
				
			||||||
                    onClick={() => {
 | 
					                <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
 | 
				
			||||||
                      toggleDeleteModal();
 | 
					              </CCol>
 | 
				
			||||||
                    }}
 | 
					            </CRow>
 | 
				
			||||||
                  >
 | 
					            <CCard>
 | 
				
			||||||
                    <CIcon name="cilTrash" size="lg" />
 | 
					              <div className="overflow-auto" style={{ height: '250px' }}>
 | 
				
			||||||
                  </CButton>
 | 
					                <CDataTable
 | 
				
			||||||
                </CPopover>
 | 
					                  items={logs ?? []}
 | 
				
			||||||
 | 
					                  fields={columns}
 | 
				
			||||||
 | 
					                  loading={loading}
 | 
				
			||||||
 | 
					                  className="text-white"
 | 
				
			||||||
 | 
					                  sorterValue={{ column: 'recorded', desc: 'true' }}
 | 
				
			||||||
 | 
					                  scopedSlots={{
 | 
				
			||||||
 | 
					                    recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
 | 
				
			||||||
 | 
					                    show_details: (item, index) => (
 | 
				
			||||||
 | 
					                      <td className="py-2">
 | 
				
			||||||
 | 
					                        <CButton
 | 
				
			||||||
 | 
					                          color="primary"
 | 
				
			||||||
 | 
					                          variant={details.includes(index) ? '' : 'outline'}
 | 
				
			||||||
 | 
					                          shape="square"
 | 
				
			||||||
 | 
					                          size="sm"
 | 
				
			||||||
 | 
					                          onClick={() => {
 | 
				
			||||||
 | 
					                            toggleDetails(index);
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          <CIcon name="cilList" size="lg" />
 | 
				
			||||||
 | 
					                        </CButton>
 | 
				
			||||||
 | 
					                      </td>
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    details: (item, index) => (
 | 
				
			||||||
 | 
					                      <CCollapse show={details.includes(index)}>
 | 
				
			||||||
 | 
					                        <CCardBody>
 | 
				
			||||||
 | 
					                          <h5>{t('common.details')}</h5>
 | 
				
			||||||
 | 
					                          <div>{getDetails(index, item)}</div>
 | 
				
			||||||
 | 
					                        </CCardBody>
 | 
				
			||||||
 | 
					                      </CCollapse>
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {showLoadingMore && (
 | 
				
			||||||
 | 
					                  <div className="mb-3">
 | 
				
			||||||
 | 
					                    <LoadingButton
 | 
				
			||||||
 | 
					                      label={t('common.view_more')}
 | 
				
			||||||
 | 
					                      isLoadingLabel={t('common.loading_more_ellipsis')}
 | 
				
			||||||
 | 
					                      isLoading={loadingMore}
 | 
				
			||||||
 | 
					                      action={showMoreLogs}
 | 
				
			||||||
 | 
					                      variant="outline"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <CRow className="mb-3">
 | 
					            </CCard>
 | 
				
			||||||
                <CCol>
 | 
					 | 
				
			||||||
                  {t('common.from')}
 | 
					 | 
				
			||||||
                  <DatePicker includeTime onChange={(date) => modifyStart(date)} />
 | 
					 | 
				
			||||||
                </CCol>
 | 
					 | 
				
			||||||
                <CCol>
 | 
					 | 
				
			||||||
                  {t('common.to')}
 | 
					 | 
				
			||||||
                  <DatePicker includeTime onChange={(date) => modifyEnd(date)} />
 | 
					 | 
				
			||||||
                </CCol>
 | 
					 | 
				
			||||||
              </CRow>
 | 
					 | 
				
			||||||
              <CCard>
 | 
					 | 
				
			||||||
                <div className="overflow-auto" style={{ height: '250px' }}>
 | 
					 | 
				
			||||||
                  <CDataTable
 | 
					 | 
				
			||||||
                    items={logs ?? []}
 | 
					 | 
				
			||||||
                    fields={columns}
 | 
					 | 
				
			||||||
                    loading={loading}
 | 
					 | 
				
			||||||
                    className="text-white"
 | 
					 | 
				
			||||||
                    sorterValue={{ column: 'recorded', desc: 'true' }}
 | 
					 | 
				
			||||||
                    scopedSlots={{
 | 
					 | 
				
			||||||
                      recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
 | 
					 | 
				
			||||||
                      show_details: (item, index) => (
 | 
					 | 
				
			||||||
                        <td className="py-2">
 | 
					 | 
				
			||||||
                          <CButton
 | 
					 | 
				
			||||||
                            color="primary"
 | 
					 | 
				
			||||||
                            variant={details.includes(index) ? '' : 'outline'}
 | 
					 | 
				
			||||||
                            shape="square"
 | 
					 | 
				
			||||||
                            size="sm"
 | 
					 | 
				
			||||||
                            onClick={() => {
 | 
					 | 
				
			||||||
                              toggleDetails(index);
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                            <CIcon name="cilList" size="lg" />
 | 
					 | 
				
			||||||
                          </CButton>
 | 
					 | 
				
			||||||
                        </td>
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      details: (item, index) => (
 | 
					 | 
				
			||||||
                        <CCollapse show={details.includes(index)}>
 | 
					 | 
				
			||||||
                          <CCardBody>
 | 
					 | 
				
			||||||
                            <h5>{t('common.details')}</h5>
 | 
					 | 
				
			||||||
                            <div>{getDetails(index, item)}</div>
 | 
					 | 
				
			||||||
                          </CCardBody>
 | 
					 | 
				
			||||||
                        </CCollapse>
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                  />
 | 
					 | 
				
			||||||
                  {showLoadingMore && (
 | 
					 | 
				
			||||||
                    <div className="mb-3">
 | 
					 | 
				
			||||||
                      <LoadingButton
 | 
					 | 
				
			||||||
                        label={t('common.view_more')}
 | 
					 | 
				
			||||||
                        isLoadingLabel={t('common.loading_more_ellipsis')}
 | 
					 | 
				
			||||||
                        isLoading={loadingMore}
 | 
					 | 
				
			||||||
                        action={showMoreLogs}
 | 
					 | 
				
			||||||
                        variant="outline"
 | 
					 | 
				
			||||||
                      />
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </CCard>
 | 
					 | 
				
			||||||
            </CCollapse>
 | 
					 | 
				
			||||||
            <CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
 | 
					 | 
				
			||||||
              <CIcon
 | 
					 | 
				
			||||||
                name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
 | 
					 | 
				
			||||||
                className="text-white"
 | 
					 | 
				
			||||||
                size="lg"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      />
 | 
					      >
 | 
				
			||||||
 | 
					        <div className="text-right float-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.delete')}>
 | 
				
			||||||
 | 
					            <CButton onClick={toggleDeleteModal} size="sm">
 | 
				
			||||||
 | 
					              <CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </CWidgetDropdown>
 | 
				
			||||||
      <DeleteLogModal
 | 
					      <DeleteLogModal
 | 
				
			||||||
        serialNumber={deviceSerialNumber}
 | 
					        serialNumber={deviceSerialNumber}
 | 
				
			||||||
        object="logs"
 | 
					        object="logs"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,42 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { DeviceStatusCard as Card, useDevice, useAuth } from 'ucentral-libs';
 | 
					import { DeviceStatusCard as Card, useDevice, useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DeviceStatusCard = () => {
 | 
					const DeviceStatusCard = () => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					  const { currentToken, endpoints } = useAuth();
 | 
				
			||||||
  const { deviceSerialNumber } = useDevice();
 | 
					  const { deviceSerialNumber } = useDevice();
 | 
				
			||||||
 | 
					  const { addToast } = useToast();
 | 
				
			||||||
  const [lastStats, setLastStats] = useState(null);
 | 
					  const [lastStats, setLastStats] = useState(null);
 | 
				
			||||||
  const [status, setStatus] = useState(null);
 | 
					  const [status, setStatus] = useState(null);
 | 
				
			||||||
 | 
					  const [deviceConfig, setDeviceConfig] = useState(null);
 | 
				
			||||||
  const [error, setError] = useState(false);
 | 
					  const [error, setError] = useState(false);
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getDevice = () => {
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        Accept: 'application/json',
 | 
				
			||||||
 | 
					        Authorization: `Bearer ${currentToken}`,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    axiosInstance
 | 
				
			||||||
 | 
					      .get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`, options)
 | 
				
			||||||
 | 
					      .then((response) => {
 | 
				
			||||||
 | 
					        setDeviceConfig(response.data);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getData = () => {
 | 
					  const getData = () => {
 | 
				
			||||||
    setLoading(true);
 | 
					    setLoading(true);
 | 
				
			||||||
    const options = {
 | 
					    const options = {
 | 
				
			||||||
@@ -45,9 +70,17 @@ const DeviceStatusCard = () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const refresh = () => {
 | 
				
			||||||
 | 
					    getData();
 | 
				
			||||||
 | 
					    getDevice();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setError(false);
 | 
					    setError(false);
 | 
				
			||||||
    if (deviceSerialNumber) getData();
 | 
					    if (deviceSerialNumber) {
 | 
				
			||||||
 | 
					      getDevice();
 | 
				
			||||||
 | 
					      getData();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }, [deviceSerialNumber]);
 | 
					  }, [deviceSerialNumber]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -56,7 +89,8 @@ const DeviceStatusCard = () => {
 | 
				
			|||||||
      loading={loading}
 | 
					      loading={loading}
 | 
				
			||||||
      error={error}
 | 
					      error={error}
 | 
				
			||||||
      deviceSerialNumber={deviceSerialNumber}
 | 
					      deviceSerialNumber={deviceSerialNumber}
 | 
				
			||||||
      getData={getData}
 | 
					      getData={refresh}
 | 
				
			||||||
 | 
					      deviceConfig={deviceConfig}
 | 
				
			||||||
      status={status}
 | 
					      status={status}
 | 
				
			||||||
      lastStats={lastStats}
 | 
					      lastStats={lastStats}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -145,10 +145,10 @@ const EditUserModal = ({ show, toggle, userId, getUsers }) => {
 | 
				
			|||||||
          getUsers();
 | 
					          getUsers();
 | 
				
			||||||
          toggle();
 | 
					          toggle();
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .catch(() => {
 | 
					        .catch((e) => {
 | 
				
			||||||
          addToast({
 | 
					          addToast({
 | 
				
			||||||
            title: t('user.update_failure_title'),
 | 
					            title: t('user.update_failure_title'),
 | 
				
			||||||
            body: t('user.update_failure'),
 | 
					            body: t('user.update_failure', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
            color: 'danger',
 | 
					            color: 'danger',
 | 
				
			||||||
            autohide: true,
 | 
					            autohide: true,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,10 +32,10 @@ const EventQueueModal = ({ show, toggle }) => {
 | 
				
			|||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
        setResult(response.data);
 | 
					        setResult(response.data);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        addToast({
 | 
				
			||||||
          title: t('common.error'),
 | 
					          title: t('common.error'),
 | 
				
			||||||
          body: t('commands.unable_queue'),
 | 
					          body: t('commands.unable_queue', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
          color: 'danger',
 | 
					          color: 'danger',
 | 
				
			||||||
          autohide: true,
 | 
					          autohide: true,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,10 @@ import {
 | 
				
			|||||||
  CForm,
 | 
					  CForm,
 | 
				
			||||||
  CSwitch,
 | 
					  CSwitch,
 | 
				
			||||||
  CAlert,
 | 
					  CAlert,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
@@ -84,8 +87,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggleModal}>
 | 
					    <CModal show={show} onClose={toggleModal}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('factory_reset.title')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('factory_reset.title')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      {hadSuccess ? (
 | 
					      {hadSuccess ? (
 | 
				
			||||||
        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
					        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,7 +46,7 @@ const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal size="xl" show={show} onClose={toggle} scrollable>
 | 
					    <CModal size="xl" show={show} onClose={toggle} scrollable>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader closeButton>
 | 
				
			||||||
        <CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">
 | 
				
			||||||
          #{serialNumber} {t('firmware.history_title')}
 | 
					          #{serialNumber} {t('firmware.history_title')}
 | 
				
			||||||
        </CModalTitle>
 | 
					        </CModalTitle>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,74 +0,0 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const UpgradeFooter = ({
 | 
					 | 
				
			||||||
  isNow,
 | 
					 | 
				
			||||||
  isShown,
 | 
					 | 
				
			||||||
  isLoading,
 | 
					 | 
				
			||||||
  action,
 | 
					 | 
				
			||||||
  color,
 | 
					 | 
				
			||||||
  variant,
 | 
					 | 
				
			||||||
  block,
 | 
					 | 
				
			||||||
  toggleParent,
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const [askingIfSure, setAskingIfSure] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const confirmingIfSure = () => {
 | 
					 | 
				
			||||||
    setAskingIfSure(true);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setAskingIfSure(false);
 | 
					 | 
				
			||||||
  }, [isShown]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CModalFooter>
 | 
					 | 
				
			||||||
      <div hidden={!askingIfSure}>{t('common.are_you_sure')}</div>
 | 
					 | 
				
			||||||
      <CButton
 | 
					 | 
				
			||||||
        disabled={isLoading}
 | 
					 | 
				
			||||||
        hidden={askingIfSure}
 | 
					 | 
				
			||||||
        color={color}
 | 
					 | 
				
			||||||
        variant={variant}
 | 
					 | 
				
			||||||
        onClick={() => confirmingIfSure()}
 | 
					 | 
				
			||||||
        block={block}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        {isNow ? t('upgrade.upgrade') : t('common.schedule')}
 | 
					 | 
				
			||||||
      </CButton>
 | 
					 | 
				
			||||||
      <CButton
 | 
					 | 
				
			||||||
        disabled={isLoading}
 | 
					 | 
				
			||||||
        hidden={!askingIfSure}
 | 
					 | 
				
			||||||
        color={color}
 | 
					 | 
				
			||||||
        onClick={() => action()}
 | 
					 | 
				
			||||||
        block={block}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        {isLoading ? t('common.loading_ellipsis') : t('common.yes')}
 | 
					 | 
				
			||||||
        <CSpinner color="light" hidden={!isLoading} component="span" size="sm" />
 | 
					 | 
				
			||||||
      </CButton>
 | 
					 | 
				
			||||||
      <CButton color="secondary" onClick={toggleParent}>
 | 
					 | 
				
			||||||
        {t('common.cancel')}
 | 
					 | 
				
			||||||
      </CButton>
 | 
					 | 
				
			||||||
    </CModalFooter>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UpgradeFooter.propTypes = {
 | 
					 | 
				
			||||||
  isNow: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  isLoading: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  block: PropTypes.bool,
 | 
					 | 
				
			||||||
  action: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  color: PropTypes.string,
 | 
					 | 
				
			||||||
  variant: PropTypes.string,
 | 
					 | 
				
			||||||
  toggleParent: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  isShown: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UpgradeFooter.defaultProps = {
 | 
					 | 
				
			||||||
  color: 'primary',
 | 
					 | 
				
			||||||
  variant: '',
 | 
					 | 
				
			||||||
  block: false,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default UpgradeFooter;
 | 
					 | 
				
			||||||
@@ -1,99 +0,0 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { CModalBody } from '@coreui/react';
 | 
					 | 
				
			||||||
import { v4 as createUuid } from 'uuid';
 | 
					 | 
				
			||||||
import { useAuth } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const UpgradeWaitingBody = ({ serialNumber }) => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					 | 
				
			||||||
  const [currentStep, setCurrentStep] = useState(0);
 | 
					 | 
				
			||||||
  const [secondsElapsed, setSecondsElapsed] = useState(0);
 | 
					 | 
				
			||||||
  const [labelsToShow, setLabelsToShow] = useState(['upgrade.command_submitted']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getDeviceConnection = () => {
 | 
					 | 
				
			||||||
    const options = {
 | 
					 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        Accept: 'application/json',
 | 
					 | 
				
			||||||
        Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`, options)
 | 
					 | 
				
			||||||
      .then((response) => response.data.connected)
 | 
					 | 
				
			||||||
      .catch(() => {});
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getFirmwareVersion = () => {
 | 
					 | 
				
			||||||
    const options = {
 | 
					 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        Accept: 'application/json',
 | 
					 | 
				
			||||||
        Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
 | 
					 | 
				
			||||||
      .then((response) => response.data.firmware)
 | 
					 | 
				
			||||||
      .catch(() => {});
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const refreshStep = () => {
 | 
					 | 
				
			||||||
    if (currentStep === 0 && !getDeviceConnection) {
 | 
					 | 
				
			||||||
      const labelsToAdd = [
 | 
					 | 
				
			||||||
        t('upgrade.device_disconnected'),
 | 
					 | 
				
			||||||
        t('upgrade.device_upgrading_firmware'),
 | 
					 | 
				
			||||||
        t('upgrade.waiting_for_device'),
 | 
					 | 
				
			||||||
      ];
 | 
					 | 
				
			||||||
      setLabelsToShow([...labelsToShow, ...labelsToAdd]);
 | 
					 | 
				
			||||||
      setCurrentStep(1);
 | 
					 | 
				
			||||||
    } else if (currentStep === 1 && getDeviceConnection()) {
 | 
					 | 
				
			||||||
      const newFirmware = `: ${getFirmwareVersion()}`;
 | 
					 | 
				
			||||||
      const labelsToAdd = [
 | 
					 | 
				
			||||||
        t('upgrade.device_reconnected'),
 | 
					 | 
				
			||||||
        `${t('upgrade.new_version')}: ${newFirmware}`,
 | 
					 | 
				
			||||||
      ];
 | 
					 | 
				
			||||||
      setLabelsToShow([...labelsToShow, ...labelsToAdd]);
 | 
					 | 
				
			||||||
      setCurrentStep(2);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    const refreshIntervalId = setInterval(() => {
 | 
					 | 
				
			||||||
      refreshStep();
 | 
					 | 
				
			||||||
    }, 5000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const timerIntervalId = setInterval(() => {
 | 
					 | 
				
			||||||
      setSecondsElapsed(secondsElapsed + 1);
 | 
					 | 
				
			||||||
    }, 1000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      clearInterval(refreshIntervalId);
 | 
					 | 
				
			||||||
      clearInterval(timerIntervalId);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CModalBody>
 | 
					 | 
				
			||||||
      <div className="consoleBox">
 | 
					 | 
				
			||||||
        {labelsToShow.map((label) => (
 | 
					 | 
				
			||||||
          <p key={createUuid()}>
 | 
					 | 
				
			||||||
            {new Date().toString()}:{label}
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        ))}
 | 
					 | 
				
			||||||
        <p>
 | 
					 | 
				
			||||||
          {t('common.seconds_elapsed')}:{secondsElapsed}
 | 
					 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </CModalBody>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UpgradeWaitingBody.propTypes = {
 | 
					 | 
				
			||||||
  serialNumber: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default UpgradeWaitingBody;
 | 
					 | 
				
			||||||
@@ -1,241 +0,0 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
  CButton,
 | 
					 | 
				
			||||||
  CModal,
 | 
					 | 
				
			||||||
  CModalHeader,
 | 
					 | 
				
			||||||
  CModalTitle,
 | 
					 | 
				
			||||||
  CModalBody,
 | 
					 | 
				
			||||||
  CSwitch,
 | 
					 | 
				
			||||||
  CCol,
 | 
					 | 
				
			||||||
  CRow,
 | 
					 | 
				
			||||||
  CInput,
 | 
					 | 
				
			||||||
  CInvalidFeedback,
 | 
					 | 
				
			||||||
  CModalFooter,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { dateToUnix } from 'utils/helper';
 | 
					 | 
				
			||||||
import 'react-widgets/styles.css';
 | 
					 | 
				
			||||||
import { useDevice, useAuth } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
import eventBus from 'utils/eventBus';
 | 
					 | 
				
			||||||
import ButtonFooter from './UpgradeFooter';
 | 
					 | 
				
			||||||
import UpgradeWaitingBody from './UpgradeWaitingBody';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const FirmwareUpgradeModal = ({ show, toggleModal }) => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					 | 
				
			||||||
  const { deviceSerialNumber, getDeviceConnection } = useDevice();
 | 
					 | 
				
			||||||
  const [isNow, setIsNow] = useState(true);
 | 
					 | 
				
			||||||
  const [waitForUpgrade, setWaitForUpgrade] = useState(false);
 | 
					 | 
				
			||||||
  const [date, setDate] = useState(new Date().toString());
 | 
					 | 
				
			||||||
  const [firmware, setFirmware] = useState('');
 | 
					 | 
				
			||||||
  const [validFirmware, setValidFirmware] = useState(true);
 | 
					 | 
				
			||||||
  const [validDate, setValidDate] = useState(true);
 | 
					 | 
				
			||||||
  const [blockFields, setBlockFields] = useState(false);
 | 
					 | 
				
			||||||
  const [disabledWaiting, setDisableWaiting] = useState(false);
 | 
					 | 
				
			||||||
  const [waitingForUpgrade, setWaitingForUpgrade] = useState(false);
 | 
					 | 
				
			||||||
  const [showWaitingConsole, setShowWaitingConsole] = useState(false);
 | 
					 | 
				
			||||||
  const [deviceConnected, setDeviceConnected] = useState(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const toggleNow = () => {
 | 
					 | 
				
			||||||
    if (isNow) {
 | 
					 | 
				
			||||||
      setWaitForUpgrade(false);
 | 
					 | 
				
			||||||
      setDisableWaiting(true);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      setDisableWaiting(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setIsNow(!isNow);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const toggleWaitForUpgrade = () => {
 | 
					 | 
				
			||||||
    setWaitForUpgrade(waitForUpgrade);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const formValidation = () => {
 | 
					 | 
				
			||||||
    let valid = true;
 | 
					 | 
				
			||||||
    if (firmware.trim() === '') {
 | 
					 | 
				
			||||||
      setValidFirmware(false);
 | 
					 | 
				
			||||||
      valid = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!isNow && date.trim() === '') {
 | 
					 | 
				
			||||||
      setValidDate(false);
 | 
					 | 
				
			||||||
      valid = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return valid;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setBlockFields(false);
 | 
					 | 
				
			||||||
    setShowWaitingConsole(false);
 | 
					 | 
				
			||||||
  }, [show]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setValidFirmware(true);
 | 
					 | 
				
			||||||
    setValidDate(true);
 | 
					 | 
				
			||||||
  }, [firmware, date]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (deviceSerialNumber !== null && show) {
 | 
					 | 
				
			||||||
      const asyncGet = async () => {
 | 
					 | 
				
			||||||
        const isConnected = await getDeviceConnection(
 | 
					 | 
				
			||||||
          deviceSerialNumber,
 | 
					 | 
				
			||||||
          currentToken,
 | 
					 | 
				
			||||||
          endpoints.owgw,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        setDisableWaiting(!isConnected);
 | 
					 | 
				
			||||||
        setDeviceConnected(isConnected);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      asyncGet();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [show]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const postUpgrade = () => {
 | 
					 | 
				
			||||||
    if (formValidation()) {
 | 
					 | 
				
			||||||
      setWaitingForUpgrade(true);
 | 
					 | 
				
			||||||
      setBlockFields(true);
 | 
					 | 
				
			||||||
      const headers = {
 | 
					 | 
				
			||||||
        Accept: 'application/json',
 | 
					 | 
				
			||||||
        Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
        serialNumber: deviceSerialNumber,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const parameters = {
 | 
					 | 
				
			||||||
        serialNumber: deviceSerialNumber,
 | 
					 | 
				
			||||||
        when: isNow ? 0 : dateToUnix(date),
 | 
					 | 
				
			||||||
        uri: firmware,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      axiosInstance
 | 
					 | 
				
			||||||
        .post(
 | 
					 | 
				
			||||||
          `${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
 | 
					 | 
				
			||||||
          parameters,
 | 
					 | 
				
			||||||
          { headers },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .then(() => {
 | 
					 | 
				
			||||||
          if (waitForUpgrade) {
 | 
					 | 
				
			||||||
            setShowWaitingConsole(true);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .catch(() => {})
 | 
					 | 
				
			||||||
        .finally(() => {
 | 
					 | 
				
			||||||
          setBlockFields(false);
 | 
					 | 
				
			||||||
          setWaitingForUpgrade(false);
 | 
					 | 
				
			||||||
          eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (showWaitingConsole) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <CModal show={show} onClose={toggleModal}>
 | 
					 | 
				
			||||||
        <CModalHeader closeButton>
 | 
					 | 
				
			||||||
          <CModalTitle>{t('upgrade.title')}</CModalTitle>
 | 
					 | 
				
			||||||
        </CModalHeader>
 | 
					 | 
				
			||||||
        <CModalBody>
 | 
					 | 
				
			||||||
          <UpgradeWaitingBody serialNumber={deviceSerialNumber} />
 | 
					 | 
				
			||||||
        </CModalBody>
 | 
					 | 
				
			||||||
        <CModalFooter>
 | 
					 | 
				
			||||||
          <CButton color="secondary" onClick={toggleModal}>
 | 
					 | 
				
			||||||
            {t('common.close')}
 | 
					 | 
				
			||||||
          </CButton>
 | 
					 | 
				
			||||||
        </CModalFooter>
 | 
					 | 
				
			||||||
      </CModal>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CModal show={show} onClose={toggleModal}>
 | 
					 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					 | 
				
			||||||
        <CModalTitle>{t('upgrade.title')}</CModalTitle>
 | 
					 | 
				
			||||||
      </CModalHeader>
 | 
					 | 
				
			||||||
      <CModalBody>
 | 
					 | 
				
			||||||
        <h6>{t('upgrade.directions')}</h6>
 | 
					 | 
				
			||||||
        <CRow className="mt-3">
 | 
					 | 
				
			||||||
          <CCol md="4" className="mt-2">
 | 
					 | 
				
			||||||
            <p>{t('upgrade.firmware_uri')}</p>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
          <CCol md="8">
 | 
					 | 
				
			||||||
            <CInput
 | 
					 | 
				
			||||||
              disabled={blockFields}
 | 
					 | 
				
			||||||
              className={`form-control ${!validFirmware ? 'is-invalid' : ''}`}
 | 
					 | 
				
			||||||
              type="text"
 | 
					 | 
				
			||||||
              id="uri"
 | 
					 | 
				
			||||||
              name="uri-input"
 | 
					 | 
				
			||||||
              autoComplete="firmware-uri"
 | 
					 | 
				
			||||||
              onChange={(event) => setFirmware(event.target.value)}
 | 
					 | 
				
			||||||
              value={firmware}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
        <CRow className="mt-3">
 | 
					 | 
				
			||||||
          <CCol md="8">
 | 
					 | 
				
			||||||
            <p>{t('common.execute_now')}</p>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
          <CCol>
 | 
					 | 
				
			||||||
            <CSwitch
 | 
					 | 
				
			||||||
              disabled={blockFields}
 | 
					 | 
				
			||||||
              color="primary"
 | 
					 | 
				
			||||||
              defaultChecked={isNow}
 | 
					 | 
				
			||||||
              onClick={toggleNow}
 | 
					 | 
				
			||||||
              labelOn={t('common.yes')}
 | 
					 | 
				
			||||||
              labelOff={t('common.no')}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
        <CRow className="mt-3" hidden={isNow}>
 | 
					 | 
				
			||||||
          <CCol md="4" className="mt-2">
 | 
					 | 
				
			||||||
            <p>{t('upgrade.time')}</p>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
          <CCol xs="12" md="8">
 | 
					 | 
				
			||||||
            <DatePicker
 | 
					 | 
				
			||||||
              selected={new Date(date)}
 | 
					 | 
				
			||||||
              value={new Date(date)}
 | 
					 | 
				
			||||||
              className={`form-control ${!validDate ? 'is-invalid' : ''}`}
 | 
					 | 
				
			||||||
              includeTime
 | 
					 | 
				
			||||||
              disabled={blockFields}
 | 
					 | 
				
			||||||
              onChange={(newDate) => setDate(newDate.toString())}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
        <CRow className="mt-3" hidden={true || !isNow || disabledWaiting || !deviceConnected}>
 | 
					 | 
				
			||||||
          <CCol md="8">
 | 
					 | 
				
			||||||
            <p>
 | 
					 | 
				
			||||||
              {t('upgrade.wait_for_upgrade')}
 | 
					 | 
				
			||||||
              <b hidden={!disabledWaiting}> {t('upgrade.offline_device')}</b>
 | 
					 | 
				
			||||||
            </p>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
          <CCol>
 | 
					 | 
				
			||||||
            <CSwitch
 | 
					 | 
				
			||||||
              disabled={blockFields || disabledWaiting}
 | 
					 | 
				
			||||||
              color="primary"
 | 
					 | 
				
			||||||
              defaultChecked={waitForUpgrade}
 | 
					 | 
				
			||||||
              onClick={toggleWaitForUpgrade}
 | 
					 | 
				
			||||||
              labelOn={t('common.yes')}
 | 
					 | 
				
			||||||
              labelOff={t('common.no')}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
      </CModalBody>
 | 
					 | 
				
			||||||
      <ButtonFooter
 | 
					 | 
				
			||||||
        isNow={isNow}
 | 
					 | 
				
			||||||
        isShown={show}
 | 
					 | 
				
			||||||
        isLoading={waitingForUpgrade}
 | 
					 | 
				
			||||||
        action={postUpgrade}
 | 
					 | 
				
			||||||
        color="primary"
 | 
					 | 
				
			||||||
        toggleParent={toggleModal}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </CModal>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FirmwareUpgradeModal.propTypes = {
 | 
					 | 
				
			||||||
  show: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  toggleModal: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default FirmwareUpgradeModal;
 | 
					 | 
				
			||||||
@@ -1,12 +1,7 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import {
 | 
					import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
				
			||||||
  CButton,
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
  CModal,
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
  CModalHeader,
 | 
					 | 
				
			||||||
  CModalBody,
 | 
					 | 
				
			||||||
  CModalTitle,
 | 
					 | 
				
			||||||
  CModalFooter,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
@@ -45,17 +40,19 @@ const LatestStatisticsModal = ({ show, toggle }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal size="lg" show={show} onClose={toggle}>
 | 
					    <CModal size="lg" show={show} onClose={toggle}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle className="text-dark">{t('statistics.latest_statistics')}</CModalTitle>
 | 
					        <CModalTitle className="text-dark">{t('statistics.latest_statistics')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre>
 | 
					        <pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre>
 | 
				
			||||||
      </CModalBody>
 | 
					      </CModalBody>
 | 
				
			||||||
      <CModalFooter>
 | 
					 | 
				
			||||||
        <CButton color="secondary" onClick={toggle}>
 | 
					 | 
				
			||||||
          {t('common.close')}
 | 
					 | 
				
			||||||
        </CButton>
 | 
					 | 
				
			||||||
      </CModalFooter>
 | 
					 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import React, { useState } from 'react';
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { useHistory, useParams } from 'react-router-dom';
 | 
					import { useHistory, useParams } from 'react-router-dom';
 | 
				
			||||||
import { CCard, CCardHeader, CCardBody, CRow, CCol, CPopover, CButton } from '@coreui/react';
 | 
					import { CCard, CCardHeader, CCardBody, CPopover, CButton } from '@coreui/react';
 | 
				
			||||||
import { cilSync } from '@coreui/icons';
 | 
					import { cilSync } from '@coreui/icons';
 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
import eventBus from 'utils/eventBus';
 | 
					import eventBus from 'utils/eventBus';
 | 
				
			||||||
@@ -36,38 +36,31 @@ const DeviceStatisticsCard = () => {
 | 
				
			|||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <CCard>
 | 
					      <CCard>
 | 
				
			||||||
        <CCardHeader>
 | 
					        <CCardHeader>
 | 
				
			||||||
          <CRow>
 | 
					          <div className="d-flex flex-row-reverse align-items-center">
 | 
				
			||||||
            <CCol>
 | 
					            <div className="pl-2">
 | 
				
			||||||
              <div className="text-value-xxl pt-2">{t('statistics.title')}</div>
 | 
					              <CPopover content={t('common.refresh')}>
 | 
				
			||||||
            </CCol>
 | 
					                <CButton color="primary" variant="outline" onClick={refresh}>
 | 
				
			||||||
            <CCol sm="6" xxl="6">
 | 
					                  <CIcon content={cilSync} />
 | 
				
			||||||
              <CRow>
 | 
					                </CButton>
 | 
				
			||||||
                <CCol sm="1" xxl="5" />
 | 
					              </CPopover>
 | 
				
			||||||
                <CCol sm="4" xxl="2" className="text-right">
 | 
					            </div>
 | 
				
			||||||
                  <CButton color="secondary" onClick={goToAnalysis}>
 | 
					            <div className="pl-2">
 | 
				
			||||||
                    {t('wifi_analysis.title')}
 | 
					              <CButton color="primary" variant="outline" onClick={toggleLifetimeModal}>
 | 
				
			||||||
                  </CButton>
 | 
					                Lifetime Statistics
 | 
				
			||||||
                </CCol>
 | 
					              </CButton>
 | 
				
			||||||
                <CCol sm="3" xxl="2" className="text-right">
 | 
					            </div>
 | 
				
			||||||
                  <CButton color="secondary" onClick={toggleLatestModal}>
 | 
					            <div className="pl-2">
 | 
				
			||||||
                    {t('statistics.show_latest')}
 | 
					              <CButton color="primary" variant="outline" onClick={toggleLatestModal}>
 | 
				
			||||||
                  </CButton>
 | 
					                {t('statistics.show_latest')}
 | 
				
			||||||
                </CCol>
 | 
					              </CButton>
 | 
				
			||||||
                <CCol sm="3" xxl="2" className="text-right">
 | 
					            </div>
 | 
				
			||||||
                  <CButton color="secondary" onClick={toggleLifetimeModal}>
 | 
					            <div>
 | 
				
			||||||
                    Lifetime Statistics
 | 
					              <CButton color="primary" variant="outline" onClick={goToAnalysis}>
 | 
				
			||||||
                  </CButton>
 | 
					                {t('wifi_analysis.title')}
 | 
				
			||||||
                </CCol>
 | 
					              </CButton>
 | 
				
			||||||
                <CCol sm="1" xxl="1" className="text-center">
 | 
					            </div>
 | 
				
			||||||
                  <CPopover content={t('common.refresh')}>
 | 
					            <div className="text-value-lg mr-auto">{t('statistics.title')}</div>
 | 
				
			||||||
                    <CButton color="secondary" onClick={refresh} size="sm">
 | 
					          </div>
 | 
				
			||||||
                      <CIcon content={cilSync} />
 | 
					 | 
				
			||||||
                    </CButton>
 | 
					 | 
				
			||||||
                  </CPopover>
 | 
					 | 
				
			||||||
                </CCol>
 | 
					 | 
				
			||||||
              </CRow>
 | 
					 | 
				
			||||||
            </CCol>
 | 
					 | 
				
			||||||
          </CRow>
 | 
					 | 
				
			||||||
        </CCardHeader>
 | 
					        </CCardHeader>
 | 
				
			||||||
        <CCardBody className="p-5">
 | 
					        <CCardBody className="p-5">
 | 
				
			||||||
          <StatisticsChartList />
 | 
					          <StatisticsChartList />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,10 @@ import {
 | 
				
			|||||||
  CSwitch,
 | 
					  CSwitch,
 | 
				
			||||||
  CCol,
 | 
					  CCol,
 | 
				
			||||||
  CRow,
 | 
					  CRow,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import DatePicker from 'react-widgets/DatePicker';
 | 
					import DatePicker from 'react-widgets/DatePicker';
 | 
				
			||||||
@@ -82,8 +85,15 @@ const ActionModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggleModal}>
 | 
					    <CModal show={show} onClose={toggleModal}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('reboot.title')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('reboot.title')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      {result === 'success' ? (
 | 
					      {result === 'success' ? (
 | 
				
			||||||
        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
					        <SuccessfulActionModalBody toggleModal={toggleModal} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,10 @@ import {
 | 
				
			|||||||
  CInputRadio,
 | 
					  CInputRadio,
 | 
				
			||||||
  CFormGroup,
 | 
					  CFormGroup,
 | 
				
			||||||
  CLabel,
 | 
					  CLabel,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
@@ -256,8 +259,15 @@ const TraceModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal show={show} onClose={toggleModal}>
 | 
					    <CModal show={show} onClose={toggleModal}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('trace.title')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('trace.title')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      {getBody()}
 | 
					      {getBody()}
 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,10 @@ import {
 | 
				
			|||||||
  CSwitch,
 | 
					  CSwitch,
 | 
				
			||||||
  CCol,
 | 
					  CCol,
 | 
				
			||||||
  CSpinner,
 | 
					  CSpinner,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
@@ -124,8 +127,15 @@ const WifiScanModal = ({ show, toggleModal }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal size="lg" show={show} onClose={toggleModal}>
 | 
					    <CModal size="lg" show={show} onClose={toggleModal}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader className="p-1">
 | 
				
			||||||
        <CModalTitle>{t('actions.wifi_scan')}</CModalTitle>
 | 
					        <CModalTitle className="pl-1 pt-1">{t('actions.wifi_scan')}</CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        <div hidden={hideOptions || waiting}>
 | 
					        <div hidden={hideOptions || waiting}>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,9 @@
 | 
				
			|||||||
/* eslint-disable-rule prefer-destructuring */
 | 
					/* eslint-disable-rule prefer-destructuring */
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import {
 | 
					import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
 | 
				
			||||||
  CButton,
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
  CModal,
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
  CModalHeader,
 | 
					 | 
				
			||||||
  CModalBody,
 | 
					 | 
				
			||||||
  CModalTitle,
 | 
					 | 
				
			||||||
  CModalFooter,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { prettyDate } from 'utils/helper';
 | 
					import { prettyDate } from 'utils/helper';
 | 
				
			||||||
import WifiChannelTable from './WifiChannelTable';
 | 
					import WifiChannelTable from './WifiChannelTable';
 | 
				
			||||||
@@ -48,21 +43,23 @@ const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CModal size="lg" show={show} onClose={toggle}>
 | 
					    <CModal size="lg" show={show} onClose={toggle}>
 | 
				
			||||||
      <CModalHeader closeButton>
 | 
					      <CModalHeader>
 | 
				
			||||||
        <CModalTitle className="text-dark">
 | 
					        <CModalTitle className="text-dark">
 | 
				
			||||||
          {date !== '' ? prettyDate(date) : ''} {t('scan.results')}
 | 
					          {date !== '' ? prettyDate(date) : ''} {t('scan.results')}
 | 
				
			||||||
        </CModalTitle>
 | 
					        </CModalTitle>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
				
			||||||
 | 
					              <CIcon content={cilX} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </CModalHeader>
 | 
					      </CModalHeader>
 | 
				
			||||||
      <CModalBody>
 | 
					      <CModalBody>
 | 
				
			||||||
        {scanResults === null ? null : (
 | 
					        {scanResults === null ? null : (
 | 
				
			||||||
          <WifiChannelTable channels={parseThroughList(scanResults)} />
 | 
					          <WifiChannelTable channels={parseThroughList(scanResults)} />
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </CModalBody>
 | 
					      </CModalBody>
 | 
				
			||||||
      <CModalFooter>
 | 
					 | 
				
			||||||
        <CButton color="secondary" onClick={toggle}>
 | 
					 | 
				
			||||||
          {t('common.close')}
 | 
					 | 
				
			||||||
        </CButton>
 | 
					 | 
				
			||||||
      </CModalFooter>
 | 
					 | 
				
			||||||
    </CModal>
 | 
					    </CModal>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ const TheLayout = () => {
 | 
				
			|||||||
            <PageContainer t={t} routes={routes} redirectTo="/devices" />
 | 
					            <PageContainer t={t} routes={routes} redirectTo="/devices" />
 | 
				
			||||||
          </ToastProvider>
 | 
					          </ToastProvider>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <Footer t={t} version="2.1.10" />
 | 
					        <Footer t={t} version={process.env.VERSION} />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ import React from 'react';
 | 
				
			|||||||
import { useParams } from 'react-router-dom';
 | 
					import { useParams } from 'react-router-dom';
 | 
				
			||||||
import { CRow, CCol } from '@coreui/react';
 | 
					import { CRow, CCol } from '@coreui/react';
 | 
				
			||||||
import DeviceHealth from 'components/DeviceHealth';
 | 
					import DeviceHealth from 'components/DeviceHealth';
 | 
				
			||||||
import DeviceConfiguration from 'components/DeviceConfiguration';
 | 
					 | 
				
			||||||
import DeviceStatusCard from 'components/DeviceStatusCard';
 | 
					import DeviceStatusCard from 'components/DeviceStatusCard';
 | 
				
			||||||
import CommandHistory from 'components/CommandHistory';
 | 
					import CommandHistory from 'components/CommandHistory';
 | 
				
			||||||
import DeviceLogs from 'components/DeviceLogs';
 | 
					import DeviceLogs from 'components/DeviceLogs';
 | 
				
			||||||
@@ -18,20 +17,25 @@ const DevicePage = () => {
 | 
				
			|||||||
    <div className="App">
 | 
					    <div className="App">
 | 
				
			||||||
      <DeviceProvider axiosInstance={axiosInstance} serialNumber={deviceId}>
 | 
					      <DeviceProvider axiosInstance={axiosInstance} serialNumber={deviceId}>
 | 
				
			||||||
        <CRow>
 | 
					        <CRow>
 | 
				
			||||||
          <CCol xs="12" lg="6">
 | 
					          <CCol>
 | 
				
			||||||
            <DeviceStatusCard />
 | 
					            <DeviceStatusCard />
 | 
				
			||||||
            <DeviceConfiguration />
 | 
					 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
          <CCol xs="12" lg="6">
 | 
					        </CRow>
 | 
				
			||||||
            <DeviceLogs />
 | 
					        <CRow>
 | 
				
			||||||
            <DeviceHealth />
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <CommandHistory />
 | 
				
			||||||
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
            <DeviceActionCard />
 | 
					            <DeviceActionCard />
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
        <CRow>
 | 
					        <CRow>
 | 
				
			||||||
          <CCol>
 | 
					          <CCol>
 | 
				
			||||||
            <DeviceStatisticsCard />
 | 
					            <DeviceStatisticsCard />
 | 
				
			||||||
            <CommandHistory />
 | 
					          </CCol>
 | 
				
			||||||
 | 
					          <CCol>
 | 
				
			||||||
 | 
					            <DeviceHealth />
 | 
				
			||||||
 | 
					            <DeviceLogs />
 | 
				
			||||||
          </CCol>
 | 
					          </CCol>
 | 
				
			||||||
        </CRow>
 | 
					        </CRow>
 | 
				
			||||||
      </DeviceProvider>
 | 
					      </DeviceProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React, { useState, useEffect } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { CCard, CCardBody } from '@coreui/react';
 | 
					import { CCard, CCardBody, CCardHeader, CButton, CPopover } from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilSave } from '@coreui/icons';
 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					import axiosInstance from 'utils/axiosInstance';
 | 
				
			||||||
import { testRegex } from 'utils/helper';
 | 
					import { testRegex } from 'utils/helper';
 | 
				
			||||||
import { useUser, EditMyProfile, useAuth, useToast } from 'ucentral-libs';
 | 
					import { useUser, EditMyProfile, useAuth, useToast } from 'ucentral-libs';
 | 
				
			||||||
@@ -122,10 +124,10 @@ const ProfilePage = () => {
 | 
				
			|||||||
        setNewAvatarFile(null);
 | 
					        setNewAvatarFile(null);
 | 
				
			||||||
        setFileInputKey(fileInputKey + 1);
 | 
					        setFileInputKey(fileInputKey + 1);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        addToast({
 | 
				
			||||||
          title: t('user.update_failure_title'),
 | 
					          title: t('user.update_failure_title'),
 | 
				
			||||||
          body: t('user.update_failure'),
 | 
					          body: t('user.update_failure', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
          color: 'danger',
 | 
					          color: 'danger',
 | 
				
			||||||
          autohide: true,
 | 
					          autohide: true,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -180,10 +182,10 @@ const ProfilePage = () => {
 | 
				
			|||||||
            autohide: true,
 | 
					            autohide: true,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .catch(() => {
 | 
					        .catch((e) => {
 | 
				
			||||||
          addToast({
 | 
					          addToast({
 | 
				
			||||||
            title: t('user.update_failure_title'),
 | 
					            title: t('user.update_failure_title'),
 | 
				
			||||||
            body: t('user.update_failure'),
 | 
					            body: t('user.update_failure', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
            color: 'danger',
 | 
					            color: 'danger',
 | 
				
			||||||
            autohide: true,
 | 
					            autohide: true,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
@@ -260,12 +262,20 @@ const ProfilePage = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CCard>
 | 
					    <CCard>
 | 
				
			||||||
 | 
					      <CCardHeader>
 | 
				
			||||||
 | 
					        <div className="text-right">
 | 
				
			||||||
 | 
					          <CPopover content={t('common.save')}>
 | 
				
			||||||
 | 
					            <CButton onClick={updateUser} color="primary" variant="outline" disabled={loading}>
 | 
				
			||||||
 | 
					              <CIcon content={cilSave} />
 | 
				
			||||||
 | 
					            </CButton>
 | 
				
			||||||
 | 
					          </CPopover>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </CCardHeader>
 | 
				
			||||||
      <CCardBody>
 | 
					      <CCardBody>
 | 
				
			||||||
        <EditMyProfile
 | 
					        <EditMyProfile
 | 
				
			||||||
          t={t}
 | 
					          t={t}
 | 
				
			||||||
          user={userForm}
 | 
					          user={userForm}
 | 
				
			||||||
          updateUserWithId={updateWithId}
 | 
					          updateUserWithId={updateWithId}
 | 
				
			||||||
          saveUser={updateUser}
 | 
					 | 
				
			||||||
          loading={loading}
 | 
					          loading={loading}
 | 
				
			||||||
          policies={policies}
 | 
					          policies={policies}
 | 
				
			||||||
          addNote={addNote}
 | 
					          addNote={addNote}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,7 +45,13 @@ const UserListPage = () => {
 | 
				
			|||||||
      .then((response) => {
 | 
					      .then((response) => {
 | 
				
			||||||
        setUsers(response.data.users);
 | 
					        setUsers(response.data.users);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('user.error_fetching_users', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -113,7 +119,13 @@ const UserListPage = () => {
 | 
				
			|||||||
        setUsersToDisplay(newUsers);
 | 
					        setUsersToDisplay(newUsers);
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
 | 
					        addToast({
 | 
				
			||||||
 | 
					          title: t('common.error'),
 | 
				
			||||||
 | 
					          body: t('user.error_fetching_users', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
 | 
					          color: 'danger',
 | 
				
			||||||
 | 
					          autohide: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        setLoading(false);
 | 
					        setLoading(false);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -139,10 +151,10 @@ const UserListPage = () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
        getUsers();
 | 
					        getUsers();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(() => {
 | 
					      .catch((e) => {
 | 
				
			||||||
        addToast({
 | 
					        addToast({
 | 
				
			||||||
          title: t('common.error'),
 | 
					          title: t('common.error'),
 | 
				
			||||||
          body: t('user.delete_failure'),
 | 
					          body: t('user.delete_failure', { error: e.response?.data?.ErrorDescription }),
 | 
				
			||||||
          color: 'danger',
 | 
					          color: 'danger',
 | 
				
			||||||
          autohide: true,
 | 
					          autohide: true,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -186,6 +198,7 @@ const UserListPage = () => {
 | 
				
			|||||||
        usersPerPage={usersPerPage}
 | 
					        usersPerPage={usersPerPage}
 | 
				
			||||||
        setUsersPerPage={updateUsersPerPage}
 | 
					        setUsersPerPage={updateUsersPerPage}
 | 
				
			||||||
        pageCount={pageCount}
 | 
					        pageCount={pageCount}
 | 
				
			||||||
 | 
					        currentPage={page.selected}
 | 
				
			||||||
        setPage={setPage}
 | 
					        setPage={setPage}
 | 
				
			||||||
        deleteUser={deleteUser}
 | 
					        deleteUser={deleteUser}
 | 
				
			||||||
        deleteLoading={deleteLoading}
 | 
					        deleteLoading={deleteLoading}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,12 @@ import {
 | 
				
			|||||||
  CModal,
 | 
					  CModal,
 | 
				
			||||||
  CModalHeader,
 | 
					  CModalHeader,
 | 
				
			||||||
  CModalBody,
 | 
					  CModalBody,
 | 
				
			||||||
 | 
					  CModalTitle,
 | 
				
			||||||
  CRow,
 | 
					  CRow,
 | 
				
			||||||
 | 
					  CPopover,
 | 
				
			||||||
} from '@coreui/react';
 | 
					} from '@coreui/react';
 | 
				
			||||||
 | 
					import CIcon from '@coreui/icons-react';
 | 
				
			||||||
 | 
					import { cilX } from '@coreui/icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WifiAnalysisPage = () => {
 | 
					const WifiAnalysisPage = () => {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
@@ -198,7 +202,7 @@ const WifiAnalysisPage = () => {
 | 
				
			|||||||
              <h5 className="mb-0">{t('common.device', { serialNumber: deviceId })}</h5>
 | 
					              <h5 className="mb-0">{t('common.device', { serialNumber: deviceId })}</h5>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
            <CCol className="text-right">
 | 
					            <CCol className="text-right">
 | 
				
			||||||
              <CButton color="secondary" onClick={toggleModal}>
 | 
					              <CButton color="primary" variant="outline" onClick={toggleModal}>
 | 
				
			||||||
                {t('wifi_analysis.network_diagram')}
 | 
					                {t('wifi_analysis.network_diagram')}
 | 
				
			||||||
              </CButton>
 | 
					              </CButton>
 | 
				
			||||||
            </CCol>
 | 
					            </CCol>
 | 
				
			||||||
@@ -235,7 +239,16 @@ const WifiAnalysisPage = () => {
 | 
				
			|||||||
        </CCardBody>
 | 
					        </CCardBody>
 | 
				
			||||||
      </CCard>
 | 
					      </CCard>
 | 
				
			||||||
      <CModal size="xl" show={showModal} onClose={toggleModal}>
 | 
					      <CModal size="xl" show={showModal} onClose={toggleModal}>
 | 
				
			||||||
        <CModalHeader closeButton>{t('wifi_analysis.network_diagram')}</CModalHeader>
 | 
					        <CModalHeader className="p-1">
 | 
				
			||||||
 | 
					          <CModalTitle className="pl-1 pt-1">{t('wifi_analysis.network_diagram')}</CModalTitle>
 | 
				
			||||||
 | 
					          <div className="text-right">
 | 
				
			||||||
 | 
					            <CPopover content={t('common.close')}>
 | 
				
			||||||
 | 
					              <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
 | 
				
			||||||
 | 
					                <CIcon content={cilX} />
 | 
				
			||||||
 | 
					              </CButton>
 | 
				
			||||||
 | 
					            </CPopover>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </CModalHeader>
 | 
				
			||||||
        <CModalBody>
 | 
					        <CModalBody>
 | 
				
			||||||
          {showModal ? (
 | 
					          {showModal ? (
 | 
				
			||||||
            <NetworkDiagram
 | 
					            <NetworkDiagram
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user